Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support const evaulation #596

Closed
wants to merge 17 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Basic support for unevaluated const expressions
hayashi-stl committed Aug 19, 2020
commit eea70d61491265afa7155109de14fa2af9095214
26 changes: 23 additions & 3 deletions chalk-engine/src/slg.rs
Original file line number Diff line number Diff line change
@@ -448,7 +448,7 @@ impl<I: Interner> MayInvalidate<'_, I> {
}
}

/// Returns true if the two consts could be unequal.
/// Returns true if the two lifetimes could be unequal.
fn aggregate_lifetimes(&mut self, _: &Lifetime<I>, _: &Lifetime<I>) -> bool {
true
}
@@ -495,8 +495,28 @@ impl<I: Interner> MayInvalidate<'_, I> {
!c1.const_eq(new_ty, c2, interner)
}

// Only variants left are placeholder = concrete, which always fails
(ConstValue::Placeholder(_), _) | (ConstValue::Concrete(_), _) => true,
(ConstValue::Concrete(c), ConstValue::Unevaluated(u)) |
(ConstValue::Unevaluated(u), ConstValue::Concrete(c)) => {
if let Ok(ev) = u.try_eval(new_ty, interner) {
!c.const_eq(new_ty, &ev, interner)
} else {
true
}
}

(ConstValue::Unevaluated(u1), ConstValue::Unevaluated(u2)) => {
if u1.const_eq(new_ty, u2, interner) {
false
} else if let (Ok(c1), Ok(c2)) = (u1.try_eval(new_ty, interner), u2.try_eval(current_ty, interner)) {
!c1.const_eq(new_ty, &c2, interner)
} else {
true
}
}

// Only variants left are placeholder = concrete, which always fails, and
// placeholder = unevaluated, which we can't know for sure
(ConstValue::Placeholder(_), _) | (_, ConstValue::Placeholder(_)) => true,
}
}

28 changes: 28 additions & 0 deletions chalk-engine/src/slg/aggregate.rs
Original file line number Diff line number Diff line change
@@ -483,6 +483,34 @@ impl<I: Interner> AntiUnifier<'_, '_, I> {
}
}

(ConstValue::Concrete(e), ConstValue::Unevaluated(u)) |
(ConstValue::Unevaluated(u), ConstValue::Concrete(e)) => {
let ev = match u.try_eval(&ty, interner) {
Ok(ev) => ev,
Err(_) => return self.new_const_variable(ty),
};

if e.const_eq(&ty, &ev, interner) {
ConstData { ty: ty.clone(), value: ConstValue::Concrete(ev) }.intern(interner)
} else {
self.new_const_variable(ty)
}
}

(ConstValue::Unevaluated(u1), ConstValue::Unevaluated(u2)) => {
if u1.const_eq(&ty, u2, interner) {
c1.clone()
} else if let (Ok(e1), Ok(e2)) = (u1.try_eval(&ty, interner), u2.try_eval(&ty, interner)) {
if e1.const_eq(&ty, &e2, interner) {
ConstData { ty: ty.clone(), value: ConstValue::Concrete(e1) }.intern(interner)
} else {
self.new_const_variable(ty)
}
} else {
self.new_const_variable(ty)
}
}

(ConstValue::Placeholder(_), _) | (_, ConstValue::Placeholder(_)) => {
self.new_const_variable(ty)
}
36 changes: 34 additions & 2 deletions chalk-engine/src/slg/resolvent.rs
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ use tracing::{debug, instrument};
//
// From EWFS:
//
// Let G be an X-clause A :- D | L1,...Ln, where N > 0, and Li be selected atom.
// Let G be an X-clause A :- D | L1,...Ln, where n > 0, and Li be selected atom.
//
// Let C be an X-clause with no delayed literals. Let
//
@@ -507,14 +507,46 @@ impl<'i, I: Interner> Zipper<'i, I> for AnswerSubstitutor<'i, I> {
Ok(())
}

(ConstValue::Concrete(c), ConstValue::Unevaluated(u)) |
(ConstValue::Unevaluated(u), ConstValue::Concrete(c)) => {
match u.try_eval(answer_ty, interner) {
Ok(ev) => assert!(c.const_eq(answer_ty, &ev, interner)),

Err(ConstEvalError::TooGeneric) => panic!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly here...feels like we should have already have caught this before and the answer itself (or the goal) shouldn't have unevaluated consts. (Well...maybe the goal would?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out that unevaluated const expressions can appear in the answer and the goal in the resolvent if we so much as write impl Foo<3?> for Baz {}.

"structural mismatch between answer `{:?}` and pending goal `{:?}`",
answer, pending,
),
}
Ok(())
}

