Auto merge of #99798 - JulianKnodt:ac1, r=BoxyUwU

Add `ConstKind::Expr`

Starting to implement `ty::ConstKind::Abstract`, most of the match cases are stubbed out, some I was unsure what to add, others I didn't want to add until a more complete implementation was ready.

r? `@lcnr`
This commit is contained in:
bors 2022-11-25 22:56:59 +00:00
commit aff003becd
40 changed files with 812 additions and 822 deletions

View File

@ -561,6 +561,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
ty::ConstKind::Param(_) | ty::ConstKind::Placeholder(..) => {
throw_inval!(TooGeneric)
}
// FIXME(generic_const_exprs): `ConstKind::Expr` should be able to be evaluated
ty::ConstKind::Expr(_) => throw_inval!(TooGeneric),
ty::ConstKind::Error(reported) => {
throw_inval!(AlreadyReported(reported))
}

View File

@ -248,6 +248,7 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TypeFreshener<'a, 'tcx> {
ty::ConstKind::Param(_)
| ty::ConstKind::Value(_)
| ty::ConstKind::Unevaluated(..)
| ty::ConstKind::Expr(..)
| ty::ConstKind::Error(_) => ct.super_fold_with(self),
}
}

View File

@ -23,7 +23,6 @@ use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKin
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult};
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::traits::select;
use rustc_middle::ty::abstract_const::{AbstractConst, FailureKind};
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::fold::BoundVarReplacerDelegate;
use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
@ -713,32 +712,6 @@ impl<'tcx> InferCtxt<'tcx> {
TypeErrCtxt { infcx: self, typeck_results: None, fallback_has_occurred: false }
}
/// calls `tcx.try_unify_abstract_consts` after
/// canonicalizing the consts.
#[instrument(skip(self), level = "debug")]
pub fn try_unify_abstract_consts(
&self,
a: ty::UnevaluatedConst<'tcx>,
b: ty::UnevaluatedConst<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> bool {
// Reject any attempt to unify two unevaluated constants that contain inference
// variables, since inference variables in queries lead to ICEs.
if a.substs.has_non_region_infer()
|| b.substs.has_non_region_infer()
|| param_env.has_non_region_infer()
{
debug!("a or b or param_env contain infer vars in its substs -> cannot unify");
return false;
}
let param_env_and = param_env.and((a, b));
let erased = self.tcx.erase_regions(param_env_and);
debug!("after erase_regions: {:?}", erased);
self.tcx.try_unify_abstract_consts(erased)
}
pub fn is_in_snapshot(&self) -> bool {
self.in_snapshot.get()
}
@ -1646,26 +1619,25 @@ impl<'tcx> InferCtxt<'tcx> {
// Postpone the evaluation of constants whose substs depend on inference
// variables
let tcx = self.tcx;
if substs.has_non_region_infer() {
let ac = AbstractConst::new(self.tcx, unevaluated);
match ac {
Ok(None) => {
substs = InternalSubsts::identity_for_item(self.tcx, unevaluated.def.did);
param_env = self.tcx.param_env(unevaluated.def.did);
if let Some(ct) = tcx.bound_abstract_const(unevaluated.def)? {
let ct = tcx.expand_abstract_consts(ct.subst(tcx, substs));
if let Err(e) = ct.error_reported() {
return Err(ErrorHandled::Reported(e));
} else if ct.has_non_region_infer() || ct.has_non_region_param() {
return Err(ErrorHandled::TooGeneric);
} else {
substs = replace_param_and_infer_substs_with_placeholder(tcx, substs);
}
Ok(Some(ct)) => {
if ct.unify_failure_kind(self.tcx) == FailureKind::Concrete {
substs = replace_param_and_infer_substs_with_placeholder(self.tcx, substs);
} else {
return Err(ErrorHandled::TooGeneric);
}
}
Err(guar) => return Err(ErrorHandled::Reported(guar)),
} else {
substs = InternalSubsts::identity_for_item(tcx, unevaluated.def.did);
param_env = tcx.param_env(unevaluated.def.did);
}
}
let param_env_erased = self.tcx.erase_regions(param_env);
let substs_erased = self.tcx.erase_regions(substs);
let param_env_erased = tcx.erase_regions(param_env);
let substs_erased = tcx.erase_regions(substs);
debug!(?param_env_erased);
debug!(?substs_erased);
@ -1673,7 +1645,7 @@ impl<'tcx> InferCtxt<'tcx> {
// The return value is the evaluated value which doesn't contain any reference to inference
// variables, thus we don't need to substitute back the original values.
self.tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
tcx.const_eval_resolve_for_typeck(param_env_erased, unevaluated, span)
}
/// `ty_or_const_infer_var_changed` is equivalent to one of these two:

View File

@ -644,12 +644,6 @@ impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for Symbol {
}
}
impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for &'tcx [ty::abstract_const::Node<'tcx>] {
fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Self {
ty::codec::RefDecodable::decode(d)
}
}
impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for &'tcx [(ty::Predicate<'tcx>, Span)] {
fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Self {
ty::codec::RefDecodable::decode(d)

View File

@ -366,7 +366,7 @@ define_tables! {
mir_for_ctfe: Table<DefIndex, LazyValue<mir::Body<'static>>>,
promoted_mir: Table<DefIndex, LazyValue<IndexVec<mir::Promoted, mir::Body<'static>>>>,
// FIXME(compiler-errors): Why isn't this a LazyArray?
thir_abstract_const: Table<DefIndex, LazyValue<&'static [ty::abstract_const::Node<'static>]>>,
thir_abstract_const: Table<DefIndex, LazyValue<ty::Const<'static>>>,
impl_parent: Table<DefIndex, RawDefId>,
impl_polarity: Table<DefIndex, ty::ImplPolarity>,
constness: Table<DefIndex, hir::Constness>,

View File

@ -476,6 +476,7 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> {
// These variants shouldn't exist in the MIR.
ty::ConstKind::Placeholder(_)
| ty::ConstKind::Infer(_)
| ty::ConstKind::Expr(_)
| ty::ConstKind::Bound(..) => bug!("unexpected MIR constant: {:?}", literal),
},
ConstantKind::Unevaluated(uv, _) => {

View File

@ -1185,7 +1185,8 @@ pub enum NullOp {
AlignOf,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(HashStable, TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)]
pub enum UnOp {
/// The `!` operator for logical inversion
Not,
@ -1193,7 +1194,8 @@ pub enum UnOp {
Neg,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]
#[derive(TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
pub enum BinOp {
/// The `+` operator (addition)
Add,

View File

@ -16,9 +16,7 @@ TrivialTypeTraversalAndLiftImpls! {
UserTypeAnnotationIndex,
BorrowKind,
CastKind,
BinOp,
NullOp,
UnOp,
hir::Movability,
BasicBlock,
SwitchTargets,

View File

@ -400,7 +400,7 @@ rustc_queries! {
/// Try to build an abstract representation of the given constant.
query thir_abstract_const(
key: DefId
) -> Result<Option<&'tcx [ty::abstract_const::Node<'tcx>]>, ErrorGuaranteed> {
) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
desc {
|tcx| "building an abstract representation for `{}`", tcx.def_path_str(key),
}
@ -409,7 +409,7 @@ rustc_queries! {
/// Try to build an abstract representation of the given constant.
query thir_abstract_const_of_const_arg(
key: (LocalDefId, DefId)
) -> Result<Option<&'tcx [ty::abstract_const::Node<'tcx>]>, ErrorGuaranteed> {
) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
desc {
|tcx|
"building an abstract representation for the const argument `{}`",
@ -417,15 +417,6 @@ rustc_queries! {
}
}
query try_unify_abstract_consts(key:
ty::ParamEnvAnd<'tcx, (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>
)>) -> bool {
desc {
|tcx| "trying to unify the generic constants `{}` and `{}`",
tcx.def_path_str(key.value.0.def.did), tcx.def_path_str(key.value.1.def.did)
}
}
query mir_drops_elaborated_and_const_checked(
key: ty::WithOptConstParam<LocalDefId>
) -> &'tcx Steal<mir::Body<'tcx>> {

View File

@ -1,98 +1,13 @@
//! A subset of a mir body used for const evaluatability checking.
use crate::mir;
use crate::ty::visit::TypeVisitable;
use crate::ty::{self, EarlyBinder, SubstsRef, Ty, TyCtxt};
use crate::ty::{
self, Const, EarlyBinder, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeVisitable,
};
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::DefId;
use std::cmp;
use std::ops::ControlFlow;
rustc_index::newtype_index! {
/// An index into an `AbstractConst`.
pub struct NodeId {
derive [HashStable]
DEBUG_FORMAT = "n{}",
}
}
/// A tree representing an anonymous constant.
///
/// This is only able to represent a subset of `MIR`,
/// and should not leak any information about desugarings.
#[derive(Debug, Clone, Copy)]
pub struct AbstractConst<'tcx> {
// FIXME: Consider adding something like `IndexSlice`
// and use this here.
inner: &'tcx [Node<'tcx>],
substs: SubstsRef<'tcx>,
}
impl<'tcx> AbstractConst<'tcx> {
pub fn new(
tcx: TyCtxt<'tcx>,
uv: ty::UnevaluatedConst<'tcx>,
) -> Result<Option<AbstractConst<'tcx>>, ErrorGuaranteed> {
let inner = tcx.thir_abstract_const_opt_const_arg(uv.def)?;
debug!("AbstractConst::new({:?}) = {:?}", uv, inner);
Ok(inner.map(|inner| AbstractConst { inner, substs: tcx.erase_regions(uv.substs) }))
}
pub fn from_const(
tcx: TyCtxt<'tcx>,
ct: ty::Const<'tcx>,
) -> Result<Option<AbstractConst<'tcx>>, ErrorGuaranteed> {
match ct.kind() {
ty::ConstKind::Unevaluated(uv) => AbstractConst::new(tcx, uv),
ty::ConstKind::Error(reported) => Err(reported),
_ => Ok(None),
}
}
#[inline]
pub fn subtree(self, node: NodeId) -> AbstractConst<'tcx> {
AbstractConst { inner: &self.inner[..=node.index()], substs: self.substs }
}
#[inline]
pub fn root(self, tcx: TyCtxt<'tcx>) -> Node<'tcx> {
let node = self.inner.last().copied().unwrap();
match node {
Node::Leaf(leaf) => Node::Leaf(EarlyBinder(leaf).subst(tcx, self.substs)),
Node::Cast(kind, operand, ty) => {
Node::Cast(kind, operand, EarlyBinder(ty).subst(tcx, self.substs))
}
// Don't perform substitution on the following as they can't directly contain generic params
Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => node,
}
}
pub fn unify_failure_kind(self, tcx: TyCtxt<'tcx>) -> FailureKind {
let mut failure_kind = FailureKind::Concrete;
walk_abstract_const::<!, _>(tcx, self, |node| {
match node.root(tcx) {
Node::Leaf(leaf) => {
if leaf.has_non_region_infer() {
failure_kind = FailureKind::MentionsInfer;
} else if leaf.has_non_region_param() {
failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
}
}
Node::Cast(_, _, ty) => {
if ty.has_non_region_infer() {
failure_kind = FailureKind::MentionsInfer;
} else if ty.has_non_region_param() {
failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
}
}
Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => {}
}
ControlFlow::CONTINUE
});
failure_kind
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
#[derive(Hash, Debug, Clone, Copy, Ord, PartialOrd, PartialEq, Eq)]
#[derive(TyDecodable, TyEncodable, HashStable, TypeVisitable, TypeFoldable)]
pub enum CastKind {
/// thir::ExprKind::As
As,
@ -100,16 +15,6 @@ pub enum CastKind {
Use,
}
/// A node of an `AbstractConst`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum Node<'tcx> {
Leaf(ty::Const<'tcx>),
Binop(mir::BinOp, NodeId, NodeId),
UnaryOp(mir::UnOp, NodeId),
FunctionCall(NodeId, &'tcx [NodeId]),
Cast(CastKind, NodeId, Ty<'tcx>),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)]
pub enum NotConstEvaluatable {
Error(ErrorGuaranteed),
@ -127,68 +32,53 @@ TrivialTypeTraversalAndLiftImpls! {
NotConstEvaluatable,
}
pub type BoundAbstractConst<'tcx> = Result<Option<EarlyBinder<ty::Const<'tcx>>>, ErrorGuaranteed>;
impl<'tcx> TyCtxt<'tcx> {
#[inline]
pub fn thir_abstract_const_opt_const_arg(
/// Returns a const without substs applied
pub fn bound_abstract_const(
self,
def: ty::WithOptConstParam<DefId>,
) -> Result<Option<&'tcx [Node<'tcx>]>, ErrorGuaranteed> {
if let Some((did, param_did)) = def.as_const_arg() {
uv: ty::WithOptConstParam<DefId>,
) -> BoundAbstractConst<'tcx> {
let ac = if let Some((did, param_did)) = uv.as_const_arg() {
self.thir_abstract_const_of_const_arg((did, param_did))
} else {
self.thir_abstract_const(def.did)
}
}
}
#[instrument(skip(tcx, f), level = "debug")]
pub fn walk_abstract_const<'tcx, R, F>(
tcx: TyCtxt<'tcx>,
ct: AbstractConst<'tcx>,
mut f: F,
) -> ControlFlow<R>
where
F: FnMut(AbstractConst<'tcx>) -> ControlFlow<R>,
{
#[instrument(skip(tcx, f), level = "debug")]
fn recurse<'tcx, R>(
tcx: TyCtxt<'tcx>,
ct: AbstractConst<'tcx>,
f: &mut dyn FnMut(AbstractConst<'tcx>) -> ControlFlow<R>,
) -> ControlFlow<R> {
f(ct)?;
let root = ct.root(tcx);
debug!(?root);
match root {
Node::Leaf(_) => ControlFlow::CONTINUE,
Node::Binop(_, l, r) => {
recurse(tcx, ct.subtree(l), f)?;
recurse(tcx, ct.subtree(r), f)
}
Node::UnaryOp(_, v) => recurse(tcx, ct.subtree(v), f),
Node::FunctionCall(func, args) => {
recurse(tcx, ct.subtree(func), f)?;
args.iter().try_for_each(|&arg| recurse(tcx, ct.subtree(arg), f))
}
Node::Cast(_, operand, _) => recurse(tcx, ct.subtree(operand), f),
}
self.thir_abstract_const(uv.did)
};
Ok(ac?.map(|ac| EarlyBinder(ac)))
}
recurse(tcx, ct, &mut f)
}
pub fn expand_abstract_consts<T: TypeFoldable<'tcx>>(self, ac: T) -> T {
struct Expander<'tcx> {
tcx: TyCtxt<'tcx>,
}
// We were unable to unify the abstract constant with
// a constant found in the caller bounds, there are
// now three possible cases here.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum FailureKind {
/// The abstract const still references an inference
/// variable, in this case we return `TooGeneric`.
MentionsInfer,
/// The abstract const references a generic parameter,
/// this means that we emit an error here.
MentionsParam,
/// The substs are concrete enough that we can simply
/// try and evaluate the given constant.
Concrete,
impl<'tcx> TypeFolder<'tcx> for Expander<'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
if ty.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) {
ty.super_fold_with(self)
} else {
ty
}
}
fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> {
let ct = match c.kind() {
ty::ConstKind::Unevaluated(uv) => match self.tcx.bound_abstract_const(uv.def) {
Err(e) => self.tcx.const_error_with_guaranteed(c.ty(), e),
Ok(Some(bac)) => {
let substs = self.tcx.erase_regions(uv.substs);
bac.subst(self.tcx, substs)
}
Ok(None) => c,
},
_ => c,
};
ct.super_fold_with(self)
}
}
ac.fold_with(&mut Expander { tcx: self })
}
}

View File

@ -345,26 +345,6 @@ impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
}
}
impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
for [ty::abstract_const::Node<'tcx>]
{
fn decode(decoder: &mut D) -> &'tcx Self {
decoder.interner().arena.alloc_from_iter(
(0..decoder.read_usize()).map(|_| Decodable::decode(decoder)).collect::<Vec<_>>(),
)
}
}
impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
for [ty::abstract_const::NodeId]
{
fn decode(decoder: &mut D) -> &'tcx Self {
decoder.interner().arena.alloc_from_iter(
(0..decoder.read_usize()).map(|_| Decodable::decode(decoder)).collect::<Vec<_>>(),
)
}
}
impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
for ty::List<ty::BoundVariableKind>
{
@ -376,6 +356,15 @@ impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
}
}
impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D> for ty::List<ty::Const<'tcx>> {
fn decode(decoder: &mut D) -> &'tcx Self {
let len = decoder.read_usize();
decoder
.interner()
.mk_const_list((0..len).map::<ty::Const<'tcx>, _>(|_| Decodable::decode(decoder)))
}
}
impl_decodable_via_ref! {
&'tcx ty::TypeckResults<'tcx>,
&'tcx ty::List<Ty<'tcx>>,

View File

@ -1,10 +1,12 @@
use std::convert::TryInto;
use super::Const;
use crate::mir;
use crate::mir::interpret::{AllocId, ConstValue, Scalar};
use crate::ty::abstract_const::CastKind;
use crate::ty::subst::{InternalSubsts, SubstsRef};
use crate::ty::ParamEnv;
use crate::ty::{self, TyCtxt, TypeVisitable};
use crate::ty::{self, List, Ty, TyCtxt, TypeVisitable};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::DefId;
@ -70,8 +72,23 @@ pub enum ConstKind<'tcx> {
/// A placeholder for a const which could not be computed; this is
/// propagated to avoid useless error messages.
Error(ErrorGuaranteed),
/// Expr which contains an expression which has partially evaluated items.
Expr(Expr<'tcx>),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(HashStable, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)]
pub enum Expr<'tcx> {
Binop(mir::BinOp, Const<'tcx>, Const<'tcx>),
UnOp(mir::UnOp, Const<'tcx>),
FunctionCall(Const<'tcx>, &'tcx List<Const<'tcx>>),
Cast(CastKind, Const<'tcx>, Ty<'tcx>),
}
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(Expr<'_>, 24);
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
static_assert_size!(ConstKind<'_>, 32);

View File

@ -137,6 +137,7 @@ pub struct CtxtInterners<'tcx> {
// Specifically use a speedy hash algorithm for these hash sets, since
// they're accessed quite often.
type_: InternedSet<'tcx, WithStableHash<TyS<'tcx>>>,
const_lists: InternedSet<'tcx, List<ty::Const<'tcx>>>,
substs: InternedSet<'tcx, InternalSubsts<'tcx>>,
canonical_var_infos: InternedSet<'tcx, List<CanonicalVarInfo<'tcx>>>,
region: InternedSet<'tcx, RegionKind<'tcx>>,
@ -157,6 +158,7 @@ impl<'tcx> CtxtInterners<'tcx> {
CtxtInterners {
arena,
type_: Default::default(),
const_lists: Default::default(),
substs: Default::default(),
region: Default::default(),
poly_existential_predicates: Default::default(),
@ -2261,6 +2263,7 @@ macro_rules! slice_interners {
}
slice_interners!(
const_lists: _intern_const_list(Const<'tcx>),
substs: _intern_substs(GenericArg<'tcx>),
canonical_var_infos: _intern_canonical_var_infos(CanonicalVarInfo<'tcx>),
poly_existential_predicates:
@ -2716,6 +2719,17 @@ impl<'tcx> TyCtxt<'tcx> {
}
}
pub fn mk_const_list<I: InternAs<ty::Const<'tcx>, &'tcx List<ty::Const<'tcx>>>>(
self,
iter: I,
) -> I::Output {
iter.intern_with(|xs| self.intern_const_list(xs))
}
pub fn intern_const_list(self, cs: &[ty::Const<'tcx>]) -> &'tcx List<ty::Const<'tcx>> {
if cs.is_empty() { List::empty() } else { self._intern_const_list(cs) }
}
pub fn intern_type_list(self, ts: &[Ty<'tcx>]) -> &'tcx List<Ty<'tcx>> {
if ts.is_empty() {
List::empty()

View File

@ -356,7 +356,10 @@ impl DeepRejectCtxt {
pub fn consts_may_unify(self, obligation_ct: ty::Const<'_>, impl_ct: ty::Const<'_>) -> bool {
match impl_ct.kind() {
ty::ConstKind::Param(_) | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => {
ty::ConstKind::Expr(_)
| ty::ConstKind::Param(_)
| ty::ConstKind::Unevaluated(_)
| ty::ConstKind::Error(_) => {
return true;
}
ty::ConstKind::Value(_) => {}
@ -374,7 +377,9 @@ impl DeepRejectCtxt {
// As we don't necessarily eagerly evaluate constants,
// they might unify with any value.
ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => true,
ty::ConstKind::Expr(_) | ty::ConstKind::Unevaluated(_) | ty::ConstKind::Error(_) => {
true
}
ty::ConstKind::Value(obl) => match k {
ty::ConstKind::Value(imp) => obl == imp,
_ => true,

View File

@ -313,6 +313,26 @@ impl FlagComputation {
self.add_flags(TypeFlags::STILL_FURTHER_SPECIALIZABLE);
}
ty::ConstKind::Value(_) => {}
ty::ConstKind::Expr(e) => {
use ty::Expr;
match e {
Expr::Binop(_, l, r) => {
self.add_const(l);
self.add_const(r);
}
Expr::UnOp(_, v) => self.add_const(v),
Expr::FunctionCall(f, args) => {
self.add_const(f);
for arg in args {
self.add_const(arg);
}
}
Expr::Cast(_, c, t) => {
self.add_ty(t);
self.add_const(c);
}
}
}
ty::ConstKind::Error(_) => self.add_flags(TypeFlags::HAS_ERROR),
}
}

View File

@ -77,7 +77,7 @@ pub use self::closure::{
CAPTURE_STRUCT_LOCAL,
};
pub use self::consts::{
Const, ConstInt, ConstKind, ConstS, InferConst, ScalarInt, UnevaluatedConst, ValTree,
Const, ConstInt, ConstKind, ConstS, Expr, InferConst, ScalarInt, UnevaluatedConst, ValTree,
};
pub use self::context::{
tls, CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations,

View File

@ -4,7 +4,6 @@ use rustc_index::vec::{Idx, IndexVec};
use crate::middle::exported_symbols::ExportedSymbol;
use crate::mir::Body;
use crate::ty::abstract_const::Node;
use crate::ty::{
self, Const, FnSig, GeneratorDiagnosticData, GenericPredicates, Predicate, TraitRef, Ty,
};
@ -124,6 +123,5 @@ parameterized_over_tcx! {
Predicate,
GeneratorDiagnosticData,
Body,
Node,
ExportedSymbol,
}

View File

@ -1253,6 +1253,9 @@ pub trait PrettyPrinter<'tcx>:
self.pretty_print_bound_var(debruijn, bound_var)?
}
ty::ConstKind::Placeholder(placeholder) => p!(write("Placeholder({:?})", placeholder)),
// FIXME(generic_const_exprs):
// write out some legible representation of an abstract const?
ty::ConstKind::Expr(_) => p!("[Const Expr]"),
ty::ConstKind::Error(_) => p!("[const error]"),
};
Ok(self)

View File

@ -5,7 +5,7 @@
//! subtyping, type equality, etc.
use crate::ty::error::{ExpectedFound, TypeError};
use crate::ty::{self, ImplSubject, Term, TermKind, Ty, TyCtxt, TypeFoldable};
use crate::ty::{self, Expr, ImplSubject, Term, TermKind, Ty, TyCtxt, TypeFoldable};
use crate::ty::{GenericArg, GenericArgKind, SubstsRef};
use rustc_hir as ast;
use rustc_hir::def_id::DefId;
@ -613,7 +613,10 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
if a_ty != b_ty {
relation.tcx().sess.delay_span_bug(
DUMMY_SP,
&format!("cannot relate constants of different types: {} != {}", a_ty, b_ty),
&format!(
"cannot relate constants ({:?}, {:?}) of different types: {} != {}",
a, b, a_ty, b_ty
),
);
}
@ -623,11 +626,16 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
// an unnormalized (i.e. unevaluated) const in the param-env.
// FIXME(generic_const_exprs): Once we always lazily unify unevaluated constants
// these `eval` calls can be removed.
if !relation.tcx().features().generic_const_exprs {
if !tcx.features().generic_const_exprs {
a = a.eval(tcx, relation.param_env());
b = b.eval(tcx, relation.param_env());
}
if tcx.features().generic_const_exprs {
a = tcx.expand_abstract_consts(a);
b = tcx.expand_abstract_consts(b);
}
// Currently, the values that can be unified are primitive types,
// and those that derive both `PartialEq` and `Eq`, corresponding
// to structural-match types.
@ -644,16 +652,11 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
(ty::ConstKind::Placeholder(p1), ty::ConstKind::Placeholder(p2)) => p1 == p2,
(ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
(ty::ConstKind::Unevaluated(au), ty::ConstKind::Unevaluated(bu))
if tcx.features().generic_const_exprs =>
{
tcx.try_unify_abstract_consts(relation.param_env().and((au, bu)))
}
// While this is slightly incorrect, it shouldn't matter for `min_const_generics`
// and is the better alternative to waiting until `generic_const_exprs` can
// be stabilized.
(ty::ConstKind::Unevaluated(au), ty::ConstKind::Unevaluated(bu)) if au.def == bu.def => {
assert_eq!(a.ty(), b.ty());
let substs = relation.relate_with_variance(
ty::Variance::Invariant,
ty::VarianceDiagInfo::default(),
@ -665,6 +668,50 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
a.ty(),
));
}
// Before calling relate on exprs, it is necessary to ensure that the nested consts
// have identical types.
(ty::ConstKind::Expr(ae), ty::ConstKind::Expr(be)) => {
let r = relation;
// FIXME(generic_const_exprs): is it possible to relate two consts which are not identical
// exprs? Should we care about that?
let expr = match (ae, be) {
(Expr::Binop(a_op, al, ar), Expr::Binop(b_op, bl, br))
if a_op == b_op && al.ty() == bl.ty() && ar.ty() == br.ty() =>
{
Expr::Binop(a_op, r.consts(al, bl)?, r.consts(ar, br)?)
}
(Expr::UnOp(a_op, av), Expr::UnOp(b_op, bv))
if a_op == b_op && av.ty() == bv.ty() =>
{
Expr::UnOp(a_op, r.consts(av, bv)?)
}
(Expr::Cast(ak, av, at), Expr::Cast(bk, bv, bt))
if ak == bk && av.ty() == bv.ty() =>
{
Expr::Cast(ak, r.consts(av, bv)?, r.tys(at, bt)?)
}
(Expr::FunctionCall(af, aa), Expr::FunctionCall(bf, ba))
if aa.len() == ba.len()
&& af.ty() == bf.ty()
&& aa
.iter()
.zip(ba.iter())
.all(|(a_arg, b_arg)| a_arg.ty() == b_arg.ty()) =>
{
let func = r.consts(af, bf)?;
let mut related_args = Vec::with_capacity(aa.len());
for (a_arg, b_arg) in aa.iter().zip(ba.iter()) {
related_args.push(r.consts(a_arg, b_arg)?);
}
let related_args = tcx.mk_const_list(related_args.iter());
Expr::FunctionCall(func, related_args)
}
_ => return Err(TypeError::ConstMismatch(expected_found(r, a, b))),
};
let kind = ty::ConstKind::Expr(expr);
return Ok(tcx.mk_const(kind, a.ty()));
}
_ => false,
};
if is_match { Ok(a) } else { Err(TypeError::ConstMismatch(expected_found(relation, a, b))) }

View File

@ -601,6 +601,12 @@ impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::PolyExistentialPredicate<'t
}
}
impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ty::Const<'tcx>> {
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
ty::util::fold_list(self, folder, |tcx, v| tcx.mk_const_list(v.iter()))
}
}
impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::List<ProjectionKind> {
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
ty::util::fold_list(self, folder, |tcx, v| tcx.intern_projs(v))

View File

@ -214,6 +214,24 @@ fn push_inner<'tcx>(stack: &mut TypeWalkerStack<'tcx>, parent: GenericArg<'tcx>)
| ty::ConstKind::Value(_)
| ty::ConstKind::Error(_) => {}
ty::ConstKind::Expr(expr) => match expr {
ty::Expr::UnOp(_, v) => push_inner(stack, v.into()),
ty::Expr::Binop(_, l, r) => {
push_inner(stack, r.into());
push_inner(stack, l.into())
}
ty::Expr::FunctionCall(func, args) => {
for a in args.iter().rev() {
push_inner(stack, a.into());
}
push_inner(stack, func.into());
}
ty::Expr::Cast(_, c, t) => {
push_inner(stack, t.into());
push_inner(stack, c.into());
}
},
ty::ConstKind::Unevaluated(ct) => {
stack.extend(ct.substs.iter().rev());
}

View File

@ -3,6 +3,7 @@
#![feature(control_flow_enum)]
#![feature(rustc_private)]
#![feature(try_blocks)]
#![feature(let_chains)]
#![recursion_limit = "256"]
#![deny(rustc::untranslatable_diagnostic)]
#![deny(rustc::diagnostic_outside_of_impl)]
@ -25,7 +26,6 @@ use rustc_middle::bug;
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::privacy::{EffectiveVisibilities, Level};
use rustc_middle::span_bug;
use rustc_middle::ty::abstract_const::{walk_abstract_const, AbstractConst, Node as ACNode};
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::{self, Const, DefIdTree, GenericParamDefKind};
@ -288,19 +288,8 @@ where
}
fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow<Self::BreakTy> {
self.visit_ty(c.ty())?;
let tcx = self.def_id_visitor.tcx();
if let Ok(Some(ct)) = AbstractConst::from_const(tcx, c) {
walk_abstract_const(tcx, ct, |node| match node.root(tcx) {
ACNode::Leaf(leaf) => self.visit_const(leaf),
ACNode::Cast(_, _, ty) => self.visit_ty(ty),
ACNode::Binop(..) | ACNode::UnaryOp(..) | ACNode::FunctionCall(_, _) => {
ControlFlow::CONTINUE
}
})
} else {
ControlFlow::CONTINUE
}
tcx.expand_abstract_consts(c).super_visit_with(self)
}
}

View File

@ -812,12 +812,6 @@ impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>>
}
}
impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx [ty::abstract_const::Node<'tcx>] {
fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self {
RefDecodable::decode(d)
}
}
impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx [(ty::Predicate<'tcx>, Span)] {
fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self {
RefDecodable::decode(d)

View File

@ -575,6 +575,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
// a path), even for it we still need to encode a placeholder, as
// the path could refer back to e.g. an `impl` using the constant.
ty::ConstKind::Unevaluated(_)
| ty::ConstKind::Expr(_)
| ty::ConstKind::Param(_)
| ty::ConstKind::Infer(_)
| ty::ConstKind::Bound(..)

View File

@ -8,152 +8,18 @@
//! In this case we try to build an abstract representation of this constant using
//! `thir_abstract_const` which can then be checked for structural equality with other
//! generic constants mentioned in the `caller_bounds` of the current environment.
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_infer::infer::InferCtxt;
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::ty::abstract_const::{
walk_abstract_const, AbstractConst, FailureKind, Node, NotConstEvaluatable,
};
use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
use rustc_span::Span;
use std::iter;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::abstract_const::NotConstEvaluatable;
use rustc_middle::ty::{self, TyCtxt, TypeVisitable, TypeVisitor};
use rustc_span::Span;
use std::ops::ControlFlow;
pub struct ConstUnifyCtxt<'tcx> {
pub tcx: TyCtxt<'tcx>,
pub param_env: ty::ParamEnv<'tcx>,
}
impl<'tcx> ConstUnifyCtxt<'tcx> {
// Substitutes generics repeatedly to allow AbstractConsts to unify where a
// ConstKind::Unevaluated could be turned into an AbstractConst that would unify e.g.
// Param(N) should unify with Param(T), substs: [Unevaluated("T2", [Unevaluated("T3", [Param(N)])])]
#[inline]
#[instrument(skip(self), level = "debug")]
fn try_replace_substs_in_root(
&self,
mut abstr_const: AbstractConst<'tcx>,
) -> Option<AbstractConst<'tcx>> {
while let Node::Leaf(ct) = abstr_const.root(self.tcx) {
match AbstractConst::from_const(self.tcx, ct) {
Ok(Some(act)) => abstr_const = act,
Ok(None) => break,
Err(_) => return None,
}
}
Some(abstr_const)
}
/// Tries to unify two abstract constants using structural equality.
#[instrument(skip(self), level = "debug")]
pub fn try_unify(&self, a: AbstractConst<'tcx>, b: AbstractConst<'tcx>) -> bool {
let a = if let Some(a) = self.try_replace_substs_in_root(a) {
a
} else {
return true;
};
let b = if let Some(b) = self.try_replace_substs_in_root(b) {
b
} else {
return true;
};
let a_root = a.root(self.tcx);
let b_root = b.root(self.tcx);
debug!(?a_root, ?b_root);
match (a_root, b_root) {
(Node::Leaf(a_ct), Node::Leaf(b_ct)) => {
let a_ct = a_ct.eval(self.tcx, self.param_env);
debug!("a_ct evaluated: {:?}", a_ct);
let b_ct = b_ct.eval(self.tcx, self.param_env);
debug!("b_ct evaluated: {:?}", b_ct);
if a_ct.ty() != b_ct.ty() {
return false;
}
match (a_ct.kind(), b_ct.kind()) {
// We can just unify errors with everything to reduce the amount of
// emitted errors here.
(ty::ConstKind::Error(_), _) | (_, ty::ConstKind::Error(_)) => true,
(ty::ConstKind::Param(a_param), ty::ConstKind::Param(b_param)) => {
a_param == b_param
}
(ty::ConstKind::Value(a_val), ty::ConstKind::Value(b_val)) => a_val == b_val,
// If we have `fn a<const N: usize>() -> [u8; N + 1]` and `fn b<const M: usize>() -> [u8; 1 + M]`
// we do not want to use `assert_eq!(a(), b())` to infer that `N` and `M` have to be `1`. This
// means that we only allow inference variables if they are equal.
(ty::ConstKind::Infer(a_val), ty::ConstKind::Infer(b_val)) => a_val == b_val,
// We expand generic anonymous constants at the start of this function, so this
// branch should only be taking when dealing with associated constants, at
// which point directly comparing them seems like the desired behavior.
//
// FIXME(generic_const_exprs): This isn't actually the case.
// We also take this branch for concrete anonymous constants and
// expand generic anonymous constants with concrete substs.
(ty::ConstKind::Unevaluated(a_uv), ty::ConstKind::Unevaluated(b_uv)) => {
a_uv == b_uv
}
// FIXME(generic_const_exprs): We may want to either actually try
// to evaluate `a_ct` and `b_ct` if they are fully concrete or something like
// this, for now we just return false here.
_ => false,
}
}
(Node::Binop(a_op, al, ar), Node::Binop(b_op, bl, br)) if a_op == b_op => {
self.try_unify(a.subtree(al), b.subtree(bl))
&& self.try_unify(a.subtree(ar), b.subtree(br))
}
(Node::UnaryOp(a_op, av), Node::UnaryOp(b_op, bv)) if a_op == b_op => {
self.try_unify(a.subtree(av), b.subtree(bv))
}
(Node::FunctionCall(a_f, a_args), Node::FunctionCall(b_f, b_args))
if a_args.len() == b_args.len() =>
{
self.try_unify(a.subtree(a_f), b.subtree(b_f))
&& iter::zip(a_args, b_args)
.all(|(&an, &bn)| self.try_unify(a.subtree(an), b.subtree(bn)))
}
(Node::Cast(a_kind, a_operand, a_ty), Node::Cast(b_kind, b_operand, b_ty))
if (a_ty == b_ty) && (a_kind == b_kind) =>
{
self.try_unify(a.subtree(a_operand), b.subtree(b_operand))
}
// use this over `_ => false` to make adding variants to `Node` less error prone
(Node::Cast(..), _)
| (Node::FunctionCall(..), _)
| (Node::UnaryOp(..), _)
| (Node::Binop(..), _)
| (Node::Leaf(..), _) => false,
}
}
}
#[instrument(skip(tcx), level = "debug")]
pub fn try_unify_abstract_consts<'tcx>(
tcx: TyCtxt<'tcx>,
(a, b): (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>),
param_env: ty::ParamEnv<'tcx>,
) -> bool {
(|| {
if let Some(a) = AbstractConst::new(tcx, a)? {
if let Some(b) = AbstractConst::new(tcx, b)? {
let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
return Ok(const_unify_ctxt.try_unify(a, b));
}
}
Ok(false)
})()
.unwrap_or_else(|_: ErrorGuaranteed| true)
// FIXME(generic_const_exprs): We should instead have this
// method return the resulting `ty::Const` and return `ConstKind::Error`
// on `ErrorGuaranteed`.
}
use crate::traits::ObligationCtxt;
/// Check if a given constant can be evaluated.
#[instrument(skip(infcx), level = "debug")]
@ -166,6 +32,8 @@ pub fn is_const_evaluatable<'tcx>(
let tcx = infcx.tcx;
let uv = match ct.kind() {
ty::ConstKind::Unevaluated(uv) => uv,
// FIXME(generic_const_exprs): this seems wrong but I couldn't find a way to get this to trigger
ty::ConstKind::Expr(_) => bug!("unexpected expr in `is_const_evaluatable: {ct:?}"),
ty::ConstKind::Param(_)
| ty::ConstKind::Bound(_, _)
| ty::ConstKind::Placeholder(_)
@ -175,21 +43,25 @@ pub fn is_const_evaluatable<'tcx>(
};
if tcx.features().generic_const_exprs {
if let Some(ct) = AbstractConst::new(tcx, uv)? {
if satisfied_from_param_env(tcx, ct, param_env)? {
let ct = tcx.expand_abstract_consts(ct);
let is_anon_ct = if let ty::ConstKind::Unevaluated(uv) = ct.kind() {
tcx.def_kind(uv.def.did) == DefKind::AnonConst
} else {
false
};
if !is_anon_ct {
if satisfied_from_param_env(tcx, infcx, ct, param_env) {
return Ok(());
}
match ct.unify_failure_kind(tcx) {
FailureKind::MentionsInfer => {
return Err(NotConstEvaluatable::MentionsInfer);
}
FailureKind::MentionsParam => {
return Err(NotConstEvaluatable::MentionsParam);
}
// returned below
FailureKind::Concrete => {}
if ct.has_non_region_infer() {
return Err(NotConstEvaluatable::MentionsInfer);
} else if ct.has_non_region_param() {
return Err(NotConstEvaluatable::MentionsParam);
}
}
let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
match concrete {
Err(ErrorHandled::TooGeneric) => Err(NotConstEvaluatable::Error(
@ -211,28 +83,33 @@ pub fn is_const_evaluatable<'tcx>(
//
// See #74595 for more details about this.
let concrete = infcx.const_eval_resolve(param_env, uv, Some(span));
match concrete {
// If we're evaluating a foreign constant, under a nightly compiler without generic
// const exprs, AND it would've passed if that expression had been evaluated with
// generic const exprs, then suggest using generic const exprs.
Err(_) if tcx.sess.is_nightly_build()
&& let Ok(Some(ct)) = AbstractConst::new(tcx, uv)
&& satisfied_from_param_env(tcx, ct, param_env) == Ok(true) => {
tcx.sess
.struct_span_fatal(
// Slightly better span than just using `span` alone
if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
"failed to evaluate generic const expression",
)
.note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
.span_suggestion_verbose(
rustc_span::DUMMY_SP,
"consider enabling this feature",
"#![feature(generic_const_exprs)]\n",
rustc_errors::Applicability::MaybeIncorrect,
)
.emit()
// If we're evaluating a generic foreign constant, under a nightly compiler while
// the current crate does not enable `feature(generic_const_exprs)`, abort
// compilation with a useful error.
Err(_)
if tcx.sess.is_nightly_build()
&& satisfied_from_param_env(
tcx,
infcx,
tcx.expand_abstract_consts(ct),
param_env,
) =>
{
tcx.sess
.struct_span_fatal(
// Slightly better span than just using `span` alone
if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span },
"failed to evaluate generic const expression",
)
.note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`")
.span_suggestion_verbose(
rustc_span::DUMMY_SP,
"consider enabling this feature",
"#![feature(generic_const_exprs)]\n",
rustc_errors::Applicability::MaybeIncorrect,
)
.emit()
}
Err(ErrorHandled::TooGeneric) => {
@ -241,49 +118,82 @@ pub fn is_const_evaluatable<'tcx>(
} else if uv.has_non_region_param() {
NotConstEvaluatable::MentionsParam
} else {
let guar = infcx.tcx.sess.delay_span_bug(span, format!("Missing value for constant, but no error reported?"));
let guar = infcx.tcx.sess.delay_span_bug(
span,
format!("Missing value for constant, but no error reported?"),
);
NotConstEvaluatable::Error(guar)
};
Err(err)
},
}
Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)),
Ok(_) => Ok(()),
}
}
}
#[instrument(skip(tcx), level = "debug")]
#[instrument(skip(infcx, tcx), level = "debug")]
fn satisfied_from_param_env<'tcx>(
tcx: TyCtxt<'tcx>,
ct: AbstractConst<'tcx>,
infcx: &InferCtxt<'tcx>,
ct: ty::Const<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Result<bool, NotConstEvaluatable> {
) -> bool {
// Try to unify with each subtree in the AbstractConst to allow for
// `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
// predicate for `(N + 1) * 2`
struct Visitor<'a, 'tcx> {
ct: ty::Const<'tcx>,
param_env: ty::ParamEnv<'tcx>,
infcx: &'a InferCtxt<'tcx>,
}
impl<'a, 'tcx> TypeVisitor<'tcx> for Visitor<'a, 'tcx> {
type BreakTy = ();
fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
if let Ok(()) = self.infcx.commit_if_ok(|_| {
let ocx = ObligationCtxt::new_in_snapshot(self.infcx);
if let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty())
&& let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct)
&& ocx.select_all_or_error().is_empty()
{
Ok(())
} else {
Err(())
}
}) {
ControlFlow::BREAK
} else if let ty::ConstKind::Expr(e) = c.kind() {
e.visit_with(self)
} else {
// FIXME(generic_const_exprs): This doesn't recurse into `<T as Trait<U>>::ASSOC`'s substs.
// This is currently unobservable as `<T as Trait<{ U + 1 }>>::ASSOC` creates an anon const
// with its own `ConstEvaluatable` bound in the param env which we will visit separately.
//
// If we start allowing directly writing `ConstKind::Expr` without an intermediate anon const
// this will be incorrect. It might be worth investigating making `predicates_of` elaborate
// all of the `ConstEvaluatable` bounds rather than having a visitor here.
ControlFlow::CONTINUE
}
}
}
for pred in param_env.caller_bounds() {
match pred.kind().skip_binder() {
ty::PredicateKind::ConstEvaluatable(uv) => {
if let Some(b_ct) = AbstractConst::from_const(tcx, uv)? {
let const_unify_ctxt = ConstUnifyCtxt { tcx, param_env };
ty::PredicateKind::ConstEvaluatable(ce) => {
let b_ct = tcx.expand_abstract_consts(ce);
let mut v = Visitor { ct, infcx, param_env };
let result = b_ct.visit_with(&mut v);
// Try to unify with each subtree in the AbstractConst to allow for
// `N + 1` being const evaluatable even if theres only a `ConstEvaluatable`
// predicate for `(N + 1) * 2`
let result = walk_abstract_const(tcx, b_ct, |b_ct| {
match const_unify_ctxt.try_unify(ct, b_ct) {
true => ControlFlow::BREAK,
false => ControlFlow::CONTINUE,
}
});
if let ControlFlow::Break(()) = result {
debug!("is_const_evaluatable: abstract_const ~~> ok");
return Ok(true);
}
if let ControlFlow::Break(()) = result {
debug!("is_const_evaluatable: abstract_const ~~> ok");
return true;
}
}
_ => {} // don't care
}
}
Ok(false)
false
}

View File

@ -455,20 +455,47 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
}
ty::PredicateKind::ConstEquate(c1, c2) => {
let tcx = self.selcx.tcx();
assert!(
self.selcx.tcx().features().generic_const_exprs,
tcx.features().generic_const_exprs,
"`ConstEquate` without a feature gate: {c1:?} {c2:?}",
);
debug!(?c1, ?c2, "equating consts");
// FIXME: we probably should only try to unify abstract constants
// if the constants depend on generic parameters.
//
// Let's just see where this breaks :shrug:
if let (ty::ConstKind::Unevaluated(a), ty::ConstKind::Unevaluated(b)) =
(c1.kind(), c2.kind())
{
if infcx.try_unify_abstract_consts(a, b, obligation.param_env) {
return ProcessResult::Changed(vec![]);
let c1 = tcx.expand_abstract_consts(c1);
let c2 = tcx.expand_abstract_consts(c2);
debug!("equating consts:\nc1= {:?}\nc2= {:?}", c1, c2);
use rustc_hir::def::DefKind;
use ty::ConstKind::Unevaluated;
match (c1.kind(), c2.kind()) {
(Unevaluated(a), Unevaluated(b))
if a.def.did == b.def.did
&& tcx.def_kind(a.def.did) == DefKind::AssocConst =>
{
if let Ok(new_obligations) = infcx
.at(&obligation.cause, obligation.param_env)
.trace(c1, c2)
.eq(a.substs, b.substs)
{
return ProcessResult::Changed(mk_pending(
new_obligations.into_obligations(),
));
}
}
(_, Unevaluated(_)) | (Unevaluated(_), _) => (),
(_, _) => {
if let Ok(new_obligations) =
infcx.at(&obligation.cause, obligation.param_env).eq(c1, c2)
{
return ProcessResult::Changed(mk_pending(
new_obligations.into_obligations(),
));
}
}
}
}
@ -508,7 +535,9 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> {
.at(&obligation.cause, obligation.param_env)
.eq(c1, c2)
{
Ok(_) => ProcessResult::Changed(vec![]),
Ok(inf_ok) => {
ProcessResult::Changed(mk_pending(inf_ok.into_obligations()))
}
Err(err) => ProcessResult::Error(
FulfillmentErrorCode::CodeConstEquateError(
ExpectedFound::new(true, c1, c2),

View File

@ -932,10 +932,6 @@ pub fn provide(providers: &mut ty::query::Providers) {
vtable_trait_upcasting_coercion_new_vptr_slot,
subst_and_check_impossible_predicates,
is_impossible_method,
try_unify_abstract_consts: |tcx, param_env_and| {
let (param_env, (a, b)) = param_env_and.into_parts();
const_evaluatable::try_unify_abstract_consts(tcx, (a, b), param_env)
},
..*providers
};
}

View File

@ -17,11 +17,10 @@ use hir::def::DefKind;
use rustc_errors::{DelayDm, FatalError, MultiSpan};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::ty::abstract_const::{walk_abstract_const, AbstractConst};
use rustc_middle::ty::subst::{GenericArg, InternalSubsts};
use rustc_middle::ty::{
self, EarlyBinder, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor,
};
use rustc_middle::ty::{GenericArg, InternalSubsts};
use rustc_middle::ty::{Predicate, ToPredicate};
use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY;
use rustc_span::symbol::Symbol;
@ -837,23 +836,9 @@ fn contains_illegal_self_type_reference<'tcx, T: TypeVisitable<'tcx>>(
}
fn visit_const(&mut self, ct: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
// Constants can only influence object safety if they reference `Self`.
// Constants can only influence object safety if they are generic and reference `Self`.
// This is only possible for unevaluated constants, so we walk these here.
//
// If `AbstractConst::from_const` returned an error we already failed compilation
// so we don't have to emit an additional error here.
use rustc_middle::ty::abstract_const::Node;
if let Ok(Some(ct)) = AbstractConst::from_const(self.tcx, ct) {
walk_abstract_const(self.tcx, ct, |node| match node.root(self.tcx) {
Node::Leaf(leaf) => self.visit_const(leaf),
Node::Cast(_, _, ty) => self.visit_ty(ty),
Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => {
ControlFlow::CONTINUE
}
})
} else {
ct.super_visit_with(self)
}
self.tcx.expand_abstract_consts(ct).super_visit_with(self)
}
}

View File

@ -657,21 +657,62 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
ty::PredicateKind::ConstEquate(c1, c2) => {
let tcx = self.tcx();
assert!(
self.tcx().features().generic_const_exprs,
tcx.features().generic_const_exprs,
"`ConstEquate` without a feature gate: {c1:?} {c2:?}",
);
debug!(?c1, ?c2, "evaluate_predicate_recursively: equating consts");
// FIXME: we probably should only try to unify abstract constants
// if the constants depend on generic parameters.
//
// Let's just see where this breaks :shrug:
if let (ty::ConstKind::Unevaluated(a), ty::ConstKind::Unevaluated(b)) =
(c1.kind(), c2.kind())
{
if self.infcx.try_unify_abstract_consts(a, b, obligation.param_env) {
return Ok(EvaluatedToOk);
let c1 = tcx.expand_abstract_consts(c1);
let c2 = tcx.expand_abstract_consts(c2);
debug!(
"evalaute_predicate_recursively: equating consts:\nc1= {:?}\nc2= {:?}",
c1, c2
);
use rustc_hir::def::DefKind;
use ty::ConstKind::Unevaluated;
match (c1.kind(), c2.kind()) {
(Unevaluated(a), Unevaluated(b))
if a.def.did == b.def.did
&& tcx.def_kind(a.def.did) == DefKind::AssocConst =>
{
if let Ok(new_obligations) = self
.infcx
.at(&obligation.cause, obligation.param_env)
.trace(c1, c2)
.eq(a.substs, b.substs)
{
let mut obligations = new_obligations.obligations;
self.add_depth(
obligations.iter_mut(),
obligation.recursion_depth,
);
return self.evaluate_predicates_recursively(
previous_stack,
obligations.into_iter(),
);
}
}
(_, Unevaluated(_)) | (Unevaluated(_), _) => (),
(_, _) => {
if let Ok(new_obligations) = self
.infcx
.at(&obligation.cause, obligation.param_env)
.eq(c1, c2)
{
let mut obligations = new_obligations.obligations;
self.add_depth(
obligations.iter_mut(),
obligation.recursion_depth,
);
return self.evaluate_predicates_recursively(
previous_stack,
obligations.into_iter(),
);
}
}
}
}
@ -698,7 +739,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
.at(&obligation.cause, obligation.param_env)
.eq(c1, c2)
{
Ok(_) => Ok(EvaluatedToOk),
Ok(inf_ok) => self.evaluate_predicates_recursively(
previous_stack,
inf_ok.into_obligations(),
),
Err(_) => Ok(EvaluatedToErr),
}
}

View File

@ -476,6 +476,11 @@ impl<'tcx> WfPredicates<'tcx> {
ty::Binder::dummy(ty::PredicateKind::WellFormed(ct.into())),
));
}
// FIXME(generic_const_exprs): This seems wrong but I could not find a way to get this to trigger
ty::ConstKind::Expr(_) => {
bug!("checking wfness of `ConstKind::Expr` is unsupported")
}
ty::ConstKind::Error(_)
| ty::ConstKind::Param(_)
| ty::ConstKind::Bound(..)

View File

@ -1,10 +1,11 @@
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_index::vec::IndexVec;
use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput};
use rustc_middle::ty::abstract_const::{CastKind, Node, NodeId};
use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
use rustc_middle::thir::visit;
use rustc_middle::thir::visit::Visitor;
use rustc_middle::ty::abstract_const::CastKind;
use rustc_middle::ty::{self, ConstKind, Expr, TyCtxt, TypeVisitable};
use rustc_middle::{mir, thir};
use rustc_span::Span;
use rustc_target::abi::VariantIdx;
@ -76,334 +77,286 @@ pub(crate) fn destructure_const<'tcx>(
ty::DestructuredConst { variant, fields }
}
pub struct AbstractConstBuilder<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body_id: thir::ExprId,
body: &'a thir::Thir<'tcx>,
/// The current WIP node tree.
nodes: IndexVec<NodeId, Node<'tcx>>,
/// We do not allow all binary operations in abstract consts, so filter disallowed ones.
fn check_binop(op: mir::BinOp) -> bool {
use mir::BinOp::*;
match op {
Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le | Ne
| Ge | Gt => true,
Offset => false,
}
}
impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> {
fn root_span(&self) -> Span {
self.body.exprs[self.body_id].span
/// While we currently allow all unary operations, we still want to explicitly guard against
/// future changes here.
fn check_unop(op: mir::UnOp) -> bool {
use mir::UnOp::*;
match op {
Not | Neg => true,
}
}
fn error(&mut self, sub: GenericConstantTooComplexSub) -> Result<!, ErrorGuaranteed> {
let reported = self.tcx.sess.emit_err(GenericConstantTooComplex {
span: self.root_span(),
maybe_supported: None,
sub,
});
fn recurse_build<'tcx>(
tcx: TyCtxt<'tcx>,
body: &thir::Thir<'tcx>,
node: thir::ExprId,
root_span: Span,
) -> Result<ty::Const<'tcx>, ErrorGuaranteed> {
use thir::ExprKind;
let node = &body.exprs[node];
Err(reported)
}
let maybe_supported_error = |a| maybe_supported_error(tcx, a, root_span);
let error = |a| error(tcx, a, root_span);
fn maybe_supported_error(
&mut self,
sub: GenericConstantTooComplexSub,
) -> Result<!, ErrorGuaranteed> {
let reported = self.tcx.sess.emit_err(GenericConstantTooComplex {
span: self.root_span(),
maybe_supported: Some(()),
sub,
});
Err(reported)
}
#[instrument(skip(tcx, body, body_id), level = "debug")]
pub fn new(
tcx: TyCtxt<'tcx>,
(body, body_id): (&'a thir::Thir<'tcx>, thir::ExprId),
) -> Result<Option<AbstractConstBuilder<'a, 'tcx>>, ErrorGuaranteed> {
let builder = AbstractConstBuilder { tcx, body_id, body, nodes: IndexVec::new() };
struct IsThirPolymorphic<'a, 'tcx> {
is_poly: bool,
thir: &'a thir::Thir<'tcx>,
Ok(match &node.kind {
// I dont know if handling of these 3 is correct
&ExprKind::Scope { value, .. } => recurse_build(tcx, body, value, root_span)?,
&ExprKind::PlaceTypeAscription { source, .. }
| &ExprKind::ValueTypeAscription { source, .. } => {
recurse_build(tcx, body, source, root_span)?
}
use crate::rustc_middle::thir::visit::Visitor;
use thir::visit;
impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
fn expr_is_poly(&mut self, expr: &thir::Expr<'tcx>) -> bool {
if expr.ty.has_non_region_param() {
return true;
&ExprKind::Literal { lit, neg } => {
let sp = node.span;
match tcx.at(sp).lit_to_const(LitToConstInput { lit: &lit.node, ty: node.ty, neg }) {
Ok(c) => c,
Err(LitToConstError::Reported(guar)) => {
tcx.const_error_with_guaranteed(node.ty, guar)
}
match expr.kind {
thir::ExprKind::NamedConst { substs, .. } => substs.has_non_region_param(),
thir::ExprKind::ConstParam { .. } => true,
thir::ExprKind::Repeat { value, count } => {
self.visit_expr(&self.thir()[value]);
count.has_non_region_param()
}
_ => false,
}
}
fn pat_is_poly(&mut self, pat: &thir::Pat<'tcx>) -> bool {
if pat.ty.has_non_region_param() {
return true;
}
match pat.kind {
thir::PatKind::Constant { value } => value.has_non_region_param(),
thir::PatKind::Range(box thir::PatRange { lo, hi, .. }) => {
lo.has_non_region_param() || hi.has_non_region_param()
}
_ => false,
Err(LitToConstError::TypeError) => {
bug!("encountered type error in lit_to_const")
}
}
}
&ExprKind::NonHirLiteral { lit, user_ty: _ } => {
let val = ty::ValTree::from_scalar_int(lit);
ty::Const::from_value(tcx, val, node.ty)
}
&ExprKind::ZstLiteral { user_ty: _ } => {
let val = ty::ValTree::zst();
ty::Const::from_value(tcx, val, node.ty)
}
&ExprKind::NamedConst { def_id, substs, user_ty: _ } => {
let uneval = ty::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs);
tcx.mk_const(ty::ConstKind::Unevaluated(uneval), node.ty)
}
ExprKind::ConstParam { param, .. } => tcx.mk_const(ty::ConstKind::Param(*param), node.ty),
impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> {
fn thir(&self) -> &'a thir::Thir<'tcx> {
&self.thir
ExprKind::Call { fun, args, .. } => {
let fun = recurse_build(tcx, body, *fun, root_span)?;
let mut new_args = Vec::<ty::Const<'tcx>>::with_capacity(args.len());
for &id in args.iter() {
new_args.push(recurse_build(tcx, body, id, root_span)?);
}
#[instrument(skip(self), level = "debug")]
fn visit_expr(&mut self, expr: &thir::Expr<'tcx>) {
self.is_poly |= self.expr_is_poly(expr);
if !self.is_poly {
visit::walk_expr(self, expr)
}
}
#[instrument(skip(self), level = "debug")]
fn visit_pat(&mut self, pat: &thir::Pat<'tcx>) {
self.is_poly |= self.pat_is_poly(pat);
if !self.is_poly {
visit::walk_pat(self, pat);
}
let new_args = tcx.mk_const_list(new_args.iter());
tcx.mk_const(ConstKind::Expr(Expr::FunctionCall(fun, new_args)), node.ty)
}
&ExprKind::Binary { op, lhs, rhs } if check_binop(op) => {
let lhs = recurse_build(tcx, body, lhs, root_span)?;
let rhs = recurse_build(tcx, body, rhs, root_span)?;
tcx.mk_const(ConstKind::Expr(Expr::Binop(op, lhs, rhs)), node.ty)
}
&ExprKind::Unary { op, arg } if check_unop(op) => {
let arg = recurse_build(tcx, body, arg, root_span)?;
tcx.mk_const(ConstKind::Expr(Expr::UnOp(op, arg)), node.ty)
}
// This is necessary so that the following compiles:
//
// ```
// fn foo<const N: usize>(a: [(); N + 1]) {
// bar::<{ N + 1 }>();
// }
// ```
ExprKind::Block { block } => {
if let thir::Block { stmts: box [], expr: Some(e), .. } = &body.blocks[*block] {
recurse_build(tcx, body, *e, root_span)?
} else {
maybe_supported_error(GenericConstantTooComplexSub::BlockNotSupported(node.span))?
}
}
// `ExprKind::Use` happens when a `hir::ExprKind::Cast` is a
// "coercion cast" i.e. using a coercion or is a no-op.
// This is important so that `N as usize as usize` doesnt unify with `N as usize`. (untested)
&ExprKind::Use { source } => {
let arg = recurse_build(tcx, body, source, root_span)?;
tcx.mk_const(ConstKind::Expr(Expr::Cast(CastKind::Use, arg, node.ty)), node.ty)
}
&ExprKind::Cast { source } => {
let arg = recurse_build(tcx, body, source, root_span)?;
tcx.mk_const(ConstKind::Expr(Expr::Cast(CastKind::As, arg, node.ty)), node.ty)
}
ExprKind::Borrow { arg, .. } => {
let arg_node = &body.exprs[*arg];
let mut is_poly_vis = IsThirPolymorphic { is_poly: false, thir: body };
visit::walk_expr(&mut is_poly_vis, &body[body_id]);
debug!("AbstractConstBuilder: is_poly={}", is_poly_vis.is_poly);
if !is_poly_vis.is_poly {
return Ok(None);
// Skip reborrows for now until we allow Deref/Borrow/AddressOf
// expressions.
// FIXME(generic_const_exprs): Verify/explain why this is sound
if let ExprKind::Deref { arg } = arg_node.kind {
recurse_build(tcx, body, arg, root_span)?
} else {
maybe_supported_error(GenericConstantTooComplexSub::BorrowNotSupported(node.span))?
}
}
// FIXME(generic_const_exprs): We may want to support these.
ExprKind::AddressOf { .. } | ExprKind::Deref { .. } => maybe_supported_error(
GenericConstantTooComplexSub::AddressAndDerefNotSupported(node.span),
)?,
ExprKind::Repeat { .. } | ExprKind::Array { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::ArrayNotSupported(node.span))?
}
ExprKind::NeverToAny { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::NeverToAnyNotSupported(node.span))?
}
ExprKind::Tuple { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::TupleNotSupported(node.span))?
}
ExprKind::Index { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::IndexNotSupported(node.span))?
}
ExprKind::Field { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::FieldNotSupported(node.span))?
}
ExprKind::ConstBlock { .. } => {
maybe_supported_error(GenericConstantTooComplexSub::ConstBlockNotSupported(node.span))?
}
ExprKind::Adt(_) => {
maybe_supported_error(GenericConstantTooComplexSub::AdtNotSupported(node.span))?
}
// dont know if this is correct
ExprKind::Pointer { .. } => {
error(GenericConstantTooComplexSub::PointerNotSupported(node.span))?
}
ExprKind::Yield { .. } => {
error(GenericConstantTooComplexSub::YieldNotSupported(node.span))?
}
ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Loop { .. } => {
error(GenericConstantTooComplexSub::LoopNotSupported(node.span))?
}
ExprKind::Box { .. } => error(GenericConstantTooComplexSub::BoxNotSupported(node.span))?,
ExprKind::Unary { .. } => unreachable!(),
// we handle valid unary/binary ops above
ExprKind::Binary { .. } => {
error(GenericConstantTooComplexSub::BinaryNotSupported(node.span))?
}
ExprKind::LogicalOp { .. } => {
error(GenericConstantTooComplexSub::LogicalOpNotSupported(node.span))?
}
ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
error(GenericConstantTooComplexSub::AssignNotSupported(node.span))?
}
ExprKind::Closure { .. } | ExprKind::Return { .. } => {
error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))?
}
// let expressions imply control flow
ExprKind::Match { .. } | ExprKind::If { .. } | ExprKind::Let { .. } => {
error(GenericConstantTooComplexSub::ControlFlowNotSupported(node.span))?
}
ExprKind::InlineAsm { .. } => {
error(GenericConstantTooComplexSub::InlineAsmNotSupported(node.span))?
}
Ok(Some(builder))
// we dont permit let stmts so `VarRef` and `UpvarRef` cant happen
ExprKind::VarRef { .. }
| ExprKind::UpvarRef { .. }
| ExprKind::StaticRef { .. }
| ExprKind::ThreadLocalRef(_) => {
error(GenericConstantTooComplexSub::OperationNotSupported(node.span))?
}
})
}
struct IsThirPolymorphic<'a, 'tcx> {
is_poly: bool,
thir: &'a thir::Thir<'tcx>,
}
fn error<'tcx>(
tcx: TyCtxt<'tcx>,
sub: GenericConstantTooComplexSub,
root_span: Span,
) -> Result<!, ErrorGuaranteed> {
let reported = tcx.sess.emit_err(GenericConstantTooComplex {
span: root_span,
maybe_supported: None,
sub,
});
Err(reported)
}
fn maybe_supported_error<'tcx>(
tcx: TyCtxt<'tcx>,
sub: GenericConstantTooComplexSub,
root_span: Span,
) -> Result<!, ErrorGuaranteed> {
let reported = tcx.sess.emit_err(GenericConstantTooComplex {
span: root_span,
maybe_supported: Some(()),
sub,
});
Err(reported)
}
impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
fn expr_is_poly(&mut self, expr: &thir::Expr<'tcx>) -> bool {
if expr.ty.has_non_region_param() {
return true;
}
match expr.kind {
thir::ExprKind::NamedConst { substs, .. } => substs.has_non_region_param(),
thir::ExprKind::ConstParam { .. } => true,
thir::ExprKind::Repeat { value, count } => {
self.visit_expr(&self.thir()[value]);
count.has_non_region_param()
}
_ => false,
}
}
fn pat_is_poly(&mut self, pat: &thir::Pat<'tcx>) -> bool {
if pat.ty.has_non_region_param() {
return true;
}
match pat.kind {
thir::PatKind::Constant { value } => value.has_non_region_param(),
thir::PatKind::Range(box thir::PatRange { lo, hi, .. }) => {
lo.has_non_region_param() || hi.has_non_region_param()
}
_ => false,
}
}
}
impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> {
fn thir(&self) -> &'a thir::Thir<'tcx> {
&self.thir
}
/// We do not allow all binary operations in abstract consts, so filter disallowed ones.
fn check_binop(op: mir::BinOp) -> bool {
use mir::BinOp::*;
match op {
Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le
| Ne | Ge | Gt => true,
Offset => false,
#[instrument(skip(self), level = "debug")]
fn visit_expr(&mut self, expr: &thir::Expr<'tcx>) {
self.is_poly |= self.expr_is_poly(expr);
if !self.is_poly {
visit::walk_expr(self, expr)
}
}
/// While we currently allow all unary operations, we still want to explicitly guard against
/// future changes here.
fn check_unop(op: mir::UnOp) -> bool {
use mir::UnOp::*;
match op {
Not | Neg => true,
#[instrument(skip(self), level = "debug")]
fn visit_pat(&mut self, pat: &thir::Pat<'tcx>) {
self.is_poly |= self.pat_is_poly(pat);
if !self.is_poly {
visit::walk_pat(self, pat);
}
}
/// Builds the abstract const by walking the thir and bailing out when
/// encountering an unsupported operation.
pub fn build(mut self) -> Result<&'tcx [Node<'tcx>], ErrorGuaranteed> {
debug!("AbstractConstBuilder::build: body={:?}", &*self.body);
self.recurse_build(self.body_id)?;
Ok(self.tcx.arena.alloc_from_iter(self.nodes.into_iter()))
}
fn recurse_build(&mut self, node: thir::ExprId) -> Result<NodeId, ErrorGuaranteed> {
use thir::ExprKind;
let node = &self.body.exprs[node];
Ok(match &node.kind {
// I dont know if handling of these 3 is correct
&ExprKind::Scope { value, .. } => self.recurse_build(value)?,
&ExprKind::PlaceTypeAscription { source, .. }
| &ExprKind::ValueTypeAscription { source, .. } => self.recurse_build(source)?,
&ExprKind::Literal { lit, neg } => {
let sp = node.span;
let constant = match self.tcx.at(sp).lit_to_const(LitToConstInput {
lit: &lit.node,
ty: node.ty,
neg,
}) {
Ok(c) => c,
Err(LitToConstError::Reported(guar)) => {
self.tcx.const_error_with_guaranteed(node.ty, guar)
}
Err(LitToConstError::TypeError) => {
bug!("encountered type error in lit_to_const")
}
};
self.nodes.push(Node::Leaf(constant))
}
&ExprKind::NonHirLiteral { lit, user_ty: _ } => {
let val = ty::ValTree::from_scalar_int(lit);
self.nodes.push(Node::Leaf(ty::Const::from_value(self.tcx, val, node.ty)))
}
&ExprKind::ZstLiteral { user_ty: _ } => {
let val = ty::ValTree::zst();
self.nodes.push(Node::Leaf(ty::Const::from_value(self.tcx, val, node.ty)))
}
&ExprKind::NamedConst { def_id, substs, user_ty: _ } => {
let uneval =
ty::UnevaluatedConst::new(ty::WithOptConstParam::unknown(def_id), substs);
let constant = self.tcx.mk_const(ty::ConstKind::Unevaluated(uneval), node.ty);
self.nodes.push(Node::Leaf(constant))
}
ExprKind::ConstParam { param, .. } => {
let const_param = self.tcx.mk_const(ty::ConstKind::Param(*param), node.ty);
self.nodes.push(Node::Leaf(const_param))
}
ExprKind::Call { fun, args, .. } => {
let fun = self.recurse_build(*fun)?;
let mut new_args = Vec::<NodeId>::with_capacity(args.len());
for &id in args.iter() {
new_args.push(self.recurse_build(id)?);
}
let new_args = self.tcx.arena.alloc_slice(&new_args);
self.nodes.push(Node::FunctionCall(fun, new_args))
}
&ExprKind::Binary { op, lhs, rhs } if Self::check_binop(op) => {
let lhs = self.recurse_build(lhs)?;
let rhs = self.recurse_build(rhs)?;
self.nodes.push(Node::Binop(op, lhs, rhs))
}
&ExprKind::Unary { op, arg } if Self::check_unop(op) => {
let arg = self.recurse_build(arg)?;
self.nodes.push(Node::UnaryOp(op, arg))
}
// This is necessary so that the following compiles:
//
// ```
// fn foo<const N: usize>(a: [(); N + 1]) {
// bar::<{ N + 1 }>();
// }
// ```
ExprKind::Block { block } => {
if let thir::Block { stmts: box [], expr: Some(e), .. } = &self.body.blocks[*block]
{
self.recurse_build(*e)?
} else {
self.maybe_supported_error(GenericConstantTooComplexSub::BlockNotSupported(
node.span,
))?
}
}
// `ExprKind::Use` happens when a `hir::ExprKind::Cast` is a
// "coercion cast" i.e. using a coercion or is a no-op.
// This is important so that `N as usize as usize` doesnt unify with `N as usize`. (untested)
&ExprKind::Use { source } => {
let arg = self.recurse_build(source)?;
self.nodes.push(Node::Cast(CastKind::Use, arg, node.ty))
}
&ExprKind::Cast { source } => {
let arg = self.recurse_build(source)?;
self.nodes.push(Node::Cast(CastKind::As, arg, node.ty))
}
ExprKind::Borrow { arg, .. } => {
let arg_node = &self.body.exprs[*arg];
// Skip reborrows for now until we allow Deref/Borrow/AddressOf
// expressions.
// FIXME(generic_const_exprs): Verify/explain why this is sound
if let ExprKind::Deref { arg } = arg_node.kind {
self.recurse_build(arg)?
} else {
self.maybe_supported_error(GenericConstantTooComplexSub::BorrowNotSupported(
node.span,
))?
}
}
// FIXME(generic_const_exprs): We may want to support these.
ExprKind::AddressOf { .. } | ExprKind::Deref { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::AddressAndDerefNotSupported(node.span),
)?,
ExprKind::Repeat { .. } | ExprKind::Array { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::ArrayNotSupported(node.span),
)?,
ExprKind::NeverToAny { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::NeverToAnyNotSupported(node.span),
)?,
ExprKind::Tuple { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::TupleNotSupported(node.span),
)?,
ExprKind::Index { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::IndexNotSupported(node.span),
)?,
ExprKind::Field { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::FieldNotSupported(node.span),
)?,
ExprKind::ConstBlock { .. } => self.maybe_supported_error(
GenericConstantTooComplexSub::ConstBlockNotSupported(node.span),
)?,
ExprKind::Adt(_) => self
.maybe_supported_error(GenericConstantTooComplexSub::AdtNotSupported(node.span))?,
// dont know if this is correct
ExprKind::Pointer { .. } => {
self.error(GenericConstantTooComplexSub::PointerNotSupported(node.span))?
}
ExprKind::Yield { .. } => {
self.error(GenericConstantTooComplexSub::YieldNotSupported(node.span))?
}
ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Loop { .. } => {
self.error(GenericConstantTooComplexSub::LoopNotSupported(node.span))?
}
ExprKind::Box { .. } => {
self.error(GenericConstantTooComplexSub::BoxNotSupported(node.span))?
}
ExprKind::Unary { .. } => unreachable!(),
// we handle valid unary/binary ops above
ExprKind::Binary { .. } => {
self.error(GenericConstantTooComplexSub::BinaryNotSupported(node.span))?
}
ExprKind::LogicalOp { .. } => {
self.error(GenericConstantTooComplexSub::LogicalOpNotSupported(node.span))?
}
ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
self.error(GenericConstantTooComplexSub::AssignNotSupported(node.span))?
}
ExprKind::Closure { .. } | ExprKind::Return { .. } => {
self.error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))?
}
// let expressions imply control flow
ExprKind::Match { .. } | ExprKind::If { .. } | ExprKind::Let { .. } => {
self.error(GenericConstantTooComplexSub::ControlFlowNotSupported(node.span))?
}
ExprKind::InlineAsm { .. } => {
self.error(GenericConstantTooComplexSub::InlineAsmNotSupported(node.span))?
}
// we dont permit let stmts so `VarRef` and `UpvarRef` cant happen
ExprKind::VarRef { .. }
| ExprKind::UpvarRef { .. }
| ExprKind::StaticRef { .. }
| ExprKind::ThreadLocalRef(_) => {
self.error(GenericConstantTooComplexSub::OperationNotSupported(node.span))?
}
})
}
}
/// Builds an abstract const, do not use this directly, but use `AbstractConst::new` instead.
pub fn thir_abstract_const<'tcx>(
tcx: TyCtxt<'tcx>,
def: ty::WithOptConstParam<LocalDefId>,
) -> Result<Option<&'tcx [Node<'tcx>]>, ErrorGuaranteed> {
) -> Result<Option<ty::Const<'tcx>>, ErrorGuaranteed> {
if tcx.features().generic_const_exprs {
match tcx.def_kind(def.did) {
// FIXME(generic_const_exprs): We currently only do this for anonymous constants,
@ -416,10 +369,17 @@ pub fn thir_abstract_const<'tcx>(
}
let body = tcx.thir_body(def)?;
let (body, body_id) = (&*body.0.borrow(), body.1);
AbstractConstBuilder::new(tcx, (&*body.0.borrow(), body.1))?
.map(AbstractConstBuilder::build)
.transpose()
let mut is_poly_vis = IsThirPolymorphic { is_poly: false, thir: body };
visit::walk_expr(&mut is_poly_vis, &body[body_id]);
if !is_poly_vis.is_poly {
return Ok(None);
}
let root_span = body.exprs[body_id].span;
Some(recurse_build(tcx, body, body_id, root_span)).transpose()
} else {
Ok(None)
}

View File

@ -0,0 +1,27 @@
// check-pass
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
trait Trait {
const ASSOC: usize;
}
impl<T> Trait for T {
const ASSOC: usize = std::mem::size_of::<T>();
}
struct Foo<T: Trait>([u8; T::ASSOC])
where
[(); T::ASSOC]:;
fn bar<T: Trait>()
where
[(); T::ASSOC]:,
{
let _: Foo<T> = Foo::<_>(make());
}
fn make() -> ! {
todo!()
}
fn main() {}

View File

@ -0,0 +1,15 @@
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
trait Trait {
const ASSOC: usize;
}
fn foo<T: Trait, U: Trait>() where [(); U::ASSOC]:, {
bar::<{ T::ASSOC }>();
//~^ ERROR: unconstrained generic constant
}
fn bar<const N: usize>() {}
fn main() {}

View File

@ -0,0 +1,10 @@
error: unconstrained generic constant
--> $DIR/doesnt_unify_evaluatable.rs:9:11
|
LL | bar::<{ T::ASSOC }>();
| ^^^^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); { T::ASSOC }]:`
error: aborting due to previous error

View File

@ -0,0 +1,20 @@
// check-pass
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
trait Trait {
const ASSOC: usize;
}
struct Foo<T: Trait>(T)
where
[(); T::ASSOC]:;
impl<T: Trait> Drop for Foo<T>
where
[(); T::ASSOC]:,
{
fn drop(&mut self) {}
}
fn main() {}

View File

@ -0,0 +1,18 @@
// check-pass
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
trait Trait {
const ASSOC: usize;
}
fn foo<T: Trait, U: Trait>() where [(); T::ASSOC]:, {
bar::<{ T::ASSOC }>();
}
fn bar<const N: usize>() -> [(); N] {
[(); N]
}
fn main() {}

View File

@ -24,7 +24,8 @@ where
fn covariant(
v: &'static Foo<for<'a> fn(&'a ())>
) -> &'static Foo<fn(&'static ())> {
v //~ ERROR mismatched types
v
//~^ ERROR mismatched types
}
fn main() {

View File

@ -1,15 +1,15 @@
error[E0391]: cycle detected when resolving instance `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>::DIM`
error[E0391]: cycle detected when resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`
--> $DIR/issue-83765.rs:5:5
|
LL | const DIM: usize;
| ^^^^^^^^^^^^^^^^
|
note: ...which requires computing candidate for `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>`...
note: ...which requires computing candidate for `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>`...
--> $DIR/issue-83765.rs:4:1
|
LL | trait TensorDimension {
| ^^^^^^^^^^^^^^^^^^^^^
= note: ...which again requires resolving instance `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>::DIM`, completing the cycle
= note: ...which again requires resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`, completing the cycle
note: cycle used when computing candidate for `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>`
--> $DIR/issue-83765.rs:4:1
|

View File

@ -1,4 +1,8 @@
// revisions: cfail
// check-pass
// known-bug
// This should not compile, as the compiler should not know
// `A - 0` is satisfied `?x - 0` if `?x` is inferred to `A`.
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
@ -6,8 +10,8 @@ pub struct Ref<'a>(&'a i32);
impl<'a> Ref<'a> {
pub fn foo<const A: usize>() -> [(); A - 0] {
//~^ WARN function cannot
Self::foo()
//~^ error: type annotations needed
}
}

View File

@ -0,0 +1,14 @@
warning: function cannot return without recursing
--> $DIR/issue-85031-2.rs:12:5
|
LL | pub fn foo<const A: usize>() -> [(); A - 0] {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
LL |
LL | Self::foo()
| ----------- recursive call site
|
= help: a `loop` may express intention better if this is on purpose
= note: `#[warn(unconditional_recursion)]` on by default
warning: 1 warning emitted