about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2023-10-16 19:10:49 +0200
committerGitHub <noreply@github.com>2023-10-16 19:10:49 +0200
commit14663e09b7d316a5d48ae46eab46e4f4fedd9e8c (patch)
tree1f89663a42a0faf766f2c1b32c0e37be297dd5e3
parentcf25110defcc6e20b702a332489efa9f1c9f6c3f (diff)
parent20c622e4564bed8f6e4f9ef8b05c3a004d45e6f8 (diff)
downloadrust-14663e09b7d316a5d48ae46eab46e4f4fedd9e8c.tar.gz
rust-14663e09b7d316a5d48ae46eab46e4f4fedd9e8c.zip
Rollup merge of #116257 - estebank:issue-101351, r=b-naber
Suggest trait bounds for used associated type on type param

Fix #101351.

When an associated type on a type parameter is used, and the type parameter isn't constrained by the correct trait, suggest the appropriate trait bound:

```
error[E0220]: associated type `Associated` not found for `T`
 --> file.rs:6:15
  |
6 |     field: T::Associated,
  |               ^^^^^^^^^^ there is a similarly named associated type `Associated` in the trait `Foo`
  |
help: consider restricting type parameter `T`
  |
5 | struct Generic<T: Foo> {
  |                 +++++
  ```

When an associated type on a type parameter has a typo, suggest fixing
it:

```
error[E0220]: associated type `Baa` not found for `T`
  --> $DIR/issue-55673.rs:9:8
   |
LL |     T::Baa: std::fmt::Debug,
   |        ^^^ there is a similarly named associated type `Bar` in the trait `Foo`
   |
help: change the associated type name to use `Bar` from `Foo`
   |
LL |     T::Bar: std::fmt::Debug,
   |        ~~~
```
-rw-r--r--compiler/rustc_hir_analysis/src/astconv/bounds.rs1
-rw-r--r--compiler/rustc_hir_analysis/src/astconv/errors.rs58
-rw-r--r--compiler/rustc_hir_analysis/src/astconv/mod.rs19
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs2
-rw-r--r--tests/rustdoc-ui/issues/issue-96287.stderr7
-rw-r--r--tests/ui/resolve/issue-55673.fixed21
-rw-r--r--tests/ui/resolve/issue-55673.rs9
-rw-r--r--tests/ui/resolve/issue-55673.stderr24
-rw-r--r--tests/ui/traits/issue-59029-1.stderr4
-rw-r--r--tests/ui/type-alias-impl-trait/not_well_formed.fixed19
-rw-r--r--tests/ui/type-alias-impl-trait/not_well_formed.rs2
-rw-r--r--tests/ui/type-alias-impl-trait/not_well_formed.stderr9
12 files changed, 152 insertions, 23 deletions
diff --git a/compiler/rustc_hir_analysis/src/astconv/bounds.rs b/compiler/rustc_hir_analysis/src/astconv/bounds.rs
index 45d174cbbbb..059dfc60450 100644
--- a/compiler/rustc_hir_analysis/src/astconv/bounds.rs
+++ b/compiler/rustc_hir_analysis/src/astconv/bounds.rs
@@ -284,6 +284,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
             self.one_bound_for_assoc_type(
                 || traits::supertraits(tcx, trait_ref),
                 trait_ref.skip_binder().print_only_trait_name(),
+                None,
                 binding.item_name,
                 path_span,
                 match binding.kind {
diff --git a/compiler/rustc_hir_analysis/src/astconv/errors.rs b/compiler/rustc_hir_analysis/src/astconv/errors.rs
index ed4dde419c4..9e18767a7c3 100644
--- a/compiler/rustc_hir_analysis/src/astconv/errors.rs
+++ b/compiler/rustc_hir_analysis/src/astconv/errors.rs
@@ -6,10 +6,9 @@ use crate::errors::{
 use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{pluralize, struct_span_err, Applicability, Diagnostic, ErrorGuaranteed};
 use rustc_hir as hir;
-use rustc_hir::def_id::DefId;
+use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_infer::traits::FulfillmentError;
-use rustc_middle::ty::TyCtxt;
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, suggest_constraining_type_param, Ty, TyCtxt};
 use rustc_session::parse::feature_err;
 use rustc_span::edit_distance::find_best_match_for_name;
 use rustc_span::symbol::{sym, Ident};
@@ -102,6 +101,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         &self,
         all_candidates: impl Fn() -> I,
         ty_param_name: &str,
+        ty_param_def_id: Option<LocalDefId>,
         assoc_name: Ident,
         span: Span,
     ) -> ErrorGuaranteed
@@ -190,13 +190,61 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 })
                 .collect::<Vec<_>>()[..]
             {
+                let trait_name = self.tcx().def_path_str(*best_trait);
+                let an = if suggested_name != assoc_name.name { "a similarly named" } else { "an" };
                 err.span_label(
                     assoc_name.span,
                     format!(
-                        "there is a similarly named associated type `{suggested_name}` in the trait `{}`",
-                        self.tcx().def_path_str(*best_trait)
+                        "there is {an} associated type `{suggested_name}` in the \
+                         trait `{trait_name}`",
                     ),
                 );
+                let hir = self.tcx().hir();
+                if let Some(def_id) = ty_param_def_id
+                    && let parent = hir.get_parent_item(hir.local_def_id_to_hir_id(def_id))
+                    && let Some(generics) = hir.get_generics(parent.def_id)
+                {
+                    if generics.bounds_for_param(def_id)
+                        .flat_map(|pred| pred.bounds.iter())
+                        .any(|b| match b {
+                            hir::GenericBound::Trait(t, ..) => {
+                                t.trait_ref.trait_def_id().as_ref() == Some(best_trait)
+                            }
+                            _ => false,
+                        })
+                    {
+                        // The type param already has a bound for `trait_name`, we just need to
+                        // change the associated type.
+                        err.span_suggestion_verbose(
+                            assoc_name.span,
+                            format!(
+                                "change the associated type name to use `{suggested_name}` from \
+                                 `{trait_name}`",
+                            ),
+                            suggested_name.to_string(),
+                            Applicability::MaybeIncorrect,
+                        );
+                    } else if suggest_constraining_type_param(
+                            self.tcx(),
+                            generics,
+                            &mut err,
+                            &ty_param_name,
+                            &trait_name,
+                            None,
+                            None,
+                        )
+                        && suggested_name != assoc_name.name
+                    {
+                        // We suggested constraining a type parameter, but the associated type on it
+                        // was also not an exact match, so we also suggest changing it.
+                        err.span_suggestion_verbose(
+                            assoc_name.span,
+                            "and also change the associated type name",
+                            suggested_name.to_string(),
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                }
                 return err.emit();
             }
         }
diff --git a/compiler/rustc_hir_analysis/src/astconv/mod.rs b/compiler/rustc_hir_analysis/src/astconv/mod.rs
index ac97df0c087..6bcf33a1f6d 100644
--- a/compiler/rustc_hir_analysis/src/astconv/mod.rs
+++ b/compiler/rustc_hir_analysis/src/astconv/mod.rs
@@ -1062,6 +1062,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 )
             },
             param_name,
