Skip to content

Commit 77246f3

Browse files
committed
Clarify treatment of unification ambiguity and overhaul treatment of
normalization fallback.
1 parent d236626 commit 77246f3

File tree

12 files changed

+237
-118
lines changed

12 files changed

+237
-118
lines changed

src/bin/repl.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ fn process(command: &str, rl: &mut rustyline::Editor<()>, prog: &mut Option<Prog
8686
ir::set_current_program(&prog.ir, || -> Result<()> {
8787
match command {
8888
"print" => println!("{}", prog.text),
89-
"lowered" => println!("{:?}", prog.ir),
89+
"lowered" => println!("{:#?}", prog.env),
9090
_ => goal(command, prog)?,
9191
}
9292
Ok(())
@@ -120,11 +120,8 @@ fn goal(text: &str, prog: &Program) -> Result<()> {
120120
let goal = chalk_parse::parse_goal(text)?.lower(&*prog.ir)?;
121121
let overflow_depth = 10;
122122
let mut solver = Solver::new(&prog.env, overflow_depth);
123-
let goal = ir::Canonical {
124-
value: ir::InEnvironment::new(&ir::Environment::new(), *goal),
125-
binders: vec![],
126-
};
127-
match solver.solve_goal(goal) {
123+
let goal = ir::InEnvironment::new(&ir::Environment::new(), *goal);
124+
match solver.solve_closed_goal(goal) {
128125
Ok(v) => println!("{}\n", v),
129126
Err(e) => println!("No possible solution: {}\n", e),
130127
}

src/fold/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ macro_rules! enum_fold {
196196
}
197197

198198
enum_fold!(ParameterKind[T,L] { Ty(a), Lifetime(a) } where T: Fold, L: Fold);
199-
enum_fold!(DomainGoal[] { Implemented(a), RawNormalize(a), Normalize(a), WellFormed(a) });
199+
enum_fold!(DomainGoal[] { Implemented(a), Normalize(a), WellFormed(a) });
200200
enum_fold!(WellFormed[] { Ty(a), TraitRef(a) });
201201
enum_fold!(LeafGoal[] { EqGoal(a), DomainGoal(a) });
202202
enum_fold!(Constraint[] { LifetimeEq(a, b) });

src/ir/debug.rs

-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ impl Debug for DomainGoal {
149149
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
150150
match *self {
151151
DomainGoal::Normalize(ref n) => write!(fmt, "{:?}", n),
152-
DomainGoal::RawNormalize(ref n) => write!(fmt, "raw {{ {:?} }}", n),
153152
DomainGoal::Implemented(ref n) => {
154153
write!(fmt,
155154
"{:?}: {:?}{:?}",

src/ir/mod.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ impl Environment {
129129
push_clause(where_clause);
130130
}
131131
}
132-
DomainGoal::RawNormalize(Normalize { ref projection, ty: _ }) => {
133-
// raw { <T as Trait<U>>::Foo ===> V }
132+
DomainGoal::Normalize(Normalize { ref projection, ty: _ }) => {
133+
// <T as Trait<U>>::Foo ===> V
134134
// ----------------------------------------------------------
135135
// T: Trait<U>
136136

@@ -396,9 +396,6 @@ pub struct TraitRef {
396396
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
397397
pub enum DomainGoal {
398398
Implemented(TraitRef),
399-
/// A projection we know definitively via an impl or where clause
400-
RawNormalize(Normalize),
401-
/// A general projection, which might employ fallback
402399
Normalize(Normalize),
403400
WellFormed(WellFormed),
404401
}
@@ -414,7 +411,8 @@ impl DomainGoal {
414411
conditions: vec![],
415412
},
416413
binders: vec![],
417-
}
414+
},
415+
fallback_clause: false,
418416
}
419417
}
420418
}
@@ -478,6 +476,9 @@ impl<T> Binders<T> {
478476
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
479477
pub struct ProgramClause {
480478
pub implication: Binders<ProgramClauseImplication>,
479+
480+
/// Is this a fallback clause which should get lower priority?
481+
pub fallback_clause: bool,
481482
}
482483

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

593594
Substitution { tys, lifetimes }
594595
}
596+
597+
pub fn is_empty(&self) -> bool {
598+
self.tys.is_empty() && self.lifetimes.is_empty()
599+
}
595600
}
596601

597602
#[derive(Clone, Debug, PartialEq, Eq)]

src/lower/mod.rs

+22-55
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,7 @@ impl LowerWhereClause<ir::DomainGoal> for WhereClause {
381381
ir::DomainGoal::Implemented(trait_ref.lower(env)?)
382382
}
383383
WhereClause::ProjectionEq { ref projection, ref ty } => {
384-
// NB: here we generate a RawNormalize, because we want to make
385-
// make a maximally-strong assumption (and not allow fallback to
386-
// trigger).
387-
ir::DomainGoal::RawNormalize(ir::Normalize {
384+
ir::DomainGoal::Normalize(ir::Normalize {
388385
projection: projection.lower(env)?,
389386
ty: ty.lower(env)?,
390387
})
@@ -406,18 +403,11 @@ impl LowerWhereClause<ir::DomainGoal> for WhereClause {
406403
impl LowerWhereClause<ir::LeafGoal> for WhereClause {
407404
fn lower(&self, env: &Env) -> Result<ir::LeafGoal> {
408405
Ok(match *self {
409-
WhereClause::Implemented { .. } => {
406+
WhereClause::Implemented { .. } |
407+
WhereClause::ProjectionEq { .. } => {
410408
let g: ir::DomainGoal = self.lower(env)?;
411409
g.cast()
412410
}
413-
WhereClause::ProjectionEq { ref projection, ref ty } => {
414-
// NB: here we generate a full Normalize clause, allowing for
415-
// fallback to trigger when we're trying to *prove* a goal
416-
ir::DomainGoal::Normalize(ir::Normalize {
417-
projection: projection.lower(env)?,
418-
ty: ty.lower(env)?,
419-
}).cast()
420-
}
421411
WhereClause::TyWellFormed { ref ty } => {
422412
ir::WellFormed::Ty(ty.lower(env)?).cast()
423413
}
@@ -806,7 +796,8 @@ impl ir::ImplDatum {
806796
consequence: bound.trait_ref.clone().cast(),
807797
conditions: bound.where_clauses.clone().cast(),
808798
}
809-
})
799+
}),
800+
fallback_clause: false,
810801
}
811802
}
812803
}
@@ -824,7 +815,7 @@ impl ir::AssociatedTyValue {
824815
///
825816
/// ```notrust
826817
/// forall<'a, T> {
827-
/// (Vec<T>: Iterable<IntoIter<'a> =raw Iter<'a, T>>) :-
818+
/// (Vec<T>: Iterable<IntoIter<'a> = Iter<'a, T>>) :-
828819
/// (Vec<T>: Iterable), // (1)
829820
/// (T: 'a) // (2)
830821
/// }
@@ -868,13 +859,14 @@ impl ir::AssociatedTyValue {
868859
implication: ir::Binders {
869860
binders: all_binders.clone(),
870861
value: ir::ProgramClauseImplication {
871-
consequence: ir::DomainGoal::RawNormalize(ir::Normalize {
862+
consequence: ir::DomainGoal::Normalize(ir::Normalize {
872863
projection: projection.clone(),
873864
ty: self.value.value.ty.clone()
874865
}),
875866
conditions: conditions.clone(),
876867
}
877-
}
868+
},
869+
fallback_clause: false,
878870
};
879871

880872
vec![normalization]
@@ -932,7 +924,8 @@ impl ir::StructDatum {
932924
.map(|wc| wc.cast())
933925
.collect(),
934926
}
935-
})
927+
}),
928+
fallback_clause: false,
936929
};
937930

938931
vec![wf]
@@ -972,7 +965,8 @@ impl ir::TraitDatum {
972965
tys.chain(where_clauses).collect()
973966
}
974967
}
975-
})
968+
}),
969+
fallback_clause: false,
976970
};
977971

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

