Change:
partially addresses , but we need to resugar the predicates to unify the output of a trait obligation with related projection obligations (associated types). For example should be
I hit this today, couldn't remember the exact prototype so I wanted the compiler to suggest it to me (typing this, I realize how amazing it is that Rust gets most of these suggestions right. Thanks for all the work in this area!). Playground: Copy-pasting the suggestion leads to this: About the second error: Couldn't the compiler also deduce that is in fact here? I couldn't find an open issue specific to this. For the resugaring error, I'd be willing to tackle this, if you think this is something achievable for a contributor!
there are different parts that could potentially be tackled of different level of difficulty. The underlying problem is that by the time the compile error happens, we no longer have access to the original bound information. We know that the method requires to be , and that , but we no longer have the metadata that ties those two bounds together, as far as is concerned, they are two different things, which is why the output looks the way it does. Extending the metadata to keep those two things mergeable at that stage is probably a medium sized undertaking requiring to touch a fair amount of the compiler. I would advice against trying that as a newcomer An alternative to the "correct" fix above would be to try to unify multiple bounds into a single one when possible. Doing that is concentrated in a single part of the compiler, which can be easier to understand, but will require tricky logic. I would look at all the traits bounds (s in parlance) and see if an associated type equality constraint has a self type mentioned in a trait bound on the RHS of the bound. Meaning, if you have , look for in all bounds. If found, you have to figure out how to "print" it. There might be some existing code that can be reused to make this as resilient as any other part, potentially building a with all the necessary parts, but I'd be happy enough with "one off" for this use case with a comment. Finally, I would love to handle the equality constraint case in a more user friendly way, similar to : we should detect that this is constraining an associated type and provide the same suggestion as in the previously mentioned case. If that is too hard to do, we could at least mention_ what the solution is given is an associated type in a (not a structured suggestion). What do you think? Is any of this something of the scope size you'd like to tackle? Edit: Looking at , it seems to me like what would be necessary to accomplish the last part of my comments above would be to and perform the "unification" I mentioned for the previous cases, so doing either the first or second approach, you can get the last case almost for free
Hi sorry for the late reply. I think I can try and build something for the alternative to the "correct" fix you mention, i.e. manually merge an associated type equality constraint bound with another bound if the other bound is about as well. Making sure I understand this correctly, the difference with the previous idea would be correcting the user when he writes the equality constraint, rather fixing the suggestion provided by the compiler? (both things being complementary to each other, of course). Did I get this right? :smile: So starting with this seems like it'd be a good path? If I understand correctly, implementing the "manual merge" of related bounds would allow its use both to fix the "method impl" suggestion I hit with above, as well as add a new helpful suggestion when the user writes an equality constraint?
I'll start working on that "unification". Looking at , I'm having a hard time identifying where I'd get started though :sweatsmile: , , ...
friendly ping in case this went past you (nothing pressing if you're not available though!)
I think you got it right, we would want to support both cases independently (likely in separate PRs). One is reducing the likelihood of hitting the equality case, but anyone could try to write it without waiting for rustc to point them in that direction, so ideally we cover both cases. Correct. It would be what we need and would work for most cases (but not 100% because we're rejoining things that got separated for a reason, but it would work well enough for all the common cases). Now with a fresh set of eyes I'm thinking now that you can probably try to unify things just with the information you have in and by looking at the and try to "merge" the ones that are related. Sorry! The previous notifications did indeed get drowned and am currently with intermittent internet access. Do not hesitate to ping me again if you need any further help though!
Current output: We need to fix how we handle the incorrect syntax, but the initial suggestion is now fixed.

Discussion:
} } } <del> // Given `A: Foo, A::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`. if let TyKind::Path(None, full_path) = &predicate.lhs_ty.kind { if let [potential_param, potential_assoc] = &full_path.segments[..] { for param in &generics.params { if param.ident == potential_param.ident { for bound in &param.bounds { if let ast::GenericBound::Trait(trait_ref, TraitBoundModifiers::NONE) = bound </del> <ins> let mut suggest = |poly: &PolyTraitRef, potential_assoc: &PathSegment, predicate: &WhereEqPredicate| { if let [trait_segment] = &poly.trait_ref.path.segments[..] { let assoc = pprust::path_to_string(&ast::Path::from_ident(potential_assoc.ident)); let ty = pprust::ty_to_string(&predicate.rhs_ty); let (args, span) = match &trait_segment.args { Some(args) => match args.deref() { ast::GenericArgs::AngleBracketed(args) => { let Some(arg) = args.args.last() else { return; }; (format!(", {assoc} = {ty}"), arg.span().shrink_to_hi()) } _ => return, }, None => (format!("<{assoc} = {ty}>"), trait_segment.span().shrink_to_hi()), }; let removal_span = if generics.where_clause.predicates.len() == 1 { // We're removing th eonly where bound left, remove the whole thing. generics.where_clause.span } else { let mut span = predicate.span; let mut prev: Option<Span> = None; let mut preds = generics.where_clause.predicates.iter().peekable(); // Find the predicate that shouldn't have been in the where bound list. while let Some(pred) = preds.next() { if let WherePredicate::EqPredicate(pred) = pred && pred.span == predicate.span </ins> { <del> if let [trait_segment] = &trait_ref.trait_ref.path.segments[..] { let assoc = pprust::path_to_string(&ast::Path::from_ident( potential_assoc.ident, )); let ty = pprust::ty_to_string(&predicate.rhs_ty); let (args, span) = match &trait_segment.args { Some(args) => match args.deref() { ast::GenericArgs::AngleBracketed(args) => { let Some(arg) = args.args.last() else { continue; }; (format!(", {assoc} = {ty}"), arg.span().shrink_to_hi()) } _ => continue, }, None => ( format!("<{assoc} = {ty}>"), trait_segment.span().shrink_to_hi(), ), }; err.assoc2 = Some(errors::AssociatedSuggestion2 { span, args, predicate: predicate.span, trait_segment: trait_segment.ident, potential_assoc: potential_assoc.ident, }); </del> <ins> if let Some(next) = preds.peek() { // This is the first predicate, remove the trailing comma as well. span = span.with_hi(next.span().lo()); } else if let Some(prev) = prev { // Remove the previous comma as well. span = span.with_lo(prev.hi()); </ins> } } <ins> prev = Some(pred.span()); } span }; err.assoc2 = Some(errors::AssociatedSuggestion2 { span, args, predicate: removal_span, trait_segment: trait_segment.ident, potential_assoc: potential_assoc.ident, }); } }; if let TyKind::Path(None, full_path) = &predicate.lhs_ty.kind { // Given `A: Foo, Foo::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`. for bounds in generics.params.iter().map(|p| &p.bounds).chain( generics.where_clause.predicates.iter().filter_map(|pred| match pred { WherePredicate::BoundPredicate(p) => Some(&p.bounds), _ => None, }), ) { for bound in bounds { if let GenericBound::Trait(poly, TraitBoundModifiers::NONE) = bound { if full_path.segments[..full_path.segments.len() - 1] .iter() .map(|segment| segment.ident.name) .zip(poly.trait_ref.path.segments.iter().map(|segment| segment.ident.name)) .all(|(a, b)| a == b) && let Some(potential_assoc) = full_path.segments.iter().last() { suggest(poly, potential_assoc, predicate); } } } } // Given `A: Foo, A::Bar = RhsTy`, suggest `A: Foo<Bar = RhsTy>`. if let [potential_param, potential_assoc] = &full_path.segments[..] { for (ident, bounds) in generics.params.iter().map(|p| (p.ident, &p.bounds)).chain( generics.where_clause.predicates.iter().filter_map(|pred| match pred { WherePredicate::BoundPredicate(p) if let ast::TyKind::Path(None, path) = &p.bounded_ty.kind && let [segment] = &path.segments[..] => { Some((segment.ident, &p.bounds)) } _ => None, }), ) { if ident == potential_param.ident { for bound in bounds { if let ast::GenericBound::Trait(poly, TraitBoundModifiers::NONE) = bound { suggest(poly, potential_assoc, predicate); } </ins> } } }

