Skip to content

Commit d6a57d3

Browse files
committed
Auto merge of #94034 - willcrichton:fix-trait-suggestion-for-binops, r=estebank
Fix incorrect suggestion for trait bounds involving binary operators This PR fixes #93927, #92347, #93744 by replacing the bespoke trait-suggestion logic in `op.rs` with a more common code path. The downside is that this fix causes some suggestions to not include an `Output=` type, reducing their usefulness. Note that this causes one case in the `missing-bounds.rs` test to fail rustfix. So I would need to move that code into a separate non-fix test if this PR is otherwise acceptable.
2 parents 1c988cf + dc41dba commit d6a57d3

File tree

10 files changed

+109
-161
lines changed

10 files changed

+109
-161
lines changed

compiler/rustc_typeck/src/check/op.rs

+47-90
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ use rustc_middle::ty::adjustment::{
1111
};
1212
use rustc_middle::ty::fold::TypeFolder;
1313
use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint};
14-
use rustc_middle::ty::{
15-
self, suggest_constraining_type_param, Ty, TyCtxt, TypeFoldable, TypeVisitor,
16-
};
14+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor};
1715
use rustc_span::source_map::Spanned;
1816
use rustc_span::symbol::{sym, Ident};
1917
use rustc_span::Span;
2018
use rustc_trait_selection::infer::InferCtxtExt;
19+
use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt as _;
2120
use rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt};
2221