(ConstValue::Unevaluated(u1), ConstValue::Unevaluated(u2)) => {
if u1.const_eq(answer_ty, u2, interner) {
Ok(())
} else {
match (u1.try_eval(answer_ty, interner), u2.try_eval(answer_ty, interner)) {
(Ok(ev1), Ok(ev2)) => {
assert!(ev1.const_eq(answer_ty, &ev2, interner));
Ok(())
}

(Err(ConstEvalError::TooGeneric), _) | (_, Err(ConstEvalError::TooGeneric)) => panic!(
"structural mismatch between answer `{:?}` and pending goal `{:?}`",
answer, pending,
),
}
}
}

(ConstValue::InferenceVar(_), _) | (_, ConstValue::InferenceVar(_)) => panic!(
"unexpected inference var in answer `{:?}` or pending goal `{:?}`",
answer, pending,
),

(ConstValue::BoundVar(_), _)
| (ConstValue::Placeholder(_), _)
| (ConstValue::Concrete(_), _) => panic!(
| (ConstValue::Concrete(_), _)
| (ConstValue::Unevaluated(_), _) => panic!(
"structural mismatch between answer `{:?}` and pending goal `{:?}`",
answer, pending,
),
18 changes: 18 additions & 0 deletions chalk-integration/src/interner.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use chalk_ir::{
Constraint, FnDefId, Goals, InEnvironment, Lifetime, OpaqueTy, OpaqueTyId,
ProgramClauseImplication, ProgramClauses, ProjectionTy, QuantifiedWhereClauses,
SeparatorTraitRef, Substitution, TraitId, Ty, VariableKind, VariableKinds,
ConstEvalError
};
use chalk_ir::{
GenericArg, GenericArgData, Goal, GoalData, LifetimeData, ProgramClause, ProgramClauseData,
@@ -44,6 +45,7 @@ impl Interner for ChalkIr {
type InternedLifetime = LifetimeData<ChalkIr>;
type InternedConst = Arc<ConstData<ChalkIr>>;
type InternedConcreteConst = u32;
type InternedUnevaluatedConst = Option<u32>;
type InternedGenericArg = GenericArgData<ChalkIr>;
type InternedGoal = Arc<GoalData<ChalkIr>>;
type InternedGoals = Vec<Goal<ChalkIr>>;
@@ -240,6 +242,22 @@ impl Interner for ChalkIr {
c1 == c2
}

fn unevaluated_const_eq(
&self,
_ty: &Arc<TyData<ChalkIr>>,
c1: &Option<u32>,
c2: &Option<u32>,
) -> bool {
c1 == c2
}

fn try_eval_const(&self, _ty: &Arc<TyData<ChalkIr>>, constant: &Option<u32>) -> Result<u32, ConstEvalError> {
match constant {
Some(c) => Ok(*c),
None => Err(ConstEvalError::TooGeneric),
}
}

fn intern_generic_arg(&self, generic_arg: GenericArgData<ChalkIr>) -> GenericArgData<ChalkIr> {
generic_arg
}
6 changes: 6 additions & 0 deletions chalk-integration/src/lowering.rs
Original file line number Diff line number Diff line change
@@ -1763,11 +1763,17 @@ impl LowerConst for Const {
})
.map(|c| c.clone())
}

Const::Value(value) => Ok(chalk_ir::ConstData {
ty: get_type_of_u32(),
value: chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: *value }),
}
.intern(interner)),

Const::Unevaluated(unev) => Ok(chalk_ir::ConstData {
ty: get_type_of_u32(),
value: chalk_ir::ConstValue::Unevaluated(chalk_ir::UnevaluatedConst { interned: *unev }),
}.intern(interner)),
}
}
}
7 changes: 7 additions & 0 deletions chalk-ir/src/debug.rs
Original file line number Diff line number Diff line change
@@ -59,6 +59,12 @@ impl<I: Interner> Debug for ConcreteConst<I> {
}
}

