const_to_pat: cleanup leftovers from when we had to deal with non-structural constants

This commit is contained in:
Ralf Jung 2024-07-13 17:24:50 +02:00
parent fa74a9e6aa
commit e613bc92a1
16 changed files with 173 additions and 459 deletions

View file

@ -4411,6 +4411,7 @@ dependencies = [
name = "rustc_mir_build" name = "rustc_mir_build"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"either",
"itertools", "itertools",
"rustc_apfloat", "rustc_apfloat",
"rustc_arena", "rustc_arena",

View file

@ -1,6 +1,7 @@
use crate::middle::resolve_bound_vars as rbv; use crate::middle::resolve_bound_vars as rbv;
use crate::mir::interpret::{ErrorHandled, LitToConstInput, Scalar}; use crate::mir::interpret::{ErrorHandled, LitToConstInput, Scalar};
use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use either::Either;
use rustc_data_structures::intern::Interned; use rustc_data_structures::intern::Interned;
use rustc_error_messages::MultiSpan; use rustc_error_messages::MultiSpan;
use rustc_hir as hir; use rustc_hir as hir;
@ -312,14 +313,16 @@ impl<'tcx> Const<'tcx> {
Self::from_bits(tcx, n as u128, ParamEnv::empty().and(tcx.types.usize)) Self::from_bits(tcx, n as u128, ParamEnv::empty().and(tcx.types.usize))
} }
/// Returns the evaluated constant /// Returns the evaluated constant as a valtree;
/// if that fails due to a valtree-incompatible type, indicate which type that is
/// by returning `Err(Left(bad_type))`.
#[inline] #[inline]
pub fn eval( pub fn eval_valtree(
self, self,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>, param_env: ParamEnv<'tcx>,
span: Span, span: Span,
) -> Result<(Ty<'tcx>, ValTree<'tcx>), ErrorHandled> { ) -> Result<(Ty<'tcx>, ValTree<'tcx>), Either<Ty<'tcx>, ErrorHandled>> {
assert!(!self.has_escaping_bound_vars(), "escaping vars in {self:?}"); assert!(!self.has_escaping_bound_vars(), "escaping vars in {self:?}");
match self.kind() { match self.kind() {
ConstKind::Unevaluated(unevaluated) => { ConstKind::Unevaluated(unevaluated) => {
@ -328,24 +331,45 @@ impl<'tcx> Const<'tcx> {
let (param_env, unevaluated) = unevaluated.prepare_for_eval(tcx, param_env); let (param_env, unevaluated) = unevaluated.prepare_for_eval(tcx, param_env);
// try to resolve e.g. associated constants to their definition on an impl, and then // try to resolve e.g. associated constants to their definition on an impl, and then
// evaluate the const. // evaluate the const.
let Ok(c) = tcx.const_eval_resolve_for_typeck(param_env, unevaluated, span)? else { match tcx.const_eval_resolve_for_typeck(param_env, unevaluated, span) {
Ok(Ok(c)) => {
Ok((tcx.type_of(unevaluated.def).instantiate(tcx, unevaluated.args), c))
}
Ok(Err(bad_ty)) => Err(Either::Left(bad_ty)),
Err(err) => Err(Either::Right(err.into())),
}
}
ConstKind::Value(ty, val) => Ok((ty, val)),
ConstKind::Error(g) => Err(Either::Right(g.into())),
ConstKind::Param(_)
| ConstKind::Infer(_)
| ConstKind::Bound(_, _)
| ConstKind::Placeholder(_)
| ConstKind::Expr(_) => Err(Either::Right(ErrorHandled::TooGeneric(span))),
}
}
/// Returns the evaluated constant
#[inline]
pub fn eval(
self,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
span: Span,
) -> Result<(Ty<'tcx>, ValTree<'tcx>), ErrorHandled> {
self.eval_valtree(tcx, param_env, span).map_err(|err| {
match err {
Either::Right(err) => err,
Either::Left(_bad_ty) => {
// This can happen when we run on ill-typed code. // This can happen when we run on ill-typed code.
let e = tcx.dcx().span_delayed_bug( let e = tcx.dcx().span_delayed_bug(
span, span,
"`ty::Const::eval` called on a non-valtree-compatible type", "`ty::Const::eval` called on a non-valtree-compatible type",
); );
return Err(e.into()); e.into()
};
Ok((tcx.type_of(unevaluated.def).instantiate(tcx, unevaluated.args), c))
} }
ConstKind::Value(ty, val) => Ok((ty, val)),
ConstKind::Error(g) => Err(g.into()),
ConstKind::Param(_)
| ConstKind::Infer(_)
| ConstKind::Bound(_, _)
| ConstKind::Placeholder(_)
| ConstKind::Expr(_) => Err(ErrorHandled::TooGeneric(span)),
} }
})
} }
/// Normalizes the constant to a value or an error if possible. /// Normalizes the constant to a value or an error if possible.

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
# tidy-alphabetical-start # tidy-alphabetical-start
either = "1.5.0"
itertools = "0.12" itertools = "0.12"
rustc_apfloat = "0.2.0" rustc_apfloat = "0.2.0"
rustc_arena = { path = "../rustc_arena" } rustc_arena = { path = "../rustc_arena" }