2322
use std::ops::ControlFlow;
@@ -266,7 +265,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
266265
Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
267266
Err(errors) => {
268267
let source_map = self.tcx.sess.source_map();
269-
let (mut err, missing_trait, use_output) = match is_assign {
268+
let (mut err, missing_trait, _use_output) = match is_assign {
270269
IsAssign::Yes => {
271270
let mut err = struct_span_err!(
272271
self.tcx.sess,
@@ -449,39 +448,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
449448
// concatenation (e.g., "Hello " + "World!"). This means
450449
// we don't want the note in the else clause to be emitted
451450
} else if let [ty] = &visitor.0[..] {
452-
if let ty::Param(p) = *ty.kind() {
453-
// Check if the method would be found if the type param wasn't
454-
// involved. If so, it means that adding a trait bound to the param is
455-
// enough. Otherwise we do not give the suggestion.
456-
let mut eraser = TypeParamEraser(self, expr.span);
457-
let needs_bound = self
458-
.lookup_op_method(
459-
eraser.fold_ty(lhs_ty),
460-
Some(eraser.fold_ty(rhs_ty)),
461-
Some(rhs_expr),
462-
Op::Binary(op, is_assign),
463-
)
464-
.is_ok();
465-
if needs_bound {
466-
suggest_constraining_param(
467-
self.tcx,
468-
self.body_id,
451+
// Look for a TraitPredicate in the Fulfillment errors,
452+
// and use it to generate a suggestion.
453+
//
454+
// Note that lookup_op_method must be called again but
455+
// with a specific rhs_ty instead of a placeholder so
456+
// the resulting predicate generates a more specific
457+
// suggestion for the user.
458+
let errors = self
459+
.lookup_op_method(
460+
lhs_ty,
461+
Some(rhs_ty),
462+
Some(rhs_expr),
463+
Op::Binary(op, is_assign),
464+
)
465+
.unwrap_err();
466+
let predicates = errors
467+
.into_iter()
468+
.filter_map(|error| error.obligation.predicate.to_opt_poly_trait_pred())
469+
.collect::<Vec<_>>();
470+
if !predicates.is_empty() {
471+
for pred in predicates {
472+
self.infcx.suggest_restricting_param_bound(
469473
&mut err,
470-
*ty,
471-
rhs_ty,
472-
missing_trait,
473-
p,
474-
use_output,
474+
pred,
475+
self.body_id,
475476
);
476-
} else if *ty != lhs_ty {
477-
// When we know that a missing bound is responsible, we don't show
478-
// this note as it is redundant.
479-
err.note(&format!(
480-
"the trait `{missing_trait}` is not implemented for `{lhs_ty}`"
481-
));
482477
}
483-
} else {
484-
bug!("type param visitor stored a non type param: {:?}", ty.kind());
478+
} else if *ty != lhs_ty {
479+
// When we know that a missing bound is responsible, we don't show
480+
// this note as it is redundant.
481+
err.note(&format!(
482+
"the trait `{missing_trait}` is not implemented for `{lhs_ty}`"
483+
));
485484
}
486485
}
487486
}
@@ -671,24 +670,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
671670
ex.span,
672671
format!("cannot apply unary operator `{}`", op.as_str()),
673672
);
674-
let missing_trait = match op {
675-
hir::UnOp::Deref => unreachable!("check unary op `-` or `!` only"),
676-
hir::UnOp::Not => "std::ops::Not",
677-
hir::UnOp::Neg => "std::ops::Neg",
678-
};
673+
679674
let mut visitor = TypeParamVisitor(vec![]);
680675
visitor.visit_ty(operand_ty);
681-
if let [ty] = &visitor.0[..] && let ty::Param(p) = *operand_ty.kind() {
682-
suggest_constraining_param(
683-
self.tcx,
684-
self.body_id,
685-
&mut err,
686-
*ty,
687-
operand_ty,
688-
missing_trait,
689-
p,
690-
true,
691-
);
676+
if let [_] = &visitor.0[..] && let ty::Param(_) = *operand_ty.kind() {
677+
let predicates = errors
678+
.iter()
679+
.filter_map(|error| {
680+
error.obligation.predicate.clone().to_opt_poly_trait_pred()
681+
});
682+
for pred in predicates {
683+
self.infcx.suggest_restricting_param_bound(
684+
&mut err,
685+
pred,
686+
self.body_id,
687+
);
688+
}
692689
}
693690

694691
let sp = self.tcx.sess.source_map().start_point(ex.span);
@@ -973,46 +970,6 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool
973970
}
974971
}
975972

976-
fn suggest_constraining_param(
977-
tcx: TyCtxt<'_>,
978-
body_id: hir::HirId,
979-
mut err: &mut Diagnostic,
980-
lhs_ty: Ty<'_>,
981-
rhs_ty: Ty<'_>,
982-
missing_trait: &str,
983-
p: ty::ParamTy,
984-
set_output: bool,
985-
) {
986-
let hir = tcx.hir();
987-
let msg = &format!("`{lhs_ty}` might need a bound for `{missing_trait}`");
988-
// Try to find the def-id and details for the parameter p. We have only the index,
989-
// so we have to find the enclosing function's def-id, then look through its declared
990-
// generic parameters to get the declaration.
991-
let def_id = hir.body_owner_def_id(hir::BodyId { hir_id: body_id });
992-
let generics = tcx.generics_of(def_id);
993-
let param_def_id = generics.type_param(&p, tcx).def_id;
994-
if let Some(generics) = param_def_id
995-
.as_local()
996-
.map(|id| hir.local_def_id_to_hir_id(id))
997-
.and_then(|id| hir.find_by_def_id(hir.get_parent_item(id)))
998-
.as_ref()
999-
.and_then(|node| node.generics())
1000-
{
1001-
let output = if set_output { format!("<Output = {rhs_ty}>") } else { String::new() };
1002-
suggest_constraining_type_param(
1003-
tcx,
1004-
generics,
1005-
&mut err,
1006-
&lhs_ty.to_string(),
1007-
&format!("{missing_trait}{output}"),
1008-
None,
1009-
);
1010-
} else {
1011-
let span = tcx.def_span(param_def_id);
1012-
err.span_label(span, msg);
1013-
}
1014-
}
1015-
1016973
struct TypeParamVisitor<'tcx>(Vec<Ty<'tcx>>);
1017974

1018975
impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> {

src/test/ui/binop/issue-93927.rs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Regression test for #93927: suggested trait bound for T should be Eq, not PartialEq
2+
struct MyType<T>(T);
3+
4+
impl<T> PartialEq for MyType<T>
5+
where
6+
T: Eq,
7+
{
8+
fn eq(&self, other: &Self) -> bool {
9+
true
10+
}
11+
}
12+
13+
fn cond<T: PartialEq>(val: MyType<T>) -> bool {
14+
val == val
15+
//~^ ERROR binary operation `==` cannot be applied to type `MyType<T>`
16+
}
17+
18+
fn main() {
19+
cond(MyType(0));
20+
}

src/test/ui/binop/issue-93927.stderr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error[E0369]: binary operation `==` cannot be applied to type `MyType<T>`
2+
--> $DIR/issue-93927.rs:14:9
3+
|
4+
LL | val == val
5+
| --- ^^ --- MyType<T>
6+
| |
7+
| MyType<T>
8+
|
9+
help: consider further restricting this bound
10+
|
11+
LL | fn cond<T: PartialEq + std::cmp::Eq>(val: MyType<T>) -> bool {
12+
| ++++++++++++++
13+
14+
error: aborting due to previous error
15+
16+
For more information about this error, try `rustc --explain E0369`.

src/test/ui/generic-associated-types/missing-bounds.fixed

-46
This file was deleted.

src/test/ui/generic-associated-types/missing-bounds.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// run-rustfix
2-
31
use std::ops::Add;
42

53
struct A<B>(B);

src/test/ui/generic-associated-types/missing-bounds.stderr

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: equality constraints are not yet supported in `where` clauses
2-
--> $DIR/missing-bounds.rs:37:33
2+
--> $DIR/missing-bounds.rs:35:33
33
|
44
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
55
| ^^^^^^^^^^^^^^^^^^^^^^ not supported
@@ -11,7 +11,7 @@ LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
1111
| ~~~~~~~~~~~~~~~~~~
1212

1313
error[E0308]: mismatched types
14-
--> $DIR/missing-bounds.rs:11:11
14+
--> $DIR/missing-bounds.rs:9:11
1515
|
1616
LL | impl<B> Add for A<B> where B: Add {
1717
| - this type parameter
@@ -24,7 +24,7 @@ LL | A(self.0 + rhs.0)
2424
= note: expected type parameter `B`
2525
found associated type `<B as Add>::Output`
2626
note: tuple struct defined here
27-
--> $DIR/missing-bounds.rs:5:8
27+
--> $DIR/missing-bounds.rs:3:8
2828
|
2929
LL | struct A<B>(B);
3030
| ^
@@ -34,7 +34,7 @@ LL | impl<B> Add for A<B> where B: Add + Add<Output = B> {
3434
| +++++++++++++++++
3535

3636
error[E0308]: mismatched types
37-
--> $DIR/missing-bounds.rs:21:14
37+
--> $DIR/missing-bounds.rs:19:14
3838
|
3939
LL | impl<B: Add> Add for C<B> {
4040
| - this type parameter
@@ -47,7 +47,7 @@ LL | Self(self.0 + rhs.0)
4747
= note: expected type parameter `B`
4848
found associated type `<B as Add>::Output`
4949
note: tuple struct defined here
50-
--> $DIR/missing-bounds.rs:15:8
50+
--> $DIR/missing-bounds.rs:13:8
5151
|
5252
LL | struct C<B>(B);
5353
| ^
@@ -57,7 +57,7 @@ LL | impl<B: Add + Add<Output = B>> Add for C<B> {
5757
| +++++++++++++++++
5858

5959
error[E0369]: cannot add `B` to `B`
60-
--> $DIR/missing-bounds.rs:31:21
60+
--> $DIR/missing-bounds.rs:29:21
6161
|
6262
LL | Self(self.0 + rhs.0)
6363
| ------ ^ ----- B
@@ -66,11 +66,11 @@ LL | Self(self.0 + rhs.0)
6666
|
6767
help: consider restricting type parameter `B`
6868
|
69-
LL | impl<B: std::ops::Add<Output = B>> Add for D<B> {
70-
| +++++++++++++++++++++++++++
69+
LL | impl<B: std::ops::Add> Add for D<B> {
70+
| +++++++++++++++
7171

7272
error[E0308]: mismatched types
73-
--> $DIR/missing-bounds.rs:42:14
73+
--> $DIR/missing-bounds.rs:40:14
7474
|
7575
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
7676
| - this type parameter
@@ -83,7 +83,7 @@ LL | Self(self.0 + rhs.0)
8383
= note: expected type parameter `B`
8484
found associated type `<B as Add>::Output`
8585
note: tuple struct defined here
86-
--> $DIR/missing-bounds.rs:35:8
86+
--> $DIR/missing-bounds.rs:33:8
8787
|
8888
LL | struct E<B>(B);
8989
| ^

src/test/ui/issues/issue-35668.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ LL | a.iter().map(|a| a*a)
66
| |
77
| &T
88
|
9-
help: consider restricting type parameter `T`
9+
help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement
1010
|
11-
LL | fn func<'a, T: std::ops::Mul<Output = &T>>(a: &'a [T]) -> impl Iterator<Item=&'a T> {
12-
| ++++++++++++++++++++++++++++
11+
LL | fn func<'a, T>(a: &'a [T]) -> impl Iterator<Item=&'a T> where &T: Mul<&T> {
12+
| +++++++++++++++++
1313

1414
error: aborting due to previous error
1515

src/test/ui/suggestions/invalid-bin-op.stderr

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ note: an implementation of `PartialEq<_>` might be missing for `S<T>`
1111
|
1212
LL | struct S<T>(T);
1313
| ^^^^^^^^^^^^^^^ must implement `PartialEq<_>`
14-
= note: the trait `std::cmp::PartialEq` is not implemented for `S<T>`
1514
help: consider annotating `S<T>` with `#[derive(PartialEq)]`
1615
|
1716
LL | #[derive(PartialEq)]
1817
|
18+
help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement
19+
|
20+
LL | pub fn foo<T>(s: S<T>, t: S<T>) where S<T>: PartialEq {
21+
| +++++++++++++++++++++
1922

2023
error: aborting due to previous error
2124

src/test/ui/traits/resolution-in-overloaded-op.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ LL | a * b
66
| |
77
| &T
88
|
9-
help: consider further restricting this bound
9+
help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement
1010
|
11-
LL | fn foo<T: MyMul<f64, f64> + std::ops::Mul<Output = f64>>(a: &T, b: f64) -> f64 {
12-
| +++++++++++++++++++++++++++++
11+
LL | fn foo<T: MyMul<f64, f64>>(a: &T, b: f64) -> f64 where &T: Mul<f64> {
12+
| ++++++++++++++++++
1313

1414
error: aborting due to previous error
1515

0 commit comments

Comments
 (0)