summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs
diff options
context:
space:
mode:
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.rs380
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 }))
}
}