Skip to content

Commit bdc5805

Browse files
Rollup merge of rust-lang#156807 - kpreid:speq, r=petrochenkov
Add `T: PartialEq` bounds to derived `StructuralPartialEq` impls. Fixes <rust-lang#147714>. This is a breaking change to fix a bug, so it will need a crater run if it is to be done at all. The changes in `const_to_pat.rs` are entirely to avoid regressing diagnostic quality, and should not make any difference to what code is accepted. The changes in `compute_applicable_impls_for_diagnostics` and its callers are entirely to be able to reuse that algorithm for this purpose. @rustbot label +T-lang
2 parents eeaf122 + 6000edd commit bdc5805

9 files changed

Lines changed: 184 additions & 52 deletions

File tree

compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ pub(crate) fn expand_deriving_partial_eq(
2222
path: path_std!(marker::StructuralPartialEq),
2323
skip_path_as_bound: true, // crucial!
2424
needs_copy_as_bound_if_packed: false,
25-
additional_bounds: Vec::new(),
25+
// The `StructuralPartialEq` impl must have the *same* bounds as the `PartialEq` impl,
26+
// or it will apply in situations where it should not, such as in the bug
27+
// <https://github.com/rust-lang/rust/issues/147714>.
28+
additional_bounds: vec![ty::Ty::Path(path_std!(cmp::PartialEq))],
2629
// We really don't support unions, but that's already checked by the impl generated below;
2730
// a second check here would lead to redundant error messages.
2831
supports_unions: true,

compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ use rustc_middle::ty::{
1717
};
1818
use rustc_span::def_id::DefId;
1919
use rustc_span::{DUMMY_SP, Span};
20+
use rustc_trait_selection::error_reporting::traits::ambiguity::{
21+
CandidateSource, compute_applicable_impls_for_diagnostics,
22+
};
2023
use rustc_trait_selection::traits::ObligationCause;
2124
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
2225
use tracing::{debug, instrument, trace};
@@ -232,15 +235,35 @@ impl<'tcx> ConstToPat<'tcx> {
232235

233236
let kind = match ty.kind() {
234237
// Extremely important check for all ADTs!
235-
// Make sure they are eligible to be used in patterns, and if not, emit an error.
238+
// Make sure they are eligible to be used in patterns (structural), and if not, emit an
239+
// error.
236240
ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => {
237241
// This ADT cannot be used as a constant in patterns.
238242
debug!(?adt_def, ?value.ty, "ADT type in pattern is not `type_marked_structural`");
239243
let PartialEqImplStatus {
240-
is_derived, structural_partial_eq, non_blanket_impl, ..
244+
is_derived,
245+
possibly_inapplicable_structural_partial_eq,
246+
non_blanket_impl,
247+
possibly_inapplicable_derived_partial_eq,
248+
has_impl,
249+
..
241250
} = type_has_partial_eq_impl(self.tcx, self.typing_env, ty);
251+
252+
// If we have a derived PartialEq impl but it does not apply,
253+
// then error about that instead, because `TypeNotStructural` gives advice that is
254+
// relevant only when the problem is that `ty` does not derive `PartialEq`.
255+
//
256+
// Note that this is a duplicate of a check in `unevaluated_to_pat()`,
257+
// which we would run later if we weren’t emitting an error now.
258+
if possibly_inapplicable_derived_partial_eq && !has_impl {
259+
let mut err =
260+
self.tcx.dcx().create_err(TypeNotPartialEq { span: self.span, ty });
261+
extend_type_not_partial_eq(self.tcx, self.typing_env, ty, &mut err);
262+
return self.mk_err(err, ty);
263+
}
264+
242265
let (manual_partialeq_impl_span, manual_partialeq_impl_note) =
243-
match (structural_partial_eq, non_blanket_impl) {
266+
match (possibly_inapplicable_structural_partial_eq, non_blanket_impl) {
244267
(true, _) => (None, false),
245268
(_, Some(def_id)) if def_id.is_local() && !is_derived => {
246269
(Some(tcx.def_span(def_id)), false)
@@ -488,8 +511,9 @@ fn extend_type_not_partial_eq<'tcx>(
488511
let PartialEqImplStatus {
489512
has_impl,
490513
is_derived,
491-
structural_partial_eq,
514+
possibly_inapplicable_structural_partial_eq: structural_partial_eq,
492515
non_blanket_impl,
516+
possibly_inapplicable_derived_partial_eq: _,
493517
} = type_has_partial_eq_impl(self.tcx, self.typing_env, ty);
494518
match (has_impl, is_derived, structural_partial_eq, non_blanket_impl) {
495519
(_, _, true, _) => {}
@@ -558,10 +582,20 @@ fn extend_type_not_partial_eq<'tcx>(
558582

559583
#[derive(Debug)]
560584
struct PartialEqImplStatus {
585+
/// There is a `PartialEq` impl that applies to the type.
561586
has_impl: bool,
587+
588+
/// The `PartialEq` impl is `#[automatically_derived]`.
562589
is_derived: bool,
563-
structural_partial_eq: bool,
590+
/// The `DefId` of the same impl that `is_derived` refers to.
564591
non_blanket_impl: Option<DefId>,
592+
593+
/// If true, there is a `StructuralPartialEq` implementation,
594+
/// but its bounds might not be satisfied.
595+
possibly_inapplicable_structural_partial_eq: bool,
596+
/// If true, there is a derived `PartialEq` implementation for the type,
597+
/// but its bounds might not be satisfied.
598+
possibly_inapplicable_derived_partial_eq: bool,
565599
}
566600

567601
#[instrument(level = "trace", skip(tcx), ret)]
@@ -580,23 +614,6 @@ fn type_has_partial_eq_impl<'tcx>(
580614
let structural_partial_eq_trait_id =
581615
tcx.require_lang_item(hir::LangItem::StructuralPeq, DUMMY_SP);
582616

583-
let partial_eq_obligation = Obligation::new(
584-
tcx,
585-
ObligationCause::dummy(),
586-
param_env,
587-
ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty]),
588-
);
589-
590-
let mut automatically_derived = false;
591-
let mut structural_peq = false;
592-
let mut impl_def_id = None;
593-
for def_id in tcx.non_blanket_impls_for_ty(partial_eq_trait_id, ty) {
594-
automatically_derived = find_attr!(tcx, def_id, AutomaticallyDerived);
595-
impl_def_id = Some(def_id);
596-
}
597-
for _ in tcx.non_blanket_impls_for_ty(structural_partial_eq_trait_id, ty) {
598-
structural_peq = true;
599-
}
600617
// This *could* accept a type that isn't actually `PartialEq`, because region bounds get
601618
// ignored. However that should be pretty much impossible since consts that do not depend on
602619
// generics can only mention the `'static` lifetime, and how would one have a type that's
@@ -607,10 +624,60 @@ fn type_has_partial_eq_impl<'tcx>(
607624
// that patterns can only do things that the code could also do without patterns, but it is
608625
// needed for backwards compatibility. The actual pattern matching compares primitive values,
609626
// `PartialEq::eq` never gets invoked, so there's no risk of us running non-const code.
627+
let has_impl = {
628+
let obligation = Obligation::new(
629+
tcx,
630+
ObligationCause::dummy(),
631+
param_env,
632+
ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty]),
633+
);
634+
infcx.predicate_must_hold_modulo_regions(&obligation)
635+
};
636+
637+
// Determine whether there are is a derived `PartialEq` implementation, whether or not its
638+
// bounds are met.
639+
let possibly_inapplicable_derived_partial_eq = {
640+
let obligation = Obligation::new(
641+
tcx,
642+
ObligationCause::dummy(),
643+
param_env,
644+
ty::Binder::dummy(ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty])),
645+
);
646+
compute_applicable_impls_for_diagnostics(&infcx, &obligation, true).iter().any(
647+
|candidate_source| {
648+
matches!(
649+
candidate_source,
650+
&CandidateSource::DefId(def_id)
651+
if find_attr!(tcx, def_id, AutomaticallyDerived)
652+
)
653+
},
654+
)
655+
};
656+
657+
let possibly_inapplicable_structural_partial_eq = {
658+
let obligation = Obligation::new(
659+
tcx,
660+
ObligationCause::dummy(),
661+
param_env,
662+
ty::Binder::dummy(ty::TraitRef::new(tcx, structural_partial_eq_trait_id, [ty])),
663+
);
664+
compute_applicable_impls_for_diagnostics(&infcx, &obligation, true)
665+
.iter()
666+
.any(|candidate_source| matches!(candidate_source, CandidateSource::DefId(_)))
667+
};
668+
669+
let mut automatically_derived = false;
670+
let mut impl_def_id = None;
671+
for def_id in tcx.non_blanket_impls_for_ty(partial_eq_trait_id, ty) {
672+
automatically_derived = find_attr!(tcx, def_id, AutomaticallyDerived);
673+
impl_def_id = Some(def_id);
674+
}
675+
610676
PartialEqImplStatus {
611-
has_impl: infcx.predicate_must_hold_modulo_regions(&partial_eq_obligation),
677+
has_impl,
612678
is_derived: automatically_derived,
613-
structural_partial_eq: structural_peq,
679+
possibly_inapplicable_structural_partial_eq,
614680
non_blanket_impl: impl_def_id,
681+
possibly_inapplicable_derived_partial_eq,
615682
}
616683
}

compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
296296
let obligation =
297297
Obligation::new(tcx, ObligationCause::dummy(), param_env, ty::Binder::dummy(trait_ref));
298298

299-
let applicable_impls = compute_applicable_impls_for_diagnostics(self.infcx, &obligation);
299+
let applicable_impls =
300+
compute_applicable_impls_for_diagnostics(self.infcx, &obligation, false);
300301

301302
for candidate in applicable_impls {
302303
let impl_def_id = match candidate {

compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub enum CandidateSource {
3232
pub fn compute_applicable_impls_for_diagnostics<'tcx>(
3333
infcx: &InferCtxt<'tcx>,
3434
obligation: &PolyTraitObligation<'tcx>,
35+
ignore_predicates_of_impls: bool,
3536
) -> Vec<CandidateSource> {
3637
let tcx = infcx.tcx;
3738
let param_env = obligation.param_env;
@@ -71,26 +72,28 @@ pub fn compute_applicable_impls_for_diagnostics<'tcx>(
7172
_ => return false,
7273
}
7374

74-
let obligations = tcx
75-
.predicates_of(impl_def_id)
76-
.instantiate(tcx, impl_args)
77-
.into_iter()
78-
.map(|(predicate, _)| {
79-
Obligation::new(
80-
tcx,
81-
ObligationCause::dummy(),
82-
param_env,
83-
predicate.skip_norm_wip(),
84-
)
85-
})
86-
// Kinda hacky, but let's just throw away obligations that overflow.
87-
// This may reduce the accuracy of this check (if the obligation guides
88-
// inference or it actually resulted in error after others are processed)
89-
// ... but this is diagnostics code.
90-
.filter(|obligation| {
91-
infcx.next_trait_solver() || infcx.evaluate_obligation(obligation).is_ok()
92-
});
93-
ocx.register_obligations(obligations);
75+
if !ignore_predicates_of_impls {
76+
let obligations = tcx
77+
.predicates_of(impl_def_id)
78+
.instantiate(tcx, impl_args)
79+
.into_iter()
80+
.map(|(predicate, _)| {
81+
Obligation::new(
82+
tcx,
83+
ObligationCause::dummy(),
84+
param_env,
85+
predicate.skip_norm_wip(),
86+
)
87+
})
88+
// Kinda hacky, but let's just throw away obligations that overflow.
89+
// This may reduce the accuracy of this check (if the obligation guides
90+
// inference or it actually resulted in error after others are processed)
91+
// ... but this is diagnostics code.
92+
.filter(|obligation| {
93+
infcx.next_trait_solver() || infcx.evaluate_obligation(obligation).is_ok()
94+
});
95+
ocx.register_obligations(obligations);
96+
}
9497

9598
ocx.try_evaluate_obligations().is_empty()
9699
})
@@ -306,6 +309,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
306309
let mut ambiguities = compute_applicable_impls_for_diagnostics(
307310
self.infcx,
308311
&obligation.with(self.tcx, trait_pred),
312+
false,
309313
);
310314
let has_non_region_infer = trait_pred
311315
.skip_binder()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// `derive(PartialEq)`, which also implements `StructuralPartialEq`, must not allow the latter impl
2+
// to be used with other non-derived implementations of `PartialEq`.
3+
//
4+
// Test case by theemathas from <https://github.com/rust-lang/rust/issues/147714>.
5+
6+
#[allow(dead_code)]
7+
#[derive(PartialEq)]
8+
enum Thing<T> {
9+
A(T),
10+
B,
11+
}
12+
13+
struct Incomparable;
14+
15+
// This impl does not obey StructuralPartialEq's rules.
16+
impl PartialEq for Thing<Incomparable> {
17+
fn eq(&self, _: &Self) -> bool {
18+
panic!()
19+
}
20+
}
21+
22+
// This constant does not obey StructuralPartialEq's rules, so it should not
23+
// implement StructuralPartialEq.
24+
const X: Thing<Incomparable> = Thing::B;
25+
26+
fn main() {
27+
if let X = X {
28+
//~^ ERROR constant of non-structural type `Thing<Incomparable>` in a pattern
29+
println!("equal");
30+
}
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: constant of non-structural type `Thing<Incomparable>` in a pattern
2+
--> $DIR/derive-and-manual-partialeq-issue-147714.rs:27:12
3+
|
4+
LL | enum Thing<T> {
5+
| ------------- `Thing<Incomparable>` must be annotated with `#[derive(PartialEq)]` to be usable in patterns
6+
...
7+
LL | const X: Thing<Incomparable> = Thing::B;
8+
| ---------------------------- constant defined here
9+
...
10+
LL | if let X = X {
11+
| ^ constant of non-structural type
12+
|
13+
help: if `Thing<Incomparable>` manually implemented `PartialEq`, you could check for equality instead of pattern matching
14+
|
15+
LL - if let X = X {
16+
LL + if X == X {
17+
|
18+
19+
error: aborting due to 1 previous error
20+

tests/ui/consts/const_in_pattern/issue-65466.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const C: &[O<B>] = &[O::None];
1111
fn main() {
1212
let x = O::None;
1313
match &[x][..] {
14-
C => (), //~ ERROR constant of non-structural type `&[O<B>]` in a pattern
14+
C => (), //~ ERROR constant of non-structural type `O<B>` in a pattern
1515
_ => (),
1616
}
1717
}

tests/ui/consts/const_in_pattern/issue-65466.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: constant of non-structural type `&[O<B>]` in a pattern
1+
error: constant of non-structural type `O<B>` in a pattern
22
--> $DIR/issue-65466.rs:14:9
33
|
44
LL | struct B;

tests/ui/derives/deriving-all-codegen.stdout

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,10 @@ impl<T: ::core::hash::Hash + Trait, U: ::core::hash::Hash> ::core::hash::Hash
786786
}
787787
}
788788
#[automatically_derived]
789-
impl<T: Trait, U> ::core::marker::StructuralPartialEq for Generic<T, U> { }
789+
impl<T: ::core::cmp::PartialEq + Trait, U: ::core::cmp::PartialEq>
790+
::core::marker::StructuralPartialEq for Generic<T, U> where
791+
T::A: ::core::cmp::PartialEq {
792+
}
790793
#[automatically_derived]
791794
impl<T: ::core::cmp::PartialEq + Trait, U: ::core::cmp::PartialEq>
792795
::core::cmp::PartialEq for Generic<T, U> where
@@ -903,8 +906,9 @@ impl<T: ::core::hash::Hash + ::core::marker::Copy + Trait,
903906
}
904907
}
905908
#[automatically_derived]
906-
impl<T: Trait, U> ::core::marker::StructuralPartialEq for PackedGeneric<T, U>
907-
{
909+
impl<T: ::core::cmp::PartialEq + Trait, U: ::core::cmp::PartialEq>
910+
::core::marker::StructuralPartialEq for PackedGeneric<T, U> where
911+
T::A: ::core::cmp::PartialEq {
908912
}
909913
#[automatically_derived]
910914
impl<T: ::core::cmp::PartialEq + ::core::marker::Copy + Trait,
@@ -1595,7 +1599,9 @@ impl<T: ::core::hash::Hash, U: ::core::hash::Hash> ::core::hash::Hash for
15951599
}
15961600
}
15971601
#[automatically_derived]
1598-
impl<T, U> ::core::marker::StructuralPartialEq for EnumGeneric<T, U> { }
1602+
impl<T: ::core::cmp::PartialEq, U: ::core::cmp::PartialEq>
1603+
::core::marker::StructuralPartialEq for EnumGeneric<T, U> {
1604+
}
15991605
#[automatically_derived]
16001606
impl<T: ::core::cmp::PartialEq, U: ::core::cmp::PartialEq>
16011607
::core::cmp::PartialEq for EnumGeneric<T, U> {

0 commit comments

Comments
 (0)