Skip to content

Commit aba9fb4

Browse files
authored
Rollup merge of #110877 - compiler-errors:binop-err, r=cjgillot
Provide better type hints when a type doesn't support a binary operator For example, when checking whether `vec![A] == vec![A]` holds, we first evaluate the LHS's ty, then probe for any `PartialEq` implementations for that. If none is found, we report an error by evaluating `Vec<A>: PartialEq<?0>` for fulfillment errors, but the RHS is not yet evaluated and remains an inference variable `?0`! To fix this, we evaluate the RHS and equate it to that RHS infer var `?0`, so that we are able to provide more detailed fulfillment errors for why `Vec<A>: PartialEq<Vec<A>>` doesn't hold (namely, the nested obligation `A: PartialEq<A>` doesn't hold). Fixes #95285 Fixes #110867
2 parents f357475 + 3125979 commit aba9fb4

23 files changed

+215
-117
lines changed

compiler/rustc_hir_typeck/src/lib.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -455,15 +455,9 @@ fn fatally_break_rust(sess: &Session) {
455455
));
456456
}
457457

458-
fn has_expected_num_generic_args(
459-
tcx: TyCtxt<'_>,
460-
trait_did: Option<DefId>,
461-
expected: usize,
462-
) -> bool {
463-
trait_did.map_or(true, |trait_did| {
464-
let generics = tcx.generics_of(trait_did);
465-
generics.count() == expected + if generics.has_self { 1 } else { 0 }
466-
})
458+
fn has_expected_num_generic_args(tcx: TyCtxt<'_>, trait_did: DefId, expected: usize) -> bool {
459+
let generics = tcx.generics_of(trait_did);
460+
generics.count() == expected + if generics.has_self { 1 } else { 0 }
467461
}
468462

469463
pub fn provide(providers: &mut Providers) {

compiler/rustc_hir_typeck/src/method/suggest.rs

+26-13
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use rustc_middle::traits::util::supertraits;
2727
use rustc_middle::ty::fast_reject::DeepRejectCtxt;
2828
use rustc_middle::ty::fast_reject::{simplify_type, TreatParams};
2929
use rustc_middle::ty::print::{with_crate_prefix, with_forced_trimmed_paths};
30+
use rustc_middle::ty::IsSuggestable;
3031
use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt, TypeVisitableExt};
31-
use rustc_middle::ty::{IsSuggestable, ToPolyTraitRef};
3232
use rustc_span::symbol::{kw, sym, Ident};
3333
use rustc_span::Symbol;
3434
use rustc_span::{edit_distance, source_map, ExpnKind, FileName, MacroKind, Span};
@@ -2068,7 +2068,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20682068
let mut derives = Vec::<(String, Span, Symbol)>::new();
20692069
let mut traits = Vec::new();
20702070
for (pred, _, _) in unsatisfied_predicates {
2071-
let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = pred.kind().skip_binder() else { continue };
2071+
let Some(ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred))) =
2072+
pred.kind().no_bound_vars()
2073+
else {
2074+
continue
2075+
};
20722076
let adt = match trait_pred.self_ty().ty_adt_def() {
20732077
Some(adt) if adt.did().is_local() => adt,
20742078
_ => continue,
@@ -2085,22 +2089,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20852089
| sym::Hash
20862090
| sym::Debug => true,
20872091
_ => false,
2092+
} && match trait_pred.trait_ref.substs.as_slice() {
2093+
// Only suggest deriving if lhs == rhs...
2094+
[lhs, rhs] => {
2095+
if let Some(lhs) = lhs.as_type()
2096+
&& let Some(rhs) = rhs.as_type()
2097+
{
2098+
self.can_eq(self.param_env, lhs, rhs)
2099+
} else {
2100+
false
2101+
}
2102+
},
2103+
// Unary ops can always be derived
2104+
[_] => true,
2105+
_ => false,
20882106
};
20892107
if can_derive {
20902108
let self_name = trait_pred.self_ty().to_string();
20912109
let self_span = self.tcx.def_span(adt.did());
2092-
if let Some(poly_trait_ref) = pred.to_opt_poly_trait_pred() {
2093-
for super_trait in supertraits(self.tcx, poly_trait_ref.to_poly_trait_ref())
2110+
for super_trait in
2111+
supertraits(self.tcx, ty::Binder::dummy(trait_pred.trait_ref))
2112+
{
2113+
if let Some(parent_diagnostic_name) =
2114+
self.tcx.get_diagnostic_name(super_trait.def_id())
20942115
{
2095-
if let Some(parent_diagnostic_name) =
2096-
self.tcx.get_diagnostic_name(super_trait.def_id())
2097-
{
2098-
derives.push((
2099-
self_name.clone(),
2100-
self_span,
2101-
parent_diagnostic_name,
2102-
));
2103-
}
2116+
derives.push((self_name.clone(), self_span, parent_diagnostic_name));
21042117
}
21052118
}
21062119
derives.push((self_name, self_span, diagnostic_name));

compiler/rustc_hir_typeck/src/op.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -408,14 +408,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
408408
}
409409
};
410410

