diff --git a/compiler/rustc_typeck/src/check/op.rs b/compiler/rustc_typeck/src/check/op.rs index 811833bca8031..3be04ef6d2106 100644 --- a/compiler/rustc_typeck/src/check/op.rs +++ b/compiler/rustc_typeck/src/check/op.rs @@ -11,13 +11,12 @@ use rustc_middle::ty::adjustment::{ }; use rustc_middle::ty::fold::TypeFolder; use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint}; -use rustc_middle::ty::{ - self, suggest_constraining_type_param, Ty, TyCtxt, TypeFoldable, TypeVisitor, -}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt as _; use rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt}; use std::ops::ControlFlow; @@ -266,7 +265,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(), Err(errors) => { let source_map = self.tcx.sess.source_map(); - let (mut err, missing_trait, use_output) = match is_assign { + let (mut err, missing_trait, _use_output) = match is_assign { IsAssign::Yes => { let mut err = struct_span_err!( self.tcx.sess, @@ -449,40 +448,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // concatenation (e.g., "Hello " + "World!"). This means // we don't want the note in the else clause to be emitted } else if let [ty] = &visitor.0[..] { - if let ty::Param(p) = *ty.kind() { - // Check if the method would be found if the type param wasn't - // involved. If so, it means that adding a trait bound to the param is - // enough. Otherwise we do not give the suggestion. - let mut eraser = TypeParamEraser(self, expr.span); - let needs_bound = self - .lookup_op_method( - eraser.fold_ty(lhs_ty), - Some(eraser.fold_ty(rhs_ty)), - Some(rhs_expr), - Op::Binary(op, is_assign), - ) - .is_ok(); - if needs_bound { - suggest_constraining_param( - self.tcx, - self.body_id, - &mut err, - *ty, - rhs_ty, - missing_trait, - p, - use_output, - ); - } else if *ty != lhs_ty { - // When we know that a missing bound is responsible, we don't show - // this note as it is redundant. - err.note(&format!( - "the trait `{missing_trait}` is not implemented for `{lhs_ty}`" - )); - } - } else { - bug!("type param visitor stored a non type param: {:?}", ty.kind()); + // Look for a TraitPredicate in the Fulfillment errors, + // and use it to generate a suggestion. + // + // Note that lookup_op_method must be called again but + // with a specific rhs_ty instead of a placeholder so + // the resulting predicate generates a more specific + // suggestion for the user. + let errors = self + .lookup_op_method(lhs_ty, &[rhs_ty], Op::Binary(op, is_assign)) + .unwrap_err(); + let predicates = errors + .into_iter() + .filter_map(|error| error.obligation.predicate.to_opt_poly_trait_pred()) + .collect::>(); + if !predicates.is_empty() { + for pred in predicates { + self.infcx.suggest_restricting_param_bound(&mut err, + pred, + self.body_id, + ); } + } else if *ty != lhs_ty { + // When we know that a missing bound is responsible, we don't show + // this note as it is redundant. + err.note(&format!( + "the trait `{missing_trait}` is not implemented for `{lhs_ty}`" + )); } } err.emit(); @@ -973,46 +965,6 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool } } -fn suggest_constraining_param( - tcx: TyCtxt<'_>, - body_id: hir::HirId, - mut err: &mut Diagnostic, - lhs_ty: Ty<'_>, - rhs_ty: Ty<'_>, - missing_trait: &str, - p: ty::ParamTy, - set_output: bool, -) { - let hir = tcx.hir(); - let msg = &format!("`{lhs_ty}` might need a bound for `{missing_trait}`"); - // Try to find the def-id and details for the parameter p. We have only the index, - // so we have to find the enclosing function's def-id, then look through its declared - // generic parameters to get the declaration. - let def_id = hir.body_owner_def_id(hir::BodyId { hir_id: body_id }); - let generics = tcx.generics_of(def_id); - let param_def_id = generics.type_param(&p, tcx).def_id; - if let Some(generics) = param_def_id - .as_local() - .map(|id| hir.local_def_id_to_hir_id(id)) - .and_then(|id| hir.find_by_def_id(hir.get_parent_item(id))) - .as_ref() - .and_then(|node| node.generics()) - { - let output = if set_output { format!("") } else { String::new() }; - suggest_constraining_type_param( - tcx, - generics, - &mut err, - &lhs_ty.to_string(), - &format!("{missing_trait}{output}"), - None, - ); - } else { - let span = tcx.def_span(param_def_id); - err.span_label(span, msg); - } -} - struct TypeParamVisitor<'tcx>(Vec>); impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> { diff --git a/src/test/ui/binop/issue-93927.rs b/src/test/ui/binop/issue-93927.rs new file mode 100644 index 0000000000000..de27c9785e65a --- /dev/null +++ b/src/test/ui/binop/issue-93927.rs @@ -0,0 +1,20 @@ +// Regression test for #93927: suggested trait bound for T should be Eq, not PartialEq +struct MyType(T); + +impl PartialEq for MyType +where + T: Eq, +{ + fn eq(&self, other: &Self) -> bool { + true + } +} + +fn cond(val: MyType) -> bool { + val == val + //~^ ERROR binary operation `==` cannot be applied to type `MyType` +} + +fn main() { + cond(MyType(0)); +} diff --git a/src/test/ui/binop/issue-93927.stderr b/src/test/ui/binop/issue-93927.stderr new file mode 100644 index 0000000000000..75558b502f9dc --- /dev/null +++ b/src/test/ui/binop/issue-93927.stderr @@ -0,0 +1,16 @@ +error[E0369]: binary operation `==` cannot be applied to type `MyType` + --> $DIR/issue-93927.rs:14:9 + | +LL | val == val + | --- ^^ --- MyType + | | + | MyType + | +help: consider further restricting this bound + | +LL | fn cond(val: MyType) -> bool { + | ++++++++++++++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0369`. diff --git a/src/test/ui/generic-associated-types/missing-bounds.fixed b/src/test/ui/generic-associated-types/missing-bounds.fixed index 0e234120a51c5..8eddfe21e30fa 100644 --- a/src/test/ui/generic-associated-types/missing-bounds.fixed +++ b/src/test/ui/generic-associated-types/missing-bounds.fixed @@ -24,7 +24,7 @@ impl> Add for C { struct D(B); -impl> Add for D { +impl Add for D { type Output = Self; fn add(self, rhs: Self) -> Self { diff --git a/src/test/ui/generic-associated-types/missing-bounds.stderr b/src/test/ui/generic-associated-types/missing-bounds.stderr index 240be93cf9617..25db846109854 100644 --- a/src/test/ui/generic-associated-types/missing-bounds.stderr +++ b/src/test/ui/generic-associated-types/missing-bounds.stderr @@ -66,8 +66,8 @@ LL | Self(self.0 + rhs.0) | help: consider restricting type parameter `B` | -LL | impl> Add for D { - | +++++++++++++++++++++++++++ +LL | impl Add for D { + | +++++++++++++++ error[E0308]: mismatched types --> $DIR/missing-bounds.rs:42:14 diff --git a/src/test/ui/issues/issue-35668.stderr b/src/test/ui/issues/issue-35668.stderr index 04faea9008a11..07409e9834a46 100644 --- a/src/test/ui/issues/issue-35668.stderr +++ b/src/test/ui/issues/issue-35668.stderr @@ -6,10 +6,10 @@ LL | a.iter().map(|a| a*a) | | | &T | -help: consider restricting type parameter `T` +help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement | -LL | fn func<'a, T: std::ops::Mul>(a: &'a [T]) -> impl Iterator { - | ++++++++++++++++++++++++++++ +LL | fn func<'a, T>(a: &'a [T]) -> impl Iterator where &T: Mul<&T> { + | +++++++++++++++++ error: aborting due to previous error diff --git a/src/test/ui/suggestions/invalid-bin-op.stderr b/src/test/ui/suggestions/invalid-bin-op.stderr index d18c24e53d030..fe5e2b5816fdf 100644 --- a/src/test/ui/suggestions/invalid-bin-op.stderr +++ b/src/test/ui/suggestions/invalid-bin-op.stderr @@ -11,11 +11,14 @@ note: an implementation of `PartialEq<_>` might be missing for `S` | LL | struct S(T); | ^^^^^^^^^^^^^^^ must implement `PartialEq<_>` - = note: the trait `std::cmp::PartialEq` is not implemented for `S` help: consider annotating `S` with `#[derive(PartialEq)]` | LL | #[derive(PartialEq)] | +help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement + | +LL | pub fn foo(s: S, t: S) where S: PartialEq { + | +++++++++++++++++++++ error: aborting due to previous error diff --git a/src/test/ui/traits/resolution-in-overloaded-op.stderr b/src/test/ui/traits/resolution-in-overloaded-op.stderr index 049fffe165ab5..3ae6bf130cc7e 100644 --- a/src/test/ui/traits/resolution-in-overloaded-op.stderr +++ b/src/test/ui/traits/resolution-in-overloaded-op.stderr @@ -6,10 +6,10 @@ LL | a * b | | | &T | -help: consider further restricting this bound +help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement | -LL | fn foo + std::ops::Mul>(a: &T, b: f64) -> f64 { - | +++++++++++++++++++++++++++++ +LL | fn foo>(a: &T, b: f64) -> f64 where &T: Mul { + | ++++++++++++++++++ error: aborting due to previous error diff --git a/src/test/ui/type/type-check/missing_trait_impl.stderr b/src/test/ui/type/type-check/missing_trait_impl.stderr index 59b8692dd4d1a..fe156a8845396 100644 --- a/src/test/ui/type/type-check/missing_trait_impl.stderr +++ b/src/test/ui/type/type-check/missing_trait_impl.stderr @@ -8,8 +8,8 @@ LL | let z = x + y; | help: consider restricting type parameter `T` | -LL | fn foo>(x: T, y: T) { - | +++++++++++++++++++++++++++ +LL | fn foo(x: T, y: T) { + | +++++++++++++++ error[E0368]: binary assignment operation `+=` cannot be applied to type `T` --> $DIR/missing_trait_impl.rs:9:5