Skip to content

Commit 8f40dae

Browse files
committed
Suggest type param trait bound for binop only when appropriate
Verify that the binop trait *is* implemented for the types *if* all the involved type parameters are replaced with fresh inferred types. When this is the case, it means that the type parameter was indeed missing a trait bound. If this is not the case, provide a generic `note` refering to the type that doesn't implement the expected trait.
1 parent 09af184 commit 8f40dae

8 files changed

+78
-50
lines changed

src/librustc_typeck/check/op.rs

+45-22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi
88
use rustc_middle::ty::adjustment::{
99
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
1010
};
11+
use rustc_middle::ty::fold::TypeFolder;
1112
use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint};
1213
use rustc_middle::ty::{
1314
self, suggest_constraining_type_param, Ty, TyCtxt, TypeFoldable, TypeVisitor,
@@ -436,29 +437,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
436437
// we don't want the note in the else clause to be emitted
437438
} else if let [ty] = &visitor.0[..] {
438439
if let ty::Param(p) = ty.kind {
439-
// FIXME: This *guesses* that constraining the type param
440-
// will make the operation available, but this is only true
441-
// when the corresponding trait has a blanket
442-
// implementation, like the following:
443-
// `impl<'a> PartialEq for &'a [T] where T: PartialEq {}`
444-
// The correct thing to do would be to verify this
445-
// projection would hold.
446-
if *ty != lhs_ty {
440+
// Check if the method would be found if the type param wasn't
441+
// involved. If so, it means that adding a trait bound to the param is
442+
// enough. Otherwise we do not give the suggestion.
443+
let mut eraser = TypeParamEraser(&self, expr.span);
444+
let needs_bound = self
445+
.lookup_op_method(
446+
eraser.fold_ty(lhs_ty),
447+
&[eraser.fold_ty(rhs_ty)],
448+
Op::Binary(op, is_assign),
449+
)
450+
.is_ok();
451+
if needs_bound {
452+
suggest_constraining_param(
453+
self.tcx,
454+
self.body_id,
455+
&mut err,
456+
ty,
457+
rhs_ty,
458+
missing_trait,
459+
p,
460+
use_output,
461+
);
462+
} else if *ty != lhs_ty {
463+
// When we know that a missing bound is responsible, we don't show
464+
// this note as it is redundant.
447465
err.note(&format!(
448466
"the trait `{}` is not implemented for `{}`",
449467
missing_trait, lhs_ty
450468
));
451469
}
452-
suggest_constraining_param(
453-
self.tcx,
454-
self.body_id,
455-
&mut err,
456-
ty,
457-
rhs_ty,
458-
missing_trait,
459-
p,
460-
use_output,
461-
);
462470
} else {
463471
bug!("type param visitor stored a non type param: {:?}", ty.kind);
464472
}
@@ -656,10 +664,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
656664
);
657665
err.span_label(
658666
ex.span,
659-
format!(
660-
"cannot apply unary operator `{}`",
661-
op.as_str()
662-
),
667+
format!("cannot apply unary operator `{}`", op.as_str()),
663668
);
664669
match actual.kind {
665670
Uint(_) if op == hir::UnOp::UnNeg => {
@@ -954,3 +959,21 @@ impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> {
954959
ty.super_visit_with(self)
955960
}
956961
}
962+
963+
struct TypeParamEraser<'a, 'tcx>(&'a FnCtxt<'a, 'tcx>, Span);
964+
965+
impl TypeFolder<'tcx> for TypeParamEraser<'_, 'tcx> {
966+
fn tcx(&self) -> TyCtxt<'tcx> {
967+
self.0.tcx
968+
}
969+
970+
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
971+
match ty.kind {
972+
ty::Param(_) => self.0.next_ty_var(TypeVariableOrigin {
973+
kind: TypeVariableOriginKind::MiscVariable,
974+
span: self.1,
975+
}),
976+
_ => ty.super_fold_with(self),
977+
}
978+
}
979+
}

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

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ LL | a.iter().map(|a| a*a)
66
| |
77
| &T
88
|
9-
= note: the trait `std::ops::Mul` is not implemented for `&T`
109
help: consider restricting type parameter `T`
1110
|
1211
LL | fn func<'a, T: std::ops::Mul<Output = &T>>(a: &'a [T]) -> impl Iterator<Item=&'a T> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub fn foo<T>(s: S<T>, t: S<T>) {
2+
let _ = s == t; //~ ERROR binary operation `==` cannot be applied to type `S<T>`
3+
}
4+
5+
struct S<T>(T);
6+
7+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0369]: binary operation `==` cannot be applied to type `S<T>`
2+
--> $DIR/invalid-bin-op.rs:2:15
3+
|
4+
LL | let _ = s == t;
5+
| - ^^ - S<T>
6+
| |
7+
| S<T>
8+
|
9+
= note: the trait `std::cmp::PartialEq` is not implemented for `S<T>`
10+
11+
error: aborting due to previous error
12+
13+
For more information about this error, try `rustc --explain E0369`.
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
// run-rustfix
22