panic!() } <ins> use std::iter::FromIterator; struct X {} impl FromIterator<bool> for X { fn from_iter<T>(_: T) -> Self where T: IntoIterator, IntoIterator::Item = A, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } struct Y {} impl FromIterator<bool> for Y { fn from_iter<T>(_: T) -> Self where T: IntoIterator, T::Item = A, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } struct Z {} impl FromIterator<bool> for Z { fn from_iter<T: IntoIterator>(_: T) -> Self where IntoIterator::Item = A, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } struct K {} impl FromIterator<bool> for K { fn from_iter<T: IntoIterator>(_: T) -> Self where T::Item = A, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } struct L {} impl FromIterator<bool> for L { fn from_iter<T>(_: T) -> Self where IntoIterator::Item = A, T: IntoIterator, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } struct M {} impl FromIterator<bool> for M { fn from_iter<T>(_: T) -> Self where T::Item = A, T: IntoIterator, //~^ ERROR equality constraints are not yet supported in `where` clauses //~| ERROR cannot find type `A` in this scope { todo!() } } </ins> fn main() {}

help: if `Iterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL - fn sum<I: Iterator<Item = ()>>(i: I) -> i32 where I::Item = i32 { <del> LL + fn sum<I: Iterator<Item = (), Item = i32>>(i: I) -> i32 where  { </del> <ins> LL + fn sum<I: Iterator<Item = (), Item = i32>>(i: I) -> i32  { </ins> | error: equality constraints are not yet supported in `where` clauses

help: if `Iterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL - fn sum2<I: Iterator>(i: I) -> i32 where I::Item = i32 { <del> LL + fn sum2<I: Iterator<Item = i32>>(i: I) -> i32 where  { </del> <ins> LL + fn sum2<I: Iterator<Item = i32>>(i: I) -> i32  { </ins> | error: equality constraints are not yet supported in `where` clauses

| = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information <ins> error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:20:58 | LL |     fn from_iter<T>(_: T) -> Self where T: IntoIterator, IntoIterator::Item = A, |                                                          ^^^^^^^^^^^^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T>(_: T) -> Self where T: IntoIterator, IntoIterator::Item = A, LL +     fn from_iter<T>(_: T) -> Self where T: IntoIterator<Item = A>, | error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:31:58 | LL |     fn from_iter<T>(_: T) -> Self where T: IntoIterator, T::Item = A, |                                                          ^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T>(_: T) -> Self where T: IntoIterator, T::Item = A, LL +     fn from_iter<T>(_: T) -> Self where T: IntoIterator<Item = A>, | error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:42:55 | LL |     fn from_iter<T: IntoIterator>(_: T) -> Self where IntoIterator::Item = A, |                                                       ^^^^^^^^^^^^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T: IntoIterator>(_: T) -> Self where IntoIterator::Item = A, LL +     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self | error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:53:55 | LL |     fn from_iter<T: IntoIterator>(_: T) -> Self where T::Item = A, |                                                       ^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T: IntoIterator>(_: T) -> Self where T::Item = A, LL +     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self | error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:64:41 | LL |     fn from_iter<T>(_: T) -> Self where IntoIterator::Item = A, T: IntoIterator, |                                         ^^^^^^^^^^^^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T>(_: T) -> Self where IntoIterator::Item = A, T: IntoIterator, LL +     fn from_iter<T>(_: T) -> Self where T: IntoIterator<Item = A>, | error: equality constraints are not yet supported in `where` clauses --> $DIR/equality-bound.rs:75:41 | LL |     fn from_iter<T>(_: T) -> Self where T::Item = A, T: IntoIterator, |                                         ^^^^^^^^^^^ not supported | = note: see issue #20041 <https://github.com/rust-lang/rust/issues/20041> for more information help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax | LL -     fn from_iter<T>(_: T) -> Self where T::Item = A, T: IntoIterator, LL +     fn from_iter<T>(_: T) -> Self where T: IntoIterator<Item = A>, | error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:20:79 | LL |     fn from_iter<T>(_: T) -> Self where T: IntoIterator, IntoIterator::Item = A, |                                                                               ^ help: a struct with a similar name exists: `K` ... LL | struct K {} | -------- similarly named struct `K` defined here error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:31:68 | LL |     fn from_iter<T>(_: T) -> Self where T: IntoIterator, T::Item = A, |                                                                    ^ help: a struct with a similar name exists: `K` ... LL | struct K {} | -------- similarly named struct `K` defined here error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:42:76 | LL |     fn from_iter<T: IntoIterator>(_: T) -> Self where IntoIterator::Item = A, |                                                                            ^ help: a struct with a similar name exists: `K` ... LL | struct K {} | -------- similarly named struct `K` defined here error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:53:65 | LL | struct K {} | -------- similarly named struct `K` defined here ... LL |     fn from_iter<T: IntoIterator>(_: T) -> Self where T::Item = A, |                                                                 ^ help: a struct with a similar name exists: `K` error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:64:62 | LL | struct K {} | -------- similarly named struct `K` defined here ... LL |     fn from_iter<T>(_: T) -> Self where IntoIterator::Item = A, T: IntoIterator, |                                                              ^ help: a struct with a similar name exists: `K` error[E0412]: cannot find type `A` in this scope --> $DIR/equality-bound.rs:75:51 | LL | struct K {} | -------- similarly named struct `K` defined here ... LL |     fn from_iter<T>(_: T) -> Self where T::Item = A, T: IntoIterator, |                                                   ^ help: a struct with a similar name exists: `K` </ins> error[E0433]: failed to resolve: use of undeclared type `I` --> $DIR/equality-bound.rs:9:41 |

|                                         use of undeclared type `I` |                                         help: a type parameter with a similar name exists: `J` <del> error: aborting due to 4 previous errors </del> <ins> error: aborting due to 16 previous errors </ins> <del> For more information about this error, try `rustc --explain E0433`. </del> <ins> Some errors have detailed explanations: E0412, E0433. For more information about an error, try `rustc --explain E0412`. </ins>