impl<I: Interner> Debug for UnevaluatedConst<I> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
write!(fmt, "{:?}", self.interned)
}
}

impl<I: Interner> Debug for GenericArg<I> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
I::debug_generic_arg(self, fmt).unwrap_or_else(|| write!(fmt, "{:?}", self.interned))
@@ -320,6 +326,7 @@ impl<I: Interner> Debug for ConstData<I> {
ConstValue::InferenceVar(var) => write!(fmt, "{:?}", var),
ConstValue::Placeholder(index) => write!(fmt, "{:?}", index),
ConstValue::Concrete(evaluated) => write!(fmt, "{:?}", evaluated),
ConstValue::Unevaluated(expr) => write!(fmt, "{:?}", expr),
}
}
}
7 changes: 7 additions & 0 deletions chalk-ir/src/fold.rs
Original file line number Diff line number Diff line change
@@ -550,6 +550,13 @@ where
}),
}
.intern(folder.target_interner())),

ConstValue::Unevaluated(unev) => Ok(ConstData {
ty: fold_ty()?,
value: ConstValue::Unevaluated(UnevaluatedConst {
interned: folder.target_interner().transfer_unevaluated_const(&unev.interned),
})
}.intern(folder.target_interner())),
}
}
}
37 changes: 35 additions & 2 deletions chalk-ir/src/interner.rs
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ use crate::Ty;
use crate::TyData;
use crate::VariableKind;
use crate::VariableKinds;
use crate::{Const, ConstData};
use crate::{Const, ConstData, ConstEvalError};
use std::fmt::{self, Debug};
use std::hash::Hash;
use std::marker::PhantomData;
@@ -93,6 +93,15 @@ pub trait Interner: Debug + Copy + Eq + Ord + Hash {
/// evaluated consts.
type InternedConcreteConst: Debug + Clone + Eq + Hash;

/// "Interned" representation of an unevaluated const expression. In normal user code,
/// `Self::InternedUnevaluatedConst` is not referenced. Instead,
/// we refer to `UnevaluatedConst<Self>`, which wraps this type.
///
/// `InternedUnevaluatedConst` instances are not created by chalk,
/// it can only evaluate them with the [`try_eval_const`] method
/// and check for (syntactic for now) equality between them.
type InternedUnevaluatedConst: Debug + Clone + Eq + Hash;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we anticipate that this will carry a "substitutions"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect not, right?


/// "Interned" representation of a "generic parameter", which can
/// be either a type or a lifetime. In normal user code,
/// `Self::InternedGenericArg` is not referenced. Instead, we refer to
@@ -456,14 +465,25 @@ pub trait Interner: Debug + Copy + Eq + Ord + Hash {
/// Lookup the `ConstData` that was interned to create a `InternedConst`.
fn const_data<'a>(&self, constant: &'a Self::InternedConst) -> &'a ConstData<Self>;

/// Deterermine whether two concrete const values are equal.
/// Determine whether two concrete const values are equal.
fn const_eq(
&self,
ty: &Self::InternedType,
c1: &Self::InternedConcreteConst,
c2: &Self::InternedConcreteConst,
) -> bool;

/// Determine whether two unevaluated const expressions are equal.
fn unevaluated_const_eq(
&self,
ty: &Self::InternedType,
c1: &Self::InternedUnevaluatedConst,
c2: &Self::InternedUnevaluatedConst,
) -> bool;

/// Attempt to evaluate a const to a concrete const.
fn try_eval_const(&self, ty: &Self::InternedType, constant: &Self::InternedUnevaluatedConst) -> Result<Self::InternedConcreteConst, ConstEvalError>;

/// Create an "interned" parameter from `data`. This is not
/// normally invoked directly; instead, you invoke
/// `GenericArgData::intern` (which will ultimately call this
@@ -634,6 +654,12 @@ pub trait TargetInterner<I: Interner>: Interner {
const_evaluated: &I::InternedConcreteConst,
) -> Self::InternedConcreteConst;

/// Transfer unevaluated constant expressions to the target interner.
fn transfer_unevaluated_const(
&self,
const_unevaluated: &I::InternedUnevaluatedConst,
) -> Self::InternedUnevaluatedConst;

/// Transfer function ABI to the target interner.
fn transfer_abi(abi: I::FnAbi) -> Self::FnAbi;
}
@@ -666,6 +692,13 @@ impl<I: Interner> TargetInterner<I> for I {
const_evaluated.clone()
}

