Skip to content

Commit ac9ba07

Browse files
committed
On type error of method call arguments, look at confusables for suggestion
1 parent 3f62e00 commit ac9ba07

File tree

5 files changed

+106
-35
lines changed

5 files changed

+106
-35
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
451451
call_expr: &'tcx hir::Expr<'tcx>,
452452
) -> ErrorGuaranteed {
453453
// Next, let's construct the error
454-
let (error_span, full_call_span, call_name, is_method) = match &call_expr.kind {
454+
let (error_span, call_ident, full_call_span, call_name, is_method) = match &call_expr.kind {
455455
hir::ExprKind::Call(
456456
hir::Expr { hir_id, span, kind: hir::ExprKind::Path(qpath), .. },
457457
_,
@@ -463,20 +463,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
463463
CtorOf::Struct => "struct",
464464
CtorOf::Variant => "enum variant",
465465
};
466-
(call_span, *span, name, false)
466+
(call_span, None, *span, name, false)
467467
} else {
468-
(call_span, *span, "function", false)
468+
(call_span, None, *span, "function", false)
469469
}
470470
}
471-
hir::ExprKind::Call(hir::Expr { span, .. }, _) => (call_span, *span, "function", false),
471+
hir::ExprKind::Call(hir::Expr { span, .. }, _) => {
472+
(call_span, None, *span, "function", false)
473+
}
472474
hir::ExprKind::MethodCall(path_segment, _, _, span) => {
473475
let ident_span = path_segment.ident.span;
474476
let ident_span = if let Some(args) = path_segment.args {
475477
ident_span.with_hi(args.span_ext.hi())
476478
} else {
477479
ident_span
478480
};
479-
(*span, ident_span, "method", true)
481+
(*span, Some(path_segment.ident), ident_span, "method", true)
480482
}
481483
k => span_bug!(call_span, "checking argument types on a non-call: `{:?}`", k),
482484
};
@@ -530,6 +532,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
530532
let callee_ty = callee_expr
531533
.and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr));
532534

535+
let suggest_confusable = |err: &mut Diagnostic| {
536+
if let Some(call_name) = call_ident
537+
&& let Some(callee_ty) = callee_ty
538+
{
539+
// FIXME: check in the following order
540+
// - methods marked as `rustc_confusables` with the provided arguments (done)
541+
// - methods marked as `rustc_confusables` with the right number of arguments
542+
// - methods marked as `rustc_confusables` (done)
543+
// - methods with the same argument type/count and short levenshtein distance
544+
// - methods with short levenshtein distance
545+
// - methods with the same argument type/count
546+
self.confusable_method_name(
547+
err,
548+
callee_ty.peel_refs(),
549+
call_name,
550+
Some(provided_arg_tys.iter().map(|(ty, _)| *ty).collect()),
551+
)
552+
.or_else(|| {
553+
self.confusable_method_name(err, callee_ty.peel_refs(), call_name, None)
554+
});
555+
}
556+
};
533557
// A "softer" version of the `demand_compatible`, which checks types without persisting them,
534558
// and treats error types differently
535559
// This will allow us to "probe" for other argument orders that would likely have been correct
@@ -694,6 +718,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
694718
Some(mismatch_idx),
695719
is_method,
696720
);
721+
suggest_confusable(&mut err);
697722
return err.emit();
698723
}
699724
}
@@ -718,7 +743,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
718743
if cfg!(debug_assertions) {
719744
span_bug!(error_span, "expected errors from argument matrix");
720745
} else {
721-
return tcx.dcx().emit_err(errors::ArgMismatchIndeterminate { span: error_span });
746+
let mut err =
747+
tcx.dcx().create_err(errors::ArgMismatchIndeterminate { span: error_span });
748+
suggest_confusable(&mut err);
749+
return err.emit();
722750
}
723751
}
724752

@@ -733,7 +761,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
733761
let trace =
734762
mk_trace(provided_span, formal_and_expected_inputs[*expected_idx], provided_ty);
735763
if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308) {
736-
reported = Some(self.err_ctxt().report_and_explain_type_error(trace, *e).emit());
764+
let mut err = self.err_ctxt().report_and_explain_type_error(trace, *e);
765+
suggest_confusable(&mut err);
766+
reported = Some(err.emit());
737767
return false;
738768
}
739769
true
@@ -801,6 +831,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
801831
Some(expected_idx.as_usize()),
802832
is_method,
803833
);
834+
suggest_confusable(&mut err);
804835
return err.emit();
805836
}
806837

@@ -828,6 +859,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
828859
.with_code(err_code.to_owned())
829860
};
830861

862+
suggest_confusable(&mut err);
831863
// As we encounter issues, keep track of what we want to provide for the suggestion
832864
let mut labels = vec![];
833865
// If there is a single error, we give a specific suggestion; otherwise, we change to

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,33 +1139,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11391139
label_span_not_found(&mut err);
11401140
}
11411141

