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
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ chalk-parse/src/parser.rs

## IDE files
/.idea/
/.vscode/
27 changes: 24 additions & 3 deletions chalk-engine/src/slg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -495,8 +495,29 @@ 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 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,
}
}

Expand Down
16 changes: 16 additions & 0 deletions chalk-engine/src/slg/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@ impl<I: Interner> AntiUnifier<'_, '_, I> {
// It would be nice to check that c1 and c2 have the same type, even though
// on this stage of solving they should already have the same type.

// Turn evaluatable unevaluated consts into concrete consts
let (c1, c2) = match (c1.try_eval(interner), c2.try_eval(interner)) {
(Some(c1), Some(c2)) => (c1, c2),
(None, Some(c)) | (Some(c), None) => return c.clone(),
// FIXME: This is the case where both constants are invalid.
// What's the right thing to do here?
(None, None) => return self.new_const_variable(c1.data(interner).ty.clone()),
};

let ConstData {
ty: c1_ty,
value: c1_value,
Expand Down Expand Up @@ -483,6 +492,13 @@ impl<I: Interner> AntiUnifier<'_, '_, I> {
}
}

// For now, the unevalutable unevaluated const is too generic.
(ConstValue::Concrete(_), ConstValue::Unevaluated(_))
| (ConstValue::Unevaluated(_), ConstValue::Concrete(_)) => self.new_const_variable(ty),

// For now, the unevalutable unevaluated const is too generic.
(ConstValue::Unevaluated(_), ConstValue::Unevaluated(_)) => self.new_const_variable(ty),

(ConstValue::Placeholder(_), _) | (_, ConstValue::Placeholder(_)) => {
self.new_const_variable(ty)
}
Expand Down
14 changes: 11 additions & 3 deletions chalk-engine/src/slg/resolvent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand All @@ -42,7 +42,7 @@ use tracing::{debug, instrument};
//
// Then:
//
// S(A :- D | L1...Li-1, L1'...L'm, Li+1...Ln)
// S(A :- D | L1...Li-1, L'1...L'm, Li+1...Ln)
//
// is the SLG resolvent of G with C.

Expand Down Expand Up @@ -471,6 +471,10 @@ impl<'i, I: Interner> Zipper<'i, I> for AnswerSubstitutor<'i, I> {
return Zip::zip_with(self, answer, &pending);
}

// Turn evaluatable unevaluated consts into concrete consts
let answer = answer.try_eval(interner).ok_or(NoSolution)?;
let pending = pending.try_eval(interner).ok_or(NoSolution)?;

let ConstData {
ty: answer_ty,
value: answer_value,
Expand Down Expand Up @@ -507,14 +511,18 @@ impl<'i, I: Interner> Zipper<'i, I> for AnswerSubstitutor<'i, I> {
Ok(())
}

// For now, the unevalutable unevaluated const is too generic.
(ConstValue::Unevaluated(_), ConstValue::Unevaluated(_)) => Err(NoSolution),

(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,
),
Expand Down
31 changes: 30 additions & 1 deletion chalk-integration/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::tls;
use chalk_ir::interner::{HasInterner, Interner};
use chalk_ir::{
AdtId, AliasTy, ApplicationTy, AssocTypeId, CanonicalVarKind, CanonicalVarKinds, ConstData,
Constraint, FnDefId, Goals, InEnvironment, Lifetime, OpaqueTy, OpaqueTyId,
ConstEvalError, Constraint, FnDefId, Goals, InEnvironment, Lifetime, OpaqueTy, OpaqueTyId,
ProgramClauseImplication, ProgramClauses, ProjectionTy, QuantifiedWhereClauses,
SeparatorTraitRef, Substitution, TraitId, Ty, VariableKind, VariableKinds,
};
Expand Down Expand Up @@ -34,6 +34,23 @@ pub enum ChalkFnAbi {
C,
}

/// A mock unevaluated const expression.
/// `Some(n)` evaluates to `n`,
/// and `None` evaluates to a specific but unknown constant.
/// This exists for formatting purposes.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct UnevaluatedConstData(pub Option<u32>);

impl Debug for UnevaluatedConstData {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self.0 {
Some(n) => write!(fmt, "{}?", n)?,
None => write!(fmt, "?")?,
}
Ok(())
}
}

/// The default "interner" and the only interner used by chalk
/// itself. In this interner, no interning actually occurs.
#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
Expand All @@ -44,6 +61,7 @@ impl Interner for ChalkIr {
type InternedLifetime = LifetimeData<ChalkIr>;
type InternedConst = Arc<ConstData<ChalkIr>>;
type InternedConcreteConst = u32;
type InternedUnevaluatedConst = UnevaluatedConstData;
type InternedGenericArg = GenericArgData<ChalkIr>;
type InternedGoal = Arc<GoalData<ChalkIr>>;
type InternedGoals = Vec<Goal<ChalkIr>>;
Expand Down Expand Up @@ -240,6 +258,17 @@ impl Interner for ChalkIr {
c1 == c2
}

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

fn intern_generic_arg(&self, generic_arg: GenericArgData<ChalkIr>) -> GenericArgData<ChalkIr> {
generic_arg
}
Expand Down
10 changes: 10 additions & 0 deletions chalk-integration/src/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use string_cache::DefaultAtom as Atom;
use tracing::{debug, instrument};

use crate::error::RustIrError;
use crate::interner::UnevaluatedConstData;
use crate::program::Program as LoweredProgram;
use crate::{Identifier as Ident, RawId, TypeKind, TypeSort};

Expand Down Expand Up @@ -1763,11 +1764,20 @@ 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: UnevaluatedConstData(*unev),
}),
}
.intern(interner)),
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions chalk-ir/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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),
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions chalk-ir/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,16 @@ 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())),
}
}
}
Expand Down
33 changes: 31 additions & 2 deletions chalk-ir/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -456,14 +465,21 @@ 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;

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

Choose a reason for hiding this comment

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

I kind of expect this to take a set of substitutions. Consider how the rustc variant looks.


/// Create an "interned" parameter from `data`. This is not
/// normally invoked directly; instead, you invoke
/// `GenericArgData::intern` (which will ultimately call this
Expand Down Expand Up @@ -634,6 +650,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;
}
Expand Down Expand Up @@ -666,6 +688,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
}
Expand Down
Loading