Skip to content

Commit

Permalink
Clarify treatment of unification ambiguity and overhaul treatment of
Browse files Browse the repository at this point in the history
normalization fallback.
  • Loading branch information
aturon committed Jun 7, 2017
1 parent d236626 commit 77246f3
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 118 deletions.
9 changes: 3 additions & 6 deletions src/bin/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ fn process(command: &str, rl: &mut rustyline::Editor<()>, prog: &mut Option<Prog
ir::set_current_program(&prog.ir, || -> Result<()> {
match command {
"print" => println!("{}", prog.text),
"lowered" => println!("{:?}", prog.ir),
"lowered" => println!("{:#?}", prog.env),
_ => goal(command, prog)?,
}
Ok(())
Expand Down Expand Up @@ -120,11 +120,8 @@ fn goal(text: &str, prog: &Program) -> Result<()> {
let goal = chalk_parse::parse_goal(text)?.lower(&*prog.ir)?;
let overflow_depth = 10;
let mut solver = Solver::new(&prog.env, overflow_depth);
let goal = ir::Canonical {
value: ir::InEnvironment::new(&ir::Environment::new(), *goal),
binders: vec![],
};
match solver.solve_goal(goal) {
let goal = ir::InEnvironment::new(&ir::Environment::new(), *goal);
match solver.solve_closed_goal(goal) {
Ok(v) => println!("{}\n", v),
Err(e) => println!("No possible solution: {}\n", e),
}
Expand Down
2 changes: 1 addition & 1 deletion src/fold/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ macro_rules! enum_fold {
}

enum_fold!(ParameterKind[T,L] { Ty(a), Lifetime(a) } where T: Fold, L: Fold);
enum_fold!(DomainGoal[] { Implemented(a), RawNormalize(a), Normalize(a), WellFormed(a) });
enum_fold!(DomainGoal[] { Implemented(a), Normalize(a), WellFormed(a) });
enum_fold!(WellFormed[] { Ty(a), TraitRef(a) });
enum_fold!(LeafGoal[] { EqGoal(a), DomainGoal(a) });
enum_fold!(Constraint[] { LifetimeEq(a, b) });
Expand Down
1 change: 0 additions & 1 deletion src/ir/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ impl Debug for DomainGoal {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
match *self {
DomainGoal::Normalize(ref n) => write!(fmt, "{:?}", n),
DomainGoal::RawNormalize(ref n) => write!(fmt, "raw {{ {:?} }}", n),
DomainGoal::Implemented(ref n) => {
write!(fmt,
"{:?}: {:?}{:?}",
Expand Down
17 changes: 11 additions & 6 deletions src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ impl Environment {
push_clause(where_clause);
}
}
DomainGoal::RawNormalize(Normalize { ref projection, ty: _ }) => {
// raw { <T as Trait<U>>::Foo ===> V }
DomainGoal::Normalize(Normalize { ref projection, ty: _ }) => {
// <T as Trait<U>>::Foo ===> V
// ----------------------------------------------------------
// T: Trait<U>

Expand Down Expand Up @@ -396,9 +396,6 @@ pub struct TraitRef {
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DomainGoal {
Implemented(TraitRef),
/// A projection we know definitively via an impl or where clause
RawNormalize(Normalize),
/// A general projection, which might employ fallback
Normalize(Normalize),
WellFormed(WellFormed),
}
Expand All @@ -414,7 +411,8 @@ impl DomainGoal {
conditions: vec![],
},
binders: vec![],
}
},
fallback_clause: false,
}
}
}
Expand Down Expand Up @@ -478,6 +476,9 @@ impl<T> Binders<T> {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ProgramClause {
pub implication: Binders<ProgramClauseImplication>,

/// Is this a fallback clause which should get lower priority?
pub fallback_clause: bool,
}

/// Represents one clause of the form `consequence :- conditions`.
Expand Down Expand Up @@ -592,6 +593,10 @@ impl Substitution {

Substitution { tys, lifetimes }
}

pub fn is_empty(&self) -> bool {
self.tys.is_empty() && self.lifetimes.is_empty()
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down
77 changes: 22 additions & 55 deletions src/lower/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,10 +381,7 @@ impl LowerWhereClause<ir::DomainGoal> for WhereClause {
ir::DomainGoal::Implemented(trait_ref.lower(env)?)
}
WhereClause::ProjectionEq { ref projection, ref ty } => {
// NB: here we generate a RawNormalize, because we want to make
// make a maximally-strong assumption (and not allow fallback to
// trigger).
ir::DomainGoal::RawNormalize(ir::Normalize {
ir::DomainGoal::Normalize(ir::Normalize {
projection: projection.lower(env)?,
ty: ty.lower(env)?,
})
Expand All @@ -406,18 +403,11 @@ impl LowerWhereClause<ir::DomainGoal> for WhereClause {
impl LowerWhereClause<ir::LeafGoal> for WhereClause {
fn lower(&self, env: &Env) -> Result<ir::LeafGoal> {
Ok(match *self {
WhereClause::Implemented { .. } => {
WhereClause::Implemented { .. } |
WhereClause::ProjectionEq { .. } => {
let g: ir::DomainGoal = self.lower(env)?;
g.cast()
}
WhereClause::ProjectionEq { ref projection, ref ty } => {
// NB: here we generate a full Normalize clause, allowing for
// fallback to trigger when we're trying to *prove* a goal
ir::DomainGoal::Normalize(ir::Normalize {
projection: projection.lower(env)?,
ty: ty.lower(env)?,
}).cast()
}
WhereClause::TyWellFormed { ref ty } => {
ir::WellFormed::Ty(ty.lower(env)?).cast()
}
Expand Down Expand Up @@ -806,7 +796,8 @@ impl ir::ImplDatum {
consequence: bound.trait_ref.clone().cast(),
conditions: bound.where_clauses.clone().cast(),
}
})
}),
fallback_clause: false,
}
}
}
Expand All @@ -824,7 +815,7 @@ impl ir::AssociatedTyValue {
///
/// ```notrust
/// forall<'a, T> {
/// (Vec<T>: Iterable<IntoIter<'a> =raw Iter<'a, T>>) :-
/// (Vec<T>: Iterable<IntoIter<'a> = Iter<'a, T>>) :-
/// (Vec<T>: Iterable), // (1)
/// (T: 'a) // (2)
/// }
Expand Down Expand Up @@ -868,13 +859,14 @@ impl ir::AssociatedTyValue {
implication: ir::Binders {
binders: all_binders.clone(),
value: ir::ProgramClauseImplication {
consequence: ir::DomainGoal::RawNormalize(ir::Normalize {
consequence: ir::DomainGoal::Normalize(ir::Normalize {
projection: projection.clone(),
ty: self.value.value.ty.clone()
}),
conditions: conditions.clone(),
}
}
},
fallback_clause: false,
};

vec![normalization]
Expand Down Expand Up @@ -932,7 +924,8 @@ impl ir::StructDatum {
.map(|wc| wc.cast())
.collect(),
}
})
}),
fallback_clause: false,
};

vec![wf]
Expand Down Expand Up @@ -972,7 +965,8 @@ impl ir::TraitDatum {
tys.chain(where_clauses).collect()
}
}
})
}),
fallback_clause: false,
};

vec![wf]
Expand All @@ -981,8 +975,9 @@ impl ir::TraitDatum {

impl ir::AssociatedTyDatum {
fn to_program_clauses(&self) -> Vec<ir::ProgramClause> {
// For each associated type, we define normalization including a
// "fallback" if we can't resolve a projection using an impl/where clauses.
// For each associated type, we define a normalization "fallback" for
// projecting when we don't have constraints to say anything interesting
// about an associated type.
//
// Given:
//
Expand All @@ -992,8 +987,7 @@ impl ir::AssociatedTyDatum {
//
// we generate:
//
// ?T: Foo<Assoc = ?U> :- ?T: Foo<Assoc =raw ?U>.
// ?T: Foo<Assoc = (Foo::Assoc)<?T>> :- not { exists<U> { ?T: Foo<Assoc =raw U> } }.
// <?T as Foo>::Assoc ==> (Foo::Assoc)<?T>.

let binders: Vec<_> = self.parameter_kinds.iter().map(|pk| pk.map(|_| ())).collect();
let parameters: Vec<_> = binders.iter().zip(0..).map(|p| p.to_parameter()).collect();
Expand All @@ -1002,52 +996,25 @@ impl ir::AssociatedTyDatum {
parameters: parameters.clone(),
};

let raw = {
let binders: Vec<_> = binders.iter()
.cloned()
.chain(Some(ir::ParameterKind::Ty(())))
.collect();
let ty = ir::Ty::Var(binders.len() - 1);
let normalize = ir::Normalize { projection: projection.clone(), ty };

ir::ProgramClause {
implication: ir::Binders {
binders,
value: ir::ProgramClauseImplication {
consequence: normalize.clone().cast(),
conditions: vec![ir::DomainGoal::RawNormalize(normalize).cast()],
}
}
}
};

let fallback = {
// Construct an application from the projection. So if we have `<T as Iterator>::Item`,
// we would produce `(Iterator::Item)<T>`.
let app = ir::ApplicationTy { name: ir::TypeName::AssociatedType(self.id), parameters };
let ty = ir::Ty::Apply(app);

let raw = ir::DomainGoal::RawNormalize(ir::Normalize {
projection: projection.clone().up_shift(1),
ty: ir::Ty::Var(0),
});
let exists_binders = ir::Binders {
binders: vec![ir::ParameterKind::Ty(())],
value: Box::new(raw.cast()),
};
let exists = ir::Goal::Quantified(ir::QuantifierKind::Exists, exists_binders);

ir::ProgramClause {
implication: ir::Binders {
binders,
value: ir::ProgramClauseImplication {
consequence: ir::Normalize { projection: projection.clone(), ty }.cast(),
conditions: vec![ir::Goal::Not(Box::new(exists))]
// TODO: should probably include the TraitRef here
conditions: vec![],
}
}
},
fallback_clause: true,
}
};

vec![raw, fallback]
vec![fallback]
}
}
15 changes: 10 additions & 5 deletions src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ impl Outcome {
}
}

pub struct UnifyOutcome {
pub ambiguous: bool,
}

/// A goal that must be resolved
#[derive(Clone, Debug, PartialEq, Eq)]
enum Obligation {
Expand Down Expand Up @@ -115,7 +119,7 @@ impl<'s> Fulfill<'s> {
///
/// Wraps `InferenceTable::unify`; any resulting normalizations are added
/// into our list of pending obligations with the given environment.
pub fn unify<T>(&mut self, environment: &Arc<Environment>, a: &T, b: &T) -> Result<()>
pub fn unify<T>(&mut self, environment: &Arc<Environment>, a: &T, b: &T) -> Result<UnifyOutcome>
where T: ?Sized + Zip + Debug
{
let UnificationResult { goals, constraints, ambiguous } =
Expand All @@ -127,7 +131,7 @@ impl<'s> Fulfill<'s> {
self.constraints.extend(constraints);
self.obligations.extend(goals.into_iter().map(Obligation::Prove));
self.ambiguous = self.ambiguous || ambiguous;
Ok(())
Ok(UnifyOutcome { ambiguous })
}

/// Create obligations for the given goal in the given environment. This may
Expand Down Expand Up @@ -173,7 +177,8 @@ impl<'s> Fulfill<'s> {
self.push_goal(environment, *subgoal2);
}
Goal::Not(subgoal) => {
self.obligations.push(Obligation::Refute(InEnvironment::new(environment, *subgoal)));
let in_env = InEnvironment::new(environment, *subgoal);
self.obligations.push(Obligation::Refute(in_env));
}
Goal::Leaf(wc) => {
self.obligations.push(Obligation::Prove(InEnvironment::new(environment, wc)));
Expand Down Expand Up @@ -218,7 +223,7 @@ impl<'s> Fulfill<'s> {
}

// Negate the result
if let Ok(solution) = self.solver.solve_goal(canonicalized.quantified) {
if let Ok(solution) = self.solver.solve_closed_goal(canonicalized.quantified.value) {
match solution {
Solution::Unique(_) => Err("refutation failed")?,
Solution::Ambig(_) => Ok(NegativeSolution::Ambiguous),
Expand Down Expand Up @@ -355,7 +360,7 @@ impl<'s> Fulfill<'s> {
// need to determine how to package up what we learned about type
// inference as an ambiguous solution.

if subst.is_trivial(&mut self.infer) {
if subst.is_trivial_within(&mut self.infer) {
// In this case, we didn't learn *anything* definitively. So now, we
// go one last time through the positive obligations, this time
// applying even *tentative* inference suggestions, so that we can
Expand Down
2 changes: 1 addition & 1 deletion src/solve/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Lifetime {
impl Substitution {
/// Check whether this substitution is the identity substitution in the
/// given inference context.
pub fn is_trivial(&self, in_infer: &mut InferenceTable) -> bool {
pub fn is_trivial_within(&self, in_infer: &mut InferenceTable) -> bool {
for ty in self.tys.values() {
if let Some(var) = ty.inference_var() {
if in_infer.probe_var(var).is_some() {
Expand Down
15 changes: 12 additions & 3 deletions src/solve/infer/unify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ pub struct UnificationResult {
pub constraints: Vec<InEnvironment<Constraint>>,

/// When unifying two skolemized (forall-quantified) type names, we can
/// neither confirm nor deny their equality, so we return an ambiguous
/// result.
/// neither confirm nor deny their equality, since we interpret the
/// unification request as talking about *all possible
/// substitutions*. Instead, we return an ambiguous result.
pub ambiguous: bool,
}

Expand Down Expand Up @@ -115,8 +116,16 @@ impl<'t> Unifier<'t> {
(&Ty::Apply(ref apply1), &Ty::Apply(ref apply2)) => {
if apply1.name != apply2.name {
if apply1.name.is_for_all() || apply2.name.is_for_all() {
// we're being asked to prove something like `!0 = !1`
// or `!0 = i32`. We interpret this as being asked
// whether that holds *for all subtitutions*. Thus, the
// answer is always *maybe* (ambiguous). That means we get:
//
// forall<T, U> { T = U } // Ambig
// forall<T, U> { not { T = U } } // Ambig

self.ambiguous = true;
return Ok(());
return Ok(())
} else {
bail!("cannot equate `{:?}` and `{:?}`", apply1.name, apply2.name);
}
Expand Down
Loading

0 comments on commit 77246f3

Please sign in to comment.