Skip to content

Commit b7176b4

Browse files
committed
Auto merge of #65519 - pnkfelix:issue-63438-trait-based-structural-match, r=matthewjasper
trait-based structural match implementation Moves from using a `#[structural_match]` attribute to using a marker trait (or pair of such traits, really) instead. Fix #63438. (This however does not remove the hacks that I believe were put into place to support the previous approach of injecting the attribute based on the presence of both derives... I have left that for follow-on work.)
2 parents cf148a7 + f645e90 commit b7176b4

19 files changed

+730
-356
lines changed

src/libcore/cmp.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ pub trait PartialEq<Rhs: ?Sized = Self> {
211211
/// Derive macro generating an impl of the trait `PartialEq`.
212212
#[rustc_builtin_macro]
213213
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
214-
#[allow_internal_unstable(core_intrinsics)]
214+
#[allow_internal_unstable(core_intrinsics, structural_match)]
215215
pub macro PartialEq($item:item) { /* compiler built-in */ }
216216

217217
/// Trait for equality comparisons which are [equivalence relations](
@@ -273,7 +273,7 @@ pub trait Eq: PartialEq<Self> {
273273
/// Derive macro generating an impl of the trait `Eq`.
274274
#[rustc_builtin_macro]
275275
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
276-
#[allow_internal_unstable(core_intrinsics, derive_eq)]
276+
#[allow_internal_unstable(core_intrinsics, derive_eq, structural_match)]
277277
pub macro Eq($item:item) { /* compiler built-in */ }
278278

279279
// FIXME: this struct is used solely by #[derive] to

src/libcore/marker.rs

+87
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,85 @@ pub trait Unsize<T: ?Sized> {
126126
// Empty.
127127
}
128128

129+
/// Required trait for constants used in pattern matches.
130+
///
131+
/// Any type that derives `PartialEq` automatically implements this trait,
132+
/// *regardless* of whether its type-parameters implement `Eq`.
133+
///
134+
/// If a `const` item contains some type that does not implement this trait,
135+
/// then that type either (1.) does not implement `PartialEq` (which means the
136+
/// constant will not provide that comparison method, which code generation
137+
/// assumes is available), or (2.) it implements *its own* version of
138+
/// `PartialEq` (which we assume does not conform to a structural-equality
139+
/// comparison).
140+
///
141+
/// In either of the two scenarios above, we reject usage of such a constant in
142+
/// a pattern match.
143+
///
144+
/// See also the [structural match RFC][RFC1445], and [issue 63438][] which
145+
/// motivated migrating from attribute-based design to this trait.
146+
///
147+
/// [RFC1445]: https://github.com./rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md
148+
/// [issue 63438]: https://github.com./rust-lang/rust/issues/63438
149+
#[cfg(not(bootstrap))]
150+
#[unstable(feature = "structural_match", issue = "31434")]
151+
#[rustc_on_unimplemented(message="the type `{Self}` does not `#[derive(PartialEq)]`")]
152+
#[lang = "structural_peq"]
153+
pub trait StructuralPartialEq {
154+
// Empty.
155+
}
156+
157+
/// Required trait for constants used in pattern matches.
158+
///
159+
/// Any type that derives `Eq` automatically implements this trait, *regardless*
160+
/// of whether its type-parameters implement `Eq`.
161+
///
162+
/// This is a hack to workaround a limitation in our type-system.
163+
///
164+
/// Background:
165+
///
166+
/// We want to require that types of consts used in pattern matches
167+
/// have the attribute `#[derive(PartialEq, Eq)]`.
168+
///
169+
/// In a more ideal world, we could check that requirement by just checking that
170+
/// the given type implements both (1.) the `StructuralPartialEq` trait *and*
171+
/// (2.) the `Eq` trait. However, you can have ADTs that *do* `derive(PartialEq, Eq)`,
172+
/// and be a case that we want the compiler to accept, and yet the constant's
173+
/// type fails to implement `Eq`.
174+
///
175+
/// Namely, a case like this:
176+
///
177+
/// ```rust
178+
/// #[derive(PartialEq, Eq)]
179+
/// struct Wrap<X>(X);
180+
/// fn higher_order(_: &()) { }
181+
/// const CFN: Wrap<fn(&())> = Wrap(higher_order);
182+
/// fn main() {
183+
/// match CFN {
184+
/// CFN => {}
185+
/// _ => {}
186+
/// }
187+
/// }
188+
/// ```
189+
///
190+
/// (The problem in the above code is that `Wrap<fn(&())>` does not implement
191+
/// `PartialEq`, nor `Eq`, because `for<'a> fn(&'a _)` does not implement those
192+
/// traits.)
193+
///
194+
/// Therefore, we cannot rely on naive check for `StructuralPartialEq` and
195+
/// mere `Eq`.
196+
///
197+
/// As a hack to work around this, we use two separate traits injected by each
198+
/// of the two derives (`#[derive(PartialEq)]` and `#[derive(Eq)]`) and check
199+
/// that both of them are present as part of structural-match checking.
200+
#[cfg(not(bootstrap))]
201+
#[unstable(feature = "structural_match", issue = "31434")]
202+
#[rustc_on_unimplemented(message="the type `{Self}` does not `#[derive(Eq)]`")]
203+
#[lang = "structural_teq"]
204+
pub trait StructuralEq {
205+
// Empty.
206+
}
207+
129208
/// Types whose values can be duplicated simply by copying bits.
130209
///
131210
/// By default, variable bindings have 'move semantics.' In other
@@ -437,6 +516,14 @@ macro_rules! impls{
437516
$t
438517
}
439518
}
519+
520+
#[cfg(not(bootstrap))]
521+
#[unstable(feature = "structural_match", issue = "31434")]
522+
impl<T: ?Sized> StructuralPartialEq for $t<T> { }
523+
524+
#[cfg(not(bootstrap))]
525+
#[unstable(feature = "structural_match", issue = "31434")]
526+
impl<T: ?Sized> StructuralEq for $t<T> { }
440527
)
441528
}
442529