982976
impl ir::AssociatedTyDatum {
983977
fn to_program_clauses(&self) -> Vec<ir::ProgramClause> {
984-
// For each associated type, we define normalization including a
985-
// "fallback" if we can't resolve a projection using an impl/where clauses.
978+
// For each associated type, we define a normalization "fallback" for
979+
// projecting when we don't have constraints to say anything interesting
980+
// about an associated type.
986981
//
987982
// Given:
988983
//
@@ -992,8 +987,7 @@ impl ir::AssociatedTyDatum {
992987
//
993988
// we generate:
994989
//
995-
// ?T: Foo<Assoc = ?U> :- ?T: Foo<Assoc =raw ?U>.
996-
// ?T: Foo<Assoc = (Foo::Assoc)<?T>> :- not { exists<U> { ?T: Foo<Assoc =raw U> } }.
990+
// <?T as Foo>::Assoc ==> (Foo::Assoc)<?T>.
997991

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

1005-
let raw = {
1006-
let binders: Vec<_> = binders.iter()
1007-
.cloned()
1008-
.chain(Some(ir::ParameterKind::Ty(())))
1009-
.collect();
1010-
let ty = ir::Ty::Var(binders.len() - 1);
1011-
let normalize = ir::Normalize { projection: projection.clone(), ty };
1012-
1013-
ir::ProgramClause {
1014-
implication: ir::Binders {
1015-
binders,
1016-
value: ir::ProgramClauseImplication {
1017-
consequence: normalize.clone().cast(),
1018-
conditions: vec![ir::DomainGoal::RawNormalize(normalize).cast()],
1019-
}
1020-
}
1021-
}
1022-
};
1023-
1024999
let fallback = {
10251000
// Construct an application from the projection. So if we have `<T as Iterator>::Item`,
10261001
// we would produce `(Iterator::Item)<T>`.
10271002
let app = ir::ApplicationTy { name: ir::TypeName::AssociatedType(self.id), parameters };
10281003
let ty = ir::Ty::Apply(app);
10291004

1030-
let raw = ir::DomainGoal::RawNormalize(ir::Normalize {
1031-
projection: projection.clone().up_shift(1),
1032-
ty: ir::Ty::Var(0),
1033-
});
1034-
let exists_binders = ir::Binders {
1035-
binders: vec![ir::ParameterKind::Ty(())],
1036-
value: Box::new(raw.cast()),
1037-
};
1038-
let exists = ir::Goal::Quantified(ir::QuantifierKind::Exists, exists_binders);
1039-
10401005
ir::ProgramClause {
10411006
implication: ir::Binders {
10421007
binders,
10431008
value: ir::ProgramClauseImplication {
10441009
consequence: ir::Normalize { projection: projection.clone(), ty }.cast(),
1045-
conditions: vec![ir::Goal::Not(Box::new(exists))]
1010+
// TODO: should probably include the TraitRef here
1011+
conditions: vec![],
10461012
}
1047-
}
1013+
},
1014+
fallback_clause: true,
10481015
}
10491016
};
10501017

1051-
vec![raw, fallback]
1018+
vec![fallback]
10521019
}
10531020
}

