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> {
1 change: 1 addition & 0 deletions chalk-ir/src/visit.rs
Original file line number Diff line number Diff line change
@@ -366,6 +366,7 @@ impl<I: Interner> SuperVisit<I> for Const<I> {
visitor.visit_free_placeholder(*universe, outer_binder)
}
ConstValue::Concrete(_) => R::new(),
ConstValue::Unevaluated(_) => R::new(),
}
}
}
1 change: 1 addition & 0 deletions chalk-parse/src/ast.rs
Original file line number Diff line number Diff line change
@@ -168,6 +168,7 @@ pub enum GenericArg {
pub enum Const {
Id(Identifier),
Value(u32),
Unevaluated(Option<u32>),
}

#[derive(Clone, PartialEq, Eq, Debug)]
6 changes: 6 additions & 0 deletions chalk-parse/src/parser.lalrpop
Original file line number Diff line number Diff line change
@@ -405,6 +405,7 @@ Lifetime: Lifetime = {

ConstWithoutId: Const = {
ConstValue => Const::Value(<>),
UnevaluatedConst => Const::Unevaluated(<>),
};

Const : Const = {
@@ -610,3 +611,8 @@ LifetimeId: Identifier = {
};

ConstValue: u32 = <s:r"[0-9]+"> => u32::from_str_radix(s, 10).unwrap();

UnevaluatedConst: Option<u32> = {
"?" => None,
<s:ConstValue> "?" => Some(<>),
}
1 change: 1 addition & 0 deletions chalk-solve/src/display/ty.rs
Original file line number Diff line number Diff line change
@@ -291,6 +291,7 @@ impl<I: Interner> RenderAsRust<I> for ConstValue<I> {
ConstValue::InferenceVar(_) => write!(f, "_"),
ConstValue::Placeholder(_) => write!(f, "<const placeholder>"),
ConstValue::Concrete(_value) => unimplemented!("const values"),
ConstValue::Unevaluated(_value) => unimplemented!("unevaluated const expressions"),
}
}
}
49 changes: 46 additions & 3 deletions chalk-solve/src/infer/unify.rs
Original file line number Diff line number Diff line change
@@ -415,19 +415,35 @@ impl<'t, I: Interner> Unifier<'t, I> {
.expect("unification of two unbound variables cannot fail"))
}

// Unifying an inference variables with a non-inference variable.
// Unifying an inference variable with a non-inference variable.
(&ConstValue::InferenceVar(var), &ConstValue::Concrete(_))
| (&ConstValue::InferenceVar(var), &ConstValue::Placeholder(_)) => {
debug!(?var, ty=?b, "unify_var_ty");
debug!(?var, ty=?b, "unify_var_const");
self.unify_var_const(var, b)
}

(&ConstValue::Concrete(_), &ConstValue::InferenceVar(var))
| (&ConstValue::Placeholder(_), &ConstValue::InferenceVar(var)) => {
debug!(?var, ty=?a, "unify_var_ty");
debug!(?var, ty=?a, "unify_var_const");
self.unify_var_const(var, a)
}

// It is important not to unify an inference variable with just any
// unevaluated const expression because the expression could contain
// a copy of the inference variable.
// In particular, ?X should not unify with ?X + 1
(&ConstValue::InferenceVar(var), &ConstValue::Unevaluated(_)) => {
let c = b.try_eval(interner).map_err(|_| NoSolution)?;
debug!(?var, ty=?c, "unify_var_const");
self.unify_var_const(var, &c)
}

(&ConstValue::Unevaluated(_), &ConstValue::InferenceVar(var)) => {
let c = a.try_eval(interner).map_err(|_| NoSolution)?;
debug!(?var, ty=?c, "unify_var_const");
self.unify_var_const(var, &c)
}

(&ConstValue::Placeholder(p1), &ConstValue::Placeholder(p2)) => {
Zip::zip_with(self, &p1, &p2)
}
@@ -440,9 +456,36 @@ impl<'t, I: Interner> Unifier<'t, I> {
}
}

(&ConstValue::Concrete(ref ev), &ConstValue::Unevaluated(ref expr)) |
(&ConstValue::Unevaluated(ref expr), &ConstValue::Concrete(ref ev)) => {
if ev.const_eq(a_ty, &expr.try_eval(a_ty, interner).map_err(|_| NoSolution)?, interner) {
Ok(())
} else {
Err(NoSolution)
}
}

(&ConstValue::Unevaluated(ref expr1), &ConstValue::Unevaluated(ref expr2)) => {
if expr1.const_eq(a_ty, expr2, interner) {
Ok(())
} else if let (Ok(ev1), Ok(ev2)) = (expr1.try_eval(a_ty, interner), expr2.try_eval(b_ty, interner)) {
if ev1.const_eq(a_ty, &ev2, interner) {
Ok(())
} else {
Err(NoSolution)
}
} else {
Err(NoSolution)
}
}

(&ConstValue::Concrete(_), &ConstValue::Placeholder(_))
| (&ConstValue::Placeholder(_), &ConstValue::Concrete(_)) => Err(NoSolution),

// Can't see inside an unevaluated const expression
(&ConstValue::Unevaluated(_), &ConstValue::Placeholder(_))
| (&ConstValue::Placeholder(_), &ConstValue::Unevaluated(_)) => Err(NoSolution),

(ConstValue::BoundVar(_), _) | (_, ConstValue::BoundVar(_)) => panic!(
"unification encountered bound variable: a={:?} b={:?}",
a, b
15 changes: 15 additions & 0 deletions tests/display/const_.rs
Original file line number Diff line number Diff line change
@@ -33,6 +33,20 @@ fn test_basic_const_values_in_impls() {
);
}

#[test]
#[ignore]
fn test_const_unevaluated() {
// Test we render unevaluated const expressions with literals correctly
reparse_test!(
program {
struct Foo<const N> { }
trait Bar { }
impl Bar for Foo<0?> { }
impl Bar for Foo<?> { }
}
);
}

#[test]
#[ignore]
fn test_basic_const_values_in_opaque_ty_values() {
@@ -61,3 +75,4 @@ fn test_basic_const_values_in_assoc_ty_values() {
}
);
}

77 changes: 77 additions & 0 deletions tests/test/coherence.rs
Original file line number Diff line number Diff line change
@@ -516,3 +516,80 @@ fn orphan_check() {
}
}
}

#[test]
fn unevaluated_const_no_intersect() {
lowering_success! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<3> for Baz {}
impl Foo<2?> for Baz {}
}
}

lowering_success! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<3?> for Baz {}
impl Foo<2?> for Baz {}
}
}
}

#[test]
fn unevaluated_const_intersect() {
lowering_error! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<3> for Baz {}
impl Foo<3?> for Baz {}
} error_msg {
"overlapping impls of trait `Foo`"
}
}

lowering_error! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<3?> for Baz {}
impl Foo<3?> for Baz {}
} error_msg {
"overlapping impls of trait `Foo`"
}
}
}

#[test]
fn unevaluated_const_too_generic() {
lowering_error! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<3> for Baz {}
impl Foo<?> for Baz {}
} error_msg {
"overlapping impls of trait `Foo`"
}
}

lowering_error! {
program {
trait Foo<const N> { }
struct Baz { }

impl Foo<?> for Baz {}
impl Foo<?> for Baz {}
} error_msg {
"overlapping impls of trait `Foo`"
}
}
}
110 changes: 110 additions & 0 deletions tests/test/constants.rs
Original file line number Diff line number Diff line change
@@ -27,12 +27,29 @@ fn single_impl() {
"Unique"
}

goal {
S<3?>: Trait
} yields {
"Unique"
}

goal {
S<5>: Trait
} yields {
"No possible solution"
}

goal {
S<5?>: Trait
} yields {
"No possible solution"
}

goal {
S<?>: Trait
} yields {
"Ambiguous; no inference guidance"
}

goal {
forall<const N> {
@@ -105,6 +122,99 @@ fn generic_impl() {
}
}

#[test]
fn unevaluated_impl() {
test! {
program {
struct S<const N> {}

trait Trait {}

impl Trait for S<3?> {}
}

goal {
exists<const N> {
S<N>: Trait
}
} yields {
"Unique; substitution [?0 := 3], lifetime constraints []"
}

goal {
forall<const N> {
S<N>: Trait
}
} yields {
"No possible solution"
}

goal {
S<8>: Trait
} yields {
"No possible solution"
}

goal {
S<3>: Trait
} yields {
"Unique"
}

goal {
S<?>: Trait
} yields {
"Ambiguous; no inference guidance"
}

goal {
S<3?>: Trait
} yields {
"Unique"
}

goal {
S<8?>: Trait
} yields {
"No possible solution"
}
}
}

#[test]
fn unevaluated_impl_generic() {
test! {
program {
struct S<const N> {}

trait Trait {}

impl Trait for S<?> {}
}

goal {
S<0>: Trait
} yields {
"Ambiguous; no inference guidance"
}

goal {
S<?>: Trait
} yields {
"Unique"
Copy link
Member

Choose a reason for hiding this comment

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

See, this should be Ambiguous, for a similar reason that two InferenceVars aren't the same: we don't actually know this would evaluate to the same 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've been assuming that for chalk testing purposes that ? evaluates to a specific but unknown constant. I don't feel like breaking reflexivity.

Copy link
Member

Choose a reason for hiding this comment

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

But that's not true. The point here is that ? represents a constant that can't be evaluated yet. Importantly, if you have two constants that can't be evaluated (e.g. {N+1} and {X +1}), then we can't say that if there is a solution that covers one case, there is a solution that covers all cases.

Copy link
Author

Choose a reason for hiding this comment

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

As it turns out, removing const_eq for unevaluated consts and assuming that two ?s don't necessarily evaluate to the same value breaks LocalImplAllowed(Baz: Foo<?>) in the unevaluated_const_unknown coherence test.

Copy link
Member

Choose a reason for hiding this comment

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

That's probably okay and what we expect :) That can be a lowering_error

Copy link
Author

Choose a reason for hiding this comment

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

Wouldn't it be weird for it to fail because it violated the orphan rules, though? impl Foo<?> for Baz isn't violating the orphan rules because Foo is local.

Copy link
Member

Choose a reason for hiding this comment

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

Oops, I never responded to this. Sorry about that.

So, I think theoretically we could always say that impl Foo<?0> for Baz {} never violates orphan rules, since both Foo and Baz are local. However, I think for unevaluated consts, it's okay to say it's Ambiguous if we get TooGeneric. I would have to look more into the orphan rules code to figure out how to best handle this. For this PR, we can mark a comment or FIXME and file a followup issue for more discussion.

}

// We don't know if the constant evaluates to an invalid value
goal {
exists<N> {
S<N>: Trait
}
} yields {
"Ambiguous; no inference guidance"
}
}
}

#[test]
fn placeholders_eq() {
test! {