View file

@ -1,42 +1,46 @@
use either::Either;
use rustc_apfloat::Float; use rustc_apfloat::Float;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_index::Idx; use rustc_index::Idx;
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_infer::traits::Obligation; use rustc_infer::traits::Obligation;
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::span_bug; use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::thir::{FieldPat, Pat, PatKind}; use rustc_middle::thir::{FieldPat, Pat, PatKind};
use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; use rustc_middle::ty::{self, Ty, TyCtxt, ValTree};
use rustc_span::{ErrorGuaranteed, Span}; use rustc_span::{ErrorGuaranteed, Span};
use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_target::abi::{FieldIdx, VariantIdx};
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::{self, ObligationCause}; use rustc_trait_selection::traits::ObligationCause;
use tracing::{debug, instrument, trace}; use tracing::{debug, instrument, trace};
use std::cell::Cell; use std::cell::Cell;
use super::PatCtxt; use super::PatCtxt;
use crate::errors::{ use crate::errors::{
InvalidPattern, NaNPattern, PointerPattern, TypeNotPartialEq, TypeNotStructural, UnionPattern, ConstPatternDependsOnGenericParameter, CouldNotEvalConstPattern, InvalidPattern, NaNPattern,
UnsizedPattern, PointerPattern, TypeNotPartialEq, TypeNotStructural, UnionPattern, UnsizedPattern,
}; };
impl<'a, 'tcx> PatCtxt<'a, 'tcx> { impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
/// Converts an evaluated constant to a pattern (if possible). /// Converts a constant to a pattern (if possible).
/// This means aggregate values (like structs and enums) are converted /// This means aggregate values (like structs and enums) are converted
/// to a pattern that matches the value (as if you'd compared via structural equality). /// to a pattern that matches the value (as if you'd compared via structural equality).
/// ///
/// `cv` must be a valtree or a `mir::ConstValue`. /// Only type system constants are supported, as we are using valtrees
/// as an intermediate step. Unfortunately those don't carry a type
/// so we have to carry one ourselves.
#[instrument(level = "debug", skip(self), ret)] #[instrument(level = "debug", skip(self), ret)]
pub(super) fn const_to_pat( pub(super) fn const_to_pat(
&self, &self,
cv: mir::Const<'tcx>, c: ty::Const<'tcx>,
ty: Ty<'tcx>,
id: hir::HirId, id: hir::HirId,
span: Span, span: Span,
) -> Box<Pat<'tcx>> { ) -> Box<Pat<'tcx>> {
let infcx = self.tcx.infer_ctxt().build(); let infcx = self.tcx.infer_ctxt().build();
let mut convert = ConstToPat::new(self, id, span, infcx); let mut convert = ConstToPat::new(self, id, span, infcx);
convert.to_pat(cv) convert.to_pat(c, ty)
} }
} }
@ -56,12 +60,6 @@ struct ConstToPat<'tcx> {
treat_byte_string_as_slice: bool, treat_byte_string_as_slice: bool,
} }
/// This error type signals that we encountered a non-struct-eq situation.
/// We will fall back to calling `PartialEq::eq` on such patterns,
/// and exhaustiveness checking will consider them as matching nothing.
#[derive(Debug)]
struct FallbackToOpaqueConst;
impl<'tcx> ConstToPat<'tcx> { impl<'tcx> ConstToPat<'tcx> {
fn new( fn new(
pat_ctxt: &PatCtxt<'_, 'tcx>, pat_ctxt: &PatCtxt<'_, 'tcx>,
@ -91,116 +89,55 @@ impl<'tcx> ConstToPat<'tcx> {
ty.is_structural_eq_shallow(self.infcx.tcx) ty.is_structural_eq_shallow(self.infcx.tcx)
} }
fn to_pat(&mut self, cv: mir::Const<'tcx>) -> Box<Pat<'tcx>> { fn to_pat(&mut self, c: ty::Const<'tcx>, ty: Ty<'tcx>) -> Box<Pat<'tcx>> {
trace!(self.treat_byte_string_as_slice); trace!(self.treat_byte_string_as_slice);
// This method is just a wrapper handling a validity check; the heavy lifting is let pat_from_kind = |kind| Box::new(Pat { span: self.span, ty, kind });
// performed by the recursive `recur` method, which is not meant to be
// invoked except by this method.
//
// once indirect_structural_match is a full fledged error, this
// level of indirection can be eliminated
let have_valtree = // Get a valtree. If that fails, this const is definitely not valid for use as a pattern.
matches!(cv, mir::Const::Ty(_, c) if matches!(c.kind(), ty::ConstKind::Value(_, _))); let valtree = match c.eval_valtree(self.tcx(), self.param_env, self.span) {
let inlined_const_as_pat = match cv { Ok((_, valtree)) => valtree,
mir::Const::Ty(_, c) => match c.kind() { Err(Either::Right(e)) => {
ty::ConstKind::Param(_) let err = match e {
| ty::ConstKind::Infer(_) ErrorHandled::Reported(..) => {
| ty::ConstKind::Bound(_, _) // Let's tell the use where this failing const occurs.
| ty::ConstKind::Placeholder(_) self.tcx().dcx().emit_err(CouldNotEvalConstPattern { span: self.span })
| ty::ConstKind::Unevaluated(_)
| ty::ConstKind::Error(_)
| ty::ConstKind::Expr(_) => {
span_bug!(self.span, "unexpected const in `to_pat`: {:?}", c.kind())
} }
ty::ConstKind::Value(ty, valtree) => { ErrorHandled::TooGeneric(_) => self
self.recur(valtree, ty).unwrap_or_else(|_: FallbackToOpaqueConst| { .tcx()
Box::new(Pat { .dcx()
span: self.span, .emit_err(ConstPatternDependsOnGenericParameter { span: self.span }),
ty: cv.ty(),
kind: PatKind::Constant { value: cv },
})
})
}
},
mir::Const::Unevaluated(_, _) => {
span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}")
}
mir::Const::Val(_, _) => Box::new(Pat {
span: self.span,
ty: cv.ty(),
kind: PatKind::Constant { value: cv },
}),
}; };
return pat_from_kind(PatKind::Error(err));
}
Err(Either::Left(bad_ty)) => {
// The pattern cannot be turned into a valtree.
let e = match bad_ty.kind() {
ty::Adt(def, ..) => {
assert!(def.is_union());
self.tcx().dcx().emit_err(UnionPattern { span: self.span })
}
ty::FnPtr(..) | ty::RawPtr(..) => {
self.tcx().dcx().emit_err(PointerPattern { span: self.span })
}
_ => self
.tcx()
.dcx()
.emit_err(InvalidPattern { span: self.span, non_sm_ty: bad_ty }),
};
return pat_from_kind(PatKind::Error(e));
}
};
// Convert the valtree to a const.
let inlined_const_as_pat = self.valtree_to_pat(valtree, ty);
if self.saw_const_match_error.get().is_none() { if self.saw_const_match_error.get().is_none() {
// If we were able to successfully convert the const to some pat (possibly with some
// lints, but no errors), double-check that all types in the const implement
// `PartialEq`. Even if we have a valtree, we may have found something
// in there with non-structural-equality, meaning we match using `PartialEq`
// and we hence have to check if that impl exists.
// This is all messy but not worth cleaning up: at some point we'll emit
// a hard error when we don't have a valtree or when we find something in
// the valtree that is not structural; then this can all be made a lot simpler.
let structural = traits::search_for_structural_match_violation(self.tcx(), cv.ty());
debug!(
"search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
cv.ty(),
structural
);
if let Some(non_sm_ty) = structural {
if !self.type_has_partial_eq_impl(cv.ty()) {
// This is reachable and important even if we have a valtree: there might be
// non-structural things in a valtree, in which case we fall back to `PartialEq`
// comparison, in which case we better make sure the trait is implemented for
// each inner type (and not just for the surrounding type).
let e = if let ty::Adt(def, ..) = non_sm_ty.kind() {
if def.is_union() {
let err = UnionPattern { span: self.span };
self.tcx().dcx().emit_err(err)
} else {
// fatal avoids ICE from resolution of nonexistent method (rare case).
self.tcx()
.dcx()
.emit_fatal(TypeNotStructural { span: self.span, non_sm_ty })
}
} else {
let err = InvalidPattern { span: self.span, non_sm_ty };
self.tcx().dcx().emit_err(err)
};
// All branches above emitted an error. Don't print any more lints.
// We errored. Signal that in the pattern, so that follow up errors can be silenced.
let kind = PatKind::Error(e);
return Box::new(Pat { span: self.span, ty: cv.ty(), kind });
} else if !have_valtree {
// Not being structural prevented us from constructing a valtree,
// so this is definitely a case we want to reject.
let err = TypeNotStructural { span: self.span, non_sm_ty };
let e = self.tcx().dcx().emit_err(err);
let kind = PatKind::Error(e);
return Box::new(Pat { span: self.span, ty: cv.ty(), kind });
} else {
// This could be a violation in an inactive enum variant.
// Since we have a valtree, we trust that we have traversed the full valtree and
// complained about structural match violations there, so we don't
// have to check anything any more.
}
} else if !have_valtree {
// The only way valtree construction can fail without the structural match
// checker finding a violation is if there is a pointer somewhere.
let e = self.tcx().dcx().emit_err(PointerPattern { span: self.span });
let kind = PatKind::Error(e);
return Box::new(Pat { span: self.span, ty: cv.ty(), kind });
}
// Always check for `PartialEq` if we had no other errors yet. // Always check for `PartialEq` if we had no other errors yet.
if !self.type_has_partial_eq_impl(cv.ty()) { if !self.type_has_partial_eq_impl(ty) {
let err = TypeNotPartialEq { span: self.span, non_peq_ty: cv.ty() }; let err = TypeNotPartialEq { span: self.span, non_peq_ty: ty };
let e = self.tcx().dcx().emit_err(err); let e = self.tcx().dcx().emit_err(err);
let kind = PatKind::Error(e); let kind = PatKind::Error(e);
return Box::new(Pat { span: self.span, ty: cv.ty(), kind }); return Box::new(Pat { span: self.span, ty: ty, kind });
} }
} }
@ -243,36 +180,28 @@ impl<'tcx> ConstToPat<'tcx> {
fn field_pats( fn field_pats(
&self, &self,
vals: impl Iterator<Item = (ValTree<'tcx>, Ty<'tcx>)>, vals: impl Iterator<Item = (ValTree<'tcx>, Ty<'tcx>)>,
) -> Result<Vec<FieldPat<'tcx>>, FallbackToOpaqueConst> { ) -> Vec<FieldPat<'tcx>> {
vals.enumerate() vals.enumerate()
.map(|(idx, (val, ty))| { .map(|(idx, (val, ty))| {
let field = FieldIdx::new(idx); let field = FieldIdx::new(idx);
// Patterns can only use monomorphic types. // Patterns can only use monomorphic types.
let ty = self.tcx().normalize_erasing_regions(self.param_env, ty); let ty = self.tcx().normalize_erasing_regions(self.param_env, ty);
Ok(FieldPat { field, pattern: self.recur(val, ty)? }) FieldPat { field, pattern: self.valtree_to_pat(val, ty) }
}) })
.collect() .collect()
} }
// Recursive helper for `to_pat`; invoke that (instead of calling this directly). // Recursive helper for `to_pat`; invoke that (instead of calling this directly).
#[instrument(skip(self), level = "debug")] #[instrument(skip(self), level = "debug")]
fn recur( fn valtree_to_pat(&self, cv: ValTree<'tcx>, ty: Ty<'tcx>) -> Box<Pat<'tcx>> {
&self,
cv: ValTree<'tcx>,
ty: Ty<'tcx>,
) -> Result<Box<Pat<'tcx>>, FallbackToOpaqueConst> {
let span = self.span; let span = self.span;
let tcx = self.tcx(); let tcx = self.tcx();
let param_env = self.param_env; let param_env = self.param_env;
let kind = match ty.kind() { let kind = match ty.kind() {
ty::FnDef(..) => {
let e = tcx.dcx().emit_err(InvalidPattern { span, non_sm_ty: ty });
self.saw_const_match_error.set(Some(e));
// We errored. Signal that in the pattern, so that follow up errors can be silenced.
PatKind::Error(e)
}
ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => {
// Extremely important check for all ADTs! Make sure they opted-in to be used in
// patterns.
debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,); debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,);
let err = TypeNotStructural { span, non_sm_ty: ty }; let err = TypeNotStructural { span, non_sm_ty: ty };
let e = tcx.dcx().emit_err(err); let e = tcx.dcx().emit_err(err);
@ -294,13 +223,9 @@ impl<'tcx> ConstToPat<'tcx> {
.iter() .iter()
.map(|field| field.ty(self.tcx(), args)), .map(|field| field.ty(self.tcx(), args)),
), ),
)?, ),
} }
} }
ty::Tuple(fields) => PatKind::Leaf {
subpatterns: self
.field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter()))?,
},
ty::Adt(def, args) => { ty::Adt(def, args) => {
assert!(!def.is_union()); // Valtree construction would never succeed for unions. assert!(!def.is_union()); // Valtree construction would never succeed for unions.
PatKind::Leaf { PatKind::Leaf {
@ -311,15 +236,18 @@ impl<'tcx> ConstToPat<'tcx> {
.iter() .iter()
.map(|field| field.ty(self.tcx(), args)), .map(|field| field.ty(self.tcx(), args)),
), ),
)?, ),
} }
} }
ty::Tuple(fields) => PatKind::Leaf {
subpatterns: self.field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter())),
},
ty::Slice(elem_ty) => PatKind::Slice { ty::Slice(elem_ty) => PatKind::Slice {
prefix: cv prefix: cv
.unwrap_branch() .unwrap_branch()
.iter() .iter()
.map(|val| self.recur(*val, *elem_ty)) .map(|val| self.valtree_to_pat(*val, *elem_ty))
.collect::<Result<_, _>>()?, .collect(),
slice: None, slice: None,
suffix: Box::new([]), suffix: Box::new([]),
}, },
@ -327,8 +255,8 @@ impl<'tcx> ConstToPat<'tcx> {
prefix: cv prefix: cv
.unwrap_branch() .unwrap_branch()
.iter() .iter()
.map(|val| self.recur(*val, *elem_ty)) .map(|val| self.valtree_to_pat(*val, *elem_ty))
.collect::<Result<_, _>>()?, .collect(),
slice: None, slice: None,
suffix: Box::new([]), suffix: Box::new([]),
}, },
@ -345,6 +273,7 @@ impl<'tcx> ConstToPat<'tcx> {
if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() { if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() {
let err = UnsizedPattern { span, non_sm_ty: *pointee_ty }; let err = UnsizedPattern { span, non_sm_ty: *pointee_ty };
let e = tcx.dcx().emit_err(err); let e = tcx.dcx().emit_err(err);
self.saw_const_match_error.set(Some(e));
// We errored. Signal that in the pattern, so that follow up errors can be silenced. // We errored. Signal that in the pattern, so that follow up errors can be silenced.
PatKind::Error(e) PatKind::Error(e)
} else { } else {
@ -361,7 +290,7 @@ impl<'tcx> ConstToPat<'tcx> {
_ => *pointee_ty, _ => *pointee_ty,
}; };
// References have the same valtree representation as their pointee. // References have the same valtree representation as their pointee.
let subpattern = self.recur(cv, pointee_ty)?; let subpattern = self.valtree_to_pat(cv, pointee_ty);
PatKind::Deref { subpattern } PatKind::Deref { subpattern }
} }
} }
@ -379,7 +308,7 @@ impl<'tcx> ConstToPat<'tcx> {
// Also see <https://github.com/rust-lang/rfcs/pull/3535>. // Also see <https://github.com/rust-lang/rfcs/pull/3535>.
let e = tcx.dcx().emit_err(NaNPattern { span }); let e = tcx.dcx().emit_err(NaNPattern { span });
self.saw_const_match_error.set(Some(e)); self.saw_const_match_error.set(Some(e));
return Err(FallbackToOpaqueConst); PatKind::Error(e)
} else { } else {
PatKind::Constant { PatKind::Constant {
value: mir::Const::Ty(ty, ty::Const::new_value(tcx, cv, ty)), value: mir::Const::Ty(ty, ty::Const::new_value(tcx, cv, ty)),
@ -405,6 +334,6 @@ impl<'tcx> ConstToPat<'tcx> {
} }
}; };
Ok(Box::new(Pat { span, ty, kind })) Box::new(Pat { span, ty, kind })
} }
} }

