Skip to content

Commit 17db1df

Browse files
authored
Merge pull request #325 from nikomatsakis/goals-and-folding-goals
Goals and folding goals
2 parents af48f30 + 870e9c5 commit 17db1df

File tree

22 files changed

+485
-323
lines changed

22 files changed

+485
-323
lines changed

book/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- [Rust types](./types/rust_types.md)
1111
- [Application types](./types/rust_types/application_ty.md)
1212
- [Rust lifetimes](./types/rust_lifetimes.md)
13+
- [Operations](./types/operations.md)
14+
- [Fold and the Folder trait](./types/operations/fold.md)
1315
- [Representing traits, impls, and other parts of Rust programs](./rust_ir.md)
1416
- [Lowering Rust IR to logic](./clauses.md)
1517
- [Unification and type equality](./clauses/type_equality.md)

book/src/types/operations.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Operations
2+
3+
This chapter describes various patterns and utilities for manipulating
4+
Rust types.

book/src/types/operations/fold.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Fold and the Folder trait
2+
3+
The [`Fold`] trait permits one to traverse a type or other term in the
4+
chalk-ir and make a copy of it, possibly making small substitutions or
5+
alterations along the way. Folding also allows copying a term from one
6+
type family to another.
7+
8+
[`Fold`]: http://rust-lang.github.io/chalk/chalk_ir/fold/trait.Fold.html
9+
10+
To use the [`Fold`] trait, one invokes the [`Fold::fold_with`] method, supplying some
11+
"folder" as well as the number of "in scope binders" for that term (typically `0`
12+
to start):
13+
14+
```rust,ignore
15+
let output_ty = input_ty.fold_with(&mut folder, 0);
16+
```
17+
18+
[`Fold::fold_with`]: http://rust-lang.github.io/chalk/chalk_ir/fold/trait.Fold.html#tymethod.fold_with
19+
20+
The folder is some instance of the [`Folder`] trait. This trait
21+
defines a few key callbacks that allow you to substitute different
22+
values as the fold proceeds. For example, when a type is folded, the
23+
folder can substitute a new type in its place.
24+
25+
[`Folder`]: http://rust-lang.github.io/chalk/chalk_ir/fold/trait.Folder.html
26+
27+
## Uses for folders
28+
29+
A common use for `Fold` is to permit a substitution -- that is,
30+
replacing generic type parameters with their values.
31+
32+
## From Fold to Folder to SuperFold and back again
33+
34+
The overall flow of folding is like this.
35+
36+
1. [`Fold::fold_with`] is invoked on the outermost term. It recursively
37+
walks the term.
38+
2. For those sorts of terms (types, lifetimes, goals, program clauses) that have
39+
callbacks in the [`Folder`] trait, invoking [`Fold::fold_with`] will in turn
40+
invoke the corresponding method on the [`Folder`] trait, such as `Folder::fold_ty`.
41+
3. The default implementation of `Folder::fold_ty`, in turn, invokes
42+
`SuperFold::super_fold_with`. This will recursively fold the
43+
contents of the type. In some cases, the `super_fold_with`
44+
implementation invokes more specialized methods on [`Folder`], such
45+
as [`Folder::fold_free_var_ty`], which makes it easier to write
46+
folders that just intercept *certain* types.
47+
48+
Thus, as a user, you can customize folding by:
49+
50+
* Defining your own `Folder` type
51+
* Implementing the appropriate methods to "intercept" types/lifetimes/etc at the right level of
52+
detail
53+
* In those methods, if you find a case where you would prefer not to
54+
substitute a new value, then invoke `SuperFold::super_fold_with` to
55+
return to the default behavior.
56+
57+
## The `binders` argument
58+
59+
Each callback in the [`Folder`] trait takes a `binders` argument. This indicates
60+
the number of binders that we have traversed during folding, which is relevant for debruijn indices.
61+
So e.g. a bound variable with depth 1, if invoked with a `binders` value of 1, indicates something that was bound to something external to the fold.
62+
63+
XXX explain with examples and in more detail
64+
65+
## The `Fold::Result` associated type
66+
67+
The `Fold` trait defines a [`Result`] associated type, indicating the
68+
type that will result from folding.
69+
70+
[`Result`]: http://rust-lang.github.io/chalk/chalk_ir/fold/trait.Fold.html#associatedtype.Result
71+
72+
## When to implement the Fold and SuperFold traits
73+
74+
Any piece of IR that represents a kind of "term" (e.g., a type, part
75+
of a type, or a goal, etc) in the logic should implement `Fold`. We
76+
also implement `Fold` for common collection types like `Vec` as well
77+
as tuples, references, etc.
78+
79+
The `SuperFold` trait should only be implemented for those types that
80+
have a callback defined on the `Folder` trait (e.g., types and
81+
lifetimes).
82+
83+
## Derives
84+
85+
Using the `chalk-derive` crate, you can auto-derive the `Fold` trait.
86+
There isn't presently a derive for `SuperFold` since it is very rare
87+
to require it. The derive for `Fold` is a bit cludgy and requires:
88+
89+
* You must import `Fold` into scope.
90+
* The type you are deriving `Fold` on must have either:
91+
* A type parameter that has a `TypeFamily` bound, like `TF: TypeFamily`
92+
* A type parameter that has a `HasTypeFamily` bound, like `TF: HasTypeFamily`
93+
* The `has_type_family(XXX)` attribute.
94+
95+