411-
let is_compatible = |lhs_ty, rhs_ty| {
411+
let is_compatible_after_call = |lhs_ty, rhs_ty| {
412412
self.lookup_op_method(
413413
lhs_ty,
414414
Some((rhs_expr, rhs_ty)),
415415
Op::Binary(op, is_assign),
416416
expected,
417417
)
418418
.is_ok()
419+
// Suggest calling even if, after calling, the types don't
420+
// implement the operator, since it'll lead to better
421+
// diagnostics later.
422+
|| self.can_eq(self.param_env, lhs_ty, rhs_ty)
419423
};
420424

421425
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
@@ -436,16 +440,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
436440
suggest_deref_binop(*lhs_deref_ty);
437441
}
438442
} else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| {
439-
is_compatible(lhs_ty, rhs_ty)
443+
is_compatible_after_call(lhs_ty, rhs_ty)
440444
}) || self.suggest_fn_call(&mut err, rhs_expr, rhs_ty, |rhs_ty| {
441-
is_compatible(lhs_ty, rhs_ty)
445+
is_compatible_after_call(lhs_ty, rhs_ty)
442446
}) || self.suggest_two_fn_call(
443447
&mut err,
444448
rhs_expr,
445449
rhs_ty,
446450
lhs_expr,
447451
lhs_ty,
448-
|lhs_ty, rhs_ty| is_compatible(lhs_ty, rhs_ty),
452+
|lhs_ty, rhs_ty| is_compatible_after_call(lhs_ty, rhs_ty),
449453
) {
450454
// Cool
451455
}
@@ -719,7 +723,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
719723
Op::Binary(op, _) => op.span,
720724
Op::Unary(_, span) => span,
721725
};
722-
let (opname, trait_did) = lang_item_for_op(self.tcx, op, span);
726+
let (opname, Some(trait_did)) = lang_item_for_op(self.tcx, op, span) else {
727+
// Bail if the operator trait is not defined.
728+
return Err(vec![]);
729+
};
723730

724731
debug!(
725732
"lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
@@ -759,18 +766,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
759766
},
760767
);
761768

