Skip to content

Commit f4f3335

Browse files
authored
Rollup merge of #107108 - sulami:issue-83968-doc-alias-typo-suggestions, r=compiler-errors
Consider doc(alias) when providing typo suggestions This means that ```rust impl Foo { #[doc(alias = "quux")] fn bar(&self) {} } fn main() { (Foo {}).quux(); } ``` will suggest `bar`. This currently uses the "there is a method with a similar name" help text, because the point where we choose and emit a suggestion is different from where we gather the suggestions. Changes have mainly been made to the latter. The selection code will now fall back to aliased candidates, but generally only if there is no candidate that matches based on the existing Levenshtein methodology. Fixes #83968.
2 parents 28081a6 + f908f0b commit f4f3335

File tree

5 files changed

+77
-13
lines changed

5 files changed

+77
-13
lines changed

compiler/rustc_hir_typeck/src/method/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub struct NoMatchData<'tcx> {
7676
pub unsatisfied_predicates:
7777
Vec<(ty::Predicate<'tcx>, Option<ty::Predicate<'tcx>>, Option<ObligationCause<'tcx>>)>,
7878
pub out_of_scope_traits: Vec<DefId>,
79-
pub lev_candidate: Option<ty::AssocItem>,
79+
pub similar_candidate: Option<ty::AssocItem>,
8080
pub mode: probe::Mode,
8181
}
8282

compiler/rustc_hir_typeck/src/method/probe.rs

+45-4
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
461461
static_candidates: Vec::new(),
462462
unsatisfied_predicates: Vec::new(),
463463
out_of_scope_traits: Vec::new(),
464-
lev_candidate: None,
464+
similar_candidate: None,
465465
mode,
466466
}));
467467
}
@@ -1076,13 +1076,13 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
10761076
if let Some((kind, def_id)) = private_candidate {
10771077
return Err(MethodError::PrivateMatch(kind, def_id, out_of_scope_traits));
10781078
}
1079-
let lev_candidate = self.probe_for_lev_candidate()?;
1079+
let similar_candidate = self.probe_for_similar_candidate()?;
10801080

10811081
Err(MethodError::NoMatch(NoMatchData {
10821082
static_candidates,
10831083
unsatisfied_predicates,
10841084
out_of_scope_traits,
1085-
lev_candidate,
1085+
similar_candidate,
10861086
mode: self.mode,
10871087
}))
10881088
}
@@ -1787,7 +1787,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
17871787
/// Similarly to `probe_for_return_type`, this method attempts to find the best matching
17881788
/// candidate method where the method name may have been misspelled. Similarly to other
17891789
/// Levenshtein based suggestions, we provide at most one such suggestion.
1790-
fn probe_for_lev_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
1790+
fn probe_for_similar_candidate(&mut self) -> Result<Option<ty::AssocItem>, MethodError<'tcx>> {
17911791
debug!("probing for method names similar to {:?}", self.method_name);
17921792

17931793
let steps = self.steps.clone();
@@ -1831,6 +1831,12 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
18311831
None,
18321832
)
18331833
}
1834+
.or_else(|| {
1835+
applicable_close_candidates
1836+
.iter()
1837+
.find(|cand| self.matches_by_doc_alias(cand.def_id))
1838+
.map(|cand| cand.name)
1839+
})
18341840
.unwrap();
18351841
Ok(applicable_close_candidates.into_iter().find(|method| method.name == best_name))
18361842
}
@@ -1981,6 +1987,38 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
19811987
}
19821988
}
19831989

