about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/mod.rs4
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state.rs7
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/item.rs28
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs92
-rw-r--r--tests/ui/resolve/issue-112472-multi-generics-suggestion.fixed31
-rw-r--r--tests/ui/resolve/issue-112472-multi-generics-suggestion.rs31
-rw-r--r--tests/ui/resolve/issue-112472-multi-generics-suggestion.stderr25
7 files changed, 182 insertions, 36 deletions
diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs
index ac9e7d06c4e..83b7e13905a 100644
--- a/compiler/rustc_ast_pretty/src/pprust/mod.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs
@@ -32,6 +32,10 @@ pub fn bounds_to_string(bounds: &[ast::GenericBound]) -> String {
     State::new().bounds_to_string(bounds)
 }
 
+pub fn where_bound_predicate_to_string(where_bound_predicate: &ast::WhereBoundPredicate) -> String {
+    State::new().where_bound_predicate_to_string(where_bound_predicate)
+}
+
 pub fn pat_to_string(pat: &ast::Pat) -> String {
     State::new().pat_to_string(pat)
 }
diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs
index 3f80728a260..59239b49edd 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state.rs
@@ -824,6 +824,13 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
         Self::to_string(|s| s.print_type_bounds(bounds))
     }
 
+    fn where_bound_predicate_to_string(
+        &self,
+        where_bound_predicate: &ast::WhereBoundPredicate,
+    ) -> String {
+        Self::to_string(|s| s.print_where_bound_predicate(where_bound_predicate))
+    }
+
     fn pat_to_string(&self, pat: &ast::Pat) -> String {
         Self::to_string(|s| s.print_pat(pat))
     }
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
index c465f8c948a..5c01b7ea70a 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs
@@ -623,19 +623,8 @@ impl<'a> State<'a> {
 
     pub fn print_where_predicate(&mut self, predicate: &ast::WherePredicate) {
         match predicate {
-            ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
-                bound_generic_params,
-                bounded_ty,
-                bounds,
-                ..
-            }) => {
-                self.print_formal_generic_params(bound_generic_params);
-                self.print_type(bounded_ty);
-                self.word(":");
-                if !bounds.is_empty() {
-                    self.nbsp();
-                    self.print_type_bounds(bounds);
-                }
+            ast::WherePredicate::BoundPredicate(where_bound_predicate) => {
+                self.print_where_bound_predicate(where_bound_predicate);
             }
             ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate {
                 lifetime,
@@ -658,6 +647,19 @@ impl<'a> State<'a> {
         }
     }
 
+    pub fn print_where_bound_predicate(
+        &mut self,
+        where_bound_predicate: &ast::WhereBoundPredicate,
+    ) {
+        self.print_formal_generic_params(&where_bound_predicate.bound_generic_params);
+        self.print_type(&where_bound_predicate.bounded_ty);
+        self.word(":");
+        if !where_bound_predicate.bounds.is_empty() {
+            self.nbsp();
+            self.print_type_bounds(&where_bound_predicate.bounds);
+        }
+    }
+
     fn print_use_tree(&mut self, tree: &ast::UseTree) {
         match &tree.kind {
             ast::UseTreeKind::Simple(rename) => {
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index e4b01ef2b17..7284b33f09d 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -10,7 +10,7 @@ use rustc_ast::{
     self as ast, AssocItemKind, Expr, ExprKind, GenericParam, GenericParamKind, Item, ItemKind,
     MethodCall, NodeId, Path, Ty, TyKind, DUMMY_NODE_ID,
 };
-use rustc_ast_pretty::pprust::path_segment_to_string;
+use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{
     pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
@@ -1050,7 +1050,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
             };
 
         // Confirm that the target is an associated type.
-        let (ty, position, path) = if let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind {
+        let (ty, _, path) = if let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind {
             // use this to verify that ident is a type param.
             let Some(partial_res) = self.r.partial_res_map.get(&bounded_ty.id) else {
                 return false;
@@ -1079,7 +1079,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
                 return false;
             }
             if let (
-                [ast::PathSegment { ident: constrain_ident, args: None, .. }],
+                [ast::PathSegment { args: None, .. }],
                 [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)],
             ) = (&type_param_path.segments[..], &bounds[..])
             {
@@ -1087,29 +1087,11 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
                     &poly_trait_ref.trait_ref.path.segments[..]
                 {
                     if ident.span == span {
+                        let Some(new_where_bound_predicate) = mk_where_bound_predicate(path, poly_trait_ref, ty) else { return false; };
                         err.span_suggestion_verbose(
                             *where_span,
                             format!("constrain the associated type to `{}`", ident),
-                            format!(
-                                "{}: {}<{} = {}>",
-                                self.r
-                                    .tcx
-                                    .sess
-                                    .source_map()
-                                    .span_to_snippet(ty.span) // Account for `<&'a T as Foo>::Bar`.
-                                    .unwrap_or_else(|_| constrain_ident.to_string()),
-                                path.segments[..position]
-                                    .iter()
-                                    .map(|segment| path_segment_to_string(segment))
-                                    .collect::<Vec<_>>()
-                                    .join("::"),
-                                path.segments[position..]
-                                    .iter()
-                                    .map(|segment| path_segment_to_string(segment))
-                                    .collect::<Vec<_>>()
-                                    .join("::"),
-                                ident,
-                            ),
+                            where_bound_predicate_to_string(&new_where_bound_predicate),
                             Applicability::MaybeIncorrect,
                         );
                     }
@@ -2605,6 +2587,70 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
     }
 }
 
+fn mk_where_bound_predicate(
+    path: &Path,
+    poly_trait_ref: &ast::PolyTraitRef,
+    ty: &ast::Ty,
+) -> Option<ast::WhereBoundPredicate> {
+    use rustc_span::DUMMY_SP;
+    let modified_segments = {
+        let mut segments = path.segments.clone();
+        let [preceding @ .., second_last, last] = segments.as_mut_slice() else { return None; };
+        let mut segments = ThinVec::from(preceding);
+
+        let added_constraint = ast::AngleBracketedArg::Constraint(ast::AssocConstraint {
+            id: DUMMY_NODE_ID,
+            ident: last.ident,
+            gen_args: None,
+            kind: ast::AssocConstraintKind::Equality {
+                term: ast::Term::Ty(ast::ptr::P(ast::Ty {
+                    kind: ast::TyKind::Path(None, poly_trait_ref.trait_ref.path.clone()),
+                    id: DUMMY_NODE_ID,
+                    span: DUMMY_SP,
+                    tokens: None,
+                })),
+            },
+            span: DUMMY_SP,
+        });
+
+        match second_last.args.as_deref_mut() {
+            Some(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { args, .. })) => {
+                args.push(added_constraint);
+            }
+            Some(_) => return None,
+            None => {
+                second_last.args =
+                    Some(ast::ptr::P(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs {
+                        args: ThinVec::from([added_constraint]),
+                        span: DUMMY_SP,
+                    })));
+            }
+        }
+
+        segments.push(second_last.clone());
+        segments
+    };
+
+    let new_where_bound_predicate = ast::WhereBoundPredicate {
+        span: DUMMY_SP,
+        bound_generic_params: ThinVec::new(),
+        bounded_ty: ast::ptr::P(ty.clone()),
+        bounds: vec![ast::GenericBound::Trait(
+            ast::PolyTraitRef {
+                bound_generic_params: ThinVec::new(),
+                trait_ref: ast::TraitRef {
+                    path: ast::Path { segments: modified_segments, span: DUMMY_SP, tokens: None },
+                    ref_id: DUMMY_NODE_ID,
+                },
+                span: DUMMY_SP,
+            },
+            ast::TraitBoundModifier::None,
+        )],
+    };
+
+    Some(new_where_bound_predicate)
+}
+
 /// Report lifetime/lifetime shadowing as an error.
 pub(super) fn signal_lifetime_shadowing(sess: &Session, orig: Ident, shadower: Ident) {
     let mut err = struct_span_err!(
diff --git a/tests/ui/resolve/issue-112472-multi-generics-suggestion.fixed b/tests/ui/resolve/issue-112472-multi-generics-suggestion.fixed
new file mode 100644
index 00000000000..892697493b7
--- /dev/null
+++ b/tests/ui/resolve/issue-112472-multi-generics-suggestion.fixed
@@ -0,0 +1,31 @@
+// run-rustfix
+
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::convert::{self, TryFrom};
+
+#[allow(unused)]
+struct Codec<EncodeLine, DecodeLine> {
+    phantom_decode: PhantomData<DecodeLine>,
+    phantom_encode: PhantomData<EncodeLine>,
+}
+
+pub enum ParseError {}
+
+impl<EncodeLine, DecodeLine> Codec<EncodeLine, DecodeLine> where
+    DecodeLine: Debug + convert::TryFrom<String>,
+    DecodeLine: convert::TryFrom<String, Error = ParseError>,
+    //~^ ERROR expected trait, found enum `ParseError`
+    //~| HELP constrain the associated type to `ParseError`
+{
+}
+
+impl<EncodeLine, DecodeLine> Codec<EncodeLine, DecodeLine> where
+    DecodeLine: Debug + TryFrom<String>,
+    DecodeLine: TryFrom<String, Error = ParseError>,
+    //~^ ERROR expected trait, found enum `ParseError`
+    //~| HELP constrain the associated type to `ParseError`
+{
+}
+
+fn main() {}
diff --git a/tests/ui/resolve/issue-112472-multi-generics-suggestion.rs b/tests/ui/resolve/issue-112472-multi-generics-suggestion.rs
new file mode 100644
index 00000000000..2b2f5f1ad8d
--- /dev/null
+++ b/tests/ui/resolve/issue-112472-multi-generics-suggestion.rs
@@ -0,0 +1,31 @@
+// run-rustfix
+
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::convert::{self, TryFrom};
+
+#[allow(unused)]
+struct Codec<EncodeLine, DecodeLine> {
+    phantom_decode: PhantomData<DecodeLine>,
+    phantom_encode: PhantomData<EncodeLine>,
+}
+
+pub enum ParseError {}
+
+impl<EncodeLine, DecodeLine> Codec<EncodeLine, DecodeLine> where
+    DecodeLine: Debug + convert::TryFrom<String>,
+    <DecodeLine as convert::TryFrom<String>>::Error: ParseError,
+    //~^ ERROR expected trait, found enum `ParseError`
+    //~| HELP constrain the associated type to `ParseError`
+{
+}
+
+impl<EncodeLine, DecodeLine> Codec<EncodeLine, DecodeLine> where
+    DecodeLine: Debug + TryFrom<String>,
+    <DecodeLine as TryFrom<String>>::Error: ParseError,
+    //~^ ERROR expected trait, found enum `ParseError`
+    //~| HELP constrain the associated type to `ParseError`
+{
+}
+
+fn main() {}
diff --git a/tests/ui/resolve/issue-112472-multi-generics-suggestion.stderr b/tests/ui/resolve/issue-112472-multi-generics-suggestion.stderr
new file mode 100644
index 00000000000..f463e2dad2c
--- /dev/null
+++ b/tests/ui/resolve/issue-112472-multi-generics-suggestion.stderr
@@ -0,0 +1,25 @@
+error[E0404]: expected trait, found enum `ParseError`
+  --> $DIR/issue-112472-multi-generics-suggestion.rs:17:54
+   |
+LL |     <DecodeLine as convert::TryFrom<String>>::Error: ParseError,
+   |                                                      ^^^^^^^^^^ not a trait
+   |
+help: constrain the associated type to `ParseError`
+   |
+LL |     DecodeLine: convert::TryFrom<String, Error = ParseError>,
+   |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error[E0404]: expected trait, found enum `ParseError`
+  --> $DIR/issue-112472-multi-generics-suggestion.rs:25:45
+   |
+LL |     <DecodeLine as TryFrom<String>>::Error: ParseError,
+   |                                             ^^^^^^^^^^ not a trait
+   |
+help: constrain the associated type to `ParseError`
+   |
+LL |     DecodeLine: TryFrom<String, Error = ParseError>,
+   |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0404`.