762-
let method = trait_did.and_then(|trait_did| {
763-
self.lookup_method_in_trait(cause.clone(), opname, trait_did, lhs_ty, Some(input_types))
764-
});
765-
766-
match (method, trait_did) {
767-
(Some(ok), _) => {
769+
let method = self.lookup_method_in_trait(
770+
cause.clone(),
771+
opname,
772+
trait_did,
773+
lhs_ty,
774+
Some(input_types),
775+
);
776+
match method {
777+
Some(ok) => {
768778
let method = self.register_infer_ok_obligations(ok);
769779
self.select_obligations_where_possible(|_| {});
770780
Ok(method)
771781
}
772-
(None, None) => Err(vec![]),
773-
(None, Some(trait_did)) => {
782+
None => {
783+
// This path may do some inference, so make sure we've really
784+
// doomed compilation so as to not accidentally stabilize new
785+
// inference or something here...
786+
self.tcx.sess.delay_span_bug(span, "this path really should be doomed...");
787+
// Guide inference for the RHS expression if it's provided --
788+
// this will allow us to better error reporting, at the expense
789+
// of making some error messages a bit more specific.
790+
if let Some((rhs_expr, rhs_ty)) = opt_rhs
791+
&& rhs_ty.is_ty_var()
792+
{
793+
self.check_expr_coercible_to_type(rhs_expr, rhs_ty, None);
794+
}
795+
774796
let (obligation, _) =
775797
self.obligation_for_method(cause, trait_did, lhs_ty, Some(input_types));
776798
// FIXME: This should potentially just add the obligation to the `FnCtxt`

compiler/rustc_hir_typeck/src/place_op.rs

+22-20
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
200200
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
201201
debug!("try_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
202202

203-
let (imm_tr, imm_op) = match op {
203+
let (Some(imm_tr), imm_op) = (match op {
204204
PlaceOp::Deref => (self.tcx.lang_items().deref_trait(), sym::deref),
205205
PlaceOp::Index => (self.tcx.lang_items().index_trait(), sym::index),
206+
}) else {
207+
// Bail if `Deref` or `Index` isn't defined.
208+
return None;
206209
};
207210

208211
// If the lang item was declared incorrectly, stop here so that we don't
@@ -219,15 +222,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
219222
return None;
220223
}
221224

222-
imm_tr.and_then(|trait_did| {
223-
self.lookup_method_in_trait(
224-
self.misc(span),
225-
Ident::with_dummy_span(imm_op),
226-
trait_did,
227-
base_ty,
228-
Some(arg_tys),
229-
)
230-
})
225+
self.lookup_method_in_trait(
226+
self.misc(span),
227+
Ident::with_dummy_span(imm_op),
228+
imm_tr,
229+
base_ty,
230+
Some(arg_tys),
231+
)
231232
}
232233

233234
fn try_mutable_overloaded_place_op(
@@ -239,9 +240,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
239240
) -> Option<InferOk<'tcx, MethodCallee<'tcx>>> {
240241
debug!("try_mutable_overloaded_place_op({:?},{:?},{:?})", span, base_ty, op);
241242

242-
let (mut_tr, mut_op) = match op {
243+
let (Some(mut_tr), mut_op) = (match op {
243244
PlaceOp::Deref => (self.tcx.lang_items().deref_mut_trait(), sym::deref_mut),
244245
PlaceOp::Index => (self.tcx.lang_items().index_mut_trait(), sym::index_mut),
246+
}) else {
247+
// Bail if `DerefMut` or `IndexMut` isn't defined.
248+
return None;
245249
};
246250

247251
// If the lang item was declared incorrectly, stop here so that we don't
@@ -258,15 +262,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
258262
return None;
259263
}
260264

261-
mut_tr.and_then(|trait_did| {
262-
self.lookup_method_in_trait(
263-
self.misc(span),
264-
Ident::with_dummy_span(mut_op),
265-
trait_did,
266-
base_ty,
267-
Some(arg_tys),
268-
)
269-
})
265+
self.lookup_method_in_trait(
266+
self.misc(span),
267+
Ident::with_dummy_span(mut_op),
268+
mut_tr,
269+
base_ty,
270+
Some(arg_tys),
271+
)
270272
}
271273

272274
/// Convert auto-derefs, indices, etc of an expression from `Deref` and `Index`

tests/ui/binop/eq-arr.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
struct X;
3+
//~^ HELP consider annotating `X` with `#[derive(PartialEq)]`
4+
let xs = [X, X, X];
5+
let eq = xs == [X, X, X];
6+
//~^ ERROR binary operation `==` cannot be applied to type `[X; 3]`
7+
}

tests/ui/binop/eq-arr.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error[E0369]: binary operation `==` cannot be applied to type `[X; 3]`
2+
--> $DIR/eq-arr.rs:5:17
3+
|
4+
LL | let eq = xs == [X, X, X];
5+
| -- ^^ --------- [X; 3]
6+
| |
7+
| [X; 3]
8+
|
9+
note: an implementation of `PartialEq` might be missing for `X`
10+
--> $DIR/eq-arr.rs:2:5
11+
|
12+
LL | struct X;
13+
| ^^^^^^^^ must implement `PartialEq`
14+
help: consider annotating `X` with `#[derive(PartialEq)]`
15+
|
16+
LL + #[derive(PartialEq)]
17+
LL | struct X;
18+
|
19+
20+
error: aborting due to previous error
21+
22+
For more information about this error, try `rustc --explain E0369`.

tests/ui/binop/eq-vec.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fn main() {
2+
#[derive(Debug)]
3+
enum Foo {
4+
//~^ HELP consider annotating `Foo` with `#[derive(PartialEq)]`
5+
Bar,
6+
Qux,
7+
}
8+
9+
let vec1 = vec![Foo::Bar, Foo::Qux];
10+
let vec2 = vec![Foo::Bar, Foo::Qux];
11+
assert_eq!(vec1, vec2);
12+
//~^ ERROR binary operation `==` cannot be applied to type `Vec<Foo>`
13+
}

tests/ui/binop/eq-vec.stderr

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error[E0369]: binary operation `==` cannot be applied to type `Vec<Foo>`
2+
--> $DIR/eq-vec.rs:11:5
3+
|
4+
LL | assert_eq!(vec1, vec2);
5+
| ^^^^^^^^^^^^^^^^^^^^^^
6+
| |
7+
| Vec<Foo>
8+
| Vec<Foo>
9+
|
10+
note: an implementation of `PartialEq` might be missing for `Foo`
11+
--> $DIR/eq-vec.rs:3:5
12+
|
13+
LL | enum Foo {
14+
| ^^^^^^^^ must implement `PartialEq`
15+
= note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
help: consider annotating `Foo` with `#[derive(PartialEq)]`
17+
|
18+
LL + #[derive(PartialEq)]
19+
LL | enum Foo {
20+
|
21+
22+
error: aborting due to previous error
23+
24+
For more information about this error, try `rustc --explain E0369`.

0 commit comments

Comments
 (0)