+            Some(ty_param_def_id),
             assoc_name,
             span,
             None,
@@ -1075,6 +1076,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
         &self,
         all_candidates: impl Fn() -> I,
         ty_param_name: impl Display,
+        ty_param_def_id: Option<LocalDefId>,
         assoc_name: Ident,
         span: Span,
         is_equality: Option<ty::Term<'tcx>>,
@@ -1096,6 +1098,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                 let reported = self.complain_about_assoc_type_not_found(
                     all_candidates,
                     &ty_param_name.to_string(),
+                    ty_param_def_id,
                     assoc_name,
                     span,
                 );
@@ -1143,30 +1146,26 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                     err.span_label(
                         bound_span,
                         format!(
-                            "ambiguous `{}` from `{}`",
-                            assoc_name,
+                            "ambiguous `{assoc_name}` from `{}`",
                             bound.print_only_trait_path(),
                         ),
                     );
                     if let Some(constraint) = &is_equality {
                         where_bounds.push(format!(
-                            "        T: {trait}::{assoc} = {constraint}",
+                            "        T: {trait}::{assoc_name} = {constraint}",
                             trait=bound.print_only_trait_path(),
-                            assoc=assoc_name,
-                            constraint=constraint,
                         ));
                     } else {
                         err.span_suggestion_verbose(
                             span.with_hi(assoc_name.span.lo()),
                             "use fully qualified syntax to disambiguate",
-                            format!("<{} as {}>::", ty_param_name, bound.print_only_trait_path()),
+                            format!("<{ty_param_name} as {}>::", bound.print_only_trait_path()),
                             Applicability::MaybeIncorrect,
                         );
                     }
                 } else {
                     err.note(format!(
-                        "associated type `{}` could derive from `{}`",
-                        ty_param_name,
+                        "associated type `{ty_param_name}` could derive from `{}`",
                         bound.print_only_trait_path(),
                     ));
                 }
