diff options
| author | bors <bors@rust-lang.org> | 2020-09-26 06:44:28 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2020-09-26 06:44:28 +0000 |
| commit | fd15e6180d9c48b4f1157e44cdaff6e901e5f854 (patch) | |
| tree | 11801a40b3c4b0ccc7c10b589347b28348705871 /compiler | |
| parent | 9e1c4361780e69ed54444a3b03fef0cbbc26b547 (diff) | |
| parent | daf976f6129e5fb16effc48bf91853548774e235 (diff) | |
| download | rust-fd15e6180d9c48b4f1157e44cdaff6e901e5f854.tar.gz rust-fd15e6180d9c48b4f1157e44cdaff6e901e5f854.zip | |
Auto merge of #70743 - oli-obk:eager_const_to_pat_conversion, r=eddyb
Fully destructure constants into patterns r? `@varkor` as discussed in https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/constants.20in.20patterns/near/192789924 we should probably crater it once reviewed
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_middle/src/query/mod.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/const_eval/mod.rs | 41 | ||||
| -rw-r--r-- | compiler/rustc_mir/src/lib.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs | 450 | ||||
| -rw-r--r-- | compiler/rustc_session/src/lint/builtin.rs | 108 |
5 files changed, 482 insertions, 129 deletions
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 9c047cbfaef..784d6c3b2ce 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -742,6 +742,14 @@ rustc_queries! { desc { "destructure constant" } } + /// Dereference a constant reference or raw pointer and turn the result into a constant + /// again. + query deref_const( + key: ty::ParamEnvAnd<'tcx, &'tcx ty::Const<'tcx>> + ) -> &'tcx ty::Const<'tcx> { + desc { "deref constant" } + } + query const_caller_location(key: (rustc_span::Symbol, u32, u32)) -> ConstValue<'tcx> { desc { "get a &core::panic::Location referring to a span" } } diff --git a/compiler/rustc_mir/src/const_eval/mod.rs b/compiler/rustc_mir/src/const_eval/mod.rs index c93feb5096b..978d2fe0004 100644 --- a/compiler/rustc_mir/src/const_eval/mod.rs +++ b/compiler/rustc_mir/src/const_eval/mod.rs @@ -2,11 +2,14 @@ use std::convert::TryFrom; +use rustc_hir::Mutability; use rustc_middle::mir; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::{source_map::DUMMY_SP, symbol::Symbol}; -use crate::interpret::{intern_const_alloc_recursive, ConstValue, InternKind, InterpCx}; +use crate::interpret::{ + intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MemPlaceMeta, Scalar, +}; mod error; mod eval_queries; @@ -67,3 +70,39 @@ pub(crate) fn destructure_const<'tcx>( mir::DestructuredConst { variant, fields } } + +pub(crate) fn deref_const<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + val: &'tcx ty::Const<'tcx>, +) -> &'tcx ty::Const<'tcx> { + trace!("deref_const: {:?}", val); + let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); + let op = ecx.const_to_op(val, None).unwrap(); + let mplace = ecx.deref_operand(op).unwrap(); + if let Scalar::Ptr(ptr) = mplace.ptr { + assert_eq!( + ecx.memory.get_raw(ptr.alloc_id).unwrap().mutability, + Mutability::Not, + "deref_const cannot be used with mutable allocations as \ + that could allow pattern matching to observe mutable statics", + ); + } + + let ty = match mplace.meta { + MemPlaceMeta::None => mplace.layout.ty, + MemPlaceMeta::Poison => bug!("poison metadata in `deref_const`: {:#?}", mplace), + // In case of unsized types, figure out the real type behind. + MemPlaceMeta::Meta(scalar) => match mplace.layout.ty.kind() { + ty::Str => bug!("there's no sized equivalent of a `str`"), + ty::Slice(elem_ty) => tcx.mk_array(elem_ty, scalar.to_machine_usize(&tcx).unwrap()), + _ => bug!( + "type {} should not have metadata, but had {:?}", + mplace.layout.ty, + mplace.meta + ), + }, + }; + + tcx.mk_const(ty::Const { val: ty::ConstKind::Value(op_to_const(&ecx, mplace.into())), ty }) +} diff --git a/compiler/rustc_mir/src/lib.rs b/compiler/rustc_mir/src/lib.rs index a356ae1709d..c00c6860903 100644 --- a/compiler/rustc_mir/src/lib.rs +++ b/compiler/rustc_mir/src/lib.rs @@ -59,4 +59,8 @@ pub fn provide(providers: &mut Providers) { let (param_env, value) = param_env_and_value.into_parts(); const_eval::destructure_const(tcx, param_env, value) }; + providers.deref_const = |tcx, param_env_and_value| { + let (param_env, value) = param_env_and_value.into_parts(); + const_eval::deref_const(tcx, param_env, value) + }; } diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 2816bad7eab..a203b3a1428 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -3,7 +3,7 @@ use rustc_index::vec::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_middle::mir::Field; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; use rustc_session::lint; use rustc_span::Span; use rustc_trait_selection::traits::predicate_for_trait_def; @@ -28,10 +28,13 @@ impl<'a, 'tcx> PatCtxt<'a, '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 pat = self.tcx.infer_ctxt().enter(|infcx| { let mut convert = ConstToPat::new(self, id, span, infcx); convert.to_pat(cv, mir_structural_match_violation) - }) + }); + + debug!("const_to_pat: pat={:?}", pat); + pat } } @@ -40,17 +43,44 @@ struct ConstToPat<'a, 'tcx> { span: Span, param_env: ty::ParamEnv<'tcx>, - // This tracks if we signal some hard error for a given const value, so that + // This tracks if we emitted some hard error for a given const value, so that // we will not subsequently issue an irrelevant lint for the same const // value. saw_const_match_error: Cell<bool>, + // This tracks if we emitted some diagnostic for a given const value, so that + // we will not subsequently issue an irrelevant lint for the same const + // value. + saw_const_match_lint: Cell<bool>, + + // For backcompat we need to keep allowing non-structurally-eq types behind references. + // See also all the `cant-hide-behind` tests. + behind_reference: Cell<bool>, + // inference context used for checking `T: Structural` bounds. infcx: InferCtxt<'a, 'tcx>, include_lint_checks: bool, } +mod fallback_to_const_ref { + #[derive(Debug)] + /// This error type signals that we encountered a non-struct-eq situation behind a reference. + /// We bubble this up in order to get back to the reference destructuring and make that emit + /// a const pattern instead of a deref pattern. This allows us to simply call `PartialEq::eq` + /// on such patterns (since that function takes a reference) and not have to jump through any + /// hoops to get a reference to the value. + pub(super) struct FallbackToConstRef(()); + + pub(super) fn fallback_to_const_ref<'a, 'tcx>( + c2p: &super::ConstToPat<'a, 'tcx>, + ) -> FallbackToConstRef { + assert!(c2p.behind_reference.get()); + FallbackToConstRef(()) + } +} +use fallback_to_const_ref::{fallback_to_const_ref, FallbackToConstRef}; + impl<'a, 'tcx> ConstToPat<'a, 'tcx> { fn new( pat_ctxt: &PatCtxt<'_, 'tcx>, @@ -65,6 +95,8 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { param_env: pat_ctxt.param_env, include_lint_checks: pat_ctxt.include_lint_checks, saw_const_match_error: Cell::new(false), + saw_const_match_lint: Cell::new(false), + behind_reference: Cell::new(false), } } @@ -72,11 +104,44 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { self.infcx.tcx } - fn search_for_structural_match_violation( - &self, - ty: Ty<'tcx>, - ) -> Option<traits::NonStructuralMatchTy<'tcx>> { - traits::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty) + fn adt_derive_msg(&self, adt_def: &AdtDef) -> String { + 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, + ) + } + + fn search_for_structural_match_violation(&self, ty: Ty<'tcx>) -> Option<String> { + traits::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty).map( + |non_sm_ty| { + with_no_trimmed_paths(|| match non_sm_ty { + traits::NonStructuralMatchTy::Adt(adt) => self.adt_derive_msg(adt), + traits::NonStructuralMatchTy::Dynamic => { + "trait objects cannot be used in patterns".to_string() + } + traits::NonStructuralMatchTy::Opaque => { + "opaque types cannot be used in patterns".to_string() + } + traits::NonStructuralMatchTy::Generator => { + "generators cannot be used in patterns".to_string() + } + traits::NonStructuralMatchTy::Closure => { + "closures cannot be used in patterns".to_string() + } + traits::NonStructuralMatchTy::Param => { + bug!("use of a constant whose type is a parameter inside a pattern") + } + traits::NonStructuralMatchTy::Projection => { + bug!("use of a constant whose type is a projection inside a pattern") + } + traits::NonStructuralMatchTy::Foreign => { + bug!("use of a value of a foreign type inside a pattern") + } + }) + }, + ) } fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool { @@ -95,7 +160,7 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { // once indirect_structural_match is a full fledged error, this // level of indirection can be eliminated - let inlined_const_as_pat = self.recur(cv); + let inlined_const_as_pat = self.recur(cv, mir_structural_match_violation).unwrap(); if self.include_lint_checks && !self.saw_const_match_error.get() { // If we were able to successfully convert the const to some pat, @@ -118,70 +183,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { return inlined_const_as_pat; } - if let Some(non_sm_ty) = structural { - let msg = with_no_trimmed_paths(|| match non_sm_ty { - traits::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, - ) - } - traits::NonStructuralMatchTy::Dynamic => { - "trait objects cannot be used in patterns".to_string() - } - traits::NonStructuralMatchTy::Opaque => { - "opaque types cannot be used in patterns".to_string() - } - traits::NonStructuralMatchTy::Generator => { - "generators cannot be used in patterns".to_string() - } - traits::NonStructuralMatchTy::Closure => { - "closures cannot be used in patterns".to_string() - } - traits::NonStructuralMatchTy::Param => { - bug!("use of a constant whose type is a parameter inside a pattern") - } - traits::NonStructuralMatchTy::Projection => { - bug!("use of a constant whose type is a projection inside a pattern") - } - traits::NonStructuralMatchTy::Foreign => { - bug!("use of a value of a foreign type inside a pattern") - } - }); - - // 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().require_lang_item(hir::LangItem::PartialEq, Some(self.span)); - let obligation: PredicateObligation<'_> = predicate_for_trait_def( - self.tcx(), - 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 { + if let Some(msg) = structural { + if !self.type_may_have_partial_eq_impl(cv.ty) { // span_fatal avoids ICE from resolution of non-existent method (rare case). self.tcx().sess.span_fatal(self.span, &msg); - } else if mir_structural_match_violation { + } else if mir_structural_match_violation && !self.saw_const_match_lint.get() { self.tcx().struct_span_lint_hir( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, @@ -200,19 +206,57 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { inlined_const_as_pat } + fn type_may_have_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool { + // 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.) + let partial_eq_trait_id = + self.tcx().require_lang_item(hir::LangItem::PartialEq, Some(self.span)); + let obligation: PredicateObligation<'_> = predicate_for_trait_def( + self.tcx(), + self.param_env, + ObligationCause::misc(self.span, self.id), + partial_eq_trait_id, + 0, + ty, + &[], + ); + // FIXME: should this call a `predicate_must_hold` variant instead? + + let has_impl = self.infcx.predicate_may_hold(&obligation); + + // Note: To fix rust-lang/rust#65466, we could just remove this type + // walk hack for function pointers, 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. + has_impl + || ty.walk().any(|t| match t.unpack() { + ty::subst::GenericArgKind::Lifetime(_) => false, + ty::subst::GenericArgKind::Type(t) => t.is_fn_ptr(), + ty::subst::GenericArgKind::Const(_) => false, + }) + } + // Recursive helper for `to_pat`; invoke that (instead of calling this directly). - fn recur(&self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> { + fn recur( + &self, + cv: &'tcx ty::Const<'tcx>, + mir_structural_match_violation: bool, + ) -> Result<Pat<'tcx>, FallbackToConstRef> { let id = self.id; let span = self.span; let tcx = self.tcx(); let param_env = self.param_env; - let field_pats = |vals: &[&'tcx ty::Const<'tcx>]| { + let field_pats = |vals: &[&'tcx ty::Const<'tcx>]| -> Result<_, _> { vals.iter() .enumerate() .map(|(idx, val)| { let field = Field::new(idx); - FieldPat { field, pattern: self.recur(val) } + Ok(FieldPat { field, pattern: self.recur(val, false)? }) }) .collect() }; @@ -230,34 +274,65 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { 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"); + let msg = "cannot use unions in constant patterns"; + if self.include_lint_checks { + tcx.sess.span_err(span, msg); + } else { + tcx.sess.delay_span_bug(span, msg) + } 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, - ); + ty::Adt(..) + if !self.type_may_have_partial_eq_impl(cv.ty) + // FIXME(#73448): Find a way to bring const qualification into parity with + // `search_for_structural_match_violation` and then remove this condition. + && self.search_for_structural_match_violation(cv.ty).is_some() => + { + // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we + // could get `Option<NonStructEq>`, even though `Option` is annotated with derive. + let msg = self.search_for_structural_match_violation(cv.ty).unwrap(); self.saw_const_match_error.set(true); - tcx.sess.span_err(span, &msg); + if self.include_lint_checks { + tcx.sess.span_err(self.span, &msg); + } else { + tcx.sess.delay_span_bug(self.span, &msg) + } PatKind::Wild } - // keep old code until future-compat upgraded to errors. - ty::Ref(_, adt_ty, _) if adt_ty.is_adt() && !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. + // If the type is not structurally comparable, just emit the constant directly, + // causing the pattern match code to treat it opaquely. + // FIXME: This code doesn't emit errors itself, the caller emits the errors. + // So instead of specific errors, you just get blanket errors about the whole + // const type. See + // https://github.com/rust-lang/rust/pull/70743#discussion_r404701963 for + // details. + // Backwards compatibility hack because we can't cause hard errors on these + // types, so we compare them via `PartialEq::eq` at runtime. + ty::Adt(..) if !self.type_marked_structural(cv.ty) && self.behind_reference.get() => { + if self.include_lint_checks + && !self.saw_const_match_error.get() + && !self.saw_const_match_lint.get() + { + self.saw_const_match_lint.set(true); + let msg = format!( + "to use a constant of type `{}` in a pattern, \ + `{}` must be annotated with `#[derive(PartialEq, Eq)]`", + cv.ty, cv.ty, + ); + tcx.struct_span_lint_hir( + lint::builtin::INDIRECT_STRUCTURAL_MATCH, + id, + span, + |lint| lint.build(&msg).emit(), + ); + } + // Since we are behind a reference, we can just bubble the error up so we get a + // constant at reference type, making it easy to let the fallback call + // `PartialEq::eq` on it. + return Err(fallback_to_const_ref(self)); + } + 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, \ @@ -265,7 +340,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { path, path, ); self.saw_const_match_error.set(true); - tcx.sess.span_err(span, &msg); + if self.include_lint_checks { + tcx.sess.span_err(span, &msg); + } else { + tcx.sess.delay_span_bug(span, &msg) + } PatKind::Wild } ty::Adt(adt_def, substs) if adt_def.is_enum() => { @@ -276,30 +355,181 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> { variant_index: destructured .variant .expect("destructed const of adt without variant id"), - subpatterns: field_pats(destructured.fields), + subpatterns: field_pats(destructured.fields)?, } } - ty::Adt(_, _) => { - let destructured = tcx.destructure_const(param_env.and(cv)); - PatKind::Leaf { subpatterns: field_pats(destructured.fields) } - } - ty::Tuple(_) => { + ty::Tuple(_) | ty::Adt(_, _) => { let destructured = tcx.destructure_const(param_env.and(cv)); - PatKind::Leaf { subpatterns: field_pats(destructured.fields) } + PatKind::Leaf { subpatterns: field_pats(destructured.fields)? } } ty::Array(..) => PatKind::Array { prefix: tcx .destructure_const(param_env.and(cv)) .fields .iter() - .map(|val| self.recur(val)) - .collect(), + .map(|val| self.recur(val, false)) + .collect::<Result<_, _>>()?, slice: None, suffix: Vec::new(), }, - _ => PatKind::Constant { value: cv }, + ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() { + // These are not allowed and will error elsewhere anyway. + ty::Dynamic(..) => { + self.saw_const_match_error.set(true); + let msg = format!("`{}` cannot be used in patterns", cv.ty); + if self.include_lint_checks { + tcx.sess.span_err(span, &msg); + } else { + tcx.sess.delay_span_bug(span, &msg) + } + PatKind::Wild + } + // `&str` and `&[u8]` are represented as `ConstValue::Slice`, let's keep using this + // optimization for now. + ty::Str => PatKind::Constant { value: cv }, + ty::Slice(elem_ty) if elem_ty == tcx.types.u8 => PatKind::Constant { value: cv }, + // `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when + // matching against references, you can only use byte string literals. + // FIXME: clean this up, likely by permitting array patterns when matching on slices + ty::Array(elem_ty, _) if elem_ty == tcx.types.u8 => PatKind::Constant { value: cv }, + // Cannot merge this with the catch all branch below, because the `const_deref` + // changes the type from slice to array, and slice patterns behave differently from + // array patterns. + ty::Slice(..) => { + let old = self.behind_reference.replace(true); + let array = tcx.deref_const(self.param_env.and(cv)); + let val = PatKind::Deref { + subpattern: Pat { + kind: Box::new(PatKind::Slice { + prefix: tcx + .destructure_const(param_env.and(array)) + .fields + .iter() + .map(|val| self.recur(val, false)) + .collect::<Result<_, _>>()?, + slice: None, + suffix: vec![], + }), + span, + ty: pointee_ty, + }, + }; + self.behind_reference.set(old); + val + } + // Backwards compatibility hack: support references to non-structural types. + // We'll lower + // this pattern to a `PartialEq::eq` comparison and `PartialEq::eq` takes a + // reference. This makes the rest of the matching logic simpler as it doesn't have + // to figure out how to get a reference again. + ty::Adt(adt_def, _) if !self.type_marked_structural(pointee_ty) => { + if self.behind_reference.get() { + if self.include_lint_checks + && !self.saw_const_match_error.get() + && !self.saw_const_match_lint.get() + { + self.saw_const_match_lint.set(true); + let msg = self.adt_derive_msg(adt_def); + self.tcx().struct_span_lint_hir( + lint::builtin::INDIRECT_STRUCTURAL_MATCH, + self.id, + self.span, + |lint| lint.build(&msg).emit(), + ); + } + PatKind::Constant { value: cv } + } else { + if !self.saw_const_match_error.get() { + self.saw_const_match_error.set(true); + let msg = self.adt_derive_msg(adt_def); + if self.include_lint_checks { + tcx.sess.span_err(span, &msg); + } else { + tcx.sess.delay_span_bug(span, &msg) + } + } + PatKind::Wild + } + } + // All other references are converted into deref patterns and then recursively + // convert the dereferenced constant to a pattern that is the sub-pattern of the + // deref pattern. + _ => { + let old = self.behind_reference.replace(true); + // In case there are structural-match violations somewhere in this subpattern, + // we fall back to a const pattern. If we do not do this, we may end up with + // a !structural-match constant that is not of reference type, which makes it + // very hard to invoke `PartialEq::eq` on it as a fallback. + let val = match self.recur(tcx.deref_const(self.param_env.and(cv)), false) { + Ok(subpattern) => PatKind::Deref { subpattern }, + Err(_) => PatKind::Constant { value: cv }, + }; + self.behind_reference.set(old); + val + } + }, + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => { + PatKind::Constant { value: cv } + } + ty::RawPtr(pointee) if pointee.ty.is_sized(tcx.at(span), param_env) => { + PatKind::Constant { value: cv } + } + // FIXME: these can have very suprising behaviour where optimization levels or other + // compilation choices change the runtime behaviour of the match. + // See https://github.com/rust-lang/rust/issues/70861 for examples. + ty::FnPtr(..) | ty::RawPtr(..) => { + if self.include_lint_checks + && !self.saw_const_match_error.get() + && !self.saw_const_match_lint.get() + { + self.saw_const_match_lint.set(true); + let msg = "function pointers and unsized pointers in patterns behave \ + unpredictably and should not be relied upon. \ + See https://github.com/rust-lang/rust/issues/70861 for details."; + tcx.struct_span_lint_hir( + lint::builtin::POINTER_STRUCTURAL_MATCH, + id, + span, + |lint| lint.build(&msg).emit(), + ); + } + PatKind::Constant { value: cv } + } + _ => { + self.saw_const_match_error.set(true); + let msg = format!("`{}` cannot be used in patterns", cv.ty); + if self.include_lint_checks { + tcx.sess.span_err(span, &msg); + } else { + tcx.sess.delay_span_bug(span, &msg) + } + PatKind::Wild + } }; - Pat { span, ty: cv.ty, kind: Box::new(kind) } + if self.include_lint_checks + && !self.saw_const_match_error.get() + && !self.saw_const_match_lint.get() + && mir_structural_match_violation + // FIXME(#73448): Find a way to bring const qualification into parity with + // `search_for_structural_match_violation` and then remove this condition. + && self.search_for_structural_match_violation(cv.ty).is_some() + { + self.saw_const_match_lint.set(true); + // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we + // could get `Option<NonStructEq>`, even though `Option` is annotated with derive. + let msg = self.search_for_structural_match_violation(cv.ty).unwrap().replace( + "in a pattern,", + "in a pattern, the constant's initializer must be trivial or", + ); + tcx.struct_span_lint_hir( + lint::builtin::NONTRIVIAL_STRUCTURAL_MATCH, + id, + span, + |lint| lint.build(&msg).emit(), + ); + } + + Ok(Pat { span, ty: cv.ty, kind: Box::new(kind) }) } } diff --git a/compiler/rustc_session/src/lint/builtin.rs b/compiler/rustc_session/src/lint/builtin.rs index 562df176b14..13a4057a35b 100644 --- a/compiler/rustc_session/src/lint/builtin.rs +++ b/compiler/rustc_session/src/lint/builtin.rs @@ -2138,22 +2138,16 @@ declare_lint! { /// ```rust,compile_fail /// #![deny(indirect_structural_match)] /// - /// struct Plus(i32, i32); - /// const ONE_PLUS_TWO: &&Plus = &&Plus(1, 2); - /// - /// impl PartialEq for Plus { - /// fn eq(&self, other: &Self) -> bool { - /// self.0 + self.1 == other.0 + other.1 - /// } - /// } - /// - /// impl Eq for Plus {} - /// + /// struct NoDerive(i32); + /// impl PartialEq for NoDerive { fn eq(&self, _: &Self) -> bool { false } } + /// impl Eq for NoDerive { } + /// #[derive(PartialEq, Eq)] + /// struct WrapParam<T>(T); + /// const WRAP_INDIRECT_PARAM: & &WrapParam<NoDerive> = & &WrapParam(NoDerive(0)); /// fn main() { - /// if let ONE_PLUS_TWO = &&Plus(3, 0) { - /// println!("semantic!"); - /// } else { - /// println!("structural!"); + /// match WRAP_INDIRECT_PARAM { + /// WRAP_INDIRECT_PARAM => { } + /// _ => { } /// } /// } /// ``` @@ -2170,9 +2164,8 @@ declare_lint! { /// [issue #62411]: https://github.com/rust-lang/rust/issues/62411 /// [future-incompatible]: ../index.md#future-incompatible-lints pub INDIRECT_STRUCTURAL_MATCH, - // defaulting to allow until rust-lang/rust#62614 is fixed. - Allow, - "pattern with const indirectly referencing non-structural-match type", + Warn, + "constant used in pattern contains value of non-structural-match type in a field or a variant", @future_incompatible = FutureIncompatibleInfo { reference: "issue #62411 <https://github.com/rust-lang/rust/issues/62411>", edition: None, @@ -2198,6 +2191,83 @@ declare_lint! { } declare_lint! { + /// The `pointer_structural_match` lint detects pointers used in patterns whose behaviour + /// cannot be relied upon across compiler versions and optimization levels. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(pointer_structural_match)] + /// fn foo(a: usize, b: usize) -> usize { a + b } + /// const FOO: fn(usize, usize) -> usize = foo; + /// fn main() { + /// match FOO { + /// FOO => {}, + /// _ => {}, + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Previous versions of Rust allowed function pointers and wide raw pointers in patterns. + /// While these work in many cases as expected by users, it is possible that due to + /// optimizations pointers are "not equal to themselves" or pointers to different functions + /// compare as equal during runtime. This is because LLVM optimizations can deduplicate + /// functions if their bodies are the same, thus also making pointers to these functions point + /// to the same location. Additionally functions may get duplicated if they are instantiated + /// in different crates and not deduplicated again via LTO. + pub POINTER_STRUCTURAL_MATCH, + Allow, + "pointers are not structural-match", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #62411 <https://github.com/rust-lang/rust/issues/70861>", + edition: None, + }; +} + +declare_lint! { + /// The `nontrivial_structural_match` lint detects constants that are used in patterns, + /// whose type is not structural-match and whose initializer body actually uses values + /// that are not structural-match. So `Option<NotStruturalMatch>` is ok if the constant + /// is just `None`. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(nontrivial_structural_match)] + /// + /// #[derive(Copy, Clone, Debug)] + /// struct NoDerive(u32); + /// impl PartialEq for NoDerive { fn eq(&self, _: &Self) -> bool { false } } + /// impl Eq for NoDerive { } + /// fn main() { + /// const INDEX: Option<NoDerive> = [None, Some(NoDerive(10))][0]; + /// match None { Some(_) => panic!("whoops"), INDEX => dbg!(INDEX), }; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Previous versions of Rust accepted constants in patterns, even if those constants's types + /// did not have `PartialEq` derived. Thus the compiler falls back to runtime execution of + /// `PartialEq`, which can report that two constants are not equal even if they are + /// bit-equivalent. + pub NONTRIVIAL_STRUCTURAL_MATCH, + Warn, + "constant used in pattern of non-structural-match type and the constant's initializer \ + expression contains values of non-structural-match types", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #73448 <https://github.com/rust-lang/rust/issues/73448>", + edition: None, + }; +} + +declare_lint! { /// The `ambiguous_associated_items` lint detects ambiguity between /// [associated items] and [enum variants]. /// @@ -2630,6 +2700,8 @@ declare_lint_pass! { AMBIGUOUS_ASSOCIATED_ITEMS, MUTABLE_BORROW_RESERVATION_CONFLICT, INDIRECT_STRUCTURAL_MATCH, + POINTER_STRUCTURAL_MATCH, + NONTRIVIAL_STRUCTURAL_MATCH, SOFT_UNSTABLE, INLINE_NO_SANITIZE, ASM_SUB_REGISTER, |