1990+
/// Determine if the associated item withe the given DefId matches
1991+
/// the desired name via a doc alias.
1992+
fn matches_by_doc_alias(&self, def_id: DefId) -> bool {
1993+
let Some(name) = self.method_name else { return false; };
1994+
let Some(local_def_id) = def_id.as_local() else { return false; };
1995+
let hir_id = self.fcx.tcx.hir().local_def_id_to_hir_id(local_def_id);
1996+
let attrs = self.fcx.tcx.hir().attrs(hir_id);
1997+
for attr in attrs {
1998+
let sym::doc = attr.name_or_empty() else { continue; };
1999+
let Some(values) = attr.meta_item_list() else { continue; };
2000+
for v in values {
2001+
if v.name_or_empty() != sym::alias {
2002+
continue;
2003+
}
2004+
if let Some(nested) = v.meta_item_list() {
2005+
// #[doc(alias("foo", "bar"))]
2006+
for n in nested {
2007+
if let Some(lit) = n.lit() && name.as_str() == lit.symbol.as_str() {
2008+
return true;
2009+
}
2010+
}
2011+
} else if let Some(meta) = v.meta_item()
2012+
&& let Some(lit) = meta.name_value_literal()
2013+
&& name.as_str() == lit.symbol.as_str() {
2014+
// #[doc(alias = "foo")]
2015+
return true;
2016+
}
2017+
}
2018+
}
2019+
false
2020+
}
2021+
19842022
/// Finds the method with the appropriate name (or return type, as the case may be). If
19852023
/// `allow_similar_names` is set, find methods with close-matching names.
19862024
// The length of the returned iterator is nearly always 0 or 1 and this
@@ -1996,6 +2034,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
19962034
if !self.is_relevant_kind_for_mode(x.kind) {
19972035
return false;
19982036
}
2037+
if self.matches_by_doc_alias(x.def_id) {
2038+
return true;
2039+
}
19992040
match lev_distance_with_substrings(name.as_str(), x.name.as_str(), max_dist)
20002041
{
20012042
Some(d) => d > 0,

compiler/rustc_hir_typeck/src/method/suggest.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
262262
let ty_str = with_forced_trimmed_paths!(self.ty_to_string(rcvr_ty));
263263
let is_method = mode == Mode::MethodCall;
264264
let unsatisfied_predicates = &no_match_data.unsatisfied_predicates;
265-
let lev_candidate = no_match_data.lev_candidate;
265+
let similar_candidate = no_match_data.similar_candidate;
266266
let item_kind = if is_method {
267267
"method"
268268
} else if rcvr_ty.is_enum() {
@@ -937,7 +937,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
937937
// give a helping note that it has to be called as `(x.f)(...)`.
938938
if let SelfSource::MethodCall(expr) = source {
939939
if !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_name, &mut err)
940-
&& lev_candidate.is_none()
940+
&& similar_candidate.is_none()
941941
&& !custom_span_label
942942
{
943943
label_span_not_found(&mut err);
@@ -1015,20 +1015,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10151015
if fallback_span {
10161016
err.span_label(span, msg);
10171017
}
1018-
} else if let Some(lev_candidate) = lev_candidate {
1018+
} else if let Some(similar_candidate) = similar_candidate {
10191019
// Don't emit a suggestion if we found an actual method
10201020
// that had unsatisfied trait bounds
10211021
if unsatisfied_predicates.is_empty() {
1022-
let def_kind = lev_candidate.kind.as_def_kind();
1022+
let def_kind = similar_candidate.kind.as_def_kind();
10231023
// Methods are defined within the context of a struct and their first parameter is always self,
10241024
// which represents the instance of the struct the method is being called on
10251025
// Associated functions don’t take self as a parameter and
10261026
// they are not methods because they don’t have an instance of the struct to work with.
1027-
if def_kind == DefKind::AssocFn && lev_candidate.fn_has_self_parameter {
1027+
if def_kind == DefKind::AssocFn && similar_candidate.fn_has_self_parameter {
10281028
err.span_suggestion(
10291029
span,
10301030
"there is a method with a similar name",
1031-
lev_candidate.name,
1031+
similar_candidate.name,
10321032
Applicability::MaybeIncorrect,
10331033
);
10341034
} else {
@@ -1037,9 +1037,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10371037
&format!(
10381038
"there is {} {} with a similar name",
10391039
def_kind.article(),
1040-
def_kind.descr(lev_candidate.def_id),
1040+
def_kind.descr(similar_candidate.def_id),
10411041
),
1042-
lev_candidate.name,
1042+
similar_candidate.name,
10431043
Applicability::MaybeIncorrect,
10441044
);
10451045
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
struct Foo;
2+
3+
impl Foo {
4+
#[doc(alias = "quux")]
5+
fn bar(&self) {}
6+
}
7+
8+
fn main() {
9+
Foo.quux();
10+
//~^ ERROR no method named `quux` found for struct `Foo` in the current scope
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0599]: no method named `quux` found for struct `Foo` in the current scope
2+
--> $DIR/method-not-found-but-doc-alias.rs:9:9
3+
|
4+
LL | struct Foo;
5+
| ---------- method `quux` not found for this struct
6+
...
7+
LL | Foo.quux();
8+
| ^^^^ help: there is a method with a similar name: `bar`
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)