src/librustc/middle/lang_items.rs

+4
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ language_item_table! {
297297

298298
SizedTraitLangItem, "sized", sized_trait, Target::Trait;
299299
UnsizeTraitLangItem, "unsize", unsize_trait, Target::Trait;
300+
// trait injected by #[derive(PartialEq)], (i.e. "Partial EQ").
301+
StructuralPeqTraitLangItem, "structural_peq", structural_peq_trait, Target::Trait;
302+
// trait injected by #[derive(Eq)], (i.e. "Total EQ"; no, I will not apologize).
303+
StructuralTeqTraitLangItem, "structural_teq", structural_teq_trait, Target::Trait;
300304
CopyTraitLangItem, "copy", copy_trait, Target::Trait;
301305
CloneTraitLangItem, "clone", clone_trait, Target::Trait;
302306
SyncTraitLangItem, "sync", sync_trait, Target::Trait;

src/librustc/traits/error_reporting.rs

+3
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
21672167
ObligationCauseCode::ConstSized => {
21682168
err.note("constant expressions must have a statically known size");
21692169
}
2170+
ObligationCauseCode::ConstPatternStructural => {
2171+
err.note("constants used for pattern-matching must derive `PartialEq` and `Eq`");
2172+
}
21702173
ObligationCauseCode::SharedStatic => {
21712174
err.note("shared static variables must have a type that implements `Sync`");
21722175
}

src/librustc/traits/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ pub enum ObligationCauseCode<'tcx> {
239239
/// Computing common supertype in the pattern guard for the arms of a match expression
240240
MatchExpressionArmPattern { span: Span, ty: Ty<'tcx> },
241241

242+
/// Constants in patterns must have `Structural` type.
243+
ConstPatternStructural,
244+
242245
/// Computing common supertype in an if expression
243246
IfExpression(Box<IfExpressionCause>),
244247

src/librustc/traits/structural_impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> {
497497
super::RepeatVec => Some(super::RepeatVec),
498498
super::FieldSized { adt_kind, last } => Some(super::FieldSized { adt_kind, last }),
499499
super::ConstSized => Some(super::ConstSized),
500+
super::ConstPatternStructural => Some(super::ConstPatternStructural),
500501
super::SharedStatic => Some(super::SharedStatic),
501502
super::BuiltinDerivedObligation(ref cause) => {
502503
tcx.lift(cause).map(super::BuiltinDerivedObligation)

src/librustc/ty/mod.rs

+6-125
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use syntax::symbol::{kw, sym, Symbol};
5151
use syntax_pos::Span;
5252

5353
use smallvec;
54-
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
54+
use rustc_data_structures::fx::{FxIndexMap};
5555
use rustc_data_structures::stable_hasher::{StableHasher, HashStable};
5656
use rustc_index::vec::{Idx, IndexVec};
5757

@@ -84,6 +84,10 @@ pub use self::context::{
8484

8585
pub use self::instance::{Instance, InstanceDef};
8686

87+
pub use self::structural_match::search_for_structural_match_violation;
88+
pub use self::structural_match::type_marked_structural;
89+
pub use self::structural_match::NonStructuralMatchTy;
90+
8791
pub use self::trait_def::TraitDef;
8892

8993
pub use self::query::queries;
@@ -116,6 +120,7 @@ pub mod util;
116120
mod context;
117121
mod instance;
118122
mod structural_impls;
123+
mod structural_match;
119124
mod sty;
120125

121126
// Data types
@@ -3396,130 +3401,6 @@ fn asyncness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::IsAsync {
33963401
fn_like.asyncness()
33973402
}
33983403

3399-
pub enum NonStructuralMatchTy<'tcx> {
3400-
Adt(&'tcx AdtDef),
3401-
Param,
3402-
}
3403-
3404-
/// This method traverses the structure of `ty`, trying to find an
3405-
/// instance of an ADT (i.e. struct or enum) that was declared without
3406-
/// the `#[structural_match]` attribute, or a generic type parameter
3407-
/// (which cannot be determined to be `structural_match`).
3408-
///
3409-
/// The "structure of a type" includes all components that would be
3410-
/// considered when doing a pattern match on a constant of that
3411-
/// type.
3412-
///
3413-
/// * This means this method descends into fields of structs/enums,
3414-
/// and also descends into the inner type `T` of `&T` and `&mut T`
3415-
///
3416-
/// * The traversal doesn't dereference unsafe pointers (`*const T`,
3417-
/// `*mut T`), and it does not visit the type arguments of an
3418-
/// instantiated generic like `PhantomData<T>`.
3419-
///
3420-
/// The reason we do this search is Rust currently require all ADTs
3421-
/// reachable from a constant's type to be annotated with
3422-
/// `#[structural_match]`, an attribute which essentially says that
3423-
/// the implementation of `PartialEq::eq` behaves *equivalently* to a
3424-
/// comparison against the unfolded structure.
3425-
///
3426-
/// For more background on why Rust has this requirement, and issues
3427-
/// that arose when the requirement was not enforced completely, see
3428-
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
3429-
pub fn search_for_structural_match_violation<'tcx>(
3430-
tcx: TyCtxt<'tcx>,
3431-
ty: Ty<'tcx>,
3432-
) -> Option<NonStructuralMatchTy<'tcx>> {
3433-
let mut search = Search { tcx, found: None, seen: FxHashSet::default() };
3434-
ty.visit_with(&mut search);
3435-
return search.found;
3436-
3437-
struct Search<'tcx> {
3438-
tcx: TyCtxt<'tcx>,
3439-
3440-
// Records the first ADT or type parameter we find without `#[structural_match`.
3441-
found: Option<NonStructuralMatchTy<'tcx>>,
3442-
3443-
// Tracks ADTs previously encountered during search, so that
3444-
// we will not recurse on them again.
3445-
seen: FxHashSet<hir::def_id::DefId>,
3446-
}
3447-
3448-
impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> {
3449-
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
3450-
debug!("Search visiting ty: {:?}", ty);
3451-
3452-
let (adt_def, substs) = match ty.kind {
3453-
ty::Adt(adt_def, substs) => (adt_def, substs),
3454-
ty::Param(_) => {
3455-
self.found = Some(NonStructuralMatchTy::Param);
3456-
return true; // Stop visiting.
3457-
}
3458-
ty::RawPtr(..) => {
3459-
// `#[structural_match]` ignores substructure of
3460-
// `*const _`/`*mut _`, so skip super_visit_with
3461-
//
3462-
// (But still tell caller to continue search.)
3463-
return false;
3464-
}
3465-
ty::FnDef(..) | ty::FnPtr(..) => {
3466-
// types of formals and return in `fn(_) -> _` are also irrelevant
3467-
//
3468-
// (But still tell caller to continue search.)
3469-
return false;
3470-
}
3471-
ty::Array(_, n) if n.try_eval_usize(self.tcx, ty::ParamEnv::reveal_all()) == Some(0)
3472-
=> {
3473-
// rust-lang/rust#62336: ignore type of contents
3474-
// for empty array.
3475-
return false;
3476-
}
3477-
_ => {
3478-
ty.super_visit_with(self);
3479-
return false;
3480-
}
3481-
};
3482-
3483-
if !self.tcx.has_attr(adt_def.did, sym::structural_match) {
3484-
self.found = Some(NonStructuralMatchTy::Adt(&adt_def));
3485-
debug!("Search found adt_def: {:?}", adt_def);
3486-
return true; // Stop visiting.
3487-
}
3488-
3489-
if !self.seen.insert(adt_def.did) {
3490-
debug!("Search already seen adt_def: {:?}", adt_def);
3491-
// let caller continue its search
3492-
return false;
3493-
}
3494-
3495-
// `#[structural_match]` does not care about the
3496-
// instantiation of the generics in an ADT (it
3497-
// instead looks directly at its fields outside
3498-
// this match), so we skip super_visit_with.
3499-
//
3500-
// (Must not recur on substs for `PhantomData<T>` cf
3501-
// rust-lang/rust#55028 and rust-lang/rust#55837; but also
3502-
// want to skip substs when only uses of generic are
3503-
// behind unsafe pointers `*const T`/`*mut T`.)
3504-
3505-
// even though we skip super_visit_with, we must recur on
3506-
// fields of ADT.
3507-
let tcx = self.tcx;
3508-
for field_ty in adt_def.all_fields().map(|field| field.ty(tcx, substs)) {
3509-
if field_ty.visit_with(self) {
3510-
// found an ADT without `#[structural_match]`; halt visiting!
3511-
assert!(self.found.is_some());
3512-
return true;
3513-
}
3514-
}
3515-
3516-
// Even though we do not want to recur on substs, we do
3517-
// want our caller to continue its own search.
3518-
false
3519-
}
3520-
}
3521-
}
3522-
35233404
pub fn provide(providers: &mut ty::query::Providers<'_>) {
35243405
context::provide(providers);
35253406
erase_regions::provide(providers);

0 commit comments

Comments
 (0)