3-
pub fn strip_prefix<'a, T: std::cmp::PartialEq>(s: &'a [T], prefix: &[T]) -> Option<&'a [T]> {
4-
let n = prefix.len();
5-
if n <= s.len() {
6-
let (head, tail) = s.split_at(n);
7-
if head == prefix { //~ ERROR binary operation `==` cannot be applied to type `&[T]`
8-
return Some(tail);
9-
}
10-
}
11-
None
3+
pub fn foo<T: std::cmp::PartialEq>(s: &[T], t: &[T]) {
4+
let _ = s == t; //~ ERROR binary operation `==` cannot be applied to type `&[T]`
125
}
6+
137
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
// run-rustfix
22

3-
pub fn strip_prefix<'a, T>(s: &'a [T], prefix: &[T]) -> Option<&'a [T]> {
4-
let n = prefix.len();
5-
if n <= s.len() {
6-
let (head, tail) = s.split_at(n);
7-
if head == prefix { //~ ERROR binary operation `==` cannot be applied to type `&[T]`
8-
return Some(tail);
9-
}
10-
}
11-
None
3+
pub fn foo<T>(s: &[T], t: &[T]) {
4+
let _ = s == t; //~ ERROR binary operation `==` cannot be applied to type `&[T]`
125
}
6+
137
fn main() {}

src/test/ui/suggestions/missing-trait-bound-for-op.stderr

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
error[E0369]: binary operation `==` cannot be applied to type `&[T]`
2-
--> $DIR/missing-trait-bound-for-op.rs:7:17
2+
--> $DIR/missing-trait-bound-for-op.rs:4:15
33
|
4-
LL | if head == prefix {
5-
| ---- ^^ ------ &[T]
6-
| |
7-
| &[T]
4+
LL | let _ = s == t;
5+
| - ^^ - &[T]
6+
| |
7+
| &[T]
88
|
9-
= note: the trait `std::cmp::PartialEq` is not implemented for `&[T]`
109
help: consider restricting type parameter `T`
1110
|
12-
LL | pub fn strip_prefix<'a, T: std::cmp::PartialEq>(s: &'a [T], prefix: &[T]) -> Option<&'a [T]> {
13-
| ^^^^^^^^^^^^^^^^^^^^^
11+
LL | pub fn foo<T: std::cmp::PartialEq>(s: &[T], t: &[T]) {
12+
| ^^^^^^^^^^^^^^^^^^^^^
1413

1514
error: aborting due to previous error
1615

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

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ LL | a * b
66
| |
77
| &T
88
|
9-
= note: the trait `std::ops::Mul` is not implemented for `&T`
109
help: consider further restricting this bound
1110
|
1211
LL | fn foo<T: MyMul<f64, f64> + std::ops::Mul<Output = f64>>(a: &T, b: f64) -> f64 {

0 commit comments

Comments
 (0)