From c2b4c433a55f648a6a1f8794a644b328fa39d01e Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 8 Oct 2019 15:54:37 +0200 Subject: [PATCH 1/7] fix typo in test. --- .../ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/ui/rfc1445/issue-61118-match-slice-forbidden-without-eq.rs b/src/test/ui/rfc1445/issue-61118-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-61118-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 From f0e370f067325f40b0875f06b2e31ce3dae32377 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Tue, 8 Oct 2019 15:55:12 +0200 Subject: [PATCH 2/7] fix typo in filename. --- ...ut-eq.rs => issue-61188-match-slice-forbidden-without-eq.rs} | 0 ...derr => issue-61188-match-slice-forbidden-without-eq.stderr} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/test/ui/rfc1445/{issue-61118-match-slice-forbidden-without-eq.rs => issue-61188-match-slice-forbidden-without-eq.rs} (100%) rename src/test/ui/rfc1445/{issue-61118-match-slice-forbidden-without-eq.stderr => issue-61188-match-slice-forbidden-without-eq.stderr} (74%) 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 100% 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 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 => (), | ^ From 4f7b922afe7cca0ecdd4dd1124935d89122cb904 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 25 Oct 2019 11:27:36 +0200 Subject: [PATCH 3/7] refactor: move structural_match analysis into its own module. --- src/librustc/ty/mod.rs | 129 +-------------------------- src/librustc/ty/structural_match.rs | 131 ++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 125 deletions(-) create mode 100644 src/librustc/ty/structural_match.rs diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index 4660e8025d48e..a9415faf3312c 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,8 @@ pub use self::context::{ pub use self::instance::{Instance, InstanceDef}; +pub use self::structural_match::{search_for_structural_match_violation, NonStructuralMatchTy}; + pub use self::trait_def::TraitDef; pub use self::query::queries; @@ -116,6 +118,7 @@ pub mod util; mod context; mod instance; mod structural_impls; +mod structural_match; mod sty; // Data types @@ -3395,130 +3398,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..83f0feda168eb --- /dev/null +++ b/src/librustc/ty/structural_match.rs @@ -0,0 +1,131 @@ +use crate::hir; +use rustc_data_structures::fx::{FxHashSet}; + +use syntax::symbol::{sym}; + +use crate::ty::{self, AdtDef, Ty, TyCtxt}; +use crate::ty::fold::{TypeFoldable, TypeVisitor}; + +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 + } + } +} From 620083ad16d02686c3af2fb78ea65dc5b2d4584c Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 25 Oct 2019 11:39:12 +0200 Subject: [PATCH 4/7] refactoring: move const_to_pat code into its own submodule. --- src/librustc_mir/hair/pattern/const_to_pat.rs | 216 ++++++++++++++++++ src/librustc_mir/hair/pattern/mod.rs | 206 +---------------- 2 files changed, 217 insertions(+), 205 deletions(-) create mode 100644 src/librustc_mir/hair/pattern/const_to_pat.rs 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..c92bbce98d099 --- /dev/null +++ b/src/librustc_mir/hair/pattern/const_to_pat.rs @@ -0,0 +1,216 @@ +use crate::const_eval::const_variant_index; + +use rustc::hir; +use rustc::lint; +use rustc::mir::Field; +use rustc::traits::{ObligationCause, PredicateObligation}; +use rustc::ty; + +use rustc_index::vec::Idx; + +use syntax::symbol::sym; +use syntax_pos::Span; + +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, + 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), + } + } +} diff --git a/src/librustc_mir/hair/pattern/mod.rs b/src/librustc_mir/hair/pattern/mod.rs index 98e286e61e942..331770003b682 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)] @@ -972,206 +968,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> { From 98f5b11b6b7a1e2a424165aa629752862a5924b0 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 17 Oct 2019 10:54:37 +0200 Subject: [PATCH 5/7] 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 = Wrap(r_sm_to); let input: Wrap = 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. --- src/libcore/cmp.rs | 4 +- src/libcore/marker.rs | 87 +++++++ src/librustc/middle/lang_items.rs | 4 + src/librustc/traits/error_reporting.rs | 3 + src/librustc/traits/mod.rs | 3 + src/librustc/traits/structural_impls.rs | 1 + src/librustc/ty/mod.rs | 4 +- src/librustc/ty/structural_match.rs | 214 +++++++++++------- src/librustc_mir/hair/pattern/const_to_pat.rs | 212 +++++++++++------ src/librustc_mir/hair/pattern/mod.rs | 14 +- src/librustc_typeck/collect.rs | 4 +- src/libsyntax_ext/deriving/cmp/eq.rs | 6 + src/libsyntax_ext/deriving/cmp/partial_eq.rs | 7 +- src/libsyntax_ext/deriving/mod.rs | 84 ++++++- .../ui/rfc1445/feature-gate.no_gate.stderr | 19 +- src/test/ui/rfc1445/feature-gate.rs | 14 +- 16 files changed, 508 insertions(+), 172 deletions(-) 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 a9415faf3312c..089443905dc53 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -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; diff --git a/src/librustc/ty/structural_match.rs b/src/librustc/ty/structural_match.rs index 83f0feda168eb..86b047e2963a2 100644 --- a/src/librustc/ty/structural_match.rs +++ b/src/librustc/ty/structural_match.rs @@ -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> { - 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 + }) +} + +/// 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() +} + +struct Search<'a, 'tcx> { + id: hir::HirId, + span: Span, - struct Search<'tcx> { - tcx: TyCtxt<'tcx>, + infcx: InferCtxt<'a, 'tcx>, - // Records the first ADT or type parameter we find without `#[structural_match`. - found: Option>, + // records the first ADT we find that does not implement `Structural`. + found: Option>, - // Tracks ADTs previously encountered during search, so that - // we will not recurse on them again. - seen: FxHashSet, + // tracks ADT's 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 } - 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); + 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. } - - if !self.seen.insert(adt_def.did) { - debug!("Search already seen adt_def: {:?}", adt_def); - // let caller continue its search + ty::RawPtr(..) => { + // structural-match ignores substructure of + // `*const _`/`*mut _`, so skip super_visit_with + // + // (But still tell caller to continue 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; - } + 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; + } + }; - // Even though we do not want to recur on substs, we do - // want our caller to continue its own search. - 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 index c92bbce98d099..bfc539639db1e 100644 --- a/src/librustc_mir/hair/pattern/const_to_pat.rs +++ b/src/librustc_mir/hair/pattern/const_to_pat.rs @@ -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, + + // 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 - 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); + + 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 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"); - } + 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, diff --git a/src/librustc_mir/hair/pattern/mod.rs b/src/librustc_mir/hair/pattern/mod.rs index 331770003b682..1ecc78ba227ce 100644 --- a/src/librustc_mir/hair/pattern/mod.rs +++ b/src/librustc_mir/hair/pattern/mod.rs @@ -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); 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/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 { } From 86f7d6f21ee8473f3605cd27fdda303fdec09d40 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 25 Oct 2019 14:47:04 +0200 Subject: [PATCH 6/7] Review feedback: elaborated comments. --- src/librustc/ty/structural_match.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/librustc/ty/structural_match.rs b/src/librustc/ty/structural_match.rs index 86b047e2963a2..cdf5734f5a506 100644 --- a/src/librustc/ty/structural_match.rs +++ b/src/librustc/ty/structural_match.rs @@ -93,17 +93,20 @@ pub fn type_marked_structural(id: hir::HirId, 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 the first ADT we find that does not implement `Structural`. + /// Records first ADT that does not implement a structural-match trait. found: Option>, - // tracks ADT's previously encountered during search, so that - // we will not recur on them again. + /// Tracks ADTs previously encountered during search, so that + /// we will not recur on them again. seen: FxHashSet, } @@ -129,13 +132,26 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for Search<'a, 'tcx> { } ty::RawPtr(..) => { // structural-match ignores substructure of - // `*const _`/`*mut _`, so skip super_visit_with + // `*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 + // 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; From f645e90992d4f76a2c5e7f8b4656246769a285ff Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 18 Oct 2019 11:37:05 +0200 Subject: [PATCH 7/7] Update test output. (My inference is that the number changed from 4 to 5 because `derive(PartialEq)` now injects an extra trait impl before.) --- .../codegen-units/item-collection/overloaded-operators.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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);