View file

@ -14,8 +14,7 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator;
use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd}; use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd};
use rustc_index::Idx; use rustc_index::Idx;
use rustc_lint as lint; use rustc_lint as lint;
use rustc_middle::mir::interpret::{ErrorHandled, GlobalId, LitToConstError, LitToConstInput}; use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput};
use rustc_middle::mir::{self, Const};
use rustc_middle::thir::{ use rustc_middle::thir::{
Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary, Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary,
}; };
@ -575,24 +574,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
} }
}; };
let cid = GlobalId { instance, promoted: None }; let c = ty::Const::new_unevaluated(
// Prefer valtrees over opaque constants. self.tcx,
let const_value = self ty::UnevaluatedConst { def: instance.def_id(), args: instance.args },
.tcx );
.const_eval_global_id_for_typeck(param_env_reveal_all, cid, span)
.map(|val| match val {
Ok(valtree) => mir::Const::Ty(ty, ty::Const::new_value(self.tcx, valtree, ty)),
Err(_) => mir::Const::Val(
self.tcx
.const_eval_global_id(param_env_reveal_all, cid, span)
.expect("const_eval_global_id_for_typeck should have already failed"),
ty,
),
});
match const_value { let pattern = self.const_to_pat(c, ty, id, span);
Ok(const_) => {
let pattern = self.const_to_pat(const_, id, span);
if !is_associated_const { if !is_associated_const {
return pattern; return pattern;
@ -616,24 +603,12 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
variance: ty::Contravariant, variance: ty::Contravariant,
}, },
}, },
ty: const_.ty(), ty,
}) })
} else { } else {
pattern pattern
} }
} }
Err(ErrorHandled::TooGeneric(_)) => {
// While `Reported | Linted` cases will have diagnostics emitted already
// it is not true for TooGeneric case, so we need to give user more information.
let e = self.tcx.dcx().emit_err(ConstPatternDependsOnGenericParameter { span });
pat_from_kind(PatKind::Error(e))
}
Err(_) => {
let e = self.tcx.dcx().emit_err(CouldNotEvalConstPattern { span });
pat_from_kind(PatKind::Error(e))
}
}
}
/// Converts inline const patterns. /// Converts inline const patterns.
fn lower_inline_const( fn lower_inline_const(
@ -662,7 +637,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
}; };
if let Some(lit_input) = lit_input { if let Some(lit_input) = lit_input {
match tcx.at(expr.span).lit_to_const(lit_input) { match tcx.at(expr.span).lit_to_const(lit_input) {
Ok(c) => return self.const_to_pat(Const::Ty(ty, c), id, span).kind, Ok(c) => return self.const_to_pat(c, ty, id, span).kind,
// If an error occurred, ignore that it's a literal // If an error occurred, ignore that it's a literal
// and leave reporting the error up to const eval of // and leave reporting the error up to const eval of
// the unevaluated constant below. // the unevaluated constant below.
@ -675,32 +650,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
tcx.erase_regions(ty::GenericArgs::identity_for_item(tcx, typeck_root_def_id)); tcx.erase_regions(ty::GenericArgs::identity_for_item(tcx, typeck_root_def_id));
let args = ty::InlineConstArgs::new(tcx, ty::InlineConstArgsParts { parent_args, ty }).args; let args = ty::InlineConstArgs::new(tcx, ty::InlineConstArgsParts { parent_args, ty }).args;
let uneval = mir::UnevaluatedConst { def: def_id.to_def_id(), args, promoted: None };
debug_assert!(!args.has_free_regions()); debug_assert!(!args.has_free_regions());
let ct = ty::UnevaluatedConst { def: def_id.to_def_id(), args }; let ct = ty::UnevaluatedConst { def: def_id.to_def_id(), args };
// First try using a valtree in order to destructure the constant into a pattern. let subpattern = self.const_to_pat(ty::Const::new_unevaluated(self.tcx, ct), ty, id, span);
// FIXME: replace "try to do a thing, then fall back to another thing"
// but something more principled, like a trait query checking whether this can be turned into a valtree.
if let Ok(Ok(valtree)) = self.tcx.const_eval_resolve_for_typeck(self.param_env, ct, span) {
let subpattern = self.const_to_pat(
Const::Ty(ty, ty::Const::new_value(self.tcx, valtree, ty)),
id,
span,
);
PatKind::InlineConstant { subpattern, def: def_id } PatKind::InlineConstant { subpattern, def: def_id }
} else {
// If that fails, convert it to an opaque constant pattern.
match tcx.const_eval_resolve(self.param_env, uneval, span) {
Ok(val) => self.const_to_pat(mir::Const::Val(val, ty), id, span).kind,
Err(ErrorHandled::TooGeneric(_)) => {
// If we land here it means the const can't be evaluated because it's `TooGeneric`.
let e = self.tcx.dcx().emit_err(ConstPatternDependsOnGenericParameter { span });
PatKind::Error(e)
}
Err(ErrorHandled::Reported(err, ..)) => PatKind::Error(err.into()),
}
}
} }
/// Converts literals, paths and negation of literals to patterns. /// Converts literals, paths and negation of literals to patterns.
@ -728,9 +682,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
let ct_ty = self.typeck_results.expr_ty(expr); let ct_ty = self.typeck_results.expr_ty(expr);
let lit_input = LitToConstInput { lit: &lit.node, ty: ct_ty, neg }; let lit_input = LitToConstInput { lit: &lit.node, ty: ct_ty, neg };
match self.tcx.at(expr.span).lit_to_const(lit_input) { match self.tcx.at(expr.span).lit_to_const(lit_input) {
Ok(constant) => { Ok(constant) => self.const_to_pat(constant, ct_ty, expr.hir_id, lit.span).kind,
self.const_to_pat(Const::Ty(ct_ty, constant), expr.hir_id, lit.span).kind
}
Err(LitToConstError::Reported(e)) => PatKind::Error(e), Err(LitToConstError::Reported(e)) => PatKind::Error(e),
Err(LitToConstError::TypeError) => bug!("lower_lit: had type error"), Err(LitToConstError::TypeError) => bug!("lower_lit: had type error"),
} }

View file

@ -16,7 +16,6 @@ pub mod query;
#[allow(hidden_glob_reexports)] #[allow(hidden_glob_reexports)]
mod select; mod select;
mod specialize; mod specialize;
mod structural_match;
mod structural_normalize; mod structural_normalize;
#[allow(hidden_glob_reexports)] #[allow(hidden_glob_reexports)]
mod util; mod util;
@ -60,7 +59,6 @@ pub use self::specialize::specialization_graph::FutureCompatOverlapErrorKind;
pub use self::specialize::{ pub use self::specialize::{
specialization_graph, translate_args, translate_args_with_cause, OverlapError, specialization_graph, translate_args, translate_args_with_cause, OverlapError,
}; };
pub use self::structural_match::search_for_structural_match_violation;
pub use self::structural_normalize::StructurallyNormalizeExt; pub use self::structural_normalize::StructurallyNormalizeExt;
pub use self::util::elaborate; pub use self::util::elaborate;
pub use self::util::{expand_trait_aliases, TraitAliasExpander, TraitAliasExpansionInfo}; pub use self::util::{expand_trait_aliases, TraitAliasExpander, TraitAliasExpansionInfo};

View file

@ -1,173 +0,0 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_middle::bug;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
use std::ops::ControlFlow;
/// This method traverses the structure of `ty`, trying to find an
/// instance of an ADT (i.e. struct or enum) that doesn't implement
/// the structural-match traits, or a generic type parameter
/// (which cannot be determined to be structural-match).
///
/// The "structure of a type" includes all components that would be
/// considered when doing a pattern match on a constant of that
/// type.
///
/// * This means this method descends into fields of structs/enums,
/// and also descends into the inner type `T` of `&T` and `&mut T`
///
/// * The traversal doesn't dereference unsafe pointers (`*const T`,
/// `*mut T`), and it does not visit the type arguments of an
/// instantiated generic like `PhantomData<T>`.
///
/// The reason we do this search is Rust currently require all ADTs
/// reachable from a constant's type to implement the
/// structural-match traits, which essentially say that
/// the implementation of `PartialEq::eq` behaves *equivalently* to a
/// comparison against the unfolded structure.
///
/// For more background on why Rust has this requirement, and issues
/// that arose when the requirement was not enforced completely, see
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
pub fn search_for_structural_match_violation<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
) -> Option<Ty<'tcx>> {
ty.visit_with(&mut Search { tcx, seen: FxHashSet::default() }).break_value()
}
/// This implements the traversal over the structure of a given type to try to
/// find instances of ADTs (specifically structs or enums) that do not implement
/// `StructuralPartialEq`.
struct Search<'tcx> {
tcx: TyCtxt<'tcx>,
/// Tracks ADTs previously encountered during search, so that
/// we will not recur on them again.
seen: FxHashSet<hir::def_id::DefId>,
}
impl<'tcx> Search<'tcx> {
fn type_marked_structural(&self, adt_ty: Ty<'tcx>) -> bool {
adt_ty.is_structural_eq_shallow(self.tcx)
}
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for Search<'tcx> {
type Result = ControlFlow<Ty<'tcx>>;
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
debug!("Search visiting ty: {:?}", ty);
let (adt_def, args) = match *ty.kind() {
ty::Adt(adt_def, args) => (adt_def, args),
ty::Param(_) => {
return ControlFlow::Break(ty);
}
ty::Dynamic(..) => {
return ControlFlow::Break(ty);
}
ty::Foreign(_) => {
return ControlFlow::Break(ty);
}
ty::Alias(..) => {
return ControlFlow::Break(ty);
}
ty::Closure(..) => {
return ControlFlow::Break(ty);
}
ty::CoroutineClosure(..) => {
return ControlFlow::Break(ty);
}
ty::Coroutine(..) | ty::CoroutineWitness(..) => {
return ControlFlow::Break(ty);
}
ty::FnDef(..) => {
// Types of formals and return in `fn(_) -> _` are also irrelevant;
// so we do not recur into them via `super_visit_with`
return ControlFlow::Continue(());
}
ty::Array(_, n)
if { n.try_eval_target_usize(self.tcx, ty::ParamEnv::reveal_all()) == Some(0) } =>
{
// rust-lang/rust#62336: ignore type of contents
// for empty array.
return ControlFlow::Continue(());
}
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Str | ty::Never => {
// These primitive types are always structural match.
//
// `Never` is kind of special here, but as it is not inhabitable, this should be fine.
return ControlFlow::Continue(());
}
ty::FnPtr(..) => {
return ControlFlow::Continue(());
}
ty::RawPtr(..) => {
// structural-match ignores substructure of
// `*const _`/`*mut _`, so skip `super_visit_with`.
//
// For example, if you have:
// ```
// struct NonStructural;
// #[derive(PartialEq, Eq)]
// struct T(*const NonStructural);
// const C: T = T(std::ptr::null());
// ```
//
// Even though `NonStructural` does not implement `PartialEq`,
// structural equality on `T` does not recur into the raw
// pointer. Therefore, one can still use `C` in a pattern.
return ControlFlow::Continue(());
}
ty::Float(_) => {
return ControlFlow::Continue(());
}
ty::Pat(..) | ty::Array(..) | ty::Slice(_) | ty::Ref(..) | ty::Tuple(..) => {
// First check all contained types and then tell the caller to continue searching.
return ty.super_visit_with(self);
}
ty::Infer(_) | ty::Placeholder(_) | ty::Bound(..) => {
bug!("unexpected type during structural-match checking: {:?}", ty);
}
ty::Error(_) => {
// We still want to check other types after encountering an error,
// as this may still emit relevant errors.
return ControlFlow::Continue(());
}
};
if !self.seen.insert(adt_def.did()) {
debug!("Search already seen adt_def: {:?}", adt_def);
return ControlFlow::Continue(());
}
if !self.type_marked_structural(ty) {
debug!("Search found ty: {:?}", ty);
return ControlFlow::Break(ty);
}
// structural-match does not care about the
// instantiation of the generics in an ADT (it
// instead looks directly at its fields outside
// this match), so we skip super_visit_with.
//
// (Must not recur on args for `PhantomData<T>` cf
// rust-lang/rust#55028 and rust-lang/rust#55837; but also
// want to skip args when only uses of generic are
// behind unsafe pointers `*const T`/`*mut T`.)
// even though we skip super_visit_with, we must recur on
// fields of ADT.
let tcx = self.tcx;
adt_def.all_fields().map(|field| field.ty(tcx, args)).try_for_each(|field_ty| {
let ty = self.tcx.normalize_erasing_regions(ty::ParamEnv::empty(), field_ty);
debug!("structural-match ADT: field_ty={:?}, ty={:?}", field_ty, ty);
ty.visit_with(self)
})
}
}

