Skip to content

Commit 4e0b8eb

Browse files
committed
On type error of method call arguments, look at confusables for suggestion
1 parent 7ccd5e7 commit 4e0b8eb

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

+39-7
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

+47-27
Original file line numberDiff line numberDiff line change
@@ -1124,33 +1124,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11241124
label_span_not_found(&mut err);
11251125
}
11261126

1127-
let mut confusable_suggested = None;
1128-
if let ty::Adt(adt, _) = rcvr_ty.kind() {
1129-
'outer: for inherent_impl_did in
1130-
self.tcx.inherent_impls(adt.did()).into_iter().flatten()
1131-
{
1132-
for inherent_method in
1133-
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1134-
{
1135-
if let Some(attr) =
1136-
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
1137-
&& let Some(candidates) = parse_confusables(attr)
1138-
&& candidates.contains(&item_name.name)
1139-
{
1140-
{
1141-
err.span_suggestion_verbose(
1142-
item_name.span,
1143-
format!("you might have meant to use `{}`", inherent_method.name),
1144-
inherent_method.name,
1145-
Applicability::MaybeIncorrect,
1146-
);
1147-
confusable_suggested = Some(inherent_method.name);
1148-
break 'outer;
1149-
}
1150-
}
1151-
}
1152-
}
1153-
}
1127+
let confusable_suggested = self.confusable_method_name(&mut err, rcvr_ty, item_name, None);
11541128

11551129
// Don't suggest (for example) `expr.field.clone()` if `expr.clone()`
11561130
// can't be called due to `typeof(expr): Clone` not holding.
@@ -1333,6 +1307,52 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13331307
return Some(err);
13341308
}
13351309

1310+
pub(crate) fn confusable_method_name(
1311+
&self,
1312+
err: &mut Diagnostic,
1313+
rcvr_ty: Ty<'tcx>,
1314+
item_name: Ident,
1315+
args: Option<Vec<Ty<'tcx>>>,
1316+
) -> Option<Symbol> {
1317+
if let ty::Adt(adt, _) = rcvr_ty.kind() {
1318+
for inherent_impl_did in self.tcx.inherent_impls(adt.did()).into_iter().flatten() {
1319+
for inherent_method in
1320+
self.tcx.associated_items(inherent_impl_did).in_definition_order()
1321+
{
1322+
if let Some(attr) =
1323+
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
1324+
&& let Some(candidates) = parse_confusables(attr)
1325+
&& candidates.contains(&item_name.name)
1326+
{
1327+
let mut matches_args = args.is_none();
1328+
if let ty::AssocKind::Fn = inherent_method.kind
1329+
&& let Some(ref args) = args
1330+
{
1331+
let fn_sig =
1332+
self.tcx.fn_sig(inherent_method.def_id).instantiate_identity();
1333+
matches_args = fn_sig
1334+
.inputs()
1335+
.skip_binder()
1336+
.iter()
1337+
.skip(1)
1338+
.zip(args.into_iter())
1339+
.all(|(expected, found)| self.can_coerce(*expected, *found));
1340+
}
1341+
if matches_args {
1342+
err.span_suggestion_verbose(
1343+
item_name.span,
1344+
format!("you might have meant to use `{}`", inherent_method.name),
1345+
inherent_method.name,
1346+
Applicability::MaybeIncorrect,
1347+
);
1348+
return Some(inherent_method.name);
1349+
}
1350+
}
1351+
}
1352+
}
1353+
}
1354+
None
1355+
}
13361356
fn note_candidates_on_method_error(
13371357
&self,
13381358
rcvr_ty: Ty<'tcx>,

library/alloc/src/string.rs

+1
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

+3
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

+16-1
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)