about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_resolve/src/diagnostics.rs62
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs52
-rw-r--r--src/test/ui/associated-item/issue-87638.fixed22
-rw-r--r--src/test/ui/associated-item/issue-87638.rs22
-rw-r--r--src/test/ui/associated-item/issue-87638.stderr27
-rw-r--r--src/test/ui/associated-types/issue-22037.stderr7
-rw-r--r--src/test/ui/issues/issue-19883.stderr8
7 files changed, 172 insertions, 28 deletions
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 7439cd9a0fe..0a5da653fab 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 e3ab858541a..7c97a17b454 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<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>`.
     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 00000000000..b689777685f
--- /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 _: <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
+}
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 00000000000..5a60b20fdf3
--- /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 _: <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
+}
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 00000000000..cf6083444b0
--- /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 _: <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`.
diff --git a/src/test/ui/associated-types/issue-22037.stderr b/src/test/ui/associated-types/issue-22037.stderr
index 615628558f0..0e019f10f37 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) -> <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
 
diff --git a/src/test/ui/issues/issue-19883.stderr b/src/test/ui/issues/issue-19883.stderr
index e370b2ec1cb..bd6a86b7420 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 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