src/solve/fulfill.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ impl Outcome {
2323
}
2424
}
2525

26+
pub struct UnifyOutcome {
27+
pub ambiguous: bool,
28+
}
29+
2630
/// A goal that must be resolved
2731
#[derive(Clone, Debug, PartialEq, Eq)]
2832
enum Obligation {
@@ -115,7 +119,7 @@ impl<'s> Fulfill<'s> {
115119
///
116120
/// Wraps `InferenceTable::unify`; any resulting normalizations are added
117121
/// into our list of pending obligations with the given environment.
118-
pub fn unify<T>(&mut self, environment: &Arc<Environment>, a: &T, b: &T) -> Result<()>
122+
pub fn unify<T>(&mut self, environment: &Arc<Environment>, a: &T, b: &T) -> Result<UnifyOutcome>
119123
where T: ?Sized + Zip + Debug
120124
{
121125
let UnificationResult { goals, constraints, ambiguous } =
@@ -127,7 +131,7 @@ impl<'s> Fulfill<'s> {
127131
self.constraints.extend(constraints);
128132
self.obligations.extend(goals.into_iter().map(Obligation::Prove));
129133
self.ambiguous = self.ambiguous || ambiguous;
130-
Ok(())
134+
Ok(UnifyOutcome { ambiguous })
131135
}
132136

133137
/// Create obligations for the given goal in the given environment. This may
@@ -173,7 +177,8 @@ impl<'s> Fulfill<'s> {
173177
self.push_goal(environment, *subgoal2);
174178
}
175179
Goal::Not(subgoal) => {
176-
self.obligations.push(Obligation::Refute(InEnvironment::new(environment, *subgoal)));
180+
let in_env = InEnvironment::new(environment, *subgoal);
181+
self.obligations.push(Obligation::Refute(in_env));
177182
}
178183
Goal::Leaf(wc) => {
179184
self.obligations.push(Obligation::Prove(InEnvironment::new(environment, wc)));
@@ -218,7 +223,7 @@ impl<'s> Fulfill<'s> {
218223
}
219224

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

358-
if subst.is_trivial(&mut self.infer) {
363+
if subst.is_trivial_within(&mut self.infer) {
359364
// In this case, we didn't learn *anything* definitively. So now, we
360365
// go one last time through the positive obligations, this time
361366
// applying even *tentative* inference suggestions, so that we can

src/solve/infer/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ impl Lifetime {
159159
impl Substitution {
160160
/// Check whether this substitution is the identity substitution in the
161161
/// given inference context.
162-
pub fn is_trivial(&self, in_infer: &mut InferenceTable) -> bool {
162+
pub fn is_trivial_within(&self, in_infer: &mut InferenceTable) -> bool {
163163
for ty in self.tys.values() {
164164
if let Some(var) = ty.inference_var() {
165165
if in_infer.probe_var(var).is_some() {

src/solve/infer/unify.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ pub struct UnificationResult {
4343
pub constraints: Vec<InEnvironment<Constraint>>,
4444

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

@@ -115,8 +116,16 @@ impl<'t> Unifier<'t> {
115116
(&Ty::Apply(ref apply1), &Ty::Apply(ref apply2)) => {
116117
if apply1.name != apply2.name {
117118
if apply1.name.is_for_all() || apply2.name.is_for_all() {
119+
// we're being asked to prove something like `!0 = !1`
120+
// or `!0 = i32`. We interpret this as being asked
121+
// whether that holds *for all subtitutions*. Thus, the
122+
// answer is always *maybe* (ambiguous). That means we get:
123+
//
124+
// forall<T, U> { T = U } // Ambig
125+
// forall<T, U> { not { T = U } } // Ambig
126+
118127
self.ambiguous = true;
119-
return Ok(());
128+
return Ok(())
120129
} else {
121130
bail!("cannot equate `{:?}` and `{:?}`", apply1.name, apply2.name);
122131
}

0 commit comments

Comments
 (0)