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 + 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::>() - .join("::"), - path.segments[position..] - .iter() - .map(|segment| path_segment_to_string(segment)) - .collect::>() - .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 { + 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 { + phantom_decode: PhantomData, + phantom_encode: PhantomData, +} + +pub enum ParseError {} + +impl Codec where + DecodeLine: Debug + convert::TryFrom, + DecodeLine: convert::TryFrom, + //~^ ERROR expected trait, found enum `ParseError` + //~| HELP constrain the associated type to `ParseError` +{ +} + +impl Codec where + DecodeLine: Debug + TryFrom, + DecodeLine: TryFrom, + //~^ 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 { + phantom_decode: PhantomData, + phantom_encode: PhantomData, +} + +pub enum ParseError {} + +impl Codec where + DecodeLine: Debug + convert::TryFrom, + >::Error: ParseError, + //~^ ERROR expected trait, found enum `ParseError` + //~| HELP constrain the associated type to `ParseError` +{ +} + +impl Codec where + DecodeLine: Debug + TryFrom, + >::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 | >::Error: ParseError, + | ^^^^^^^^^^ not a trait + | +help: constrain the associated type to `ParseError` + | +LL | DecodeLine: convert::TryFrom, + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error[E0404]: expected trait, found enum `ParseError` + --> $DIR/issue-112472-multi-generics-suggestion.rs:25:45 + | +LL | >::Error: ParseError, + | ^^^^^^^^^^ not a trait + | +help: constrain the associated type to `ParseError` + | +LL | DecodeLine: TryFrom, + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0404`.