diff --git a/src/libcore/cmp.rs b/src/libcore/cmp.rs index d0ea75c7623f4..1ac51291b93d7 100644 --- a/src/libcore/cmp.rs +++ b/src/libcore/cmp.rs @@ -211,7 +211,7 @@ pub trait PartialEq { /// 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 { /// 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 diff --git a/src/libcore/marker.rs b/src/libcore/marker.rs index 7f3d958f5dc80..a25573feabb2b 100644 --- a/src/libcore/marker.rs +++ b/src/libcore/marker.rs @@ -126,6 +126,85 @@ pub trait Unsize { // 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); +/// fn higher_order(_: &()) { } +/// const CFN: Wrap = Wrap(higher_order); +/// fn main() { +/// match CFN { +/// CFN => {} +/// _ => {} +/// } +/// } +/// ``` +/// +/// (The problem in the above code is that `Wrap` 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 StructuralPartialEq for $t { } + + #[cfg(not(bootstrap))] + #[unstable(feature = "structural_match", issue = "31434")] + impl StructuralEq for $t { } ) } diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index cab929389d6a4..0db79785282a3 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -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; diff --git a/src/librustc/traits/error_reporting.rs b/src/librustc/traits/error_reporting.rs index e684cdc0a3872..e827ccf7d6741 100644 --- a/src/librustc/traits/error_reporting.rs +++ b/src/librustc/traits/error_reporting.rs @@ -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`"); } diff --git a/src/librustc/traits/mod.rs b/src/librustc/traits/mod.rs index eb4b114eb301c..134a8050d70f6 100644 --- a/src/librustc/traits/mod.rs +++ b/src/librustc/traits/mod.rs @@ -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), diff --git a/src/librustc/traits/structural_impls.rs b/src/librustc/traits/structural_impls.rs index 18db3c8d10b7c..e9411be6fc1f7 100644 --- a/src/librustc/traits/structural_impls.rs +++ b/src/librustc/traits/structural_impls.rs @@ -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) diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index 4660e8025d48e..089443905dc53 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -51,7 +51,7 @@ use syntax::symbol::{kw, sym, Symbol}; use syntax_pos::Span; use smallvec; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::{FxIndexMap}; use rustc_data_structures::stable_hasher::{StableHasher, HashStable}; use rustc_index::vec::{Idx, IndexVec}; @@ -84,6 +84,10 @@ pub use self::context::{ pub use self::instance::{Instance, InstanceDef}; +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; pub use self::query::queries; @@ -116,6 +120,7 @@ pub mod util; mod context; mod instance; mod structural_impls; +mod structural_match; mod sty; // Data types @@ -3395,130 +3400,6 @@ fn asyncness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::IsAsync { fn_like.asyncness() } -pub enum NonStructuralMatchTy<'tcx> { - Adt(&'tcx AdtDef), - Param, -} - -/// This method traverses the structure of `ty`, trying to find an -/// instance of an ADT (i.e. struct or enum) that was declared without -/// the `#[structural_match]` attribute, or a generic type parameter -/// (which cannot be determined to be `structural_match`). -/// -/// The "structure of a type" includes all components that would be -/// considered when doing a pattern match on a constant of that -/// type. -/// -/// * This means this method descends into fields of structs/enums, -/// and also descends into the inner type `T` of `&T` and `&mut T` -/// -/// * The traversal doesn't dereference unsafe pointers (`*const T`, -/// `*mut T`), and it does not visit the type arguments of an -/// instantiated generic like `PhantomData`. -/// -/// The reason we do this search is Rust currently require all ADTs -/// reachable from a constant's type to be annotated with -/// `#[structural_match]`, an attribute which essentially says that -/// the implementation of `PartialEq::eq` behaves *equivalently* to a -/// comparison against the unfolded structure. -/// -/// For more background on why Rust has this requirement, and issues -/// 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>( - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, -) -> Option> { - let mut search = Search { tcx, found: None, seen: FxHashSet::default() }; - ty.visit_with(&mut search); - return search.found; - - struct Search<'tcx> { - tcx: TyCtxt<'tcx>, - - // Records the first ADT or type parameter we find without `#[structural_match`. - found: Option>, - - // Tracks ADTs previously encountered during search, so that - // we will not recurse on them again. - seen: FxHashSet, - } - - 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` 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 - } - } -} - pub fn provide(providers: &mut ty::query::Providers<'_>) { context::provide(providers); erase_regions::provide(providers); diff --git a/src/librustc/ty/structural_match.rs b/src/librustc/ty/structural_match.rs new file mode 100644 index 0000000000000..cdf5734f5a506 --- /dev/null +++ b/src/librustc/ty/structural_match.rs @@ -0,0 +1,209 @@ +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_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, +} + +/// This method traverses the structure of `ty`, trying to find an +/// instance of an ADT (i.e. struct or enum) that was declared without +/// the `#[structural_match]` attribute, or a generic type parameter +/// (which cannot be determined to be `structural_match`). +/// +/// The "structure of a type" includes all components that would be +/// considered when doing a pattern match on a constant of that +/// type. +/// +/// * This means this method descends into fields of structs/enums, +/// and also descends into the inner type `T` of `&T` and `&mut T` +/// +/// * The traversal doesn't dereference unsafe pointers (`*const T`, +/// `*mut T`), and it does not visit the type arguments of an +/// instantiated generic like `PhantomData`. +/// +/// The reason we do this search is Rust currently require all ADTs +/// reachable from a constant's type to be annotated with +/// `#[structural_match]`, an attribute which essentially says that +/// the implementation of `PartialEq::eq` behaves *equivalently* to a +/// comparison against the unfolded structure. +/// +/// For more background on why Rust has this requirement, and issues +/// 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> { + // 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 + }) +} + +/// 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); + + // 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() +} + +/// This implements the traversal over the structure of a given type to try to +/// find instances of ADTs (specifically structs or enums) that do not implement +/// the structural-match traits (`StructuralPartialEq` and `StructuralEq`). +struct Search<'a, 'tcx> { + id: hir::HirId, + span: Span, + + infcx: InferCtxt<'a, 'tcx>, + + /// Records first ADT that does not implement a structural-match trait. + found: Option>, + + /// Tracks ADTs previously encountered during search, so that + /// we will not recur on them again. + seen: FxHashSet, +} + +impl Search<'a, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + 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`. + // + // For example, if you have: + // ``` + // struct NonStructural; + // #[derive(PartialEq, Eq)] + // struct T(*const NonStructural); + // const C: T = T(std::ptr::null()); + // ``` + // + // Even though `NonStructural` does not implement `PartialEq`, + // structural equality on `T` does not recur into the raw + // pointer. Therefore, one can still use `C` in a pattern. + + // (But still tell caller to continue search.) + return false; + } + ty::FnDef(..) | ty::FnPtr(..) => { + // types of formals and return in `fn(_) -> _` are also irrelevant; + // so we do not recur into them via `super_visit_with` + // + // (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` 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 + } +} diff --git a/src/librustc_mir/hair/pattern/const_to_pat.rs b/src/librustc_mir/hair/pattern/const_to_pat.rs new file mode 100644 index 0000000000000..bfc539639db1e --- /dev/null +++ b/src/librustc_mir/hair/pattern/const_to_pat.rs @@ -0,0 +1,282 @@ +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::{self, Ty, TyCtxt}; + +use rustc_index::vec::Idx; + +use syntax_pos::Span; + + +use std::cell::Cell; + +use super::{FieldPat, Pat, PatCtxt, PatKind}; + +impl<'a, 'tcx> PatCtxt<'a, 'tcx> { + /// Converts an evaluated constant to a pattern (if possible). + /// This means aggregate values (like structs and enums) are converted + /// to a pattern that matches the value (as if you'd compared via structural equality). + pub(super) fn const_to_pat( + &self, + cv: &'tcx ty::Const<'tcx>, + id: hir::HirId, + span: Span, + ) -> Pat<'tcx> { + 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, + + // 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::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 + + let inlined_const_as_pat = self.recur(cv); + + 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`. + + 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, + ); + + // 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.) + // + // 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 obligation: PredicateObligation<'_> = + 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(self.span, &msg); + } else { + self.tcx().lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, + self.id, + self.span, + &msg); + } + } + } + + inlined_const_as_pat + } + + // 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 adt_subpattern = |i, variant_opt| { + let field = Field::new(i); + let val = crate::const_eval::const_field( + tcx, param_env, variant_opt, field, cv + ); + self.recur(val) + }; + let adt_subpatterns = |n, variant_opt| { + (0..n).map(|i| { + let field = Field::new(i); + FieldPat { + field, + pattern: adt_subpattern(i, variant_opt), + } + }).collect::>() + }; + + + let kind = match cv.ty.kind { + ty::Float(_) => { + tcx.lint_hir( + ::rustc::lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, + id, + span, + "floating-point types cannot be used in patterns", + ); + PatKind::Constant { + value: cv, + } + } + ty::Adt(adt_def, _) if adt_def.is_union() => { + // Matching on union fields is unsafe, we can't hide it in constants + 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.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, + ); + 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(_, 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 = 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, + ); + 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(tcx, self.param_env, cv); + let subpatterns = adt_subpatterns( + adt_def.variants[variant_index].fields.len(), + Some(variant_index), + ); + PatKind::Variant { + adt_def, + substs, + variant_index, + subpatterns, + } + } + ty::Adt(adt_def, _) => { + let struct_var = adt_def.non_enum_variant(); + PatKind::Leaf { + subpatterns: adt_subpatterns(struct_var.fields.len(), None), + } + } + ty::Tuple(fields) => { + PatKind::Leaf { + subpatterns: adt_subpatterns(fields.len(), None), + } + } + ty::Array(_, n) => { + PatKind::Array { + prefix: (0..n.eval_usize(tcx, self.param_env)) + .map(|i| adt_subpattern(i as usize, None)) + .collect(), + slice: None, + suffix: Vec::new(), + } + } + _ => { + PatKind::Constant { + value: cv, + } + } + }; + + Pat { + span, + ty: cv.ty, + kind: Box::new(kind), + } + } +} diff --git a/src/librustc_mir/hair/pattern/mod.rs b/src/librustc_mir/hair/pattern/mod.rs index 98e286e61e942..1ecc78ba227ce 100644 --- a/src/librustc_mir/hair/pattern/mod.rs +++ b/src/librustc_mir/hair/pattern/mod.rs @@ -2,19 +2,16 @@ mod _match; mod check_match; +mod const_to_pat; pub(crate) use self::check_match::check_match; -use crate::const_eval::const_variant_index; - use crate::hair::util::UserAnnotatedTyHelpers; use crate::hair::constant::*; -use rustc::lint; use rustc::mir::{Field, BorrowKind, Mutability}; use rustc::mir::{UserTypeProjection}; use rustc::mir::interpret::{GlobalId, ConstValue, get_slice_bytes, sign_extend}; -use rustc::traits::{ObligationCause, PredicateObligation}; use rustc::ty::{self, Region, TyCtxt, AdtDef, Ty, UserType, DefIdTree}; use rustc::ty::{CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations}; use rustc::ty::subst::{SubstsRef, GenericArg}; @@ -29,7 +26,6 @@ use rustc_index::vec::Idx; use std::cmp::Ordering; use std::fmt; use syntax::ast; -use syntax::symbol::sym; use syntax_pos::Span; #[derive(Clone, Debug)] @@ -867,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; } @@ -934,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); @@ -956,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); @@ -972,206 +960,6 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { _ => span_bug!(expr.span, "not a literal: {:?}", expr), } } - - /// Converts an evaluated constant to a pattern (if possible). - /// This means aggregate values (like structs and enums) are converted - /// to a pattern that matches the value (as if you'd compared via structural equality). - 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 - // 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 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 && !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"); - } - }; - - // before issuing lint, double-check there even *is* a - // semantic PartialEq for us 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.) - - let ty_is_partial_eq: bool = { - 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)) - }; - - if !ty_is_partial_eq { - // span_fatal avoids ICE from resolution of non-existent method (rare case). - self.tcx.sess.span_fatal(span, &msg); - } else { - self.tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg); - } - } - } - - 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> { - - let mut 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 - ); - self.const_to_pat_inner(instance, val, id, span, saw_const_match_error) - }; - let mut adt_subpatterns = |n, variant_opt| { - (0..n).map(|i| { - let field = Field::new(i); - FieldPat { - field, - pattern: adt_subpattern(i, variant_opt), - } - }).collect::>() - }; - - - let kind = match cv.ty.kind { - ty::Float(_) => { - self.tcx.lint_hir( - ::rustc::lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, - id, - span, - "floating-point types cannot be used in patterns", - ); - PatKind::Constant { - value: cv, - } - } - 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"); - 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); - 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); - 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) => { - // 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 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); - 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 subpatterns = adt_subpatterns( - adt_def.variants[variant_index].fields.len(), - Some(variant_index), - ); - PatKind::Variant { - adt_def, - substs, - variant_index, - subpatterns, - } - } - ty::Adt(adt_def, _) => { - let struct_var = adt_def.non_enum_variant(); - PatKind::Leaf { - subpatterns: adt_subpatterns(struct_var.fields.len(), None), - } - } - ty::Tuple(fields) => { - PatKind::Leaf { - subpatterns: adt_subpatterns(fields.len(), None), - } - } - ty::Array(_, n) => { - PatKind::Array { - prefix: (0..n.eval_usize(self.tcx, self.param_env)) - .map(|i| adt_subpattern(i as usize, None)) - .collect(), - slice: None, - suffix: Vec::new(), - } - } - _ => { - PatKind::Constant { - value: cv, - } - } - }; - - Pat { - span, - ty: cv.ty, - kind: Box::new(kind), - } - } } impl UserAnnotatedTyHelpers<'tcx> for PatCtxt<'_, 'tcx> { diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 00435d67184a3..2395cb7495df4 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -1532,7 +1532,9 @@ pub fn checked_type_of(tcx: TyCtxt<'_>, def_id: DefId, fail: bool) -> Option, }], 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) } diff --git a/src/libsyntax_ext/deriving/cmp/partial_eq.rs b/src/libsyntax_ext/deriving/cmp/partial_eq.rs index 1615d99179281..c3e2b78bbe506 100644 --- a/src/libsyntax_ext/deriving/cmp/partial_eq.rs +++ b/src/libsyntax_ext/deriving/cmp/partial_eq.rs @@ -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`. diff --git a/src/libsyntax_ext/deriving/mod.rs b/src/libsyntax_ext/deriving/mod.rs index f0471a857dc93..a98cce1fd61c1 100644 --- a/src/libsyntax_ext/deriving/mod.rs +++ b/src/libsyntax_ext/deriving/mod.rs @@ -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)); +} diff --git a/src/test/codegen-units/item-collection/overloaded-operators.rs b/src/test/codegen-units/item-collection/overloaded-operators.rs index 2358d38942a71..0b4c97723d6bc 100644 --- a/src/test/codegen-units/item-collection/overloaded-operators.rs +++ b/src/test/codegen-units/item-collection/overloaded-operators.rs @@ -34,8 +34,8 @@ impl IndexMut for Indexable { } -//~ MONO_ITEM fn overloaded_operators::{{impl}}[4]::eq[0] -//~ MONO_ITEM fn overloaded_operators::{{impl}}[4]::ne[0] +//~ MONO_ITEM fn overloaded_operators::{{impl}}[5]::eq[0] +//~ MONO_ITEM fn overloaded_operators::{{impl}}[5]::ne[0] #[derive(PartialEq)] pub struct Equatable(u32); diff --git a/src/test/ui/rfc1445/feature-gate.no_gate.stderr b/src/test/ui/rfc1445/feature-gate.no_gate.stderr index fa879371628d3..42b81cd43000f 100644 --- a/src/test/ui/rfc1445/feature-gate.no_gate.stderr +++ b/src/test/ui/rfc1445/feature-gate.no_gate.stderr @@ -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`. diff --git a/src/test/ui/rfc1445/feature-gate.rs b/src/test/ui/rfc1445/feature-gate.rs index 550610c5444e9..38d6406efa50d 100644 --- a/src/test/ui/rfc1445/feature-gate.rs +++ b/src/test/ui/rfc1445/feature-gate.rs @@ -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 for Foo { + fn eq(&self, other: &Self) -> bool { + self.x == other.x + } +} +impl Eq for Foo { } diff --git a/src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.rs b/src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.rs similarity index 89% rename from src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.rs rename to src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.rs index 9a96628cac690..e288beca09081 100644 --- a/src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.rs +++ b/src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.rs @@ -1,4 +1,4 @@ -// Issue 61118 pointed out a case where we hit an ICE during code gen: +// Issue 61188 pointed out a case where we hit an ICE during code gen: // the compiler assumed that `PartialEq` was always implemented on any // use of a `const` item in a pattern context, but the pre-existing // checking for the presence of `#[structural_match]` was too shallow diff --git a/src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.stderr b/src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.stderr similarity index 74% rename from src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.stderr rename to src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.stderr index e8141f6108c5e..0bf369fa8cb87 100644 --- a/src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.stderr +++ b/src/test/ui/rfc1445/issue-61188-match-slice-forbidden-without-eq.stderr @@ -1,5 +1,5 @@ error: to use a constant of type `B` in a pattern, `B` must be annotated with `#[derive(PartialEq, Eq)]` - --> $DIR/issue-61118-match-slice-forbidden-without-eq.rs:15:9 + --> $DIR/issue-61188-match-slice-forbidden-without-eq.rs:15:9 | LL | A => (), | ^