chalk-integration/src/lowering.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,16 +1134,12 @@ impl LowerClause for Clause {
11341134
let implications = env.in_binders(self.all_parameters(), |env| {
11351135
let consequences: Vec<chalk_ir::DomainGoal<ChalkIr>> = self.consequence.lower(env)?;
11361136

1137-
let conditions: Vec<chalk_ir::Goal<ChalkIr>> = self
1138-
.conditions
1139-
.iter()
1140-
.map(|g| g.lower(env))
1141-
.rev() // (*)
1142-
.collect::<LowerResult<_>>()?;
1143-
1144-
// (*) Subtle: in the SLG solver, we pop conditions from R to
1145-
// L. To preserve the expected order (L to R), we must
1146-
// therefore reverse.
1137+
let conditions = chalk_ir::Goals::from_fallible(
1138+
// Subtle: in the SLG solver, we pop conditions from R to
1139+
// L. To preserve the expected order (L to R), we must
1140+
// therefore reverse.
1141+
self.conditions.iter().map(|g| g.lower(env)).rev(),
1142+
)?;
11471143

11481144
let implications = consequences
11491145
.into_iter()
@@ -1270,11 +1266,9 @@ impl<'k> LowerGoal<Env<'k>> for Goal {
12701266
Ok(chalk_ir::GoalData::Implies(where_clauses?, g.lower(env)?).intern())
12711267
}
12721268
Goal::And(g1, g2s) => {
1273-
let mut goals = vec![];
1274-
goals.push(g1.lower(env)?);
1275-
for g2 in g2s {
1276-
goals.push(g2.lower(env)?);
1277-
}
1269+
let goals = chalk_ir::Goals::from_fallible(
1270+
Some(g1).into_iter().chain(g2s).map(|g| g.lower(env)),
1271+
)?;
12781272
Ok(chalk_ir::GoalData::All(goals).intern())
12791273
}
12801274
Goal::Not(g) => Ok(chalk_ir::GoalData::Not(g.lower(env)?).intern()),

chalk-ir/src/cast.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ where
183183
fn cast_to(self) -> ProgramClause<TF> {
184184
ProgramClause::Implies(ProgramClauseImplication {
185185
consequence: self.cast(),
186-
conditions: vec![],
186+
conditions: Goals::new(),
187187
})
188188
}
189189
}
@@ -199,7 +199,7 @@ where
199199
} else {
200200
ProgramClause::ForAll(self.map(|bound| ProgramClauseImplication {
201201
consequence: bound.cast(),
202-
conditions: vec![],
202+
conditions: Goals::new(),
203203
}))
204204
}
205205
}

chalk-ir/src/debug.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,7 @@ impl<TF: TypeFamily> Debug for Goal<TF> {
279279
write!(fmt, "> {{ {:?} }}", subgoal.value)
280280
}
281281
GoalData::Implies(ref wc, ref g) => write!(fmt, "if ({:?}) {{ {:?} }}", wc, g),
282-
GoalData::All(ref goals) => {
283-
write!(fmt, "all(")?;
284-
for (goal, index) in goals.iter().zip(0..) {
285-
if index > 0 {
286-
write!(fmt, ", ")?;
287-
}
288-
write!(fmt, "{:?}", goal)?;
289-
}
290-
write!(fmt, ")")?;
291-
Ok(())
292-
}
282+
GoalData::All(ref goals) => write!(fmt, "all{:?}", goals),
293283
GoalData::Not(ref g) => write!(fmt, "not {{ {:?} }}", g),
294284
GoalData::EqGoal(ref wc) => write!(fmt, "{:?}", wc),
295285
GoalData::DomainGoal(ref wc) => write!(fmt, "{:?}", wc),
@@ -298,6 +288,20 @@ impl<TF: TypeFamily> Debug for Goal<TF> {
298288
}
299289
}
300290

