diff options
Diffstat (limited to 'compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs')
-rw-r--r-- | compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs | 380 |
1 files changed, 184 insertions, 196 deletions
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 b243f1dc8..050b01294 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 @@ -1,13 +1,14 @@ use rustc_hir as hir; +use rustc_hir::def_id::DefId; use rustc_index::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; use rustc_session::lint; use rustc_span::Span; -use rustc_target::abi::FieldIdx; +use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCause}; @@ -29,11 +30,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { cv: mir::ConstantKind<'tcx>, id: hir::HirId, span: Span, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option<DefId>, ) -> Box<Pat<'tcx>> { let infcx = self.tcx.infer_ctxt().build(); let mut convert = ConstToPat::new(self, id, span, infcx); - convert.to_pat(cv, mir_structural_match_violation) + convert.to_pat(cv, check_body_for_struct_match_violation) } } @@ -104,7 +105,7 @@ impl<'tcx> ConstToPat<'tcx> { fn to_pat( &mut self, cv: mir::ConstantKind<'tcx>, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option<DefId>, ) -> Box<Pat<'tcx>> { trace!(self.treat_byte_string_as_slice); // This method is just a wrapper handling a validity check; the heavy lifting is @@ -114,14 +115,44 @@ impl<'tcx> ConstToPat<'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, mir_structural_match_violation).unwrap_or_else(|_| { - Box::new(Pat { - span: self.span, - ty: cv.ty(), - kind: PatKind::Constant { value: cv }, - }) - }); + let mir_structural_match_violation = check_body_for_struct_match_violation.map(|def_id| { + // `mir_const_qualif` must be called with the `DefId` of the item where the const is + // defined, not where it is declared. The difference is significant for associated + // constants. + self.tcx().mir_const_qualif(def_id).custom_eq + }); + debug!(?check_body_for_struct_match_violation, ?mir_structural_match_violation); + + let inlined_const_as_pat = match cv { + mir::ConstantKind::Ty(c) => match c.kind() { + ty::ConstKind::Param(_) + | ty::ConstKind::Infer(_) + | ty::ConstKind::Bound(_, _) + | ty::ConstKind::Placeholder(_) + | ty::ConstKind::Unevaluated(_) + | ty::ConstKind::Error(_) + | ty::ConstKind::Expr(_) => { + span_bug!(self.span, "unexpected const in `to_pat`: {:?}", c.kind()) + } + ty::ConstKind::Value(valtree) => self + .recur(valtree, cv.ty(), mir_structural_match_violation.unwrap_or(false)) + .unwrap_or_else(|_| { + Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }) + }), + }, + mir::ConstantKind::Unevaluated(_, _) => { + span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}") + } + mir::ConstantKind::Val(_, _) => Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }), + }; if !self.saw_const_match_error.get() { // If we were able to successfully convert the const to some pat, @@ -141,29 +172,70 @@ impl<'tcx> ConstToPat<'tcx> { // // FIXME(#73448): Find a way to bring const qualification into parity with // `search_for_structural_match_violation`. - if structural.is_none() && mir_structural_match_violation { + if structural.is_none() && mir_structural_match_violation.unwrap_or(false) { warn!("MIR const-checker found novel structural match violation. See #73448."); return inlined_const_as_pat; } if let Some(non_sm_ty) = structural { if !self.type_may_have_partial_eq_impl(cv.ty()) { - // fatal avoids ICE from resolution of nonexistent method (rare case). - self.tcx() - .sess - .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty: non_sm_ty }); - } else if mir_structural_match_violation && !self.saw_const_match_lint.get() { - self.tcx().emit_spanned_lint( - lint::builtin::INDIRECT_STRUCTURAL_MATCH, - self.id, - self.span, - IndirectStructuralMatch { non_sm_ty }, - ); - } else { - debug!( - "`search_for_structural_match_violation` found one, but `CustomEq` was \ - not in the qualifs for that `const`" - ); + if let ty::Adt(def, ..) = non_sm_ty.kind() { + if def.is_union() { + let err = UnionPattern { span: self.span }; + self.tcx().sess.emit_err(err); + } else { + // fatal avoids ICE from resolution of nonexistent method (rare case). + self.tcx() + .sess + .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty }); + } + } else { + let err = InvalidPattern { span: self.span, non_sm_ty }; + self.tcx().sess.emit_err(err); + return Box::new(Pat { span: self.span, ty: cv.ty(), kind: PatKind::Wild }); + } + } else if !self.saw_const_match_lint.get() { + if let Some(mir_structural_match_violation) = mir_structural_match_violation { + match non_sm_ty.kind() { + ty::RawPtr(pointee) + if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + ty::Adt(..) if mir_structural_match_violation => { + self.tcx().emit_spanned_lint( + lint::builtin::INDIRECT_STRUCTURAL_MATCH, + self.id, + self.span, + IndirectStructuralMatch { non_sm_ty }, + ); + } + _ => { + debug!( + "`search_for_structural_match_violation` found one, but `CustomEq` was \ + not in the qualifs for that `const`" + ); + } + } + } + } + } else if !self.saw_const_match_lint.get() { + match cv.ty().kind() { + ty::RawPtr(pointee) if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + _ => {} } } } @@ -171,6 +243,7 @@ impl<'tcx> ConstToPat<'tcx> { inlined_const_as_pat } + #[instrument(level = "trace", skip(self), ret)] fn type_may_have_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool { // double-check there even *is* a semantic `PartialEq` to dispatch to. // @@ -187,29 +260,19 @@ impl<'tcx> ConstToPat<'tcx> { ); // FIXME: should this call a `predicate_must_hold` variant instead? - let has_impl = self.infcx.predicate_may_hold(&partial_eq_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, - }) + self.infcx.predicate_may_hold(&partial_eq_obligation) } fn field_pats( &self, - vals: impl Iterator<Item = mir::ConstantKind<'tcx>>, + vals: impl Iterator<Item = (ValTree<'tcx>, Ty<'tcx>)>, ) -> Result<Vec<FieldPat<'tcx>>, FallbackToConstRef> { vals.enumerate() - .map(|(idx, val)| { + .map(|(idx, (val, ty))| { let field = FieldIdx::new(idx); - Ok(FieldPat { field, pattern: self.recur(val, false)? }) + // Patterns can only use monomorphic types. + let ty = self.tcx().normalize_erasing_regions(self.param_env, ty); + Ok(FieldPat { field, pattern: self.recur(val, ty, false)? }) }) .collect() } @@ -218,7 +281,8 @@ impl<'tcx> ConstToPat<'tcx> { #[instrument(skip(self), level = "debug")] fn recur( &self, - cv: mir::ConstantKind<'tcx>, + cv: ValTree<'tcx>, + ty: Ty<'tcx>, mir_structural_match_violation: bool, ) -> Result<Box<Pat<'tcx>>, FallbackToConstRef> { let id = self.id; @@ -226,8 +290,9 @@ impl<'tcx> ConstToPat<'tcx> { let tcx = self.tcx(); let param_env = self.param_env; - let kind = match cv.ty().kind() { + let kind = match ty.kind() { ty::Float(_) => { + self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, id, @@ -236,27 +301,6 @@ impl<'tcx> ConstToPat<'tcx> { ); return Err(FallbackToConstRef); } - 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); - let err = UnionPattern { span }; - tcx.sess.emit_err(err); - PatKind::Wild - } - 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. - - // 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 Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) => - { - self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty }; - tcx.sess.emit_err(err); - PatKind::Wild - } // 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. @@ -266,16 +310,14 @@ impl<'tcx> ConstToPat<'tcx> { // 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.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { + ty::Adt(..) if !self.type_marked_structural(ty) && self.behind_reference.get() => { + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, - IndirectStructuralMatch { non_sm_ty: cv.ty() }, + IndirectStructuralMatch { non_sm_ty: ty }, ); } // Since we are behind a reference, we can just bubble the error up so we get a @@ -283,108 +325,64 @@ impl<'tcx> ConstToPat<'tcx> { // `PartialEq::eq` on it. return Err(FallbackToConstRef); } - 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() - ); + ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { + debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,); self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty: cv.ty() }; + let err = TypeNotStructural { span, non_sm_ty: ty }; tcx.sess.emit_err(err); PatKind::Wild } ty::Adt(adt_def, substs) if adt_def.is_enum() => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - + let (&variant_index, fields) = cv.unwrap_branch().split_first().unwrap(); + let variant_index = + VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); PatKind::Variant { adt_def: *adt_def, substs, - variant_index: destructured - .variant - .expect("destructed const of adt without variant id"), - subpatterns: self.field_pats(destructured.fields.iter().copied())?, + variant_index, + subpatterns: self.field_pats( + fields.iter().copied().zip( + adt_def.variants()[variant_index] + .fields + .iter() + .map(|field| field.ty(self.tcx(), substs)), + ), + )?, } } - ty::Tuple(_) | ty::Adt(_, _) => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - PatKind::Leaf { subpatterns: self.field_pats(destructured.fields.iter().copied())? } - } - ty::Array(..) => PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, cv) - .fields + ty::Tuple(fields) => PatKind::Leaf { + subpatterns: self + .field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter()))?, + }, + ty::Adt(def, substs) => PatKind::Leaf { + subpatterns: self.field_pats(cv.unwrap_branch().iter().copied().zip( + def.non_enum_variant().fields.iter().map(|field| field.ty(self.tcx(), substs)), + ))?, + }, + ty::Slice(elem_ty) => PatKind::Slice { + prefix: cv + .unwrap_branch() + .iter() + .map(|val| self.recur(*val, *elem_ty, false)) + .collect::<Result<_, _>>()?, + slice: None, + suffix: Box::new([]), + }, + ty::Array(elem_ty, _) => PatKind::Array { + prefix: cv + .unwrap_branch() .iter() - .map(|val| self.recur(*val, false)) + .map(|val| self.recur(*val, *elem_ty, false)) .collect::<Result<_, _>>()?, slice: None, suffix: Box::new([]), }, 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 err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); - PatKind::Wild - } - // `&str` is represented as `ConstValue::Slice`, let's keep using this + // `&str` is represented as a valtree, let's keep using this // optimization for now. - ty::Str => 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. - // The typechecker has a special case for byte string literals, by treating them - // as slices. This means we turn `&[T; N]` constants into slice patterns, which - // has no negative effects on pattern matching, even if we're actually matching on - // arrays. - ty::Array(..) if !self.treat_byte_string_as_slice => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::<Result<_, _>>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: *pointee_ty, - }), - }; - self.behind_reference.set(old); - val - } - ty::Array(elem_ty, _) | - // Cannot merge this with the catch all branch below, because the `const_deref` - // changes the type from slice to array, we need to keep the original type in the - // pattern. - ty::Slice(elem_ty) => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Slice { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::<Result<_, _>>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: tcx.mk_slice(elem_ty), - }), - }; - self.behind_reference.set(old); - val - } + ty::Str => PatKind::Constant { + value: mir::ConstantKind::Ty(ty::Const::new_value(tcx, cv, ty)), + }, // Backwards compatibility hack: support references to non-structural types, // but hard error if we aren't behind a double reference. We could just use // the fallback code path below, but that would allow *more* of this fishy @@ -392,11 +390,9 @@ impl<'tcx> ConstToPat<'tcx> { // instead of a hard error. ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => { if self.behind_reference.get() { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { + self.saw_const_match_lint.set(true); + tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, span, @@ -417,49 +413,41 @@ impl<'tcx> ConstToPat<'tcx> { // convert the dereferenced constant to a pattern that is the sub-pattern of the // deref pattern. _ => { - if !pointee_ty.is_sized(tcx, param_env) { - // `tcx.deref_mir_constant()` below will ICE with an unsized type - // (except slices, which are handled in a separate arm above). - + if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() { let err = UnsizedPattern { span, non_sm_ty: *pointee_ty }; tcx.sess.emit_err(err); + // FIXME: introduce PatKind::Error to silence follow up diagnostics due to unreachable patterns. PatKind::Wild } else { let old = self.behind_reference.replace(true); - let subpattern = self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false)?; + // `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. + // The typechecker has a special case for byte string literals, by treating them + // as slices. This means we turn `&[T; N]` constants into slice patterns, which + // has no negative effects on pattern matching, even if we're actually matching on + // arrays. + let pointee_ty = match *pointee_ty.kind() { + ty::Array(elem_ty, _) if self.treat_byte_string_as_slice => { + Ty::new_slice(tcx, elem_ty) + } + _ => *pointee_ty, + }; + // References have the same valtree representation as their pointee. + let subpattern = self.recur(cv, pointee_ty, false)?; self.behind_reference.set(old); PatKind::Deref { subpattern } } } }, - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => { - PatKind::Constant { value: cv } - } - ty::RawPtr(pointee) if pointee.ty.is_sized(tcx, param_env) => { - return Err(FallbackToConstRef); - } - // FIXME: these can have very surprising 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.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( - lint::builtin::POINTER_STRUCTURAL_MATCH, - id, - span, - PointerPattern - ); - } - return Err(FallbackToConstRef); - } + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => PatKind::Constant { + value: mir::ConstantKind::Ty(ty::Const::new_value(tcx, cv, ty)), + }, + ty::FnPtr(..) | ty::RawPtr(..) => unreachable!(), _ => { self.saw_const_match_error.set(true); - let err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); + let err = InvalidPattern { span, non_sm_ty: ty }; + tcx.sess.emit_err(err); PatKind::Wild } }; @@ -472,7 +460,7 @@ impl<'tcx> ConstToPat<'tcx> { // 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 Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) + && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, ty) { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( @@ -483,6 +471,6 @@ impl<'tcx> ConstToPat<'tcx> { ); } - Ok(Box::new(Pat { span, ty: cv.ty(), kind })) + Ok(Box::new(Pat { span, ty, kind })) } } |