1
Fork 0

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
This commit is contained in:
bors 2020-09-26 06:44:28 +00:00
commit fd15e6180d
66 changed files with 796 additions and 435 deletions

View file

@ -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" }
}

View file

@ -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 })
}

View file

@ -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)
};
}

View file

@ -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,10 +274,63 @@ 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(..)
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);
if self.include_lint_checks {
tcx.sess.span_err(self.span, &msg);
} else {
tcx.sess.delay_span_bug(self.span, &msg)
}
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.
// 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);
@ -243,29 +340,11 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
path, path,
);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
PatKind::Wild
}
// keep old code until future-compat upgraded to errors.
ty::Ref(_, adt_ty, _) 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.
let path = tcx.def_path_str(adt_def.did);
let msg = format!(
"to use a constant of type `{}` in a pattern, \
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
path, path,
);
self.saw_const_match_error.set(true);
tcx.sess.span_err(span, &msg);
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(_, _) => {
ty::Tuple(_) | ty::Adt(_, _) => {
let destructured = tcx.destructure_const(param_env.and(cv));
PatKind::Leaf { subpatterns: field_pats(destructured.fields) }
}
ty::Tuple(_) => {
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) })
}
}

View file

@ -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,
@ -2197,6 +2190,83 @@ declare_lint! {
report_in_external_macro
}
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,