Skip to content

Commit

Permalink
Suggest trait bounds for used associated type on type param
Browse files Browse the repository at this point in the history
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,
   |        ~~~
```
  • Loading branch information
estebank committed Sep 29, 2023
1 parent c545019 commit 0608151
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 19 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/astconv/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
57 changes: 52 additions & 5 deletions compiler/rustc_hir_analysis/src/astconv/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -190,13 +190,60 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
})
.collect::<Vec<_>>()[..]
{
let trait_name = self.tcx().def_path_str(*best_trait);
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 a similarly named 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();
}
}
Expand Down
19 changes: 9 additions & 10 deletions compiler/rustc_hir_analysis/src/astconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
)
},
param_name,
Some(ty_param_def_id),
assoc_name,
span,
None,
Expand All @@ -1074,6 +1075,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>>,
Expand All @@ -1095,6 +1097,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,
);
Expand Down Expand Up @@ -1142,39 +1145,34 @@ 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(),
));
}
}
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"),
));
}
Expand Down Expand Up @@ -1394,6 +1392,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
)
},
kw::SelfUpper,
None,
assoc_ident,
span,
None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
5 changes: 5 additions & 0 deletions tests/rustdoc-ui/issues/issue-96287.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ error[E0220]: associated type `Assoc` not found for `V`
|
LL | pub type Foo<V> = impl Trait<V::Assoc>;
| ^^^^^ there is a similarly named 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

Expand Down
21 changes: 21 additions & 0 deletions tests/ui/resolve/issue-55673.fixed
Original file line number Diff line number Diff line change
@@ -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() {}
9 changes: 9 additions & 0 deletions tests/ui/resolve/issue-55673.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// run-rustfix
#![allow(dead_code)]
trait Foo {
type Bar;
}
Expand All @@ -9,4 +11,11 @@ where
{
}

fn bar<T>()
where
T::Baa: std::fmt::Debug,
//~^ ERROR associated type `Baa` not found for `T`
{
}

fn main() {}
24 changes: 22 additions & 2 deletions tests/ui/resolve/issue-55673.stderr
Original file line number Diff line number Diff line change
@@ -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`.
19 changes: 19 additions & 0 deletions tests/ui/type-alias-impl-trait/not_well_formed.fixed
Original file line number Diff line number Diff line change
@@ -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> {
()
}
2 changes: 2 additions & 0 deletions tests/ui/type-alias-impl-trait/not_well_formed.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// run-rustfix
#![feature(type_alias_impl_trait)]
#![allow(dead_code)]

fn main() {}

Expand Down
7 changes: 6 additions & 1 deletion tests/ui/type-alias-impl-trait/not_well_formed.stderr
Original file line number Diff line number Diff line change
@@ -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`
|
help: consider restricting type parameter `V`
|
LL | type Foo<V: TraitWithAssoc> = impl Trait<V::Assoc>;
| ++++++++++++++++

error: aborting due to previous error

Expand Down

0 comments on commit 0608151

Please sign in to comment.