fn transfer_unevaluated_const(
&self,
const_unevaluated: &I::InternedUnevaluatedConst,
) -> Self::InternedUnevaluatedConst {
const_unevaluated.clone()
}

fn transfer_abi(abi: I::FnAbi) -> Self::FnAbi {
abi
}
65 changes: 64 additions & 1 deletion chalk-ir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -929,8 +929,15 @@ impl<I: Interner> Const<I> {
ConstValue::InferenceVar(_) => false,
ConstValue::Placeholder(_) => false,
ConstValue::Concrete(_) => false,
ConstValue::Unevaluated(_) => todo!("needs_shift for ConstValue::Unevaluated"),
}
}

/// Tries to evaluate the constant.
/// This is guaranteed to return a concrete constant or an error.
pub fn try_eval(&self, interner: &I) -> Result<Const<I>, ConstEvalError> {
Ok(Self::new(interner, self.data(interner).try_eval(interner)?))
}
}

/// Constant data, containing the constant's type and value.
@@ -942,6 +949,21 @@ pub struct ConstData<I: Interner> {
pub value: ConstValue<I>,
}

impl<I: Interner> ConstData<I> {
/// Tries to evaluate the constant.
/// This is guaranteed to return a concrete constant or an error.
pub fn try_eval(&self, interner: &I) -> Result<ConstData<I>, ConstEvalError> {
match &self.value {
ConstValue::BoundVar(_) | ConstValue::InferenceVar(_) | ConstValue::Placeholder(_) => Err(ConstEvalError::TooGeneric),
ConstValue::Concrete(_) => Ok(self.clone()),
ConstValue::Unevaluated(expr) => Ok(ConstData {
ty: self.ty.clone(),
value: ConstValue::Concrete(expr.try_eval(&self.ty, interner)?)
}),
}
}
}

/// A constant value, not necessarily concrete.
#[derive(Clone, PartialEq, Eq, Hash, HasInterner)]
pub enum ConstValue<I: Interner> {
@@ -953,9 +975,14 @@ pub enum ConstValue<I: Interner> {
Placeholder(PlaceholderIndex),
/// Concrete constant value.
Concrete(ConcreteConst<I>),
/// Unevaluated constant expression.
Unevaluated(UnevaluatedConst<I>),
}

impl<I: Interner> Copy for ConstValue<I> where I::InternedConcreteConst: Copy {}
impl<I: Interner> Copy for ConstValue<I>
where
I::InternedConcreteConst: Copy,
I::InternedUnevaluatedConst: Copy, {}

impl<I: Interner> ConstData<I> {
/// Wraps the constant data in a `Const`.
@@ -979,6 +1006,42 @@ impl<I: Interner> ConcreteConst<I> {
}
}

/// Unevaluated const expression, which can be evaluated
#[derive(Copy, Clone, PartialEq, Eq, Hash, HasInterner)]
pub struct UnevaluatedConst<I: Interner> {
/// The interned expression
pub interned: I::InternedUnevaluatedConst,
}

impl<I: Interner> UnevaluatedConst<I> {
/// Attempts to evaluate the const expression.
pub fn try_eval(&self, ty: &Ty<I>, interner: &I) -> Result<ConcreteConst<I>, ConstEvalError> {
Ok(ConcreteConst {interned: interner.try_eval_const(&ty.interned, &self.interned)?})
}

/// Checks whether two unevaluated constants are equal.
pub fn const_eq(&self, ty: &Ty<I>, other: &UnevaluatedConst<I>, interner: &I) -> bool {
interner.unevaluated_const_eq(&ty.interned, &self.interned, &other.interned)
}
}

/// An error that can occur when evaluating a const expression.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ConstEvalError {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably also want an Unevaluatable. Though I don't exactly know how Chalk would handle these differently. (TooGeneric is obvious Ambiguous, but would Unevaluable return Ambiguous or NoSolution?)

/// A generic argument's value was not known.
TooGeneric,
}

impl std::error::Error for ConstEvalError {}

impl std::fmt::Display for ConstEvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
ConstEvalError::TooGeneric => write!(f, "Const expression was too generic"),
}
}
}

/// A Rust lifetime.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, HasInterner)]
pub struct Lifetime<I: Interner> {
Loading