@@ -1174,8 +1173,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
             if !where_bounds.is_empty() {
                 err.help(format!(
                     "consider introducing a new type parameter `T` and adding `where` constraints:\
-                     \n    where\n        T: {},\n{}",
-                    ty_param_name,
+                     \n    where\n        T: {ty_param_name},\n{}",
                     where_bounds.join(",\n"),
                 ));
             }
@@ -1397,6 +1395,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
                         )
                     },
                     kw::SelfUpper,
+                    None,
                     assoc_ident,
                     span,
                     None,
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index ea8ceda87be..78f7f915f72 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -364,7 +364,7 @@ fn predicate_constraint(generics: &hir::Generics<'_>, pred: ty::Predicate<'_>) -
 /// Type parameter needs more bounds. The trivial case is `T` `where T: Bound`, but
 /// it can also be an `impl Trait` param that needs to be decomposed to a type
 /// param for cleaner code.
-fn suggest_restriction<'tcx>(
+pub fn suggest_restriction<'tcx>(
     tcx: TyCtxt<'tcx>,
     item_id: LocalDefId,
     hir_generics: &hir::Generics<'tcx>,
diff --git a/tests/rustdoc-ui/issues/issue-96287.stderr b/tests/rustdoc-ui/issues/issue-96287.stderr
index 7722eb96028..c4809a311fc 100644
--- a/tests/rustdoc-ui/issues/issue-96287.stderr
+++ b/tests/rustdoc-ui/issues/issue-96287.stderr
@@ -2,7 +2,12 @@ error[E0220]: associated type `Assoc` not found for `V`
   --> $DIR/issue-96287.rs:7:33
    |
 LL | pub type Foo<V> = impl Trait<V::Assoc>;
-   |                                 ^^^^^ there is a similarly named associated type `Assoc` in the trait `TraitWithAssoc`
+   |                                 ^^^^^ there is an associated type `Assoc` in the trait `TraitWithAssoc`
+   |
+help: consider restricting type parameter `V`
+   |
+LL | pub type Foo<V: TraitWithAssoc> = impl Trait<V::Assoc>;
+   |               ++++++++++++++++
 
 error: aborting due to previous error
 
diff --git a/tests/ui/resolve/issue-55673.fixed b/tests/ui/resolve/issue-55673.fixed
new file mode 100644
index 00000000000..261742a26cb
--- /dev/null
+++ b/tests/ui/resolve/issue-55673.fixed
@@ -0,0 +1,21 @@
+// run-rustfix
+#![allow(dead_code)]
+trait Foo {
+    type Bar;
+}
+
+fn foo<T: Foo>()
+where
+    T::Bar: std::fmt::Debug,
+    //~^ ERROR associated type `Baa` not found for `T`
+{
+}
+
+fn bar<T>()
+where
+    T::Bar: std::fmt::Debug, T: Foo
+    //~^ ERROR associated type `Baa` not found for `T`
+{
+}
+
+fn main() {}
diff --git a/tests/ui/resolve/issue-55673.rs b/tests/ui/resolve/issue-55673.rs
index 0436bd39742..6ac49be141c 100644
--- a/tests/ui/resolve/issue-55673.rs
+++ b/tests/ui/resolve/issue-55673.rs
@@ -1,3 +1,5 @@
+// run-rustfix
+#![allow(dead_code)]
 trait Foo {
     type Bar;
 }
@@ -9,4 +11,11 @@ where
 {
 }
 
+fn bar<T>()
+where
+    T::Baa: std::fmt::Debug,
+    //~^ ERROR associated type `Baa` not found for `T`
+{
+}
+
 fn main() {}
diff --git a/tests/ui/resolve/issue-55673.stderr b/tests/ui/resolve/issue-55673.stderr
index 39318f95905..ffc3252230a 100644
--- a/tests/ui/resolve/issue-55673.stderr
+++ b/tests/ui/resolve/issue-55673.stderr
@@ -1,9 +1,29 @@
 error[E0220]: associated type `Baa` not found for `T`
-  --> $DIR/issue-55673.rs:7:8
+  --> $DIR/issue-55673.rs:9:8
    |
 LL |     T::Baa: std::fmt::Debug,
    |        ^^^ there is a similarly named associated type `Bar` in the trait `Foo`
