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.
This commit is contained in:
bors 2022-04-26 07:29:15 +00:00
commit d6a57d3730
10 changed files with 109 additions and 161 deletions

View File

@ -11,13 +11,12 @@ use rustc_middle::ty::adjustment::{
}; };
use rustc_middle::ty::fold::TypeFolder; use rustc_middle::ty::fold::TypeFolder;
use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint}; use rustc_middle::ty::TyKind::{Adt, Array, Char, FnDef, Never, Ref, Str, Tuple, Uint};
use rustc_middle::ty::{ use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitor};
self, suggest_constraining_type_param, Ty, TyCtxt, TypeFoldable, TypeVisitor,
};
use rustc_span::source_map::Spanned; use rustc_span::source_map::Spanned;
use rustc_span::symbol::{sym, Ident}; use rustc_span::symbol::{sym, Ident};
use rustc_span::Span; use rustc_span::Span;
use rustc_trait_selection::infer::InferCtxtExt; 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 rustc_trait_selection::traits::{FulfillmentError, TraitEngine, TraitEngineExt};
use std::ops::ControlFlow; 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(_) if lhs_ty.references_error() || rhs_ty.references_error() => self.tcx.ty_error(),
Err(errors) => { Err(errors) => {
let source_map = self.tcx.sess.source_map(); 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 => { IsAssign::Yes => {
let mut err = struct_span_err!( let mut err = struct_span_err!(
self.tcx.sess, self.tcx.sess,
@ -449,39 +448,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// concatenation (e.g., "Hello " + "World!"). This means // concatenation (e.g., "Hello " + "World!"). This means
// we don't want the note in the else clause to be emitted // we don't want the note in the else clause to be emitted
} else if let [ty] = &visitor.0[..] { } else if let [ty] = &visitor.0[..] {
if let ty::Param(p) = *ty.kind() { // Look for a TraitPredicate in the Fulfillment errors,
// Check if the method would be found if the type param wasn't // and use it to generate a suggestion.
// involved. If so, it means that adding a trait bound to the param is //
// enough. Otherwise we do not give the suggestion. // Note that lookup_op_method must be called again but
let mut eraser = TypeParamEraser(self, expr.span); // with a specific rhs_ty instead of a placeholder so
let needs_bound = self // the resulting predicate generates a more specific
.lookup_op_method( // suggestion for the user.
eraser.fold_ty(lhs_ty), let errors = self
Some(eraser.fold_ty(rhs_ty)), .lookup_op_method(
Some(rhs_expr), lhs_ty,
Op::Binary(op, is_assign), Some(rhs_ty),
) Some(rhs_expr),
.is_ok(); Op::Binary(op, is_assign),
if needs_bound { )
suggest_constraining_param( .unwrap_err();
self.tcx, let predicates = errors
self.body_id, .into_iter()
.filter_map(|error| error.obligation.predicate.to_opt_poly_trait_pred())
.collect::<Vec<_>>();
if !predicates.is_empty() {
for pred in predicates {
self.infcx.suggest_restricting_param_bound(
&mut err, &mut err,
*ty, pred,
rhs_ty, self.body_id,
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 { } else if *ty != lhs_ty {
bug!("type param visitor stored a non type param: {:?}", ty.kind()); // 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}`"
));
} }
} }
} }
@ -671,24 +670,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ex.span, ex.span,
format!("cannot apply unary operator `{}`", op.as_str()), format!("cannot apply unary operator `{}`", op.as_str()),
); );
let missing_trait = match op {
hir::UnOp::Deref => unreachable!("check unary op `-` or `!` only"),
hir::UnOp::Not => "std::ops::Not",
hir::UnOp::Neg => "std::ops::Neg",
};
let mut visitor = TypeParamVisitor(vec![]); let mut visitor = TypeParamVisitor(vec![]);
visitor.visit_ty(operand_ty); visitor.visit_ty(operand_ty);
if let [ty] = &visitor.0[..] && let ty::Param(p) = *operand_ty.kind() { if let [_] = &visitor.0[..] && let ty::Param(_) = *operand_ty.kind() {
suggest_constraining_param( let predicates = errors
self.tcx, .iter()
self.body_id, .filter_map(|error| {
&mut err, error.obligation.predicate.clone().to_opt_poly_trait_pred()
*ty, });
operand_ty, for pred in predicates {
missing_trait, self.infcx.suggest_restricting_param_bound(
p, &mut err,
true, pred,
); self.body_id,
);
}
} }
let sp = self.tcx.sess.source_map().start_point(ex.span); 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
} }
} }
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!("<Output = {rhs_ty}>") } 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<Ty<'tcx>>); struct TypeParamVisitor<'tcx>(Vec<Ty<'tcx>>);
impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> { impl<'tcx> TypeVisitor<'tcx> for TypeParamVisitor<'tcx> {

View File

@ -0,0 +1,20 @@
// Regression test for #93927: suggested trait bound for T should be Eq, not PartialEq
struct MyType<T>(T);
impl<T> PartialEq for MyType<T>
where
T: Eq,
{
fn eq(&self, other: &Self) -> bool {
true
}
}
fn cond<T: PartialEq>(val: MyType<T>) -> bool {
val == val
//~^ ERROR binary operation `==` cannot be applied to type `MyType<T>`
}
fn main() {
cond(MyType(0));
}

View File

@ -0,0 +1,16 @@
error[E0369]: binary operation `==` cannot be applied to type `MyType<T>`
--> $DIR/issue-93927.rs:14:9
|
LL | val == val
| --- ^^ --- MyType<T>
| |
| MyType<T>
|
help: consider further restricting this bound
|
LL | fn cond<T: PartialEq + std::cmp::Eq>(val: MyType<T>) -> bool {
| ++++++++++++++
error: aborting due to previous error
For more information about this error, try `rustc --explain E0369`.

View File

@ -1,46 +0,0 @@
// run-rustfix
use std::ops::Add;
struct A<B>(B);
impl<B> Add for A<B> where B: Add + Add<Output = B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
A(self.0 + rhs.0) //~ ERROR mismatched types
}
}
struct C<B>(B);
impl<B: Add + Add<Output = B>> Add for C<B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR mismatched types
}
}
struct D<B>(B);
impl<B: std::ops::Add<Output = B>> Add for D<B> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR cannot add `B` to `B`
}
}
struct E<B>(B);
impl<B: Add> Add for E<B> where B: Add<Output = B>, B: Add<Output = B> {
//~^ ERROR equality constraints are not yet supported in `where` clauses
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0) //~ ERROR mismatched types
}
}
fn main() {}