1142-
let mut confusable_suggested = None;
1143-
if let ty::Adt(adt, _) = rcvr_ty.kind() {
1144-
'outer: for inherent_impl_did in
1145-
self.tcx.inherent_impls(adt.did()).into_iter().flatten()
1146-
{
1147-
for inherent_method in
1148-
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1149-
{
1150-
if let Some(attr) =
1151-
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
1152-
&& let Some(candidates) = parse_confusables(attr)
1153-
&& candidates.contains(&item_name.name)
1154-
{
1155-
{
1156-
err.span_suggestion_verbose(
1157-
item_name.span,
1158-
format!("you might have meant to use `{}`", inherent_method.name),
1159-
inherent_method.name,
1160-
Applicability::MaybeIncorrect,
1161-
);
1162-
confusable_suggested = Some(inherent_method.name);
1163-
break 'outer;
1164-
}
1165-
}
1166-
}
1167-
}
1168-
}
1142+
let confusable_suggested = self.confusable_method_name(&mut err, rcvr_ty, item_name, None);
11691143

11701144
// Don't suggest (for example) `expr.field.clone()` if `expr.clone()`
11711145
// can't be called due to `typeof(expr): Clone` not holding.
@@ -1347,6 +1321,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13471321
Some(err)
13481322
}
13491323

1324+
pub(crate) fn confusable_method_name(
1325+
&self,
1326+
err: &mut Diagnostic,
1327+
rcvr_ty: Ty<'tcx>,
1328+
item_name: Ident,
1329+
args: Option<Vec<Ty<'tcx>>>,
1330+
) -> Option<Symbol> {
1331+
if let ty::Adt(adt, _) = rcvr_ty.kind() {
1332+
for inherent_impl_did in self.tcx.inherent_impls(adt.did()).into_iter().flatten() {
1333+
for inherent_method in
1334+
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1335+
{
1336+
if let Some(attr) =
1337+
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
1338+
&& let Some(candidates) = parse_confusables(attr)
1339+
&& candidates.contains(&item_name.name)
1340+
{
1341+
let mut matches_args = args.is_none();
1342+
if let ty::AssocKind::Fn = inherent_method.kind
1343+
&& let Some(ref args) = args
1344+
{
1345+
let fn_sig =
1346+
self.tcx.fn_sig(inherent_method.def_id).instantiate_identity();
1347+
matches_args = fn_sig
1348+
.inputs()
1349+
.skip_binder()
1350+
.iter()
1351+
.skip(1)
1352+
.zip(args.into_iter())
1353+
.all(|(expected, found)| self.can_coerce(*expected, *found));
1354+
}
1355+
if matches_args {
1356+
err.span_suggestion_verbose(
1357+
item_name.span,
1358+
format!("you might have meant to use `{}`", inherent_method.name),
1359+
inherent_method.name,
1360+
Applicability::MaybeIncorrect,
1361+
);
1362+
return Some(inherent_method.name);
1363+
}
1364+
}
1365+
}
1366+
}
1367+
}
1368+
None
1369+
}
13501370
fn note_candidates_on_method_error(
13511371
&self,
13521372
rcvr_ty: Ty<'tcx>,

library/alloc/src/string.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ impl String {
10491049
#[cfg(not(no_global_oom_handling))]
10501050
#[inline]
10511051
#[stable(feature = "rust1", since = "1.0.0")]
1052+
#[rustc_confusables("append", "push")]
10521053
pub fn push_str(&mut self, string: &str) {
10531054
self.vec.extend_from_slice(string.as_bytes())
10541055
}

tests/ui/attributes/rustc_confusables_std_cases.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ fn main() {
1818
//~^ HELP you might have meant to use `len`
1919
//~| HELP there is a method with a similar name
2020
String::new().push(""); //~ ERROR E0308
21+
//~^ HELP you might have meant to use `push_str`
22+
String::new().append(""); //~ ERROR E0599
23+
//~^ HELP you might have meant to use `push_str`
2124
}

tests/ui/attributes/rustc_confusables_std_cases.stderr

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,23 @@ LL | String::new().push("");
6767
|
6868
note: method defined here
6969
--> $SRC_DIR/alloc/src/string.rs:LL:COL
70+
help: you might have meant to use `push_str`
71+
|
72+
LL | String::new().push_str("");
73+
| ~~~~~~~~
74+
75+
error[E0599]: no method named `append` found for struct `String` in the current scope
76+
--> $DIR/rustc_confusables_std_cases.rs:22:19
77+
|
78+
LL | String::new().append("");
79+
| ^^^^^^ method not found in `String`
80+
|
81+
help: you might have meant to use `push_str`
82+
|
83+
LL | String::new().push_str("");
84+
| ~~~~~~~~
7085

71-
error: aborting due to 6 previous errors
86+
error: aborting due to 7 previous errors
7287

7388
Some errors have detailed explanations: E0308, E0599.
7489
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)