View file

@ -23,9 +23,20 @@ const BAR_BAZ: Foo = if 42 == 42 {
Foo::Qux(CustomEq) // dead arm Foo::Qux(CustomEq) // dead arm
}; };
const EMPTY: &[CustomEq] = &[];
fn main() { fn main() {
// BAR_BAZ itself is fine but the enum has other variants
// that are non-structural. Still, this should be accepted.
match Foo::Qux(CustomEq) { match Foo::Qux(CustomEq) {
BAR_BAZ => panic!(), BAR_BAZ => panic!(),
_ => {} _ => {}
} }
// Similarly, an empty slice of a type that is non-structural
// is accepted.
match &[CustomEq] as &[CustomEq] {
EMPTY => panic!(),
_ => {},
}
} }

View file

@ -26,7 +26,7 @@ fn main() {
match None { match None {
NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"), NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
//~^ ERROR must be annotated with `#[derive(PartialEq)]` //~^ ERROR must implement `PartialEq`
_ => panic!("whoops"), _ => panic!("whoops"),
} }
} }

View file

@ -1,11 +1,8 @@
error: to use a constant of type `NoPartialEq` in a pattern, `NoPartialEq` must be annotated with `#[derive(PartialEq)]` error: to use a constant of type `Option<NoPartialEq>` in a pattern, the type must implement `PartialEq`
--> $DIR/reject_non_partial_eq.rs:28:9 --> $DIR/reject_non_partial_eq.rs:28:9
| |
LL | NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"), LL | NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
|
= note: the traits must be derived, manual `impl`s are not sufficient
= note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html for details
error: aborting due to 1 previous error error: aborting due to 1 previous error

