Skip to content

Commit a1063b6

Browse files
committed
Provide more and more accurate suggestions when calling the wrong method
``` error[E0308]: mismatched types --> $DIR/rustc_confusables_std_cases.rs:20:14 | LL | x.append(42); | ------ ^^ expected `&mut Vec<{integer}>`, found integer | | | arguments to this method are incorrect | = note: expected mutable reference `&mut Vec<{integer}>` found type `{integer}` note: method defined here --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL help: you might have meant to use `push` | LL | x.push(42); | ~~~~ ```
1 parent 93bbb54 commit a1063b6

File tree

5 files changed

+189
-31
lines changed

5 files changed

+189
-31
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+98-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use crate::coercion::CoerceMany;
22
use crate::errors::SuggestPtrNullMut;
33
use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx};
4+
use crate::fn_ctxt::infer::FnCall;
45
use crate::gather_locals::Declaration;
6+
use crate::method::probe::IsSuggestion;
7+
use crate::method::probe::Mode::MethodCall;
8+
use crate::method::probe::ProbeScope::TraitsInScope;
59
use crate::method::MethodCallee;
610
use crate::TupleArgumentsFlag::*;
711
use crate::{errors, Expectation::*};
@@ -532,25 +536,111 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
532536
let callee_ty = callee_expr
533537
.and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr));
534538

539+
// Obtain another method on `Self` that have similar name.
540+
let similar_assoc = |call_name: Ident| -> Option<(ty::AssocItem, ty::FnSig<'_>)> {
541+
if let Some(callee_ty) = callee_ty
542+
&& let Ok(Some(assoc)) = self.probe_op(
543+
call_name.span,
544+
MethodCall,
545+
Some(call_name),
546+
None,
547+
IsSuggestion(true),
548+
callee_ty.peel_refs(),
549+
callee_expr.unwrap().hir_id,
550+
TraitsInScope,
551+
|mut ctxt| ctxt.probe_for_similar_candidate(),
552+
)
553+
&& let ty::AssocKind::Fn = assoc.kind
554+
&& assoc.fn_has_self_parameter
555+
{
556+
let fn_sig =
557+
if let ty::Adt(_, args) = callee_ty.peel_refs().kind() {
558+
let args = ty::GenericArgs::identity_for_item(tcx, assoc.def_id)
559+
.rebase_onto(tcx, assoc.container_id(tcx), args);
560+
tcx.fn_sig(assoc.def_id).instantiate(tcx, args)
561+
} else {
562+
tcx.fn_sig(assoc.def_id).instantiate_identity()
563+
};
564+
let fn_sig =
565+
self.instantiate_binder_with_fresh_vars(call_name.span, FnCall, fn_sig);
566+
Some((assoc, fn_sig));
567+
}
568+
None
569+
};
570+
535571
let suggest_confusable = |err: &mut Diagnostic| {
536572
if let Some(call_name) = call_ident
537573
&& let Some(callee_ty) = callee_ty
538574
{
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
575+
let input_types: Vec<Ty<'_>> = provided_arg_tys.iter().map(|(ty, _)| *ty).collect();
576+
// Check for other methods in the following order
577+
// - methods marked as `rustc_confusables` with the provided arguments
578+
// - methods with the same argument type/count and short levenshtein distance
579+
// - methods marked as `rustc_confusables` (done)
580+
// - methods with short levenshtein distance
581+
582+
// Look for commonly confusable method names considering arguments.
546583
self.confusable_method_name(
547584
err,
548585
callee_ty.peel_refs(),
549586
call_name,
550-
Some(provided_arg_tys.iter().map(|(ty, _)| *ty).collect()),
587+
Some(input_types.clone()),
551588
)
552589
.or_else(|| {
590+
// Look for method names with short levenshtein distance, considering arguments.
591+
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
592+
&& fn_sig.inputs()[1..]
593+
.iter()
594+
.zip(input_types.iter())
595+
.all(|(expected, found)| self.can_coerce(*expected, *found))
596+
&& fn_sig.inputs()[1..].len() == input_types.len()
597+
{
598+
err.span_suggestion_verbose(
599+
call_name.span,
600+
format!("you might have meant to use `{}`", assoc.name),
601+
assoc.name,
602+
Applicability::MaybeIncorrect,
603+
);
604+
return Some(assoc.name);
605+
}
606+
None
607+
})
608+
.or_else(|| {
609+
// Look for commonly confusable method names disregarding arguments.
553610
self.confusable_method_name(err, callee_ty.peel_refs(), call_name, None)
611+
})
612+
.or_else(|| {
613+
// Look for similarly named methods with levenshtein distance with the right
614+
// number of arguments.
615+
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
616+
&& fn_sig.inputs()[1..].len() == input_types.len()
617+
{
618+
err.span_note(
619+
tcx.def_span(assoc.def_id),
620+
format!(
621+
"there's is a method with similar name `{}`, but the arguments \
622+
don't match",
623+
assoc.name,
624+
),
625+
);
626+
return Some(assoc.name);
627+
}
628+
None
629+
})
630+
.or_else(|| {
631+
// Fallthrough: look for similarly named methods with levenshtein distance.
632+
if let Some((assoc, _)) = similar_assoc(call_name) {
633+
err.span_note(
634+
tcx.def_span(assoc.def_id),
635+
format!(
636+
"there's is a method with similar name `{}`, but their argument \
637+
count doesn't match",
638+
assoc.name,
639+
),
640+
);
641+
return Some(assoc.name);
642+
}
643+
None
554644
});
555645
}
556646
};