+   |
+help: change the associated type name to use `Bar` from `Foo`
+   |
+LL |     T::Bar: std::fmt::Debug,
+   |        ~~~
+
+error[E0220]: associated type `Baa` not found for `T`
+  --> $DIR/issue-55673.rs:16:8
+   |
+LL |     T::Baa: std::fmt::Debug,
+   |        ^^^ there is a similarly named associated type `Bar` in the trait `Foo`
+   |
+help: consider further restricting type parameter `T`
+   |
+LL |     T::Baa: std::fmt::Debug, T: Foo
+   |                            ~~~~~~~~
+help: and also change the associated type name
+   |
+LL |     T::Bar: std::fmt::Debug,
+   |        ~~~
 
-error: aborting due to previous error
+error: aborting due to 2 previous errors
 
 For more information about this error, try `rustc --explain E0220`.
diff --git a/tests/ui/traits/issue-59029-1.stderr b/tests/ui/traits/issue-59029-1.stderr
index 51354bcc545..5c47eefcd6c 100644
--- a/tests/ui/traits/issue-59029-1.stderr
+++ b/tests/ui/traits/issue-59029-1.stderr
@@ -2,13 +2,13 @@ error[E0220]: associated type `Res` not found for `Self`
   --> $DIR/issue-59029-1.rs:5:52
    |
 LL | trait MkSvc<Target, Req> = Svc<Target> where Self::Res: Svc<Req>;
-   |                                                    ^^^ there is a similarly named associated type `Res` in the trait `Svc`
+   |                                                    ^^^ there is an associated type `Res` in the trait `Svc`
 
 error[E0220]: associated type `Res` not found for `Self`
   --> $DIR/issue-59029-1.rs:5:52
    |
 LL | trait MkSvc<Target, Req> = Svc<Target> where Self::Res: Svc<Req>;
-   |                                                    ^^^ there is a similarly named associated type `Res` in the trait `Svc`
+   |                                                    ^^^ there is an associated type `Res` in the trait `Svc`
    |
    = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
 
diff --git a/tests/ui/type-alias-impl-trait/not_well_formed.fixed b/tests/ui/type-alias-impl-trait/not_well_formed.fixed
new file mode 100644
index 00000000000..d98e83ff6dd
--- /dev/null
+++ b/tests/ui/type-alias-impl-trait/not_well_formed.fixed
@@ -0,0 +1,19 @@
+// run-rustfix
+#![feature(type_alias_impl_trait)]
+#![allow(dead_code)]
+
+fn main() {}
+
+trait TraitWithAssoc {
+    type Assoc;
+}
+
+type Foo<V: TraitWithAssoc> = impl Trait<V::Assoc>; //~ associated type `Assoc` not found for `V`
+
+trait Trait<U> {}
+
+impl<W> Trait<W> for () {}
+
+fn foo_desugared<T: TraitWithAssoc>(_: T) -> Foo<T> {
+    ()
+}
diff --git a/tests/ui/type-alias-impl-trait/not_well_formed.rs b/tests/ui/type-alias-impl-trait/not_well_formed.rs
index fbb7a4d58e4..18f173a693d 100644
--- a/tests/ui/type-alias-impl-trait/not_well_formed.rs
+++ b/tests/ui/type-alias-impl-trait/not_well_formed.rs
@@ -1,4 +1,6 @@
+// run-rustfix
 #![feature(type_alias_impl_trait)]
+#![allow(dead_code)]
 
 fn main() {}
 
diff --git a/tests/ui/type-alias-impl-trait/not_well_formed.stderr b/tests/ui/type-alias-impl-trait/not_well_formed.stderr
index c36b95f47e8..b267e6a7544 100644
--- a/tests/ui/type-alias-impl-trait/not_well_formed.stderr
+++ b/tests/ui/type-alias-impl-trait/not_well_formed.stderr
@@ -1,8 +1,13 @@
 error[E0220]: associated type `Assoc` not found for `V`
-  --> $DIR/not_well_formed.rs:9:29
+  --> $DIR/not_well_formed.rs:11:29
    |
 LL | type Foo<V> = impl Trait<V::Assoc>;
-   |                             ^^^^^ there is a similarly named associated type `Assoc` in the trait `TraitWithAssoc`
+   |                             ^^^^^ there is an associated type `Assoc` in the trait `TraitWithAssoc`
+   |
+help: consider restricting type parameter `V`
+   |
+LL | type Foo<V: TraitWithAssoc> = impl Trait<V::Assoc>;
+   |           ++++++++++++++++
 
 error: aborting due to previous error