View file

@ -4,5 +4,6 @@ fn main() {
match () { match () {
const { (|| {})() } => {} const { (|| {})() } => {}
//~^ ERROR cannot call non-const closure in constants //~^ ERROR cannot call non-const closure in constants
//~| ERROR could not evaluate constant pattern
} }
} }

View file

@ -11,6 +11,12 @@ help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
LL + #![feature(const_trait_impl)] LL + #![feature(const_trait_impl)]
| |
error: aborting due to 1 previous error error: could not evaluate constant pattern
--> $DIR/invalid-inline-const-in-match-arm.rs:5:9
|
LL | const { (|| {})() } => {}
| ^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0015`. For more information about this error, try `rustc --explain E0015`.

View file

@ -13,7 +13,7 @@ const A: &[B] = &[];
pub fn main() { pub fn main() {
match &[][..] { match &[][..] {
A => (), A => (),
//~^ ERROR must be annotated with `#[derive(PartialEq)]` //~^ ERROR must implement `PartialEq`
_ => (), _ => (),
} }
} }

View file

@ -1,11 +1,8 @@
error: to use a constant of type `B` in a pattern, `B` must be annotated with `#[derive(PartialEq)]` error: to use a constant of type `&[B]` in a pattern, the type must implement `PartialEq`
--> $DIR/issue-61188-match-slice-forbidden-without-eq.rs:15:9 --> $DIR/issue-61188-match-slice-forbidden-without-eq.rs:15:9
| |
LL | A => (), LL | A => (),
| ^ | ^
|
= note: the traits must be derived, manual `impl`s are not sufficient
= note: see https://doc.rust-lang.org/stable/std/marker/trait.StructuralPartialEq.html for details
error: aborting due to 1 previous error error: aborting due to 1 previous error

