Add hint for unresolved associated trait items if the trait has a single item
This commit is contained in:
parent
6be8a06bcf
commit
d0d4947775
7 changed files with 172 additions and 28 deletions
|
@ -38,14 +38,25 @@ crate type Suggestion = (Vec<(Span, String)>, String, Applicability);
|
||||||
/// similarly named label and whether or not it is reachable.
|
/// similarly named label and whether or not it is reachable.
|
||||||
crate type LabelSuggestion = (Ident, bool);
|
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 {
|
crate struct TypoSuggestion {
|
||||||
pub candidate: Symbol,
|
pub candidate: Symbol,
|
||||||
pub res: Res,
|
pub res: Res,
|
||||||
|
pub target: SuggestionTarget,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypoSuggestion {
|
impl TypoSuggestion {
|
||||||
crate fn from_res(candidate: Symbol, res: Res) -> TypoSuggestion {
|
crate fn typo_from_res(candidate: Symbol, res: Res) -> TypoSuggestion {
|
||||||
TypoSuggestion { candidate, res }
|
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 {
|
if let Some(binding) = resolution.borrow().binding {
|
||||||
let res = binding.res();
|
let res = binding.res();
|
||||||
if filter_fn(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)
|
.get(&expn_id)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.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(
|
suggestions.extend(
|
||||||
ext.helper_attrs
|
ext.helper_attrs
|
||||||
.iter()
|
.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() {
|
if let MacroRulesScope::Binding(macro_rules_binding) = macro_rules_scope.get() {
|
||||||
let res = macro_rules_binding.binding.res();
|
let res = macro_rules_binding.binding.res();
|
||||||
if filter_fn(res) {
|
if filter_fn(res) {
|
||||||
suggestions
|
suggestions.push(TypoSuggestion::typo_from_res(
|
||||||
.push(TypoSuggestion::from_res(macro_rules_binding.ident.name, res))
|
macro_rules_binding.ident.name,
|
||||||
|
res,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -671,7 +684,7 @@ impl<'a> Resolver<'a> {
|
||||||
suggestions.extend(
|
suggestions.extend(
|
||||||
this.registered_attrs
|
this.registered_attrs
|
||||||
.iter()
|
.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(
|
suggestions.extend(this.macro_use_prelude.iter().filter_map(
|
||||||
|(name, binding)| {
|
|(name, binding)| {
|
||||||
let res = binding.res();
|
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(
|
suggestions.extend(
|
||||||
BUILTIN_ATTRIBUTES
|
BUILTIN_ATTRIBUTES
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, ..)| TypoSuggestion::from_res(*name, res)),
|
.map(|(name, ..)| TypoSuggestion::typo_from_res(*name, res)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scope::ExternPrelude => {
|
Scope::ExternPrelude => {
|
||||||
suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, _)| {
|
suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, _)| {
|
||||||
let res = Res::Def(DefKind::Mod, DefId::local(CRATE_DEF_INDEX));
|
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 => {
|
Scope::ToolPrelude => {
|
||||||
|
@ -704,7 +717,7 @@ impl<'a> Resolver<'a> {
|
||||||
suggestions.extend(
|
suggestions.extend(
|
||||||
this.registered_tools
|
this.registered_tools
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ident| TypoSuggestion::from_res(ident.name, res)),
|
.map(|ident| TypoSuggestion::typo_from_res(ident.name, res)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Scope::StdLibPrelude => {
|
Scope::StdLibPrelude => {
|
||||||
|
@ -721,7 +734,7 @@ impl<'a> Resolver<'a> {
|
||||||
Scope::BuiltinTypes => {
|
Scope::BuiltinTypes => {
|
||||||
suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| {
|
suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| {
|
||||||
let res = Res::PrimTy(*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;
|
return false;
|
||||||
}
|
}
|
||||||
|
let prefix = match suggestion.target {
|
||||||
|
SuggestionTarget::SimilarlyNamed => "similarly named ",
|
||||||
|
SuggestionTarget::SingleItem => "",
|
||||||
|
};
|
||||||
|
|
||||||
err.span_label(
|
err.span_label(
|
||||||
self.session.source_map().guess_head_span(def_span),
|
self.session.source_map().guess_head_span(def_span),
|
||||||
&format!(
|
&format!(
|
||||||
"similarly named {} `{}` defined here",
|
"{}{} `{}` defined here",
|
||||||
|
prefix,
|
||||||
suggestion.res.descr(),
|
suggestion.res.descr(),
|
||||||
suggestion.candidate.as_str(),
|
suggestion.candidate.as_str(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let msg = format!(
|
let msg = match suggestion.target {
|
||||||
"{} {} with a similar name exists",
|
SuggestionTarget::SimilarlyNamed => format!(
|
||||||
suggestion.res.article(),
|
"{} {} with a similar name exists",
|
||||||
suggestion.res.descr()
|
suggestion.res.article(),
|
||||||
);
|
suggestion.res.descr()
|
||||||
|
),
|
||||||
|
SuggestionTarget::SingleItem => {
|
||||||
|
format!("maybe you meant this {}", suggestion.res.descr())
|
||||||
|
}
|
||||||
|
};
|
||||||
err.span_suggestion(
|
err.span_suggestion(
|
||||||
span,
|
span,
|
||||||
&msg,
|
&msg,
|
||||||
|
|
|
@ -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 {
|
if fallback {
|
||||||
// Fallback label.
|
// Fallback label.
|
||||||
|
@ -585,6 +589,40 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
(err, candidates)
|
(err, candidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_single_associated_item(
|
||||||
|
&mut self,
|
||||||
|
path: &[Segment],
|
||||||
|
span: Span,
|
||||||
|
source: &PathSource<'_>,
|
||||||
|
filter_fn: &impl Fn(Res) -> bool,
|
||||||
|
) -> Option<TypoSuggestion> {
|
||||||
|
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 <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
|
/// Given `where <T as Bar>::Baz: String`, suggest `where T: Bar<Baz = String>`.
|
||||||
fn restrict_assoc_type_in_where_clause(
|
fn restrict_assoc_type_in_where_clause(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -1208,7 +1246,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
// Locals and type parameters
|
// Locals and type parameters
|
||||||
for (ident, &res) in &rib.bindings {
|
for (ident, &res) in &rib.bindings {
|
||||||
if filter_fn(res) {
|
if filter_fn(res) {
|
||||||
names.push(TypoSuggestion::from_res(ident.name, res));
|
names.push(TypoSuggestion::typo_from_res(ident.name, res));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Items in scope
|
// Items in scope
|
||||||
|
@ -1231,7 +1269,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
);
|
);
|
||||||
|
|
||||||
if filter_fn(crate_mod) {
|
if filter_fn(crate_mod) {
|
||||||
Some(TypoSuggestion::from_res(ident.name, crate_mod))
|
Some(TypoSuggestion::typo_from_res(
|
||||||
|
ident.name, crate_mod,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1249,11 +1289,9 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
}
|
}
|
||||||
// Add primitive types to the mix
|
// Add primitive types to the mix
|
||||||
if filter_fn(Res::PrimTy(PrimTy::Bool)) {
|
if filter_fn(Res::PrimTy(PrimTy::Bool)) {
|
||||||
names.extend(
|
names.extend(PrimTy::ALL.iter().map(|prim_ty| {
|
||||||
PrimTy::ALL.iter().map(|prim_ty| {
|
TypoSuggestion::typo_from_res(prim_ty.name(), Res::PrimTy(*prim_ty))
|
||||||
TypoSuggestion::from_res(prim_ty.name(), Res::PrimTy(*prim_ty))
|
}))
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Search in module.
|
// Search in module.
|
||||||
|
|
22
src/test/ui/associated-item/issue-87638.fixed
Normal file
22
src/test/ui/associated-item/issue-87638.fixed
Normal file
|
@ -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 _: <S as Trait>::Target; //~ ERROR cannot find associated type `Output` in trait `Trait`
|
||||||
|
//~^ HELP maybe you meant this associated type
|
||||||
|
|
||||||
|
let _ = <S as Trait>::FOO; //~ ERROR cannot find method or associated constant `BAR` in trait `Trait`
|
||||||
|
//~^ HELP maybe you meant this associated constant
|
||||||
|
}
|
22
src/test/ui/associated-item/issue-87638.rs
Normal file
22
src/test/ui/associated-item/issue-87638.rs
Normal file
|
@ -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 _: <S as Trait>::Output; //~ ERROR cannot find associated type `Output` in trait `Trait`
|
||||||
|
//~^ HELP maybe you meant this associated type
|
||||||
|
|
||||||
|
let _ = <S as Trait>::BAR; //~ ERROR cannot find method or associated constant `BAR` in trait `Trait`
|
||||||
|
//~^ HELP maybe you meant this associated constant
|
||||||
|
}
|
27
src/test/ui/associated-item/issue-87638.stderr
Normal file
27
src/test/ui/associated-item/issue-87638.stderr
Normal file
|
@ -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 _: <S as Trait>::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 _ = <S as Trait>::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`.
|
|
@ -1,8 +1,13 @@
|
||||||
error[E0576]: cannot find associated type `X` in trait `A`
|
error[E0576]: cannot find associated type `X` in trait `A`
|
||||||
--> $DIR/issue-22037.rs:3:33
|
--> $DIR/issue-22037.rs:3:33
|
||||||
|
|
|
|
||||||
|
LL | type Output;
|
||||||
|
| ------------ associated type `Output` defined here
|
||||||
LL | fn a(&self) -> <Self as A>::X;
|
LL | fn a(&self) -> <Self as A>::X;
|
||||||
| ^ not found in `A`
|
| ^
|
||||||
|
| |
|
||||||
|
| not found in `A`
|
||||||
|
| help: maybe you meant this associated type: `Output`
|
||||||
|
|
||||||
error: aborting due to previous error
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
error[E0576]: cannot find associated type `Dst` in trait `From`
|
error[E0576]: cannot find associated type `Dst` in trait `From`
|
||||||
--> $DIR/issue-19883.rs:9:30
|
--> $DIR/issue-19883.rs:9:30
|
||||||
|
|
|
|
||||||
|
LL | type Output;
|
||||||
|
| ------------ associated type `Output` defined here
|
||||||
|
...
|
||||||
LL | <Dst as From<Self>>::Dst
|
LL | <Dst as From<Self>>::Dst
|
||||||
| ^^^ not found in `From`
|
| ^^^
|
||||||
|
| |
|
||||||
|
| not found in `From`
|
||||||
|
| help: maybe you meant this associated type: `Output`
|
||||||
|
|
||||||
error: aborting due to previous error
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue