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 8 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
38 changes: 38 additions & 0 deletions chalk-engine/src/slg/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,44 @@ impl<I: Interner> AntiUnifier<'_, '_, I> {
}
}

(ConstValue::Concrete(e), ConstValue::Unevaluated(u))
Copy link
Member

Choose a reason for hiding this comment

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

Initial thought: Will we ever actually give an answer with an Unevaluated const?

Copy link
Author

Choose a reason for hiding this comment

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

I don't see why not. Chalk can return associated type projections in answers, and an unevalutated const is basically a projection.

| (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)) => {
match (u1.try_eval(&ty, interner), u2.try_eval(&ty, interner)) {
(Ok(e1), Ok(e2)) => {
if e1.const_eq(&ty, &e2, interner) {
ConstData {
ty: ty.clone(),
value: ConstValue::Concrete(e1),
}
.intern(interner)
} else {
self.new_const_variable(ty)
}
}

(Err(ConstEvalError::TooGeneric), _) | (_, Err(ConstEvalError::TooGeneric)) => {
self.new_const_variable(ty)
}
}
}

(ConstValue::Placeholder(_), _) | (_, ConstValue::Placeholder(_)) => {
self.new_const_variable(ty)
}
Expand Down
37 changes: 34 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 @@ -507,14 +507,45 @@ 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)) => {
match (
u1.try_eval(answer_ty, interner),
u2.try_eval(answer_ty, interner),
) {
(Ok(c1), Ok(c2)) => assert!(c1.const_eq(answer_ty, &c2, interner)),

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

(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