View file

@ -28,13 +28,9 @@ fn main() {
// Also cover range patterns // Also cover range patterns
match x { match x {
NAN..=1.0 => {}, //~ ERROR cannot use NaN in patterns NAN..=1.0 => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
-1.0..=NAN => {}, //~ ERROR cannot use NaN in patterns -1.0..=NAN => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
NAN.. => {}, //~ ERROR cannot use NaN in patterns NAN.. => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than or equal to upper
..NAN => {}, //~ ERROR cannot use NaN in patterns ..NAN => {}, //~ ERROR cannot use NaN in patterns
//~^ ERROR lower range bound must be less than upper
_ => {}, _ => {},
}; };
} }

View file

@ -34,14 +34,8 @@ LL | NAN..=1.0 => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match = note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead = help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper
--> $DIR/issue-6804-nan-match.rs:30:9
|
LL | NAN..=1.0 => {},
| ^^^^^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:32:16 --> $DIR/issue-6804-nan-match.rs:31:16
| |
LL | -1.0..=NAN => {}, LL | -1.0..=NAN => {},
| ^^^ | ^^^
@ -49,29 +43,17 @@ LL | -1.0..=NAN => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match = note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead = help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:32:9 --> $DIR/issue-6804-nan-match.rs:32:9
| |
LL | -1.0..=NAN => {},
| ^^^^^^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:34:9
|
LL | NAN.. => {}, LL | NAN.. => {},
| ^^^ | ^^^
| |
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match = note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead = help: try using the `is_nan` method instead
error[E0030]: lower range bound must be less than or equal to upper
--> $DIR/issue-6804-nan-match.rs:34:9
|
LL | NAN.. => {},
| ^^^^^ lower bound larger than upper bound
error: cannot use NaN in patterns error: cannot use NaN in patterns
--> $DIR/issue-6804-nan-match.rs:36:11 --> $DIR/issue-6804-nan-match.rs:33:11
| |
LL | ..NAN => {}, LL | ..NAN => {},
| ^^^ | ^^^
@ -79,13 +61,5 @@ LL | ..NAN => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match = note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead = help: try using the `is_nan` method instead
error[E0579]: lower range bound must be less than upper error: aborting due to 7 previous errors
--> $DIR/issue-6804-nan-match.rs:36:9
|
LL | ..NAN => {},
| ^^^^^
error: aborting due to 11 previous errors
Some errors have detailed explanations: E0030, E0579.
For more information about an error, try `rustc --explain E0030`.