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"
version = "0.0.0"
dependencies = [
"either",
"itertools",
"rustc_apfloat",
"rustc_arena",

View File

@ -1,6 +1,7 @@
use crate::middle::resolve_bound_vars as rbv;
use crate::mir::interpret::{ErrorHandled, LitToConstInput, Scalar};
use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
use either::Either;
use rustc_data_structures::intern::Interned;
use rustc_error_messages::MultiSpan;
use rustc_hir as hir;
@ -312,14 +313,16 @@ pub fn from_target_usize(tcx: TyCtxt<'tcx>, n: u64) -> Self {
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]
pub fn eval(
pub fn eval_valtree(
self,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
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:?}");
match self.kind() {
ConstKind::Unevaluated(unevaluated) => {
@ -328,24 +331,45 @@ pub fn eval(
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
// 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.
let e = tcx.dcx().span_delayed_bug(
span,
"`ty::Const::eval` called on a non-valtree-compatible type",
);
return Err(e.into());
};
Ok((tcx.type_of(unevaluated.def).instantiate(tcx, unevaluated.args), c))
e.into()
}
}
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.

View File

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

View File

@ -1,42 +1,46 @@
use either::Either;
use rustc_apfloat::Float;
use rustc_hir as hir;
use rustc_index::Idx;
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_infer::traits::Obligation;
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::ty::{self, Ty, TyCtxt, ValTree};
use rustc_span::{ErrorGuaranteed, Span};
use rustc_target::abi::{FieldIdx, VariantIdx};
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 std::cell::Cell;
use super::PatCtxt;
use crate::errors::{
InvalidPattern, NaNPattern, PointerPattern, TypeNotPartialEq, TypeNotStructural, UnionPattern,
UnsizedPattern,
ConstPatternDependsOnGenericParameter, CouldNotEvalConstPattern, InvalidPattern, NaNPattern,
PointerPattern, TypeNotPartialEq, TypeNotStructural, UnionPattern, UnsizedPattern,
};
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
/// 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)]
pub(super) fn const_to_pat(
&self,
cv: mir::Const<'tcx>,
c: ty::Const<'tcx>,
ty: Ty<'tcx>,
id: hir::HirId,
span: Span,
) -> Box<Pat<'tcx>> {
let infcx = self.tcx.infer_ctxt().build();
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,
}
/// 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> {
fn new(
pat_ctxt: &PatCtxt<'_, 'tcx>,
@ -91,116 +89,55 @@ fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
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);
// This method is just a wrapper handling a validity check; the heavy lifting is
// 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 pat_from_kind = |kind| Box::new(Pat { span: self.span, ty, kind });
let have_valtree =
matches!(cv, mir::Const::Ty(_, c) if matches!(c.kind(), ty::ConstKind::Value(_, _)));
let inlined_const_as_pat = match cv {
mir::Const::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(ty, valtree) => {
self.recur(valtree, ty).unwrap_or_else(|_: FallbackToOpaqueConst| {
Box::new(Pat {
span: self.span,
ty: cv.ty(),
kind: PatKind::Constant { value: cv },
})
})
}
},
mir::Const::Unevaluated(_, _) => {
span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}")
// Get a valtree. If that fails, this const is definitely not valid for use as a pattern.
let valtree = match c.eval_valtree(self.tcx(), self.param_env, self.span) {
Ok((_, valtree)) => valtree,
Err(Either::Right(e)) => {
let err = match e {
ErrorHandled::Reported(..) => {
// Let's tell the use where this failing const occurs.
self.tcx().dcx().emit_err(CouldNotEvalConstPattern { span: self.span })
}
ErrorHandled::TooGeneric(_) => self
.tcx()
.dcx()
.emit_err(ConstPatternDependsOnGenericParameter { span: self.span }),
};
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));
}
mir::Const::Val(_, _) => Box::new(Pat {
span: self.span,
ty: cv.ty(),
kind: PatKind::Constant { value: cv },
}),
};
// 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 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.
if !self.type_has_partial_eq_impl(cv.ty()) {
let err = TypeNotPartialEq { span: self.span, non_peq_ty: cv.ty() };
if !self.type_has_partial_eq_impl(ty) {
let err = TypeNotPartialEq { span: self.span, non_peq_ty: 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 });
return Box::new(Pat { span: self.span, ty: ty, kind });
}
}
@ -243,36 +180,28 @@ fn type_has_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool {
fn field_pats(
&self,
vals: impl Iterator<Item = (ValTree<'tcx>, Ty<'tcx>)>,
) -> Result<Vec<FieldPat<'tcx>>, FallbackToOpaqueConst> {
) -> Vec<FieldPat<'tcx>> {
vals.enumerate()
.map(|(idx, (val, ty))| {
let field = FieldIdx::new(idx);
// 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)? })
FieldPat { field, pattern: self.valtree_to_pat(val, ty) }
})
.collect()
}
// Recursive helper for `to_pat`; invoke that (instead of calling this directly).
#[instrument(skip(self), level = "debug")]
fn recur(
&self,
cv: ValTree<'tcx>,
ty: Ty<'tcx>,
) -> Result<Box<Pat<'tcx>>, FallbackToOpaqueConst> {
fn valtree_to_pat(&self, cv: ValTree<'tcx>, ty: Ty<'tcx>) -> Box<Pat<'tcx>> {
let span = self.span;
let tcx = self.tcx();
let param_env = self.param_env;
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) => {
// 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,);
let err = TypeNotStructural { span, non_sm_ty: ty };
let e = tcx.dcx().emit_err(err);
@ -294,13 +223,9 @@ fn recur(
.iter()
.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) => {
assert!(!def.is_union()); // Valtree construction would never succeed for unions.
PatKind::Leaf {
@ -311,15 +236,18 @@ fn recur(
.iter()
.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 {
prefix: cv
.unwrap_branch()
.iter()
.map(|val| self.recur(*val, *elem_ty))
.collect::<Result<_, _>>()?,
.map(|val| self.valtree_to_pat(*val, *elem_ty))
.collect(),
slice: None,
suffix: Box::new([]),
},
@ -327,8 +255,8 @@ fn recur(
prefix: cv
.unwrap_branch()
.iter()
.map(|val| self.recur(*val, *elem_ty))
.collect::<Result<_, _>>()?,
.map(|val| self.valtree_to_pat(*val, *elem_ty))
.collect(),
slice: None,
suffix: Box::new([]),
},
@ -345,6 +273,7 @@ fn recur(
if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() {
let err = UnsizedPattern { span, non_sm_ty: *pointee_ty };
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.
PatKind::Error(e)
} else {
@ -361,7 +290,7 @@ fn recur(
_ => *pointee_ty,
};
// 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 }
}
}
@ -379,7 +308,7 @@ fn recur(
// Also see <https://github.com/rust-lang/rfcs/pull/3535>.
let e = tcx.dcx().emit_err(NaNPattern { span });
self.saw_const_match_error.set(Some(e));
return Err(FallbackToOpaqueConst);
PatKind::Error(e)
} else {
PatKind::Constant {
value: mir::Const::Ty(ty, ty::Const::new_value(tcx, cv, ty)),
@ -405,6 +334,6 @@ fn recur(
}
};
Ok(Box::new(Pat { span, ty, kind }))
Box::new(Pat { span, ty, kind })
}
}

View File

@ -14,8 +14,7 @@
use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd};
use rustc_index::Idx;
use rustc_lint as lint;
use rustc_middle::mir::interpret::{ErrorHandled, GlobalId, LitToConstError, LitToConstInput};
use rustc_middle::mir::{self, Const};
use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput};
use rustc_middle::thir::{
Ascription, FieldPat, LocalVarId, Pat, PatKind, PatRange, PatRangeBoundary,
};
@ -575,63 +574,39 @@ fn lower_path(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, span: Span) ->
}
};
let cid = GlobalId { instance, promoted: None };
// Prefer valtrees over opaque constants.
let const_value = self
.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,
),
});
let c = ty::Const::new_unevaluated(
self.tcx,
ty::UnevaluatedConst { def: instance.def_id(), args: instance.args },
);
match const_value {
Ok(const_) => {
let pattern = self.const_to_pat(const_, id, span);
let pattern = self.const_to_pat(c, ty, id, span);
if !is_associated_const {
return pattern;
}
if !is_associated_const {
return pattern;
}
let user_provided_types = self.typeck_results().user_provided_types();
if let Some(&user_ty) = user_provided_types.get(id) {
let annotation = CanonicalUserTypeAnnotation {
user_ty: Box::new(user_ty),
span,
inferred_ty: self.typeck_results().node_type(id),
};
Box::new(Pat {
span,
kind: PatKind::AscribeUserType {
subpattern: pattern,
ascription: Ascription {
annotation,
// Note that use `Contravariant` here. See the
// `variance` field documentation for details.
variance: ty::Contravariant,
},
},
ty: const_.ty(),
})
} else {
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))
}
let user_provided_types = self.typeck_results().user_provided_types();
if let Some(&user_ty) = user_provided_types.get(id) {
let annotation = CanonicalUserTypeAnnotation {
user_ty: Box::new(user_ty),
span,
inferred_ty: self.typeck_results().node_type(id),
};
Box::new(Pat {
span,
kind: PatKind::AscribeUserType {
subpattern: pattern,
ascription: Ascription {
annotation,
// Note that use `Contravariant` here. See the
// `variance` field documentation for details.
variance: ty::Contravariant,
},
},
ty,
})
} else {
pattern
}
}
@ -662,7 +637,7 @@ fn lower_inline_const(
};
if let Some(lit_input) = 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
// and leave reporting the error up to const eval of
// the unevaluated constant below.
@ -675,32 +650,11 @@ fn lower_inline_const(
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 uneval = mir::UnevaluatedConst { def: def_id.to_def_id(), args, promoted: None };
debug_assert!(!args.has_free_regions());
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.
// 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 }
} 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()),
}
}
let subpattern = self.const_to_pat(ty::Const::new_unevaluated(self.tcx, ct), ty, id, span);
PatKind::InlineConstant { subpattern, def: def_id }
}
/// Converts literals, paths and negation of literals to patterns.
@ -728,9 +682,7 @@ fn lower_lit(&mut self, expr: &'tcx hir::Expr<'tcx>) -> PatKind<'tcx> {
let ct_ty = self.typeck_results.expr_ty(expr);
let lit_input = LitToConstInput { lit: &lit.node, ty: ct_ty, neg };
match self.tcx.at(expr.span).lit_to_const(lit_input) {
Ok(constant) => {
self.const_to_pat(Const::Ty(ct_ty, constant), expr.hir_id, lit.span).kind
}
Ok(constant) => self.const_to_pat(constant, ct_ty, expr.hir_id, lit.span).kind,
Err(LitToConstError::Reported(e)) => PatKind::Error(e),
Err(LitToConstError::TypeError) => bug!("lower_lit: had type error"),
}