291+
impl<TF: TypeFamily> Debug for Goals<TF> {
292+
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
293+
write!(fmt, "(")?;
294+
for (goal, index) in self.iter().zip(0..) {
295+
if index > 0 {
296+
write!(fmt, ", ")?;
297+
}
298+
write!(fmt, "{:?}", goal)?;
299+
}
300+
write!(fmt, ")")?;
301+
Ok(())
302+
}
303+
}
304+
301305
impl<T: Debug> Debug for Binders<T> {
302306
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
303307
let Binders {
@@ -334,16 +338,18 @@ impl<TF: TypeFamily> Debug for ProgramClauseImplication<TF> {
334338
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
335339
write!(fmt, "{:?}", self.consequence)?;
336340

337-
let conds = self.conditions.len();
341+
let conditions = self.conditions.as_slice();
342+
343+
let conds = conditions.len();
338344
if conds == 0 {
339345
return Ok(());
340346
}
341347

342348
write!(fmt, " :- ")?;
343-
for cond in &self.conditions[..conds - 1] {
349+
for cond in &conditions[..conds - 1] {
344350
write!(fmt, "{:?}, ", cond)?;
345351
}
346-
write!(fmt, "{:?}", self.conditions[conds - 1])
352+
write!(fmt, "{:?}", conditions[conds - 1])
347353
}
348354
}
349355

chalk-ir/src/family.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::tls;
22
use crate::AliasTy;
33
use crate::AssocTypeId;
4+
use crate::Goal;
45
use crate::GoalData;
56
use crate::LifetimeData;
67
use crate::Parameter;
@@ -69,6 +70,14 @@ pub trait TypeFamily: Debug + Copy + Eq + Ord + Hash {
6970
/// converted back to its underlying data via `goal_data`.
7071
type InternedGoal: Debug + Clone + Eq + Ord + Hash;
7172

73+
/// "Interned" representation of a list of goals. In normal user code,
74+
/// `Self::InternedGoals` is not referenced. Instead, we refer to
75+
/// `Goals<Self>`, which wraps this type.
76+
///
77+
/// An `InternedGoals` is created by `intern_goals` and can be
78+
/// converted back to its underlying data via `goals_data`.
79+
type InternedGoals: Debug + Clone + Eq + Ord + Hash;
80+
7281
/// "Interned" representation of a "substitution". In normal user code,
7382
/// `Self::InternedSubstitution` is not referenced. Instead, we refer to
7483
/// `Substitution<Self>`, which wraps this type.
@@ -155,6 +164,15 @@ pub trait TypeFamily: Debug + Copy + Eq + Ord + Hash {
155164
/// Lookup the `GoalData` that was interned to create a `InternedGoal`.
156165
fn goal_data(goal: &Self::InternedGoal) -> &GoalData<Self>;
157166

167+
/// Create an "interned" goals from `data`. This is not
168+
/// normally invoked directly; instead, you invoke
169+
/// `GoalsData::intern` (which will ultimately call this
170+
/// method).
171+
fn intern_goals(data: impl IntoIterator<Item = Goal<Self>>) -> Self::InternedGoals;
172+
173+
/// Lookup the `GoalsData` that was interned to create a `InternedGoals`.
174+
fn goals_data(goals: &Self::InternedGoals) -> &[Goal<Self>];
175+
158176
/// Create an "interned" substitution from `data`. This is not
159177
/// normally invoked directly; instead, you invoke
160178
/// `SubstitutionData::intern` (which will ultimately call this
@@ -198,6 +216,7 @@ impl TypeFamily for ChalkIr {
198216
type InternedLifetime = LifetimeData<ChalkIr>;
199217
type InternedParameter = ParameterData<ChalkIr>;
200218
type InternedGoal = Arc<GoalData<ChalkIr>>;
219+
type InternedGoals = Vec<Goal<ChalkIr>>;
201220
type InternedSubstitution = Vec<Parameter<ChalkIr>>;
202221
type DefId = RawId;
203222

@@ -258,6 +277,14 @@ impl TypeFamily for ChalkIr {
258277
goal
259278
}
260279

280+
fn intern_goals(data: impl IntoIterator<Item = Goal<ChalkIr>>) -> Vec<Goal<ChalkIr>> {
281+
data.into_iter().collect()
282+
}
283+
284+
fn goals_data(goals: &Vec<Goal<ChalkIr>>) -> &[Goal<ChalkIr>] {
285+
goals
286+
}
287+
261288
fn intern_substitution<E>(
262289
data: impl IntoIterator<Item = Result<Parameter<ChalkIr>, E>>,
263290
) -> Result<Vec<Parameter<ChalkIr>>, E> {

0 commit comments

Comments
 (0)