Skip to content

Commit c461e3b

Browse files
authored
Rollup merge of rust-lang#120730 - estebank:confusable-api, r=oli-obk
Provide suggestions through `rustc_confusables` annotations Help with common API confusion, like asking for `push` when the data structure really has `append`. ``` error[E0599]: no method named `size` found for struct `Vec<{integer}>` in the current scope --> $DIR/rustc_confusables_std_cases.rs:17:7 | LL | x.size(); | ^^^^ | help: you might have meant to use `len` | LL | x.len(); | ~~~ help: there is a method with a similar name | LL | x.resize(); | ~~~~~~ ``` Fix rust-lang#59450 (we can open subsequent tickets for specific cases). Fix rust-lang#108437: ``` error[E0599]: `Option<{integer}>` is not an iterator --> f101.rs:3:9 | 3 | opt.flat_map(|val| Some(val)); | ^^^^^^^^ `Option<{integer}>` is not an iterator | ::: /home/gh-estebank/rust/library/core/src/option.rs:571:1 | 571 | pub enum Option<T> { | ------------------ doesn't satisfy `Option<{integer}>: Iterator` | = note: the following trait bounds were not satisfied: `Option<{integer}>: Iterator` which is required by `&mut Option<{integer}>: Iterator` help: you might have meant to use `and_then` | 3 | opt.and_then(|val| Some(val)); | ~~~~~~~~ ``` On type error of method call arguments, look at confusables for suggestion. Fix rust-lang#87212: ``` error[E0308]: mismatched types --> f101.rs:8:18 | 8 | stuff.append(Thing); | ------ ^^^^^ expected `&mut Vec<Thing>`, found `Thing` | | | arguments to this method are incorrect | = note: expected mutable reference `&mut Vec<Thing>` found struct `Thing` note: method defined here --> /home/gh-estebank/rust/library/alloc/src/vec/mod.rs:2025:12 | 2025 | pub fn append(&mut self, other: &mut Self) { | ^^^^^^ help: you might have meant to use `push` | 8 | stuff.push(Thing); | ~~~~ ```
2 parents 1f1d765 + 9a950a5 commit c461e3b

File tree

70 files changed

+826
-192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+826
-192
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+119-7
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::*};
@@ -451,7 +455,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
451455
call_expr: &'tcx hir::Expr<'tcx>,
452456
) -> ErrorGuaranteed {
453457
// Next, let's construct the error
454-
let (error_span, full_call_span, call_name, is_method) = match &call_expr.kind {
458+
let (error_span, call_ident, full_call_span, call_name, is_method) = match &call_expr.kind {
455459
hir::ExprKind::Call(
456460
hir::Expr { hir_id, span, kind: hir::ExprKind::Path(qpath), .. },
457461
_,
@@ -463,20 +467,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
463467
CtorOf::Struct => "struct",
464468
CtorOf::Variant => "enum variant",
465469
};
466-
(call_span, *span, name, false)
470+
(call_span, None, *span, name, false)
467471
} else {
468-
(call_span, *span, "function", false)
472+
(call_span, None, *span, "function", false)
469473
}
470474
}
471-
hir::ExprKind::Call(hir::Expr { span, .. }, _) => (call_span, *span, "function", false),
475+
hir::ExprKind::Call(hir::Expr { span, .. }, _) => {
476+
(call_span, None, *span, "function", false)
477+
}
472478
hir::ExprKind::MethodCall(path_segment, _, _, span) => {
473479
let ident_span = path_segment.ident.span;
474480
let ident_span = if let Some(args) = path_segment.args {
475481
ident_span.with_hi(args.span_ext.hi())
476482
} else {
477483
ident_span
478484
};
479-
(*span, ident_span, "method", true)
485+
(*span, Some(path_segment.ident), ident_span, "method", true)
480486
}
481487
k => span_bug!(call_span, "checking argument types on a non-call: `{:?}`", k),
482488
};
@@ -530,6 +536,104 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
530536
let callee_ty = callee_expr
531537
.and_then(|callee_expr| self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr));
532538

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 args = self.infcx.fresh_args_for_item(call_name.span, assoc.def_id);
557+
let fn_sig = tcx.fn_sig(assoc.def_id).instantiate(tcx, args);
558+
let fn_sig =
559+
self.instantiate_binder_with_fresh_vars(call_name.span, FnCall, fn_sig);
560+
Some((assoc, fn_sig));
561+
}
562+
None
563+
};
564+
565+
let suggest_confusable = |err: &mut Diagnostic| {
566+
let Some(call_name) = call_ident else {
567+
return;
568+
};
569+
let Some(callee_ty) = callee_ty else {
570+
return;
571+
};
572+
let input_types: Vec<Ty<'_>> = provided_arg_tys.iter().map(|(ty, _)| *ty).collect();
573+
// Check for other methods in the following order
574+
// - methods marked as `rustc_confusables` with the provided arguments
575+
// - methods with the same argument type/count and short levenshtein distance
576+
// - methods marked as `rustc_confusables` (done)
577+
// - methods with short levenshtein distance
578+
579+
// Look for commonly confusable method names considering arguments.
580+
if let Some(_name) = self.confusable_method_name(
581+
err,
582+
callee_ty.peel_refs(),
583+
call_name,
584+
Some(input_types.clone()),
585+
) {
586+
return;
587+
}
588+
// Look for method names with short levenshtein distance, considering arguments.
589+
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
590+
&& fn_sig.inputs()[1..]
591+
.iter()
592+
.zip(input_types.iter())
593+
.all(|(expected, found)| self.can_coerce(*expected, *found))
594+
&& fn_sig.inputs()[1..].len() == input_types.len()
595+
{
596+
err.span_suggestion_verbose(
597+
call_name.span,
598+
format!("you might have meant to use `{}`", assoc.name),
599+
assoc.name,
600+
Applicability::MaybeIncorrect,
601+
);
602+
return;
603+
}
604+
// Look for commonly confusable method names disregarding arguments.
605+
if let Some(_name) =
606+
self.confusable_method_name(err, callee_ty.peel_refs(), call_name, None)
607+
{
608+
return;
609+
}
610+
// Look for similarly named methods with levenshtein distance with the right
611+
// number of arguments.
612+
if let Some((assoc, fn_sig)) = similar_assoc(call_name)
613+
&& fn_sig.inputs()[1..].len() == input_types.len()
614+
{
615+
err.span_note(
616+
tcx.def_span(assoc.def_id),
617+
format!(
618+
"there's is a method with similar name `{}`, but the arguments don't match",
619+
assoc.name,
620+
),
621+
);
622+
return;
623+
}
624+
// Fallthrough: look for similarly named methods with levenshtein distance.
625+
if let Some((assoc, _)) = similar_assoc(call_name) {
626+
err.span_note(
627+
tcx.def_span(assoc.def_id),
628+
format!(
629+
"there's is a method with similar name `{}`, but their argument count \
630+
doesn't match",
631+
assoc.name,
632+
),
633+
);
634+
return;
635+
}
636+
};
533637
// A "softer" version of the `demand_compatible`, which checks types without persisting them,
534638
// and treats error types differently
535639
// This will allow us to "probe" for other argument orders that would likely have been correct
@@ -694,6 +798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
694798
Some(mismatch_idx),
695799
is_method,
696800
);
801+
suggest_confusable(&mut err);
697802
return err.emit();
698803
}
699804
}
@@ -718,7 +823,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
718823
if cfg!(debug_assertions) {
719824
span_bug!(error_span, "expected errors from argument matrix");
720825
} else {
721-
return tcx.dcx().emit_err(errors::ArgMismatchIndeterminate { span: error_span });
826+
let mut err =
827+
tcx.dcx().create_err(errors::ArgMismatchIndeterminate { span: error_span });
828+
suggest_confusable(&mut err);
829+
return err.emit();
722830
}
723831
}
724832

