diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 7439cd9a0fe3d..0a5da653fab07 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -38,14 +38,25 @@ crate type Suggestion = (Vec<(Span, String)>, String, Applicability); /// similarly named label and whether or not it is reachable. crate type LabelSuggestion = (Ident, bool); +crate enum SuggestionTarget { + /// The target has a similar name as the name used by the programmer (probably a typo) + SimilarlyNamed, + /// The target is the only valid item that can be used in the corresponding context + SingleItem, +} + crate struct TypoSuggestion { pub candidate: Symbol, pub res: Res, + pub target: SuggestionTarget, } impl TypoSuggestion { - crate fn from_res(candidate: Symbol, res: Res) -> TypoSuggestion { - TypoSuggestion { candidate, res } + crate fn typo_from_res(candidate: Symbol, res: Res) -> TypoSuggestion { + Self { candidate, res, target: SuggestionTarget::SimilarlyNamed } + } + crate fn single_item_from_res(candidate: Symbol, res: Res) -> TypoSuggestion { + Self { candidate, res, target: SuggestionTarget::SingleItem } } } @@ -80,7 +91,7 @@ impl<'a> Resolver<'a> { if let Some(binding) = resolution.borrow().binding { let res = binding.res(); if filter_fn(res) { - names.push(TypoSuggestion::from_res(key.ident.name, res)); + names.push(TypoSuggestion::typo_from_res(key.ident.name, res)); } } } @@ -623,7 +634,7 @@ impl<'a> Resolver<'a> { .get(&expn_id) .into_iter() .flatten() - .map(|ident| TypoSuggestion::from_res(ident.name, res)), + .map(|ident| TypoSuggestion::typo_from_res(ident.name, res)), ); } } @@ -642,7 +653,7 @@ impl<'a> Resolver<'a> { suggestions.extend( ext.helper_attrs .iter() - .map(|name| TypoSuggestion::from_res(*name, res)), + .map(|name| TypoSuggestion::typo_from_res(*name, res)), ); } } @@ -652,8 +663,10 @@ impl<'a> Resolver<'a> { if let MacroRulesScope::Binding(macro_rules_binding) = macro_rules_scope.get() { let res = macro_rules_binding.binding.res(); if filter_fn(res) { - suggestions - .push(TypoSuggestion::from_res(macro_rules_binding.ident.name, res)) + suggestions.push(TypoSuggestion::typo_from_res( + macro_rules_binding.ident.name, + res, + )) } } } @@ -671,7 +684,7 @@ impl<'a> Resolver<'a> { suggestions.extend( this.registered_attrs .iter() - .map(|ident| TypoSuggestion::from_res(ident.name, res)), + .map(|ident| TypoSuggestion::typo_from_res(ident.name, res)), ); } } @@ -679,7 +692,7 @@ impl<'a> Resolver<'a> { suggestions.extend(this.macro_use_prelude.iter().filter_map( |(name, binding)| { let res = binding.res(); - filter_fn(res).then_some(TypoSuggestion::from_res(*name, res)) + filter_fn(res).then_some(TypoSuggestion::typo_from_res(*name, res)) }, )); } @@ -689,14 +702,14 @@ impl<'a> Resolver<'a> { suggestions.extend( BUILTIN_ATTRIBUTES .iter() - .map(|(name, ..)| TypoSuggestion::from_res(*name, res)), + .map(|(name, ..)| TypoSuggestion::typo_from_res(*name, res)), ); } } Scope::ExternPrelude => { suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, _)| { let res = Res::Def(DefKind::Mod, DefId::local(CRATE_DEF_INDEX)); - filter_fn(res).then_some(TypoSuggestion::from_res(ident.name, res)) + filter_fn(res).then_some(TypoSuggestion::typo_from_res(ident.name, res)) })); } Scope::ToolPrelude => { @@ -704,7 +717,7 @@ impl<'a> Resolver<'a> { suggestions.extend( this.registered_tools .iter() - .map(|ident| TypoSuggestion::from_res(ident.name, res)), + .map(|ident| TypoSuggestion::typo_from_res(ident.name, res)), ); } Scope::StdLibPrelude => { @@ -721,7 +734,7 @@ impl<'a> Resolver<'a> { Scope::BuiltinTypes => { suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| { let res = Res::PrimTy(*prim_ty); - filter_fn(res).then_some(TypoSuggestion::from_res(prim_ty.name(), res)) + filter_fn(res).then_some(TypoSuggestion::typo_from_res(prim_ty.name(), res)) })) } } @@ -993,20 +1006,31 @@ impl<'a> Resolver<'a> { // | ^ return false; } + let prefix = match suggestion.target { + SuggestionTarget::SimilarlyNamed => "similarly named ", + SuggestionTarget::SingleItem => "", + }; + err.span_label( self.session.source_map().guess_head_span(def_span), &format!( - "similarly named {} `{}` defined here", + "{}{} `{}` defined here", + prefix, suggestion.res.descr(), suggestion.candidate.as_str(), ), ); } - let msg = format!( - "{} {} with a similar name exists", - suggestion.res.article(), - suggestion.res.descr() - ); + let msg = match suggestion.target { + SuggestionTarget::SimilarlyNamed => format!( + "{} {} with a similar name exists", + suggestion.res.article(), + suggestion.res.descr() + ), + SuggestionTarget::SingleItem => { + format!("maybe you meant this {}", suggestion.res.descr()) + } + }; err.span_suggestion( span, &msg, diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index e3ab858541af4..7c97a17b45406 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -541,6 +541,10 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { } _ => {} } + + // If the trait has a single item (which wasn't matched by Levenshtein), suggest it + let suggestion = self.get_single_associated_item(&path, span, &source, is_expected); + self.r.add_typo_suggestion(&mut err, suggestion, ident_span); } if fallback { // Fallback label. @@ -585,6 +589,40 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { (err, candidates) } + fn get_single_associated_item( + &mut self, + path: &[Segment], + span: Span, + source: &PathSource<'_>, + filter_fn: &impl Fn(Res) -> bool, + ) -> Option { + if let crate::PathSource::TraitItem(_) = source { + let mod_path = &path[..path.len() - 1]; + if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(mod_path, None, false, span, CrateLint::No) + { + let resolutions = self.r.resolutions(module).borrow(); + let targets: Vec<_> = + resolutions + .iter() + .filter_map(|(key, resolution)| { + resolution.borrow().binding.map(|binding| binding.res()).and_then( + |res| if filter_fn(res) { Some((key, res)) } else { None }, + ) + }) + .collect(); + if targets.len() == 1 { + let target = targets[0]; + return Some(TypoSuggestion::single_item_from_res( + target.0.ident.name, + target.1, + )); + } + } + } + None + } + /// Given `where ::Baz: String`, suggest `where T: Bar`. fn restrict_assoc_type_in_where_clause( &mut self, @@ -1208,7 +1246,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { // Locals and type parameters for (ident, &res) in &rib.bindings { if filter_fn(res) { - names.push(TypoSuggestion::from_res(ident.name, res)); + names.push(TypoSuggestion::typo_from_res(ident.name, res)); } } // Items in scope @@ -1231,7 +1269,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { ); if filter_fn(crate_mod) { - Some(TypoSuggestion::from_res(ident.name, crate_mod)) + Some(TypoSuggestion::typo_from_res( + ident.name, crate_mod, + )) } else { None } @@ -1249,11 +1289,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> { } // Add primitive types to the mix if filter_fn(Res::PrimTy(PrimTy::Bool)) { - names.extend( - PrimTy::ALL.iter().map(|prim_ty| { - TypoSuggestion::from_res(prim_ty.name(), Res::PrimTy(*prim_ty)) - }), - ) + names.extend(PrimTy::ALL.iter().map(|prim_ty| { + TypoSuggestion::typo_from_res(prim_ty.name(), Res::PrimTy(*prim_ty)) + })) } } else { // Search in module. diff --git a/src/test/ui/associated-item/issue-87638.fixed b/src/test/ui/associated-item/issue-87638.fixed new file mode 100644 index 0000000000000..b689777685f3d --- /dev/null +++ b/src/test/ui/associated-item/issue-87638.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +trait Trait { + const FOO: usize; + + type Target; +} + +struct S; + +impl Trait for S { + const FOO: usize = 0; + type Target = (); +} + +fn main() { + let _: ::Target; //~ ERROR cannot find associated type `Output` in trait `Trait` + //~^ HELP maybe you meant this associated type + + let _ = ::FOO; //~ ERROR cannot find method or associated constant `BAR` in trait `Trait` + //~^ HELP maybe you meant this associated constant +} diff --git a/src/test/ui/associated-item/issue-87638.rs b/src/test/ui/associated-item/issue-87638.rs new file mode 100644 index 0000000000000..5a60b20fdf382 --- /dev/null +++ b/src/test/ui/associated-item/issue-87638.rs @@ -0,0 +1,22 @@ +// run-rustfix + +trait Trait { + const FOO: usize; + + type Target; +} + +struct S; + +impl Trait for S { + const FOO: usize = 0; + type Target = (); +} + +fn main() { + let _: ::Output; //~ ERROR cannot find associated type `Output` in trait `Trait` + //~^ HELP maybe you meant this associated type + + let _ = ::BAR; //~ ERROR cannot find method or associated constant `BAR` in trait `Trait` + //~^ HELP maybe you meant this associated constant +} diff --git a/src/test/ui/associated-item/issue-87638.stderr b/src/test/ui/associated-item/issue-87638.stderr new file mode 100644 index 0000000000000..cf6083444b0e6 --- /dev/null +++ b/src/test/ui/associated-item/issue-87638.stderr @@ -0,0 +1,27 @@ +error[E0576]: cannot find associated type `Output` in trait `Trait` + --> $DIR/issue-87638.rs:17:26 + | +LL | type Target; + | ------------ associated type `Target` defined here +... +LL | let _: ::Output; + | ^^^^^^ + | | + | not found in `Trait` + | help: maybe you meant this associated type: `Target` + +error[E0576]: cannot find method or associated constant `BAR` in trait `Trait` + --> $DIR/issue-87638.rs:20:27 + | +LL | const FOO: usize; + | ----------------- associated constant `FOO` defined here +... +LL | let _ = ::BAR; + | ^^^ + | | + | not found in `Trait` + | help: maybe you meant this associated constant: `FOO` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0576`. diff --git a/src/test/ui/associated-types/issue-22037.stderr b/src/test/ui/associated-types/issue-22037.stderr index 615628558f08b..0e019f10f37f2 100644 --- a/src/test/ui/associated-types/issue-22037.stderr +++ b/src/test/ui/associated-types/issue-22037.stderr @@ -1,8 +1,13 @@ error[E0576]: cannot find associated type `X` in trait `A` --> $DIR/issue-22037.rs:3:33 | +LL | type Output; + | ------------ associated type `Output` defined here LL | fn a(&self) -> ::X; - | ^ not found in `A` + | ^ + | | + | not found in `A` + | help: maybe you meant this associated type: `Output` error: aborting due to previous error diff --git a/src/test/ui/issues/issue-19883.stderr b/src/test/ui/issues/issue-19883.stderr index e370b2ec1cb42..bd6a86b742089 100644 --- a/src/test/ui/issues/issue-19883.stderr +++ b/src/test/ui/issues/issue-19883.stderr @@ -1,8 +1,14 @@ error[E0576]: cannot find associated type `Dst` in trait `From` --> $DIR/issue-19883.rs:9:30 | +LL | type Output; + | ------------ associated type `Output` defined here +... LL | >::Dst - | ^^^ not found in `From` + | ^^^ + | | + | not found in `From` + | help: maybe you meant this associated type: `Output` error: aborting due to previous error