View File

@ -1,5 +1,3 @@
// run-rustfix
use std::ops::Add; use std::ops::Add;
struct A<B>(B); struct A<B>(B);

View File

@ -1,5 +1,5 @@
error: equality constraints are not yet supported in `where` clauses error: equality constraints are not yet supported in `where` clauses
--> $DIR/missing-bounds.rs:37:33 --> $DIR/missing-bounds.rs:35:33
| |
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B { LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
| ^^^^^^^^^^^^^^^^^^^^^^ not supported | ^^^^^^^^^^^^^^^^^^^^^^ not supported
@ -11,7 +11,7 @@ LL | impl<B: Add> Add for E<B> where B: Add<Output = B> {
| ~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:11:11 --> $DIR/missing-bounds.rs:9:11
| |
LL | impl<B> Add for A<B> where B: Add { LL | impl<B> Add for A<B> where B: Add {
| - this type parameter | - this type parameter
@ -24,7 +24,7 @@ LL | A(self.0 + rhs.0)
= note: expected type parameter `B` = note: expected type parameter `B`
found associated type `<B as Add>::Output` found associated type `<B as Add>::Output`
note: tuple struct defined here note: tuple struct defined here
--> $DIR/missing-bounds.rs:5:8 --> $DIR/missing-bounds.rs:3:8
| |
LL | struct A<B>(B); LL | struct A<B>(B);
| ^ | ^
@ -34,7 +34,7 @@ LL | impl<B> Add for A<B> where B: Add + Add<Output = B> {
| +++++++++++++++++ | +++++++++++++++++
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:21:14 --> $DIR/missing-bounds.rs:19:14
| |
LL | impl<B: Add> Add for C<B> { LL | impl<B: Add> Add for C<B> {
| - this type parameter | - this type parameter
@ -47,7 +47,7 @@ LL | Self(self.0 + rhs.0)
= note: expected type parameter `B` = note: expected type parameter `B`
found associated type `<B as Add>::Output` found associated type `<B as Add>::Output`
note: tuple struct defined here note: tuple struct defined here
--> $DIR/missing-bounds.rs:15:8 --> $DIR/missing-bounds.rs:13:8
| |
LL | struct C<B>(B); LL | struct C<B>(B);
| ^ | ^
@ -57,7 +57,7 @@ LL | impl<B: Add + Add<Output = B>> Add for C<B> {
| +++++++++++++++++ | +++++++++++++++++
error[E0369]: cannot add `B` to `B` error[E0369]: cannot add `B` to `B`
--> $DIR/missing-bounds.rs:31:21 --> $DIR/missing-bounds.rs:29:21
| |
LL | Self(self.0 + rhs.0) LL | Self(self.0 + rhs.0)
| ------ ^ ----- B | ------ ^ ----- B
@ -66,11 +66,11 @@ LL | Self(self.0 + rhs.0)
| |
help: consider restricting type parameter `B` help: consider restricting type parameter `B`
| |
LL | impl<B: std::ops::Add<Output = B>> Add for D<B> { LL | impl<B: std::ops::Add> Add for D<B> {
| +++++++++++++++++++++++++++ | +++++++++++++++
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/missing-bounds.rs:42:14 --> $DIR/missing-bounds.rs:40:14
| |
LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B { LL | impl<B: Add> Add for E<B> where <B as Add>::Output = B {
| - this type parameter | - this type parameter
@ -83,7 +83,7 @@ LL | Self(self.0 + rhs.0)
= note: expected type parameter `B` = note: expected type parameter `B`
found associated type `<B as Add>::Output` found associated type `<B as Add>::Output`
note: tuple struct defined here note: tuple struct defined here
--> $DIR/missing-bounds.rs:35:8 --> $DIR/missing-bounds.rs:33:8
| |
LL | struct E<B>(B); LL | struct E<B>(B);
| ^ | ^

View File

@ -6,10 +6,10 @@ LL | a.iter().map(|a| a*a)
| | | |
| &T | &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<Output = &T>>(a: &'a [T]) -> impl Iterator<Item=&'a T> { LL | fn func<'a, T>(a: &'a [T]) -> impl Iterator<Item=&'a T> where &T: Mul<&T> {
| ++++++++++++++++++++++++++++ | +++++++++++++++++
error: aborting due to previous error error: aborting due to previous error

View File

@ -11,11 +11,14 @@ note: an implementation of `PartialEq<_>` might be missing for `S<T>`
| |
LL | struct S<T>(T); LL | struct S<T>(T);
| ^^^^^^^^^^^^^^^ must implement `PartialEq<_>` | ^^^^^^^^^^^^^^^ must implement `PartialEq<_>`
= note: the trait `std::cmp::PartialEq` is not implemented for `S<T>`
help: consider annotating `S<T>` with `#[derive(PartialEq)]` help: consider annotating `S<T>` with `#[derive(PartialEq)]`
| |
LL | #[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<T>(s: S<T>, t: S<T>) where S<T>: PartialEq {
| +++++++++++++++++++++
error: aborting due to previous error error: aborting due to previous error

View File

@ -6,10 +6,10 @@ LL | a * b
| | | |
| &T | &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<T: MyMul<f64, f64> + std::ops::Mul<Output = f64>>(a: &T, b: f64) -> f64 { LL | fn foo<T: MyMul<f64, f64>>(a: &T, b: f64) -> f64 where &T: Mul<f64> {
| +++++++++++++++++++++++++++++ | ++++++++++++++++++
error: aborting due to previous error error: aborting due to previous error

View File

@ -8,8 +8,8 @@ LL | let z = x + y;
| |
help: consider restricting type parameter `T` help: consider restricting type parameter `T`
| |
LL | fn foo<T: std::ops::Add<Output = T>>(x: T, y: T) { LL | fn foo<T: std::ops::Add>(x: T, y: T) {
| +++++++++++++++++++++++++++ | +++++++++++++++
error[E0368]: binary assignment operation `+=` cannot be applied to type `T` error[E0368]: binary assignment operation `+=` cannot be applied to type `T`
--> $DIR/missing_trait_impl.rs:9:5 --> $DIR/missing_trait_impl.rs:9:5
@ -32,8 +32,8 @@ LL | let y = -x;
| |
help: consider restricting type parameter `T` help: consider restricting type parameter `T`
| |
LL | fn baz<T: std::ops::Neg<Output = T>>(x: T) { LL | fn baz<T: std::ops::Neg>(x: T) {
| +++++++++++++++++++++++++++ | +++++++++++++++
error[E0600]: cannot apply unary operator `!` to type `T` error[E0600]: cannot apply unary operator `!` to type `T`
--> $DIR/missing_trait_impl.rs:14:13 --> $DIR/missing_trait_impl.rs:14:13
@ -43,8 +43,8 @@ LL | let y = !x;
| |
help: consider restricting type parameter `T` help: consider restricting type parameter `T`
| |
LL | fn baz<T: std::ops::Not<Output = T>>(x: T) { LL | fn baz<T: std::ops::Not>(x: T) {
| +++++++++++++++++++++++++++ | +++++++++++++++
error[E0614]: type `T` cannot be dereferenced error[E0614]: type `T` cannot be dereferenced
--> $DIR/missing_trait_impl.rs:15:13 --> $DIR/missing_trait_impl.rs:15:13