Migrate from #[structural_match] attribute a lang-item trait.

(Or more precisely, a pair of such traits: one for `derive(PartialEq)` and one
for `derive(Eq)`.)

((The addition of the second marker trait, `StructuralEq`, is largely a hack to
work-around `fn (&T)` not implementing `PartialEq` and `Eq`; see also issue
rust-lang/rust#46989; otherwise I would just check if `Eq` is implemented.))

Note: this does not use trait fulfillment error-reporting machinery; it just
uses the trait system to determine if the ADT was tagged or not. (Nonetheless, I
have kept an `on_unimplemented` message on the new trait for structural_match
check, even though it is currently not used.)

Note also: this does *not* resolve the ICE from rust-lang/rust#65466, as noted
in a comment added in this commit. Further work is necessary to resolve that and
other problems with the structural match checking, especially to do so without
breaking stable code (adapted from test fn-ptr-is-structurally-matchable.rs):

```rust
fn r_sm_to(_: &SM) {}

fn main() {
    const CFN6: Wrap<fn(&SM)> = Wrap(r_sm_to);
    let input: Wrap<fn(&SM)> = Wrap(r_sm_to);
    match Wrap(input) {
        Wrap(CFN6) => {}
        Wrap(_) => {}
    };
}
```

where we would hit a problem with the strategy of unconditionally checking for
`PartialEq` because the type `for <'a> fn(&'a SM)` does not currently even
*implement* `PartialEq`.

----

added review feedback:
* use an or-pattern
* eschew `return` when tail position will do.
* don't need fresh_expansion; just add `structural_match` to appropriate `allow_internal_unstable` attributes.

also fixed example in doc comment so that it actually compiles.
This commit is contained in:
Felix S. Klock II 2019-10-17 10:54:37 +02:00
parent 620083ad16
commit 98f5b11b6b
16 changed files with 514 additions and 178 deletions

View File

@ -211,7 +211,7 @@ pub trait PartialEq<Rhs: ?Sized = Self> {
/// Derive macro generating an impl of the trait `PartialEq`.
#[rustc_builtin_macro]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow_internal_unstable(core_intrinsics)]
#[allow_internal_unstable(core_intrinsics, structural_match)]
pub macro PartialEq($item:item) { /* compiler built-in */ }
/// Trait for equality comparisons which are [equivalence relations](
@ -273,7 +273,7 @@ pub trait Eq: PartialEq<Self> {
/// Derive macro generating an impl of the trait `Eq`.
#[rustc_builtin_macro]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow_internal_unstable(core_intrinsics, derive_eq)]
#[allow_internal_unstable(core_intrinsics, derive_eq, structural_match)]
pub macro Eq($item:item) { /* compiler built-in */ }
// FIXME: this struct is used solely by #[derive] to

View File

@ -126,6 +126,85 @@ pub trait Unsize<T: ?Sized> {
// Empty.
}
/// Required trait for constants used in pattern matches.
///
/// Any type that derives `PartialEq` automatically implements this trait,
/// *regardless* of whether its type-parameters implement `Eq`.
///
/// If a `const` item contains some type that does not implement this trait,
/// then that type either (1.) does not implement `PartialEq` (which means the
/// constant will not provide that comparison method, which code generation
/// assumes is available), or (2.) it implements *its own* version of
/// `PartialEq` (which we assume does not conform to a structural-equality
/// comparison).
///
/// In either of the two scenarios above, we reject usage of such a constant in
/// a pattern match.
///
/// See also the [structural match RFC][RFC1445], and [issue 63438][] which
/// motivated migrating from attribute-based design to this trait.
///
/// [RFC1445]: https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md
/// [issue 63438]: https://github.com/rust-lang/rust/issues/63438
#[cfg(not(bootstrap))]
#[unstable(feature = "structural_match", issue = "31434")]
#[rustc_on_unimplemented(message="the type `{Self}` does not `#[derive(PartialEq)]`")]
#[lang = "structural_peq"]
pub trait StructuralPartialEq {
// Empty.
}
/// Required trait for constants used in pattern matches.
///
/// Any type that derives `Eq` automatically implements this trait, *regardless*
/// of whether its type-parameters implement `Eq`.
///
/// This is a hack to workaround a limitation in our type-system.
///
/// Background:
///
/// We want to require that types of consts used in pattern matches
/// have the attribute `#[derive(PartialEq, Eq)]`.
///
/// In a more ideal world, we could check that requirement by just checking that
/// the given type implements both (1.) the `StructuralPartialEq` trait *and*
/// (2.) the `Eq` trait. However, you can have ADTs that *do* `derive(PartialEq, Eq)`,
/// and be a case that we want the compiler to accept, and yet the constant's
/// type fails to implement `Eq`.
///
/// Namely, a case like this:
///
/// ```rust
/// #[derive(PartialEq, Eq)]
/// struct Wrap<X>(X);
/// fn higher_order(_: &()) { }
/// const CFN: Wrap<fn(&())> = Wrap(higher_order);
/// fn main() {
/// match CFN {
/// CFN => {}
/// _ => {}
/// }
/// }
/// ```
///
/// (The problem in the above code is that `Wrap<fn(&())>` does not implement
/// `PartialEq`, nor `Eq`, because `for<'a> fn(&'a _)` does not implement those
/// traits.)
///
/// Therefore, we cannot rely on naive check for `StructuralPartialEq` and
/// mere `Eq`.
///
/// As a hack to work around this, we use two separate traits injected by each
/// of the two derives (`#[derive(PartialEq)]` and `#[derive(Eq)]`) and check
/// that both of them are present as part of structural-match checking.
#[cfg(not(bootstrap))]
#[unstable(feature = "structural_match", issue = "31434")]
#[rustc_on_unimplemented(message="the type `{Self}` does not `#[derive(Eq)]`")]
#[lang = "structural_teq"]
pub trait StructuralEq {
// Empty.
}
/// Types whose values can be duplicated simply by copying bits.
///
/// By default, variable bindings have 'move semantics.' In other
@ -437,6 +516,14 @@ macro_rules! impls{
$t
}
}
#[cfg(not(bootstrap))]
#[unstable(feature = "structural_match", issue = "31434")]
impl<T: ?Sized> StructuralPartialEq for $t<T> { }
#[cfg(not(bootstrap))]
#[unstable(feature = "structural_match", issue = "31434")]
impl<T: ?Sized> StructuralEq for $t<T> { }
)
}

View File

@ -297,6 +297,10 @@ language_item_table! {
SizedTraitLangItem, "sized", sized_trait, Target::Trait;
UnsizeTraitLangItem, "unsize", unsize_trait, Target::Trait;
// trait injected by #[derive(PartialEq)], (i.e. "Partial EQ").
StructuralPeqTraitLangItem, "structural_peq", structural_peq_trait, Target::Trait;
// trait injected by #[derive(Eq)], (i.e. "Total EQ"; no, I will not apologize).
StructuralTeqTraitLangItem, "structural_teq", structural_teq_trait, Target::Trait;
CopyTraitLangItem, "copy", copy_trait, Target::Trait;
CloneTraitLangItem, "clone", clone_trait, Target::Trait;
SyncTraitLangItem, "sync", sync_trait, Target::Trait;

View File

@ -2151,6 +2151,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
ObligationCauseCode::ConstSized => {
err.note("constant expressions must have a statically known size");
}
ObligationCauseCode::ConstPatternStructural => {
err.note("constants used for pattern-matching must derive `PartialEq` and `Eq`");
}
ObligationCauseCode::SharedStatic => {
err.note("shared static variables must have a type that implements `Sync`");
}

View File

@ -239,6 +239,9 @@ pub enum ObligationCauseCode<'tcx> {
/// Computing common supertype in the pattern guard for the arms of a match expression
MatchExpressionArmPattern { span: Span, ty: Ty<'tcx> },
/// Constants in patterns must have `Structural` type.
ConstPatternStructural,
/// Computing common supertype in an if expression
IfExpression(Box<IfExpressionCause>),

View File

@ -497,6 +497,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> {
super::RepeatVec => Some(super::RepeatVec),
super::FieldSized { adt_kind, last } => Some(super::FieldSized { adt_kind, last }),
super::ConstSized => Some(super::ConstSized),
super::ConstPatternStructural => Some(super::ConstPatternStructural),
super::SharedStatic => Some(super::SharedStatic),
super::BuiltinDerivedObligation(ref cause) => {
tcx.lift(cause).map(super::BuiltinDerivedObligation)

View File

@ -84,7 +84,9 @@ pub use self::context::{
pub use self::instance::{Instance, InstanceDef};
pub use self::structural_match::{search_for_structural_match_violation, NonStructuralMatchTy};
pub use self::structural_match::search_for_structural_match_violation;
pub use self::structural_match::type_marked_structural;
pub use self::structural_match::NonStructuralMatchTy;
pub use self::trait_def::TraitDef;

View File

@ -1,11 +1,16 @@
use crate::hir;
use rustc::infer::InferCtxt;
use rustc::traits::{self, ConstPatternStructural, TraitEngine};
use rustc::traits::ObligationCause;
use rustc_data_structures::fx::{FxHashSet};
use syntax::symbol::{sym};
use syntax_pos::Span;
use crate::ty::{self, AdtDef, Ty, TyCtxt};
use crate::ty::fold::{TypeFoldable, TypeVisitor};
#[derive(Debug)]
pub enum NonStructuralMatchTy<'tcx> {
Adt(&'tcx AdtDef),
Param,
@ -37,95 +42,152 @@ pub enum NonStructuralMatchTy<'tcx> {
/// that arose when the requirement was not enforced completely, see
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
pub fn search_for_structural_match_violation<'tcx>(
id: hir::HirId,
span: Span,
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
) -> Option<NonStructuralMatchTy<'tcx>> {
let mut search = Search { tcx, found: None, seen: FxHashSet::default() };
ty.visit_with(&mut search);
return search.found;
// FIXME: we should instead pass in an `infcx` from the outside.
tcx.infer_ctxt().enter(|infcx| {
let mut search = Search { id, span, infcx, found: None, seen: FxHashSet::default() };
ty.visit_with(&mut search);
search.found
})
}
struct Search<'tcx> {
tcx: TyCtxt<'tcx>,
/// This method returns true if and only if `adt_ty` itself has been marked as
/// eligible for structural-match: namely, if it implements both
/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by
/// `#[derive(PartialEq)]` and `#[derive(Eq)]`).
///
/// Note that this does *not* recursively check if the substructure of `adt_ty`
/// implements the traits.
pub fn type_marked_structural(id: hir::HirId,
span: Span,
infcx: &InferCtxt<'_, 'tcx>,
adt_ty: Ty<'tcx>)
-> bool
{
let mut fulfillment_cx = traits::FulfillmentContext::new();
let cause = ObligationCause::new(span, id, ConstPatternStructural);
// require `#[derive(PartialEq)]`
let structural_peq_def_id = infcx.tcx.lang_items().structural_peq_trait().unwrap();
fulfillment_cx.register_bound(
infcx, ty::ParamEnv::empty(), adt_ty, structural_peq_def_id, cause);
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
let cause = ObligationCause::new(span, id, ConstPatternStructural);
let structural_teq_def_id = infcx.tcx.lang_items().structural_teq_trait().unwrap();
fulfillment_cx.register_bound(
infcx, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id, cause);
// Records the first ADT or type parameter we find without `#[structural_match`.
found: Option<NonStructuralMatchTy<'tcx>>,
// We deliberately skip *reporting* fulfillment errors (via
// `report_fulfillment_errors`), for two reasons:
//
// 1. The error messages would mention `std::marker::StructuralPartialEq`
// (a trait which is solely meant as an implementation detail
// for now), and
//
// 2. We are sometimes doing future-incompatibility lints for
// now, so we do not want unconditional errors here.
fulfillment_cx.select_all_or_error(infcx).is_ok()
}
// Tracks ADTs previously encountered during search, so that
// we will not recurse on them again.
seen: FxHashSet<hir::def_id::DefId>,
struct Search<'a, 'tcx> {
id: hir::HirId,
span: Span,
infcx: InferCtxt<'a, 'tcx>,
// records the first ADT we find that does not implement `Structural`.
found: Option<NonStructuralMatchTy<'tcx>>,
// tracks ADT's previously encountered during search, so that
// we will not recur on them again.
seen: FxHashSet<hir::def_id::DefId>,
}
impl Search<'a, 'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.infcx.tcx
}
impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> {
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
debug!("Search visiting ty: {:?}", ty);
let (adt_def, substs) = match ty.kind {
ty::Adt(adt_def, substs) => (adt_def, substs),
ty::Param(_) => {
self.found = Some(NonStructuralMatchTy::Param);
return true; // Stop visiting.
}
ty::RawPtr(..) => {
// `#[structural_match]` ignores substructure of
// `*const _`/`*mut _`, so skip super_visit_with
//
// (But still tell caller to continue search.)
return false;
}
ty::FnDef(..) | ty::FnPtr(..) => {
// types of formals and return in `fn(_) -> _` are also irrelevant
//
// (But still tell caller to continue search.)
return false;
}
ty::Array(_, n) if n.try_eval_usize(self.tcx, ty::ParamEnv::reveal_all()) == Some(0)
=> {
// rust-lang/rust#62336: ignore type of contents
// for empty array.
return false;
}
_ => {
ty.super_visit_with(self);
return false;
}
};
if !self.tcx.has_attr(adt_def.did, sym::structural_match) {
self.found = Some(NonStructuralMatchTy::Adt(&adt_def));
debug!("Search found adt_def: {:?}", adt_def);
return true; // Stop visiting.
}
if !self.seen.insert(adt_def.did) {
debug!("Search already seen adt_def: {:?}", adt_def);
// let caller continue its search
return false;
}
// `#[structural_match]` does not care about the
// instantiation of the generics in an ADT (it
// instead looks directly at its fields outside
// this match), so we skip super_visit_with.
//
// (Must not recur on substs for `PhantomData<T>` cf
// rust-lang/rust#55028 and rust-lang/rust#55837; but also
// want to skip substs when only uses of generic are
// behind unsafe pointers `*const T`/`*mut T`.)
// even though we skip super_visit_with, we must recur on
// fields of ADT.
let tcx = self.tcx;
for field_ty in adt_def.all_fields().map(|field| field.ty(tcx, substs)) {
if field_ty.visit_with(self) {
// found an ADT without `#[structural_match]`; halt visiting!
assert!(self.found.is_some());
return true;
}
}
// Even though we do not want to recur on substs, we do
// want our caller to continue its own search.
false
}
fn type_marked_structural(&self, adt_ty: Ty<'tcx>) -> bool {
type_marked_structural(self.id, self.span, &self.infcx, adt_ty)
}
}
impl<'a, 'tcx> TypeVisitor<'tcx> for Search<'a, 'tcx> {
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
debug!("Search visiting ty: {:?}", ty);
let (adt_def, substs) = match ty.kind {
ty::Adt(adt_def, substs) => (adt_def, substs),
ty::Param(_) => {
self.found = Some(NonStructuralMatchTy::Param);
return true; // Stop visiting.
}
ty::RawPtr(..) => {
// structural-match ignores substructure of
// `*const _`/`*mut _`, so skip super_visit_with
//
// (But still tell caller to continue search.)
return false;
}
ty::FnDef(..) | ty::FnPtr(..) => {
// types of formals and return in `fn(_) -> _` are also irrelevant
//
// (But still tell caller to continue search.)
return false;
}
ty::Array(_, n) if {
n.try_eval_usize(self.tcx(), ty::ParamEnv::reveal_all()) == Some(0)
} => {
// rust-lang/rust#62336: ignore type of contents
// for empty array.
return false;
}
_ => {
ty.super_visit_with(self);
return false;
}
};
if !self.seen.insert(adt_def.did) {
debug!("Search already seen adt_def: {:?}", adt_def);
// let caller continue its search
return false;
}
if !self.type_marked_structural(ty) {
debug!("Search found ty: {:?}", ty);
self.found = Some(NonStructuralMatchTy::Adt(&adt_def));
return true; // Halt visiting!
}
// structural-match does not care about the
// instantiation of the generics in an ADT (it
// instead looks directly at its fields outside
// this match), so we skip super_visit_with.
//
// (Must not recur on substs for `PhantomData<T>` cf
// rust-lang/rust#55028 and rust-lang/rust#55837; but also
// want to skip substs when only uses of generic are
// behind unsafe pointers `*const T`/`*mut T`.)
// even though we skip super_visit_with, we must recur on
// fields of ADT.
let tcx = self.tcx();
for field_ty in adt_def.all_fields().map(|field| field.ty(tcx, substs)) {
if field_ty.visit_with(self) {
// found an ADT without structural-match; halt visiting!
assert!(self.found.is_some());
return true;
}
}
// Even though we do not want to recur on substs, we do
// want our caller to continue its own search.
false
}
}

View File

@ -3,14 +3,17 @@ use crate::const_eval::const_variant_index;
use rustc::hir;
use rustc::lint;
use rustc::mir::Field;
use rustc::infer::InferCtxt;
use rustc::traits::{ObligationCause, PredicateObligation};
use rustc::ty;
use rustc::ty::{self, Ty, TyCtxt};
use rustc_index::vec::Idx;
use syntax::symbol::sym;
use syntax_pos::Span;
use std::cell::Cell;
use super::{FieldPat, Pat, PatCtxt, PatKind};
impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
@ -19,69 +22,126 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
/// to a pattern that matches the value (as if you'd compared via structural equality).
pub(super) fn const_to_pat(
&self,
instance: ty::Instance<'tcx>,
cv: &'tcx ty::Const<'tcx>,
id: hir::HirId,
span: Span,
) -> Pat<'tcx> {
// This method is just a warpper handling a validity check; the heavy lifting is
// performed by the recursive const_to_pat_inner method, which is not meant to be
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
self.tcx.infer_ctxt().enter(|infcx| {
let mut convert = ConstToPat::new(self, id, span, infcx);
convert.to_pat(cv)
})
}
}
struct ConstToPat<'a, 'tcx> {
id: hir::HirId,
span: Span,
param_env: ty::ParamEnv<'tcx>,
// This tracks if we signal some hard error for a given const value, so that
// we will not subsequently issue an irrelevant lint for the same const
// value.
saw_const_match_error: Cell<bool>,
// inference context used for checking `T: Structural` bounds.
infcx: InferCtxt<'a, 'tcx>,
include_lint_checks: bool,
}
impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
fn new(pat_ctxt: &PatCtxt<'_, 'tcx>,
id: hir::HirId,
span: Span,
infcx: InferCtxt<'a, 'tcx>) -> Self {
ConstToPat {
id, span, infcx,
param_env: pat_ctxt.param_env,
include_lint_checks: pat_ctxt.include_lint_checks,
saw_const_match_error: Cell::new(false),
}
}
fn tcx(&self) -> TyCtxt<'tcx> { self.infcx.tcx }
fn search_for_structural_match_violation(&self,
ty: Ty<'tcx>)
-> Option<ty::NonStructuralMatchTy<'tcx>>
{
ty::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty)
}
fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
ty::type_marked_structural(self.id, self.span, &self.infcx, ty)
}
fn to_pat(&mut self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
// This method is just a wrapper handling a validity check; the heavy lifting is
// performed by the recursive `recur` method, which is not meant to be
// invoked except by this method.
//
// once indirect_structural_match is a full fledged error, this
// level of indirection can be eliminated
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
let inlined_const_as_pat = self.recur(cv);
let mut saw_error = false;
let inlined_const_as_pat = self.const_to_pat_inner(instance, cv, id, span, &mut saw_error);
if self.include_lint_checks && !self.saw_const_match_error.get() {
// If we were able to successfully convert the const to some pat,
// double-check that all types in the const implement `Structural`.
if self.include_lint_checks && !saw_error {
// If we were able to successfully convert the const to some pat, double-check
// that the type of the const obeys `#[structural_match]` constraint.
if let Some(non_sm_ty) = ty::search_for_structural_match_violation(self.tcx, cv.ty) {
let msg = match non_sm_ty {
ty::NonStructuralMatchTy::Adt(adt_def) => {
let path = self.tcx.def_path_str(adt_def.did);
format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path,
path,
)
}
ty::NonStructuralMatchTy::Param => {
bug!("use of constant whose type is a parameter inside a pattern");
}
let structural = self.search_for_structural_match_violation(cv.ty);
debug!("search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
cv.ty, structural);
if let Some(non_sm_ty) = structural {
let adt_def = match non_sm_ty {
ty::NonStructuralMatchTy::Adt(adt_def) => adt_def,
ty::NonStructuralMatchTy::Param =>
bug!("use of constant whose type is a parameter inside a pattern"),
};
let path = self.tcx().def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path,
path,
);
// before issuing lint, double-check there even *is* a
// semantic PartialEq for us to dispatch to.
// double-check there even *is* a semantic `PartialEq` to dispatch to.
//
// (If there isn't, then we can safely issue a hard
// error, because that's never worked, due to compiler
// using PartialEq::eq in this scenario in the past.)
// using `PartialEq::eq` in this scenario in the past.)
//
// Note: To fix rust-lang/rust#65466, one could lift this check
// *before* any structural-match checking, and unconditionally error
// if `PartialEq` is not implemented. However, that breaks stable
// code at the moment, because types like `for <'a> fn(&'a ())` do
// not *yet* implement `PartialEq`. So for now we leave this here.
let ty_is_partial_eq: bool = {
let partial_eq_trait_id = self.tcx.lang_items().eq_trait().unwrap();
let partial_eq_trait_id = self.tcx().lang_items().eq_trait().unwrap();
let obligation: PredicateObligation<'_> =
self.tcx.predicate_for_trait_def(self.param_env,
ObligationCause::misc(span, id),
partial_eq_trait_id,
0,
cv.ty,
&[]);
self.tcx
.infer_ctxt()
.enter(|infcx| infcx.predicate_may_hold(&obligation))
self.tcx().predicate_for_trait_def(
self.param_env,
ObligationCause::misc(self.span, self.id),
partial_eq_trait_id,
0,
cv.ty,
&[]);
// FIXME: should this call a `predicate_must_hold` variant instead?
self.infcx.predicate_may_hold(&obligation)
};
if !ty_is_partial_eq {
// span_fatal avoids ICE from resolution of non-existent method (rare case).
self.tcx.sess.span_fatal(span, &msg);
self.tcx().sess.span_fatal(self.span, &msg);
} else {
self.tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg);
self.tcx().lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH,
self.id,
self.span,
&msg);
}
}
}
@ -89,27 +149,21 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
inlined_const_as_pat
}
/// Recursive helper for `const_to_pat`; invoke that (instead of calling this directly).
fn const_to_pat_inner(
&self,
instance: ty::Instance<'tcx>,
cv: &'tcx ty::Const<'tcx>,
id: hir::HirId,
span: Span,
// This tracks if we signal some hard error for a given const
// value, so that we will not subsequently issue an irrelevant
// lint for the same const value.
saw_const_match_error: &mut bool,
) -> Pat<'tcx> {
// Recursive helper for `to_pat`; invoke that (instead of calling this directly).
fn recur(&self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
let id = self.id;
let span = self.span;
let tcx = self.tcx();
let param_env = self.param_env;
let mut adt_subpattern = |i, variant_opt| {
let adt_subpattern = |i, variant_opt| {
let field = Field::new(i);
let val = crate::const_eval::const_field(
self.tcx, self.param_env, variant_opt, field, cv
tcx, param_env, variant_opt, field, cv
);
self.const_to_pat_inner(instance, val, id, span, saw_const_match_error)
self.recur(val)
};
let mut adt_subpatterns = |n, variant_opt| {
let adt_subpatterns = |n, variant_opt| {
(0..n).map(|i| {
let field = Field::new(i);
FieldPat {
@ -122,7 +176,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
let kind = match cv.ty.kind {
ty::Float(_) => {
self.tcx.lint_hir(
tcx.lint_hir(
::rustc::lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
id,
span,
@ -134,41 +188,53 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
}
ty::Adt(adt_def, _) if adt_def.is_union() => {
// Matching on union fields is unsafe, we can't hide it in constants
*saw_const_match_error = true;
self.tcx.sess.span_err(span, "cannot use unions in constant patterns");
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, "cannot use unions in constant patterns");
PatKind::Wild
}
// keep old code until future-compat upgraded to errors.
ty::Adt(adt_def, _) if !self.tcx.has_attr(adt_def.did, sym::structural_match) => {
let path = self.tcx.def_path_str(adt_def.did);
ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty) => {
debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}",
adt_def, cv.ty);
let path = tcx.def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path,
path,
);
*saw_const_match_error = true;
self.tcx.sess.span_err(span, &msg);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
PatKind::Wild
}
// keep old code until future-compat upgraded to errors.
ty::Ref(_, ty::TyS { kind: ty::Adt(adt_def, _), .. }, _)
if !self.tcx.has_attr(adt_def.did, sym::structural_match) => {
ty::Ref(_, adt_ty @ ty::TyS { kind: ty::Adt(_, _), .. }, _)
if !self.type_marked_structural(adt_ty) =>
{
let adt_def = if let ty::Adt(adt_def, _) = adt_ty.kind {
adt_def
} else {
unreachable!()
};
debug!("adt_def {:?} has !type_marked_structural for adt_ty: {:?}",
adt_def, adt_ty);
// HACK(estebank): Side-step ICE #53708, but anything other than erroring here
// would be wrong. Returnging `PatKind::Wild` is not technically correct.
let path = self.tcx.def_path_str(adt_def.did);
let path = tcx.def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path,
path,
);
*saw_const_match_error = true;
self.tcx.sess.span_err(span, &msg);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
PatKind::Wild
}
ty::Adt(adt_def, substs) if adt_def.is_enum() => {
let variant_index = const_variant_index(self.tcx, self.param_env, cv);
let variant_index = const_variant_index(tcx, self.param_env, cv);
let subpatterns = adt_subpatterns(
adt_def.variants[variant_index].fields.len(),
Some(variant_index),
@ -193,7 +259,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
}
ty::Array(_, n) => {
PatKind::Array {
prefix: (0..n.eval_usize(self.tcx, self.param_env))
prefix: (0..n.eval_usize(tcx, self.param_env))
.map(|i| adt_subpattern(i as usize, None))
.collect(),
slice: None,

View File

@ -863,7 +863,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
};
match self.tcx.at(span).const_eval(self.param_env.and(cid)) {
Ok(value) => {
let pattern = self.const_to_pat(instance, value, id, span);
let pattern = self.const_to_pat(value, id, span);
if !is_associated_const {
return pattern;
}
@ -930,11 +930,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
let ty = self.tables.expr_ty(expr);
match lit_to_const(&lit.node, self.tcx, ty, false) {
Ok(val) => {
let instance = ty::Instance::new(
self.tables.local_id_root.expect("literal outside any scope"),
self.substs,
);
*self.const_to_pat(instance, val, expr.hir_id, lit.span).kind
*self.const_to_pat(val, expr.hir_id, lit.span).kind
},
Err(LitToConstError::UnparseableFloat) => {
self.errors.push(PatternError::FloatBug);
@ -952,11 +948,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
};
match lit_to_const(&lit.node, self.tcx, ty, true) {
Ok(val) => {
let instance = ty::Instance::new(
self.tables.local_id_root.expect("literal outside any scope"),
self.substs,
);
*self.const_to_pat(instance, val, expr.hir_id, lit.span).kind
*self.const_to_pat(val, expr.hir_id, lit.span).kind
},
Err(LitToConstError::UnparseableFloat) => {
self.errors.push(PatternError::FloatBug);

View File

@ -1532,7 +1532,9 @@ pub fn checked_type_of(tcx: TyCtxt<'_>, def_id: DefId, fail: bool) -> Option<Ty<
);
};
}
if ty::search_for_structural_match_violation(tcx, ty).is_some() {
if ty::search_for_structural_match_violation(
param.hir_id, param.span, tcx, ty).is_some()
{
struct_span_err!(
tcx.sess,
hir_ty.span,

View File

@ -42,6 +42,12 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt<'_>,
}],
associated_types: Vec::new(),
};
super::inject_impl_of_structural_trait(
cx, span, item,
path_std!(cx, marker::StructuralEq),
push);
trait_def.expand_ext(cx, mitem, item, push, true)
}

View File

@ -6,7 +6,7 @@ use syntax::ast::{BinOpKind, Expr, MetaItem};
use syntax_expand::base::{Annotatable, ExtCtxt, SpecialDerives};
use syntax::ptr::P;
use syntax::symbol::sym;
use syntax_pos::Span;
use syntax_pos::{self, Span};
pub fn expand_deriving_partial_eq(cx: &mut ExtCtxt<'_>,
span: Span,
@ -81,6 +81,11 @@ pub fn expand_deriving_partial_eq(cx: &mut ExtCtxt<'_>,
} }
}
super::inject_impl_of_structural_trait(
cx, span, item,
path_std!(cx, marker::StructuralPartialEq),
push);
// avoid defining `ne` if we can
// c-like enums, enums without any fields and structs without fields
// can safely define only `eq`.

View File

@ -1,6 +1,6 @@
//! The compiler code necessary to implement the `#[derive]` extensions.
use syntax::ast::{self, MetaItem};
use syntax::ast::{self, ItemKind, MetaItem};
use syntax_expand::base::{Annotatable, ExtCtxt, MultiItemModifier};
use syntax::ptr::P;
use syntax::symbol::{Symbol, sym};
@ -74,3 +74,85 @@ fn call_intrinsic(cx: &ExtCtxt<'_>,
span,
}))
}
// Injects `impl<...> Structural for ItemType<...> { }`. In particular,
// does *not* add `where T: Structural` for parameters `T` in `...`.
// (That's the main reason we cannot use TraitDef here.)
fn inject_impl_of_structural_trait(cx: &mut ExtCtxt<'_>,
span: Span,
item: &Annotatable,
structural_path: generic::ty::Path<'_>,
push: &mut dyn FnMut(Annotatable)) {
let item = match *item {
Annotatable::Item(ref item) => item,
_ => {
// Non-Item derive is an error, but it should have been
// set earlier; see
// libsyntax/ext/expand.rs:MacroExpander::expand()
return;
}
};
let generics = match item.kind {
ItemKind::Struct(_, ref generics) |
ItemKind::Enum(_, ref generics) => generics,
// Do not inject `impl Structural for Union`. (`PartialEq` does not
// support unions, so we will see error downstream.)
ItemKind::Union(..) => return,
_ => unreachable!(),
};
// Create generics param list for where clauses and impl headers
let mut generics = generics.clone();
// Create the type of `self`.
//
// in addition, remove defaults from type params (impls cannot have them).
let self_params: Vec<_> = generics.params.iter_mut().map(|param| match &mut param.kind {
ast::GenericParamKind::Lifetime => {
ast::GenericArg::Lifetime(cx.lifetime(span, param.ident))
}
ast::GenericParamKind::Type { default } => {
*default = None;
ast::GenericArg::Type(cx.ty_ident(span, param.ident))
}
ast::GenericParamKind::Const { ty: _ } => {
ast::GenericArg::Const(cx.const_ident(span, param.ident))
}
}).collect();
let type_ident = item.ident;
let trait_ref = cx.trait_ref(structural_path.to_path(cx, span, type_ident, &generics));
let self_type = cx.ty_path(cx.path_all(span, false, vec![type_ident], self_params));
// It would be nice to also encode constraint `where Self: Eq` (by adding it
// onto `generics` cloned above). Unfortunately, that strategy runs afoul of
// rust-lang/rust#48214. So we perform that additional check in the compiler
// itself, instead of encoding it here.
// Keep the lint and stability attributes of the original item, to control
// how the generated implementation is linted.
let mut attrs = Vec::new();
attrs.extend(item.attrs
.iter()
.filter(|a| {
[sym::allow, sym::warn, sym::deny, sym::forbid, sym::stable, sym::unstable]
.contains(&a.name_or_empty())
})
.cloned());
let newitem = cx.item(span,
ast::Ident::invalid(),
attrs,
ItemKind::Impl(ast::Unsafety::Normal,
ast::ImplPolarity::Positive,
ast::Defaultness::Final,
generics,
Some(trait_ref),
self_type,
Vec::new()));
push(Annotatable::Item(newitem));
}

View File

@ -1,12 +1,21 @@
error[E0658]: the semantics of constant patterns is not yet settled
--> $DIR/feature-gate.rs:13:1
error[E0658]: use of unstable library feature 'structural_match'
--> $DIR/feature-gate.rs:29:6
|
LL | #[structural_match]
| ^^^^^^^^^^^^^^^^^^^
LL | impl std::marker::StructuralPartialEq for Foo { }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/31434
= help: add `#![feature(structural_match)]` to the crate attributes to enable
error: aborting due to previous error
error[E0658]: use of unstable library feature 'structural_match'
--> $DIR/feature-gate.rs:31:6
|
LL | impl std::marker::StructuralEq for Foo { }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/31434
= help: add `#![feature(structural_match)]` to the crate attributes to enable
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View File

@ -10,7 +10,7 @@
#![feature(rustc_attrs)]
#![cfg_attr(with_gate, feature(structural_match))]
#[structural_match] //[no_gate]~ ERROR semantics of constant patterns is not yet settled
struct Foo {
x: u32
}
@ -25,3 +25,15 @@ fn main() { //[with_gate]~ ERROR compilation successful
_ => { }
}
}
impl std::marker::StructuralPartialEq for Foo { }
//[no_gate]~^ ERROR use of unstable library feature 'structural_match'
impl std::marker::StructuralEq for Foo { }
//[no_gate]~^ ERROR use of unstable library feature 'structural_match'
impl PartialEq<Foo> for Foo {
fn eq(&self, other: &Self) -> bool {
self.x == other.x
}
}
impl Eq for Foo { }