compiler/rustc_hir_typeck/src/method/probe.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub use self::PickKind::*;
5454
#[derive(Clone, Copy, Debug)]
5555
pub struct IsSuggestion(pub bool);
5656

57-
struct ProbeContext<'a, 'tcx> {
57+
pub(crate) struct ProbeContext<'a, 'tcx> {
5858
fcx: &'a FnCtxt<'a, 'tcx>,
5959
span: Span,
6060
mode: Mode,
@@ -355,7 +355,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
355355
.unwrap()
356356
}
357357

358-
fn probe_op<OP, R>(
358+
pub(crate) fn probe_op<OP, R>(
359359
&'a self,
360360
span: Span,
361361
mode: Mode,
@@ -1751,7 +1751,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
17511751
/// Similarly to `probe_for_return_type`, this method attempts to find the best matching
17521752
/// candidate method where the method name may have been misspelled. Similarly to other
17531753
/// edit distance based suggestions, we provide at most one such suggestion.
1754-
fn probe_for_similar_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
1754+
pub(crate) fn probe_for_similar_candidate(
1755+
&mut self,
1756+
) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
17551757
debug!("probing for method names similar to {:?}", self.method_name);
17561758

17571759
self.probe(|_| {
@@ -1943,7 +1945,21 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
19431945
let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
19441946
let attrs = self.fcx.tcx.hir().attrs(hir_id);
19451947
for attr in attrs {
1946-
let sym::doc = attr.name_or_empty() else {
1948+
if sym::doc == attr.name_or_empty() {
1949+
} else if sym::rustc_confusables == attr.name_or_empty() {
1950+
let Some(confusables) = attr.meta_item_list() else {
1951+
continue;
1952+
};
1953+
// #[rustc_confusables("foo", "bar"))]
1954+
for n in confusables {
1955+
if let Some(lit) = n.lit()
1956+
&& name.as_str() == lit.symbol.as_str()
1957+
{
1958+
return true;
1959+
}
1960+
}
1961+
continue;
1962+
} else {
19471963
continue;
19481964
};
19491965
let Some(values) = attr.meta_item_list() else {

compiler/rustc_hir_typeck/src/method/suggest.rs

+49-16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc_hir::PatKind::Binding;
2323
use rustc_hir::PathSegment;
2424
use rustc_hir::{ExprKind, Node, QPath};
2525
use rustc_infer::infer::{
26+
self,
2627
type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
2728
RegionVariableOrigin,
2829
};
@@ -1124,7 +1125,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11241125
label_span_not_found(&mut err);
11251126
}
11261127

1127-
let confusable_suggested = self.confusable_method_name(&mut err, rcvr_ty, item_name, None);
1128+
let confusable_suggested = self.confusable_method_name(
1129+
&mut err,
1130+
rcvr_ty,
1131+
item_name,
1132+
args.map(|args| {
1133+
args.iter()
1134+
.map(|expr| {
1135+
self.node_ty_opt(expr.hir_id).unwrap_or_else(|| {
1136+
self.next_ty_var(TypeVariableOrigin {
1137+
kind: TypeVariableOriginKind::MiscVariable,
1138+
span: expr.span,
1139+
})
1140+
})
1141+
})
1142+
.collect()
1143+
}),
1144+
);
11281145

11291146
// Don't suggest (for example) `expr.field.clone()` if `expr.clone()`
11301147
// can't be called due to `typeof(expr): Clone` not holding.
@@ -1312,9 +1329,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13121329
err: &mut Diagnostic,
13131330
rcvr_ty: Ty<'tcx>,
13141331
item_name: Ident,
1315-
args: Option<Vec<Ty<'tcx>>>,
1332+
call_args: Option<Vec<Ty<'tcx>>>,
13161333
) -> Option<Symbol> {
1317-
if let ty::Adt(adt, _) = rcvr_ty.kind() {
1334+
if let ty::Adt(adt, adt_args) = rcvr_ty.kind() {
13181335
for inherent_impl_did in self.tcx.inherent_impls(adt.did()).into_iter().flatten() {
13191336
for inherent_method in
13201337
self.tcx.associated_items(inherent_impl_did).in_definition_order()
@@ -1323,29 +1340,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13231340
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
13241341
&& let Some(candidates) = parse_confusables(attr)
13251342
&& candidates.contains(&item_name.name)
1343+
&& let ty::AssocKind::Fn = inherent_method.kind
13261344
{
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()
1345+
let args =
1346+
ty::GenericArgs::identity_for_item(self.tcx, inherent_method.def_id)
1347+
.rebase_onto(
1348+
self.tcx,
1349+
inherent_method.container_id(self.tcx),
1350+
adt_args,
1351+
);
1352+
let fn_sig =
1353+
self.tcx.fn_sig(inherent_method.def_id).instantiate(self.tcx, args);
1354+
let fn_sig = self.instantiate_binder_with_fresh_vars(
1355+
item_name.span,
1356+
infer::FnCall,
1357+
fn_sig,
1358+
);
1359+
if let Some(ref args) = call_args
1360+
&& fn_sig.inputs()[1..]
13361361
.iter()
1337-
.skip(1)
13381362
.zip(args.into_iter())
1339-
.all(|(expected, found)| self.can_coerce(*expected, *found));
1340-
}
1341-
if matches_args {
1363+
.all(|(expected, found)| self.can_coerce(*expected, *found))
1364+
&& fn_sig.inputs()[1..].len() == args.len()
1365+
{
13421366
err.span_suggestion_verbose(
13431367
item_name.span,
13441368
format!("you might have meant to use `{}`", inherent_method.name),
13451369
inherent_method.name,
13461370
Applicability::MaybeIncorrect,
13471371
);
13481372
return Some(inherent_method.name);
1373+
} else if let None = call_args {
1374+
err.span_note(
1375+
self.tcx.def_span(inherent_method.def_id),
1376+
format!(
1377+
"you might have meant to use method `{}`",
1378+
inherent_method.name,
1379+
),
1380+
);
1381+
return Some(inherent_method.name);
13491382
}
13501383
}
13511384
}

tests/ui/attributes/rustc_confusables_std_cases.rs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ fn main() {
1717
x.size(); //~ ERROR E0599
1818
//~^ HELP you might have meant to use `len`
1919
//~| HELP there is a method with a similar name
20+
x.append(42); //~ ERROR E0308
21+
//~^ HELP you might have meant to use `push`
2022
String::new().push(""); //~ ERROR E0308
2123
//~^ HELP you might have meant to use `push_str`
2224
String::new().append(""); //~ ERROR E0599

tests/ui/attributes/rustc_confusables_std_cases.stderr

+20-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,24 @@ LL | x.resize();
5858
| ~~~~~~
5959

6060
error[E0308]: mismatched types
61-
--> $DIR/rustc_confusables_std_cases.rs:20:24
61+
--> $DIR/rustc_confusables_std_cases.rs:20:14
62+
|
63+
LL | x.append(42);
64+
| ------ ^^ expected `&mut Vec<{integer}>`, found integer
65+
| |
66+
| arguments to this method are incorrect
67+
|
68+
= note: expected mutable reference `&mut Vec<{integer}>`
69+
found type `{integer}`
70+
note: method defined here
71+
--> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL
72+
help: you might have meant to use `push`
73+
|
74+
LL | x.push(42);
75+
| ~~~~
76+
77+
error[E0308]: mismatched types
78+
--> $DIR/rustc_confusables_std_cases.rs:22:24
6279
|
6380
LL | String::new().push("");
6481
| ---- ^^ expected `char`, found `&str`
@@ -73,7 +90,7 @@ LL | String::new().push_str("");
7390
| ~~~~~~~~
7491

7592
error[E0599]: no method named `append` found for struct `String` in the current scope
76-
--> $DIR/rustc_confusables_std_cases.rs:22:19
93+
--> $DIR/rustc_confusables_std_cases.rs:24:19
7794
|
7895
LL | String::new().append("");
7996
| ^^^^^^ method not found in `String`
@@ -83,7 +100,7 @@ help: you might have meant to use `push_str`
83100
LL | String::new().push_str("");
84101
| ~~~~~~~~
85102

86-
error: aborting due to 7 previous errors
103+
error: aborting due to 8 previous errors
87104

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

0 commit comments

Comments
 (0)