-
Notifications
You must be signed in to change notification settings - Fork 183
Semantic equality to syntactic equality #401
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -856,6 +856,7 @@ impl<I: Interner> ToProgramClauses<I> for AssociatedTyDatum<I> { | |
|
||
// add new type parameter U | ||
builder.push_bound_ty(|builder, ty| { | ||
let alias = AliasTy::Projection(projection.clone()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just a random refactoring? |
||
// `Normalize(<T as Foo>::Assoc -> U)` | ||
let normalize = Normalize { | ||
alias: AliasTy::Projection(projection.clone()), | ||
|
@@ -864,8 +865,8 @@ impl<I: Interner> ToProgramClauses<I> for AssociatedTyDatum<I> { | |
|
||
// `AliasEq(<T as Foo>::Assoc = U)` | ||
let projection_eq = AliasEq { | ||
alias: AliasTy::Projection(projection), | ||
ty, | ||
alias: alias.clone(), | ||
ty: ty.clone(), | ||
}; | ||
|
||
// Projection equality rule from above. | ||
|
@@ -874,7 +875,7 @@ impl<I: Interner> ToProgramClauses<I> for AssociatedTyDatum<I> { | |
// AliasEq(<T as Foo>::Assoc = U) :- | ||
// Normalize(<T as Foo>::Assoc -> U). | ||
// } | ||
builder.push_clause(projection_eq, Some(normalize)); | ||
builder.push_clause(projection_eq.clone(), Some(normalize)); | ||
}); | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
use std::{iter, mem::replace}; | ||
|
||
use chalk_engine::fallible::Fallible; | ||
use chalk_ir::{ | ||
cast::Cast, | ||
fold::{shift::Shift, Fold, Folder, SuperFold}, | ||
interner::Interner, | ||
AliasEq, AliasTy, Binders, BoundVar, DebruijnIndex, Goal, GoalData, Goals, ProgramClause, | ||
ProgramClauseData, ProgramClauseImplication, QuantifierKind, Ty, TyData, VariableKind, | ||
VariableKinds, | ||
}; | ||
|
||
pub fn syn_eq_lower<I: Interner, T: Fold<I>>(interner: &I, clause: &T) -> <T as Fold<I>>::Result { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs a comment. Also, I wonder if it's worth adding some other trait since you only really want to fold program clauses and goals here..? Nah, probably not, but we should add a comment about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think making it into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, two separate functions would be fine |
||
let mut folder = SynEqFolder { | ||
interner, | ||
new_params: vec![], | ||
new_goals: vec![], | ||
binders_len: 0, | ||
}; | ||
|
||
clause | ||
.fold_with(&mut folder, DebruijnIndex::INNERMOST) | ||
.unwrap() | ||
} | ||
|
||
struct SynEqFolder<'i, I: Interner> { | ||
interner: &'i I, | ||
new_params: Vec<VariableKind<I>>, | ||
new_goals: Vec<Goal<I>>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For each new parameter |
||
binders_len: usize, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stores the number of variables in the binder that we are adding parameters into thus far. Incremented for each new variable we add. |
||
} | ||
|
||
impl<'i, I: Interner> Folder<'i, I> for SynEqFolder<'i, I> { | ||
fn as_dyn(&mut self) -> &mut dyn Folder<'i, I> { | ||
self | ||
} | ||
|
||
fn fold_ty(&mut self, ty: &Ty<I>, outer_binder: DebruijnIndex) -> Fallible<Ty<I>> { | ||
let interner = self.interner; | ||
let bound_var = BoundVar::new(DebruijnIndex::INNERMOST, self.binders_len); | ||
|
||
let new_ty = TyData::BoundVar(bound_var).intern(interner); | ||
match ty.data(interner) { | ||
TyData::Alias(alias @ AliasTy::Projection(_)) => { | ||
self.new_params.push(VariableKind::Ty); | ||
self.new_goals.push( | ||
AliasEq { | ||
alias: alias.clone(), | ||
ty: new_ty.clone(), | ||
} | ||
.cast(interner), | ||
); | ||
self.binders_len += 1; | ||
ty.super_fold_with(self, outer_binder)?; | ||
Ok(new_ty) | ||
} | ||
TyData::Function(_) => Ok(ty.clone()), | ||
_ => Ok(ty.super_fold_with(self, outer_binder)?), | ||
} | ||
} | ||
|
||
fn fold_program_clause( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would include a comment here that gives the overall strategy, which the function body can reference. Of course this was outlined in great detail in the module comment, so something like this would work. In these cases, I like to give very concrete example so that, in the code comments, I can refer to very specific things. e.g.,
|
||
&mut self, | ||
clause: &ProgramClause<I>, | ||
outer_binder: DebruijnIndex, | ||
) -> Fallible<ProgramClause<I>> { | ||
let interner = self.interner; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should either
I don't see why they would be known to be empty, though, so I think we should save the values of the vector. The alternative would be to rewrite the code to not touch pre-existing things and just save the lengths. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we need to save There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We know them to be empty because only domain goals and program clauses contain types (and EqGoal, but it is unique), and when folding domain goals we are guaranteed to not encounter another goal or clause. If we restrict input types of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, then add assertions -- I guess it's true that we only have to worry about domain goal, and it cannot contain clauses. Right. I was thinking about how goals can contain clauses in general (and vice versa, of course). |
||
|
||
let ((binders, implication), in_binders) = match clause.data(interner) { | ||
ProgramClauseData::ForAll(for_all) => (for_all.clone().into(), true), | ||
// introduce a dummy binder and shift implication through it | ||
ProgramClauseData::Implies(implication) => ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I keep meaning to factor away |
||
( | ||
VariableKinds::new(interner), | ||
implication.shifted_in(interner), | ||
), | ||
false, | ||
), | ||
}; | ||
let mut binders: Vec<_> = binders.as_slice(interner).clone().into(); | ||
|
||
let outer_binder = outer_binder.shifted_in(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment: // Adjust the outer binder to account for the binder in the program clause |
||
|
||
self.binders_len = binders.len(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment here, referencing the function comment:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment: Set binders_len to |
||
let consequence = implication.consequence.fold_with(self, outer_binder)?; | ||
// Immediately move `new_params` out of of the folder struct so it's safe | ||
// to call `.fold_with` again | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (then I think you can remove this comment, it's explained more usefully above) |
||
let new_params = replace(&mut self.new_params, vec![]); | ||
let new_goals = replace(&mut self.new_goals, vec![]); | ||
|
||
let mut conditions = implication.conditions.fold_with(self, outer_binder)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment: // Now fold the conditions (in our example, |
||
if new_params.is_empty() && !in_binders { | ||
// shift the clause out since we didn't use the dummy binder | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. imo not worth this special case :) |
||
return Ok(ProgramClauseData::Implies( | ||
ProgramClauseImplication { | ||
consequence, | ||
conditions, | ||
priority: implication.priority, | ||
} | ||
.shifted_out(interner)?, | ||
) | ||
.intern(interner)); | ||
} | ||
|
||
binders.extend(new_params.into_iter()); | ||
|
||
conditions = Goals::from( | ||
interner, | ||
conditions.iter(interner).cloned().chain(new_goals), | ||
); | ||
|
||
Ok(ProgramClauseData::ForAll(Binders::new( | ||
VariableKinds::from(interner, binders), | ||
ProgramClauseImplication { | ||
consequence, | ||
conditions, | ||
priority: implication.priority, | ||
}, | ||
)) | ||
.intern(interner)) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (we would restore the saved fields here) |
||
|
||
fn fold_goal(&mut self, goal: &Goal<I>, outer_binder: DebruijnIndex) -> Fallible<Goal<I>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add comments with a concrete example, siimlar to above |
||
assert!(self.new_params.is_empty(), true); | ||
|
||
let interner = self.interner; | ||
match goal.data(interner) { | ||
GoalData::DomainGoal(_) | GoalData::EqGoal(_) => (), | ||
_ => return goal.super_fold_with(self, outer_binder), | ||
}; | ||
|
||
self.binders_len = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment: // Set |
||
// shifted in because we introduce a new binder | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then here we can talk specifically about what binder you are talking about. I presume it's the new |
||
let outer_binder = outer_binder.shifted_in(); | ||
let syn_goal = goal | ||
.shifted_in(interner) | ||
.super_fold_with(self, outer_binder)?; | ||
let new_params = replace(&mut self.new_params, vec![]); | ||
let new_goals = replace(&mut self.new_goals, vec![]); | ||
|
||
if new_params.is_empty() { | ||
return Ok(goal.clone()); | ||
} | ||
|
||
let goal = GoalData::All(Goals::from( | ||
interner, | ||
iter::once(syn_goal).into_iter().chain(new_goals), | ||
)) | ||
.intern(interner); | ||
|
||
Ok(GoalData::Quantified( | ||
QuantifierKind::Exists, | ||
Binders::new(VariableKinds::from(interner, new_params), goal), | ||
) | ||
.intern(interner)) | ||
} | ||
|
||
fn interner(&self) -> &'i I { | ||
self.interner | ||
} | ||
|
||
fn target_interner(&self) -> &'i I { | ||
self.interner | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing I was considering in doing this was making
ClauseBuilder
generic over twoInterner
values, one of which represented "Semantically equal" predicates and one of which represented "syntactically equal". This helps ensure we are not failing to do the conversion. I'm not sure how much value it really adds, though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I thought about making
syn_eq_lower
to return aSynLowered<Fold::Result>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow -- what is
SynLowered
, just a newtype?