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:
parent
620083ad16
commit
98f5b11b6b
@ -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
|
||||
|
@ -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> { }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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`");
|
||||
}
|
||||
|
@ -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>),
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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`.
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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`.
|
||||
|
@ -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 { }
|
||||
|
Loading…
x
Reference in New Issue
Block a user