diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index b0a8188e5e0..7b07c2a4633 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -803,6 +803,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { predicates .iter() .map(|(param, constraint)| (param.name.as_str(), &**constraint, None)), + None, ); } } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs index 782a62accad..3e416b89ca6 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs @@ -136,6 +136,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { ¶m_ty.name.as_str(), &constraint, None, + None, ); } } diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index 6600e4216bd..8c2423e3ca0 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -176,6 +176,7 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) { bounds.iter().map(|(param, constraint, def_id)| { (param.as_str(), constraint.as_str(), *def_id) }), + None, ); err.emit(); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 51e3e3ec73d..05e97653412 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -1457,6 +1457,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { generics, diag, vec![(param.name.as_str(), "Clone", Some(clone_trait_did))].into_iter(), + None, ); } else { self.suggest_derive(diag, &[(trait_ref.to_predicate(self.tcx), None, None)]); diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs index 39b3c98f0a5..984e8cf6a0e 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs @@ -77,49 +77,86 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { (ty::Param(p), ty::Alias(ty::Projection, proj)) | (ty::Alias(ty::Projection, proj), ty::Param(p)) if tcx.def_kind(proj.def_id) != DefKind::ImplTraitPlaceholder => { - let generics = tcx.generics_of(body_owner_def_id); - let p_span = tcx.def_span(generics.type_param(p, tcx).def_id); + let p_def_id = tcx + .generics_of(body_owner_def_id) + .type_param(p, tcx) + .def_id; + let p_span = tcx.def_span(p_def_id); if !sp.contains(p_span) { diag.span_label(p_span, "this type parameter"); } let hir = tcx.hir(); let mut note = true; - if let Some(generics) = generics - .type_param(p, tcx) - .def_id + let parent = p_def_id .as_local() - .map(|id| hir.local_def_id_to_hir_id(id)) - .and_then(|id| tcx.hir().find_parent(id)) - .as_ref() - .and_then(|node| node.generics()) + .and_then(|id| { + let local_id = hir.local_def_id_to_hir_id(id); + let generics = tcx.hir().find_parent(local_id)?.generics()?; + Some((id, generics)) + }); + if let Some((local_id, generics)) = parent { // Synthesize the associated type restriction `Add`. // FIXME: extract this logic for use in other diagnostics. let (trait_ref, assoc_substs) = proj.trait_ref_and_own_substs(tcx); - let path = - tcx.def_path_str_with_substs(trait_ref.def_id, trait_ref.substs); let item_name = tcx.item_name(proj.def_id); let item_args = self.format_generic_args(assoc_substs); - let path = if path.ends_with('>') { - format!( - "{}, {}{} = {}>", - &path[..path.len() - 1], - item_name, - item_args, - p - ) + // Here, we try to see if there's an existing + // trait implementation that matches the one that + // we're suggesting to restrict. If so, find the + // "end", whether it be at the end of the trait + // or the end of the generic arguments. + let mut matching_span = None; + let mut matched_end_of_args = false; + for bound in generics.bounds_for_param(local_id) { + let potential_spans = bound + .bounds + .iter() + .find_map(|bound| { + let bound_trait_path = bound.trait_ref()?.path; + let def_id = bound_trait_path.res.opt_def_id()?; + let generic_args = bound_trait_path.segments.iter().last().map(|path| path.args()); + (def_id == trait_ref.def_id).then_some((bound_trait_path.span, generic_args)) + }); + + if let Some((end_of_trait, end_of_args)) = potential_spans { + let args_span = end_of_args.and_then(|args| args.span()); + matched_end_of_args = args_span.is_some(); + matching_span = args_span + .or_else(|| Some(end_of_trait)) + .map(|span| span.shrink_to_hi()); + break; + } + } + + if matched_end_of_args { + // Append suggestion to the end of our args + let path = format!(", {}{} = {}",item_name, item_args, p); + note = !suggest_constraining_type_param( + tcx, + generics, + diag, + &format!("{}", proj.self_ty()), + &path, + None, + matching_span, + ); } else { - format!("{}<{}{} = {}>", path, item_name, item_args, p) - }; - note = !suggest_constraining_type_param( - tcx, - generics, - diag, - &format!("{}", proj.self_ty()), - &path, - None, - ); + // Suggest adding a bound to an existing trait + // or if the trait doesn't exist, add the trait + // and the suggested bounds. + let path = format!("<{}{} = {}>", item_name, item_args, p); + note = !suggest_constraining_type_param( + tcx, + generics, + diag, + &format!("{}", proj.self_ty()), + &path, + None, + matching_span, + ); + } } if note { diag.note("you might be missing a type parameter or trait bound"); diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs index cd9b9270140..0a30ae9d0aa 100644 --- a/compiler/rustc_middle/src/ty/diagnostics.rs +++ b/compiler/rustc_middle/src/ty/diagnostics.rs @@ -193,6 +193,9 @@ fn suggest_removing_unsized_bound( } /// Suggest restricting a type param with a new bound. +/// +/// If `span_to_replace` is provided, then that span will be replaced with the +/// `constraint`. If one wasn't provided, then the full bound will be suggested. pub fn suggest_constraining_type_param( tcx: TyCtxt<'_>, generics: &hir::Generics<'_>, @@ -200,12 +203,14 @@ pub fn suggest_constraining_type_param( param_name: &str, constraint: &str, def_id: Option, + span_to_replace: Option, ) -> bool { suggest_constraining_type_params( tcx, generics, err, [(param_name, constraint, def_id)].into_iter(), + span_to_replace, ) } @@ -215,6 +220,7 @@ pub fn suggest_constraining_type_params<'a>( generics: &hir::Generics<'_>, err: &mut Diagnostic, param_names_and_constraints: impl Iterator)>, + span_to_replace: Option, ) -> bool { let mut grouped = FxHashMap::default(); param_names_and_constraints.for_each(|(param_name, constraint, def_id)| { @@ -253,7 +259,9 @@ pub fn suggest_constraining_type_params<'a>( let mut suggest_restrict = |span, bound_list_non_empty| { suggestions.push(( span, - if bound_list_non_empty { + if span_to_replace.is_some() { + constraint.clone() + } else if bound_list_non_empty { format!(" + {}", constraint) } else { format!(" {}", constraint) @@ -262,6 +270,11 @@ pub fn suggest_constraining_type_params<'a>( )) }; + if let Some(span) = span_to_replace { + suggest_restrict(span, true); + continue; + } + // When the type parameter has been provided bounds // // Message: 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 87dbf7c3fd6..91da690a000 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -679,6 +679,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ¶m_name, &constraint, Some(trait_pred.def_id()), + None, ) { return; } @@ -1087,6 +1088,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { param.name.as_str(), "Clone", Some(clone_trait), + None, ); } err.span_suggestion_verbose( diff --git a/tests/ui/associated-types/hr-associated-type-projection-1.stderr b/tests/ui/associated-types/hr-associated-type-projection-1.stderr index a65f84ae58e..2281d9419b4 100644 --- a/tests/ui/associated-types/hr-associated-type-projection-1.stderr +++ b/tests/ui/associated-types/hr-associated-type-projection-1.stderr @@ -16,8 +16,8 @@ LL | for<'b> >::Item: std::ops::Deref, | ^^^^^^^^^^ required by this bound in `UnsafeCopy` help: consider further restricting this bound | -LL | impl> UnsafeCopy<'_, T> for T { - | +++++++++++++++++++ +LL | impl> UnsafeCopy<'_, T> for T { + | ++++++++++++ error: aborting due to previous error diff --git a/tests/ui/generic-associated-types/issue-68656-unsized-values.stderr b/tests/ui/generic-associated-types/issue-68656-unsized-values.stderr index e8770aedfa1..f0212e985a9 100644 --- a/tests/ui/generic-associated-types/issue-68656-unsized-values.stderr +++ b/tests/ui/generic-associated-types/issue-68656-unsized-values.stderr @@ -15,8 +15,8 @@ LL | type Item<'a>: std::ops::Deref; | ^^^^^^^^^^ required by this bound in `UnsafeCopy::Item` help: consider further restricting this bound | -LL | impl> UnsafeCopy for T { - | +++++++++++++++++++ +LL | impl> UnsafeCopy for T { + | ++++++++++++ error: aborting due to previous error diff --git a/tests/ui/generic-associated-types/missing-bounds.fixed b/tests/ui/generic-associated-types/missing-bounds.fixed index ee758f19ec1..054adbffbea 100644 --- a/tests/ui/generic-associated-types/missing-bounds.fixed +++ b/tests/ui/generic-associated-types/missing-bounds.fixed @@ -4,7 +4,7 @@ use std::ops::Add; struct A(B); -impl Add for A where B: Add + Add { +impl Add for A where B: Add { type Output = Self; fn add(self, rhs: Self) -> Self { @@ -14,7 +14,7 @@ impl Add for A where B: Add + Add { struct C(B); -impl> Add for C { +impl> Add for C { type Output = Self; fn add(self, rhs: Self) -> Self { @@ -34,7 +34,7 @@ impl> Add for D { struct E(B); -impl> Add for E where B: Add { +impl> Add for E where B: Add { //~^ ERROR equality constraints are not yet supported in `where` clauses type Output = Self; diff --git a/tests/ui/generic-associated-types/missing-bounds.stderr b/tests/ui/generic-associated-types/missing-bounds.stderr index 9f669b9a521..535edec575a 100644 --- a/tests/ui/generic-associated-types/missing-bounds.stderr +++ b/tests/ui/generic-associated-types/missing-bounds.stderr @@ -37,8 +37,8 @@ LL | struct A(B); | ^ help: consider further restricting this bound | -LL | impl Add for A where B: Add + Add { - | +++++++++++++++++ +LL | impl Add for A where B: Add { + | ++++++++++++ error[E0308]: mismatched types --> $DIR/missing-bounds.rs:21:14 @@ -60,8 +60,8 @@ LL | struct C(B); | ^ help: consider further restricting this bound | -LL | impl> Add for C { - | +++++++++++++++++ +LL | impl> Add for C { + | ++++++++++++ error[E0369]: cannot add `B` to `B` --> $DIR/missing-bounds.rs:31:21 @@ -96,8 +96,8 @@ LL | struct E(B); | ^ help: consider further restricting this bound | -LL | impl> Add for E where ::Output = B { - | +++++++++++++++++ +LL | impl> Add for E where ::Output = B { + | ++++++++++++ error: aborting due to 5 previous errors diff --git a/tests/ui/suggestions/restrict-existing-type-bounds.rs b/tests/ui/suggestions/restrict-existing-type-bounds.rs new file mode 100644 index 00000000000..07712ce0de6 --- /dev/null +++ b/tests/ui/suggestions/restrict-existing-type-bounds.rs @@ -0,0 +1,30 @@ +pub trait TryAdd { + type Error; + type Output; + + fn try_add(self, rhs: Rhs) -> Result; +} + +impl TryAdd for Option { + type Error = ::Error; + type Output = Option<::Output>; + + fn try_add(self, rhs: Self) -> Result { + Ok(self) //~ ERROR mismatched types + } +} + +struct Other(A); + +struct X; + +impl> TryAdd for Other { + type Error = ::Error; + type Output = Other<::Output>; + + fn try_add(self, rhs: Self) -> Result { + Ok(self) //~ ERROR mismatched types + } +} + +fn main() {} diff --git a/tests/ui/suggestions/restrict-existing-type-bounds.stderr b/tests/ui/suggestions/restrict-existing-type-bounds.stderr new file mode 100644 index 00000000000..14a244b790a --- /dev/null +++ b/tests/ui/suggestions/restrict-existing-type-bounds.stderr @@ -0,0 +1,57 @@ +error[E0308]: mismatched types + --> $DIR/restrict-existing-type-bounds.rs:13:12 + | +LL | impl TryAdd for Option { + | - this type parameter +... +LL | Ok(self) + | -- ^^^^ expected `Option<::Output>`, found `Option` + | | + | arguments to this enum variant are incorrect + | + = note: expected enum `Option<::Output>` + found enum `Option` +help: the type constructed contains `Option` due to the type of the argument passed + --> $DIR/restrict-existing-type-bounds.rs:13:9 + | +LL | Ok(self) + | ^^^----^ + | | + | this argument influences the type of `Ok` +note: tuple variant defined here + --> $SRC_DIR/core/src/result.rs:LL:COL +help: consider further restricting this bound + | +LL | impl> TryAdd for Option { + | ++++++++++++ + +error[E0308]: mismatched types + --> $DIR/restrict-existing-type-bounds.rs:26:12 + | +LL | impl> TryAdd for Other { + | - this type parameter +... +LL | Ok(self) + | -- ^^^^ expected `Other<::Output>`, found `Other` + | | + | arguments to this enum variant are incorrect + | + = note: expected struct `Other<::Output>` + found struct `Other` +help: the type constructed contains `Other` due to the type of the argument passed + --> $DIR/restrict-existing-type-bounds.rs:26:9 + | +LL | Ok(self) + | ^^^----^ + | | + | this argument influences the type of `Ok` +note: tuple variant defined here + --> $SRC_DIR/core/src/result.rs:LL:COL +help: consider further restricting this bound + | +LL | impl> TryAdd for Other { + | ++++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`.