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

Clarify ambiguity during unification; overhaul normalization fallback #38

Merged
merged 2 commits into from
Jun 13, 2017
Merged
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
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
14 changes: 12 additions & 2 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,7 +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(())
} else {
bail!("cannot equate `{:?}` and `{:?}`", apply1.name, apply2.name);
}
Expand Down
Loading