@@ -733,7 +841,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
733841
let trace =
734842
mk_trace(provided_span, formal_and_expected_inputs[*expected_idx], provided_ty);
735843
if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308) {
736-
reported = Some(self.err_ctxt().report_and_explain_type_error(trace, *e).emit());
844+
let mut err = self.err_ctxt().report_and_explain_type_error(trace, *e);
845+
suggest_confusable(&mut err);
846+
reported = Some(err.emit());
737847
return false;
738848
}
739849
true
@@ -801,6 +911,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
801911
Some(expected_idx.as_usize()),
802912
is_method,
803913
);
914+
suggest_confusable(&mut err);
804915
return err.emit();
805916
}
806917

@@ -828,6 +939,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
828939
.with_code(err_code.to_owned())
829940
};
830941

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

compiler/rustc_hir_typeck/src/method/probe.rs

+22-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(|_| {
@@ -1767,6 +1769,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
17671769
);
17681770
pcx.allow_similar_names = true;
17691771
pcx.assemble_inherent_candidates();
1772+
pcx.assemble_extension_candidates_for_all_traits();
17701773

17711774
let method_names = pcx.candidate_method_names(|_| true);
17721775
pcx.allow_similar_names = false;
@@ -1776,6 +1779,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
17761779
pcx.reset();
17771780
pcx.method_name = Some(method_name);
17781781
pcx.assemble_inherent_candidates();
1782+
pcx.assemble_extension_candidates_for_all_traits();
17791783
pcx.pick_core().and_then(|pick| pick.ok()).map(|pick| pick.item)
17801784
})
17811785
.collect();
@@ -1943,7 +1947,21 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
19431947
let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
19441948
let attrs = self.fcx.tcx.hir().attrs(hir_id);
19451949
for attr in attrs {
1946-
let sym::doc = attr.name_or_empty() else {
1950+
if sym::doc == attr.name_or_empty() {
1951+
} else if sym::rustc_confusables == attr.name_or_empty() {
1952+
let Some(confusables) = attr.meta_item_list() else {
1953+
continue;
1954+
};
1955+
// #[rustc_confusables("foo", "bar"))]
1956+
for n in confusables {
1957+
if let Some(lit) = n.lit()
1958+
&& name.as_str() == lit.symbol.as_str()
1959+
{
1960+
return true;
1961+
}
1962+
}
1963+
continue;
1964+
} else {
19471965
continue;
19481966
};
19491967
let Some(values) = attr.meta_item_list() else {

0 commit comments

Comments
 (0)