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);
   |       ~~~~
```
This commit is contained in:
Esteban Küber 2024-02-09 20:54:26 +00:00
parent e13d452111
commit d30dfb0af7
5 changed files with 189 additions and 31 deletions

View File

@ -1,7 +1,11 @@
use crate::coercion::CoerceMany; use crate::coercion::CoerceMany;
use crate::errors::SuggestPtrNullMut; use crate::errors::SuggestPtrNullMut;
use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx};
use crate::fn_ctxt::infer::FnCall;
use crate::gather_locals::Declaration; use crate::gather_locals::Declaration;
use crate::method::probe::IsSuggestion;
use crate::method::probe::Mode::MethodCall;
use crate::method::probe::ProbeScope::TraitsInScope;
use crate::method::MethodCallee; use crate::method::MethodCallee;
use crate::TupleArgumentsFlag::*; use crate::TupleArgumentsFlag::*;
use crate::{errors, Expectation::*}; use crate::{errors, Expectation::*};
@ -532,25 +536,111 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let callee_ty = callee_expr let callee_ty = callee_expr
.and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr)); .and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr));
// Obtain another method on `Self` that have similar name.
let similar_assoc = |call_name: Ident| -> Option<(ty::AssocItem, ty::FnSig<'_>)> {
if let Some(callee_ty) = callee_ty
&& let Ok(Some(assoc)) = self.probe_op(
call_name.span,
MethodCall,
Some(call_name),
None,
IsSuggestion(true),
callee_ty.peel_refs(),
callee_expr.unwrap().hir_id,
TraitsInScope,
|mut ctxt| ctxt.probe_for_similar_candidate(),
)
&& let ty::AssocKind::Fn = assoc.kind
&& assoc.fn_has_self_parameter
{
let fn_sig =
if let ty::Adt(_, args) = callee_ty.peel_refs().kind() {
let args = ty::GenericArgs::identity_for_item(tcx, assoc.def_id)
.rebase_onto(tcx, assoc.container_id(tcx), args);
tcx.fn_sig(assoc.def_id).instantiate(tcx, args)
} else {
tcx.fn_sig(assoc.def_id).instantiate_identity()
};
let fn_sig =
self.instantiate_binder_with_fresh_vars(call_name.span, FnCall, fn_sig);
Some((assoc, fn_sig));
}
None
};
let suggest_confusable = |err: &mut Diagnostic| { let suggest_confusable = |err: &mut Diagnostic| {
if let Some(call_name) = call_ident if let Some(call_name) = call_ident
&& let Some(callee_ty) = callee_ty && let Some(callee_ty) = callee_ty
{ {
// FIXME: check in the following order let input_types: Vec<Ty<'_>> = provided_arg_tys.iter().map(|(ty, _)| *ty).collect();
// - methods marked as `rustc_confusables` with the provided arguments (done) // Check for other methods in the following order
// - methods marked as `rustc_confusables` with the right number of arguments // - methods marked as `rustc_confusables` with the provided arguments
// - methods marked as `rustc_confusables` (done) // - methods with the same argument type/count and short levenshtein distance
// - methods with the same argument type/count and short levenshtein distance // - methods marked as `rustc_confusables` (done)
// - methods with short levenshtein distance // - methods with short levenshtein distance
// - methods with the same argument type/count
// Look for commonly confusable method names considering arguments.
self.confusable_method_name( self.confusable_method_name(
err, err,
callee_ty.peel_refs(), callee_ty.peel_refs(),
call_name, call_name,
Some(provided_arg_tys.iter().map(|(ty, _)| *ty).collect()), Some(input_types.clone()),
) )
.or_else(|| { .or_else(|| {
// Look for method names with short levenshtein distance, considering arguments.
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
&& fn_sig.inputs()[1..]
.iter()
.zip(input_types.iter())
.all(|(expected, found)| self.can_coerce(*expected, *found))
&& fn_sig.inputs()[1..].len() == input_types.len()
{
err.span_suggestion_verbose(
call_name.span,
format!("you might have meant to use `{}`", assoc.name),
assoc.name,
Applicability::MaybeIncorrect,
);
return Some(assoc.name);
}
None
})
.or_else(|| {
// Look for commonly confusable method names disregarding arguments.
self.confusable_method_name(err, callee_ty.peel_refs(), call_name, None) self.confusable_method_name(err, callee_ty.peel_refs(), call_name, None)
})
.or_else(|| {
// Look for similarly named methods with levenshtein distance with the right
// number of arguments.
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
&& fn_sig.inputs()[1..].len() == input_types.len()
{
err.span_note(
tcx.def_span(assoc.def_id),
format!(
"there's is a method with similar name `{}`, but the arguments \
don't match",
assoc.name,
),
);
return Some(assoc.name);
}
None
})
.or_else(|| {
// Fallthrough: look for similarly named methods with levenshtein distance.
if let Some((assoc, _)) = similar_assoc(call_name) {
err.span_note(
tcx.def_span(assoc.def_id),
format!(
"there's is a method with similar name `{}`, but their argument \
count doesn't match",
assoc.name,
),
);
return Some(assoc.name);
}
None
}); });
} }
}; };

View File

@ -54,7 +54,7 @@ pub use self::PickKind::*;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct IsSuggestion(pub bool); pub struct IsSuggestion(pub bool);
struct ProbeContext<'a, 'tcx> { pub(crate) struct ProbeContext<'a, 'tcx> {
fcx: &'a FnCtxt<'a, 'tcx>, fcx: &'a FnCtxt<'a, 'tcx>,
span: Span, span: Span,
mode: Mode, mode: Mode,
@ -355,7 +355,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.unwrap() .unwrap()
} }
fn probe_op<OP, R>( pub(crate) fn probe_op<OP, R>(
&'a self, &'a self,
span: Span, span: Span,
mode: Mode, mode: Mode,
@ -1750,7 +1750,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
/// Similarly to `probe_for_return_type`, this method attempts to find the best matching /// Similarly to `probe_for_return_type`, this method attempts to find the best matching
/// candidate method where the method name may have been misspelled. Similarly to other /// candidate method where the method name may have been misspelled. Similarly to other
/// edit distance based suggestions, we provide at most one such suggestion. /// edit distance based suggestions, we provide at most one such suggestion.
fn probe_for_similar_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> { pub(crate) fn probe_for_similar_candidate(
&mut self,
) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
debug!("probing for method names similar to {:?}", self.method_name); debug!("probing for method names similar to {:?}", self.method_name);
self.probe(|_| { self.probe(|_| {
@ -1942,7 +1944,21 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id); let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
let attrs = self.fcx.tcx.hir().attrs(hir_id); let attrs = self.fcx.tcx.hir().attrs(hir_id);
for attr in attrs { for attr in attrs {
let sym::doc = attr.name_or_empty() else { if sym::doc == attr.name_or_empty() {
} else if sym::rustc_confusables == attr.name_or_empty() {
let Some(confusables) = attr.meta_item_list() else {
continue;
};
// #[rustc_confusables("foo", "bar"))]
for n in confusables {
if let Some(lit) = n.lit()
&& name.as_str() == lit.symbol.as_str()
{
return true;
}
}
continue;
} else {
continue; continue;
}; };
let Some(values) = attr.meta_item_list() else { let Some(values) = attr.meta_item_list() else {

View File

@ -23,6 +23,7 @@ use rustc_hir::PatKind::Binding;
use rustc_hir::PathSegment; use rustc_hir::PathSegment;
use rustc_hir::{ExprKind, Node, QPath}; use rustc_hir::{ExprKind, Node, QPath};
use rustc_infer::infer::{ use rustc_infer::infer::{
self,
type_variable::{TypeVariableOrigin, TypeVariableOriginKind}, type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
RegionVariableOrigin, RegionVariableOrigin,
}; };
@ -1234,7 +1235,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
label_span_not_found(&mut err); label_span_not_found(&mut err);
} }
let confusable_suggested = self.confusable_method_name(&mut err, rcvr_ty, item_name, None); let confusable_suggested = self.confusable_method_name(
&mut err,
rcvr_ty,
item_name,
args.map(|args| {
args.iter()
.map(|expr| {
self.node_ty_opt(expr.hir_id).unwrap_or_else(|| {
self.next_ty_var(TypeVariableOrigin {
kind: TypeVariableOriginKind::MiscVariable,
span: expr.span,
})
})
})
.collect()
}),
);
// Don't suggest (for example) `expr.field.clone()` if `expr.clone()` // Don't suggest (for example) `expr.field.clone()` if `expr.clone()`
// can't be called due to `typeof(expr): Clone` not holding. // can't be called due to `typeof(expr): Clone` not holding.
@ -1421,9 +1438,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err: &mut Diagnostic, err: &mut Diagnostic,
rcvr_ty: Ty<'tcx>, rcvr_ty: Ty<'tcx>,
item_name: Ident, item_name: Ident,
args: Option<Vec<Ty<'tcx>>>, call_args: Option<Vec<Ty<'tcx>>>,
) -> Option<Symbol> { ) -> Option<Symbol> {
if let ty::Adt(adt, _) = rcvr_ty.kind() { if let ty::Adt(adt, adt_args) = rcvr_ty.kind() {
for inherent_impl_did in self.tcx.inherent_impls(adt.did()).into_iter().flatten() { for inherent_impl_did in self.tcx.inherent_impls(adt.did()).into_iter().flatten() {
for inherent_method in for inherent_method in
self.tcx.associated_items(inherent_impl_did).in_definition_order() self.tcx.associated_items(inherent_impl_did).in_definition_order()
@ -1432,22 +1449,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables) self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables)
&& let Some(candidates) = parse_confusables(attr) && let Some(candidates) = parse_confusables(attr)
&& candidates.contains(&item_name.name) && candidates.contains(&item_name.name)
&& let ty::AssocKind::Fn = inherent_method.kind
{ {
let mut matches_args = args.is_none(); let args =
if let ty::AssocKind::Fn = inherent_method.kind ty::GenericArgs::identity_for_item(self.tcx, inherent_method.def_id)
&& let Some(ref args) = args .rebase_onto(
{ self.tcx,
let fn_sig = inherent_method.container_id(self.tcx),
self.tcx.fn_sig(inherent_method.def_id).instantiate_identity(); adt_args,
matches_args = fn_sig );
.inputs() let fn_sig =
.skip_binder() self.tcx.fn_sig(inherent_method.def_id).instantiate(self.tcx, args);
let fn_sig = self.instantiate_binder_with_fresh_vars(
item_name.span,
infer::FnCall,
fn_sig,
);
if let Some(ref args) = call_args
&& fn_sig.inputs()[1..]
.iter() .iter()
.skip(1)
.zip(args.into_iter()) .zip(args.into_iter())
.all(|(expected, found)| self.can_coerce(*expected, *found)); .all(|(expected, found)| self.can_coerce(*expected, *found))
} && fn_sig.inputs()[1..].len() == args.len()
if matches_args { {
err.span_suggestion_verbose( err.span_suggestion_verbose(
item_name.span, item_name.span,
format!("you might have meant to use `{}`", inherent_method.name), format!("you might have meant to use `{}`", inherent_method.name),
@ -1455,6 +1479,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MaybeIncorrect, Applicability::MaybeIncorrect,
); );
return Some(inherent_method.name); return Some(inherent_method.name);
} else if let None = call_args {
err.span_note(
self.tcx.def_span(inherent_method.def_id),
format!(
"you might have meant to use method `{}`",
inherent_method.name,
),
);
return Some(inherent_method.name);
} }
} }
} }

View File

@ -17,6 +17,8 @@ fn main() {
x.size(); //~ ERROR E0599 x.size(); //~ ERROR E0599
//~^ HELP you might have meant to use `len` //~^ HELP you might have meant to use `len`
//~| HELP there is a method with a similar name //~| HELP there is a method with a similar name
x.append(42); //~ ERROR E0308
//~^ HELP you might have meant to use `push`
String::new().push(""); //~ ERROR E0308 String::new().push(""); //~ ERROR E0308
//~^ HELP you might have meant to use `push_str` //~^ HELP you might have meant to use `push_str`
String::new().append(""); //~ ERROR E0599 String::new().append(""); //~ ERROR E0599

View File

@ -58,7 +58,24 @@ LL | x.resize();
| ~~~~~~ | ~~~~~~
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/rustc_confusables_std_cases.rs:20:24 --> $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);
| ~~~~
error[E0308]: mismatched types
--> $DIR/rustc_confusables_std_cases.rs:22:24
| |
LL | String::new().push(""); LL | String::new().push("");
| ---- ^^ expected `char`, found `&str` | ---- ^^ expected `char`, found `&str`
@ -73,7 +90,7 @@ LL | String::new().push_str("");
| ~~~~~~~~ | ~~~~~~~~
error[E0599]: no method named `append` found for struct `String` in the current scope error[E0599]: no method named `append` found for struct `String` in the current scope
--> $DIR/rustc_confusables_std_cases.rs:22:19 --> $DIR/rustc_confusables_std_cases.rs:24:19
| |
LL | String::new().append(""); LL | String::new().append("");
| ^^^^^^ method not found in `String` | ^^^^^^ method not found in `String`
@ -83,7 +100,7 @@ help: you might have meant to use `push_str`
LL | String::new().push_str(""); LL | String::new().push_str("");
| ~~~~~~~~ | ~~~~~~~~
error: aborting due to 7 previous errors error: aborting due to 8 previous errors
Some errors have detailed explanations: E0308, E0599. Some errors have detailed explanations: E0308, E0599.
For more information about an error, try `rustc --explain E0308`. For more information about an error, try `rustc --explain E0308`.