View File

@ -16,7 +16,6 @@
#[allow(hidden_glob_reexports)]
mod select;
mod specialize;
mod structural_match;
mod structural_normalize;
#[allow(hidden_glob_reexports)]
mod util;
@ -60,7 +59,6 @@
pub use self::specialize::{
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::util::elaborate;
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 @@ enum Foo {
Foo::Qux(CustomEq) // dead arm
};
const EMPTY: &[CustomEq] = &[];
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) {
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 {
NO_PARTIAL_EQ_NONE => println!("NO_PARTIAL_EQ_NONE"),
//~^ ERROR must be annotated with `#[derive(PartialEq)]`
//~^ ERROR must implement `PartialEq`
_ => 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
|
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

View File

@ -4,5 +4,6 @@ fn main() {
match () {
const { (|| {})() } => {}
//~^ 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)]
|
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`.

View File

@ -13,7 +13,7 @@
pub fn main() {
match &[][..] {
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
|
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

View File

@ -28,13 +28,9 @@ fn main() {
// Also cover range patterns
match x {
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
//~^ ERROR lower range bound must be less than or equal to upper
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
//~^ 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
= 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
--> $DIR/issue-6804-nan-match.rs:32:16
--> $DIR/issue-6804-nan-match.rs:31:16
|
LL | -1.0..=NAN => {},
| ^^^
@ -49,14 +43,8 @@ LL | -1.0..=NAN => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= 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: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
--> $DIR/issue-6804-nan-match.rs:32:9
|
LL | NAN.. => {},
| ^^^
@ -64,14 +52,8 @@ LL | NAN.. => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= 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
--> $DIR/issue-6804-nan-match.rs:36:11
--> $DIR/issue-6804-nan-match.rs:33:11
|
LL | ..NAN => {},
| ^^^
@ -79,13 +61,5 @@ LL | ..NAN => {},
= note: NaNs compare inequal to everything, even themselves, so this pattern would never match
= help: try using the `is_nan` method instead
error[E0579]: lower range bound must be less than upper
--> $DIR/issue-6804-nan-match.rs:36:9
|
LL | ..NAN => {},
| ^^^^^
error: aborting due to 7 previous errors
error: aborting due to 11 previous errors
Some errors have detailed explanations: E0030, E0579.
For more information about an error, try `rustc --explain E0030`.