Skip to content

Commit c76f01c

Browse files
committed
Improve comments about type folding/visiting.
I have found this code confusing for years. I've always roughly understood it, but never exactly. I just made my fourth(?) attempt and finally cracked it. This commit improves the comments. In particular, it explicitly describes how you can't do a custom fold/visit of any type; there are actually a handful of "types of interest" (e.g. `Ty`, `Predicate`, `Region`, `Const`) that can be custom folded/visted, and all other types just get a generic traversal. I think this was the part that eluded me on all my prior attempts at understanding. The commit also updates comments to account for some newer changes such as the fallible/infallible folding distinction, does some minor reorderings, and moves one `impl` to a better place.
1 parent 502d6aa commit c76f01c

File tree

2 files changed

+103
-78
lines changed

2 files changed

+103
-78
lines changed

compiler/rustc_middle/src/ty/fold.rs

+92-70
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,45 @@
1-
//! Generalized type folding mechanism. The setup is a bit convoluted
2-
//! but allows for convenient usage. Let T be an instance of some
3-
//! "foldable type" (one which implements `TypeFoldable`) and F be an
4-
//! instance of a "folder" (a type which implements `TypeFolder`). Then
5-
//! the setup is intended to be:
1+
//! A generalized traversal mechanism for complex data structures that contain
2+
//! type information.
63
//!
7-
//! T.fold_with(F) --calls--> F.fold_T(T) --calls--> T.super_fold_with(F)
4+
//! There are two types of traversal.
5+
//! - Folding. This is a modifying traversal. It consumes the data structure,
6+
//! producing a (possibly) modified version of it. Both fallible and
7+
//! infallible versions are available. The name is potentially
8+
//! confusing, because this traversal is more like `Iterator::map` than
9+
//! `Iterator::fold`.
10+
//! - Visiting. This is a read-only traversal of the data structure.
811
//!
9-
//! This way, when you define a new folder F, you can override
10-
//! `fold_T()` to customize the behavior, and invoke `T.super_fold_with()`
11-
//! to get the original behavior. Meanwhile, to actually fold
12-
//! something, you can just write `T.fold_with(F)`, which is
13-
//! convenient. (Note that `fold_with` will also transparently handle
14-
//! things like a `Vec<T>` where T is foldable and so on.)
12+
//! These traversals have limited flexibility. Only a small number of "types of
13+
//! interest" within the complex data structures can receive custom
14+
//! modification (when folding) or custom visitation (when visiting). These are
15+
//! the ones containing the most important type-related information, such as
16+
//! `Ty`, `Predicate`, `Region`, and `Const`.
1517
//!
16-
//! In this ideal setup, the only function that actually *does*
17-
//! anything is `T.super_fold_with()`, which traverses the type `T`.
18-
//! Moreover, `T.super_fold_with()` should only ever call `T.fold_with()`.
18+
//! There are two traits involved in each traversal type.
19+
//! - The first trait is `TypeFoldable`, which is implemented once for many
20+
//! types. This includes both (a) types of interest, and (b) all other
21+
//! relevant types, including generic containers like `Vec` and `Option`. It
22+
//! defines a "skeleton" of how they should be traversed, for both folding
23+
//! and visiting.
24+
//! - The second trait is `TypeFolder`/`FallibleTypeFolder` (for
25+
//! infallible/fallible folding traversals) or `TypeVisitor` (for visiting
26+
//! traversals). One of these is implemented for each folder/visitor. This
27+
//! defines how types of interest are handled.
1928
//!
20-
//! In some cases, we follow a degenerate pattern where we do not have
21-
//! a `fold_T` method. Instead, `T.fold_with` traverses the structure directly.
22-
//! This is suboptimal because the behavior cannot be overridden, but it's
23-
//! much less work to implement. If you ever *do* need an override that
24-
//! doesn't exist, it's not hard to convert the degenerate pattern into the
25-
//! proper thing.
26-
//!
27-
//! A `TypeFoldable` T can also be visited by a `TypeVisitor` V using similar setup:
28-
//!
29-
//! T.visit_with(V) --calls--> V.visit_T(T) --calls--> T.super_visit_with(V).
30-
//!
31-
//! These methods return true to indicate that the visitor has found what it is
32-
//! looking for, and does not need to visit anything else.
29+
//! This means each traversal is a mixture of (a) generic traversal operations,
30+
//! and (b) custom fold/visit operations that are specific to the
31+
//! folder/visitor.
32+
//! - The `TypeFoldable` impls handle most of the traversal, and call into
33+
//! `TypeFolder`/`FallibleTypeFolder`/`TypeVisitor` when they encounter a
34+
//! type of interest.
35+
//! - A `TypeFolder`/`FallibleTypeFolder`/`TypeVisitor` may also call back into
36+
//! a `TypeFoldable` impl, because (a) the types of interest are recursive
37+
//! and can contain other types of interest, and (b) each folder/visitor
38+
//! might provide custom handling only for some types of interest, or only
39+
//! for some variants of each type of interest, and then use default
40+
//! traversal for the remaining cases.
3341
use crate::mir;
3442
use crate::ty::{self, flags::FlagComputation, Binder, Ty, TyCtxt, TypeFlags};
35-
use rustc_hir as hir;
3643
use rustc_hir::def_id::DefId;
3744

3845
use rustc_data_structures::fx::FxHashSet;
@@ -41,42 +48,67 @@ use std::collections::BTreeMap;
4148
use std::fmt;
4249
use std::ops::ControlFlow;
4350

44-
/// This trait is implemented for every type that can be folded.
45-
/// Basically, every type that has a corresponding method in `TypeFolder`.
51+
/// This trait is implemented for every type that can be folded/visited,
52+
/// providing the skeleton of the traversal.
4653
///
47-
/// To implement this conveniently, use the derive macro located in `rustc_macros`.
54+
/// To implement this conveniently, use the derive macro located in
55+
/// `rustc_macros`.
4856
pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
49-
/// Consumers may find this more convenient to use with infallible folders than
50-
/// [`try_super_fold_with`][`TypeFoldable::try_super_fold_with`], to which the
51-
/// provided default definition delegates. Implementors **should not** override
52-
/// this provided default definition, to ensure that the two methods are coherent
53-
/// (provide a definition of `try_super_fold_with` instead).
54-
fn super_fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
55-
self.try_super_fold_with(folder).into_ok()
57+
/// The main entry point for folding. To fold a value `t` with a folder `f`
58+
/// call: `t.try_fold_with(f)`.
59+
///
60+
/// For types of interest (such as `Ty`), this default is overridden with a
61+
/// method that calls a folder method specifically for that type (such as
62+
/// `F::try_fold_ty`). This is where control transfers from `TypeFoldable`
63+
/// to `TypeFolder`.
64+
///
65+
/// For other types, this default is used.
66+
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
67+
self.try_super_fold_with(folder)
5668
}
57-
/// Consumers may find this more convenient to use with infallible folders than
58-
/// [`try_fold_with`][`TypeFoldable::try_fold_with`], to which the provided
59-
/// default definition delegates. Implementors **should not** override this
60-
/// provided default definition, to ensure that the two methods are coherent
61-
/// (provide a definition of `try_fold_with` instead).
69+
70+
/// A convenient alternative to [`try_fold_with`] for use with infallible
71+
/// folders. Do not override this method, to ensure coherence with
72+
/// `try_fold_with`.
6273
fn fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
6374
self.try_fold_with(folder).into_ok()
6475
}
6576

77+
/// Traverses the type in question, typically by calling `try_fold_with` on
78+
/// each field/element. This is true even for types of interest such as
79+
/// `Ty`. This should only be called within `TypeFolder` methods, when
80+
/// non-custom traversals are desired for types of interest.
6681
fn try_super_fold_with<F: FallibleTypeFolder<'tcx>>(
6782
self,
6883
folder: &mut F,
6984
) -> Result<Self, F::Error>;
7085

71-
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
72-
self.try_super_fold_with(folder)
86+
/// A convenient alternative to [`try_super_fold_with`] for use with
87+
/// infallible folders. Do not override this method, to ensure coherence
88+
/// with `try_super_fold_with`.
89+
fn super_fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
90+
self.try_super_fold_with(folder).into_ok()
7391
}
7492

75-
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
93+
/// The entry point for visiting. To visit a value `t` with a visitor `v`
94+
/// call: `t.visit_with(v)`.
95+
///
96+
/// For types of interest (such as `Ty`), this default is overridden with a
97+
/// method that calls a visitor method specifically for that type (such as
98+
/// `V::visit_ty`). This is where control transfers from `TypeFoldable` to
99+
/// `TypeFolder`.
100+
///
101+
/// For other types, this default is used.
76102
fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
77103
self.super_visit_with(visitor)
78104
}
79105

106+
/// Traverses the type in question, typically by calling `visit_with` on
107+
/// each field/element. This is true even for types of interest such as
108+
/// `Ty`. This should only be called within `TypeVisitor` methods, when
109+
/// non-custom traversals are desired for types of interest.
110+
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
111+
80112
/// Returns `true` if `self` has any late-bound regions that are either
81113
/// bound by `binder` or bound by some binder outside of `binder`.
82114
/// If `binder` is `ty::INNERMOST`, this indicates whether
@@ -168,24 +200,13 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
168200
}
169201
}
170202

171-
impl<'tcx> TypeFoldable<'tcx> for hir::Constness {
172-
fn try_super_fold_with<F: TypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
173-
Ok(self)
174-
}
175-
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
176-
ControlFlow::CONTINUE
177-
}
178-
}
179-
180-
/// The `TypeFolder` trait defines the actual *folding*. There is a
181-
/// method defined for every foldable type. Each of these has a
182-
/// default implementation that does an "identity" fold. Within each
183-
/// identity fold, it should invoke `foo.fold_with(self)` to fold each
184-
/// sub-item.
203+
/// This trait is implemented for every folding traversal. There is a fold
204+
/// method defined for every type of interest. Each such method has a default
205+
/// that does an "identity" fold.
185206
///
186207
/// If this folder is fallible (and therefore its [`Error`][`TypeFolder::Error`]
187-
/// associated type is something other than the default, never),
188-
/// [`FallibleTypeFolder`] should be implemented manually; otherwise,
208+
/// associated type is something other than the default `!`) then
209+
/// [`FallibleTypeFolder`] should be implemented manually. Otherwise,
189210
/// a blanket implementation of [`FallibleTypeFolder`] will defer to
190211
/// the infallible methods of this trait to ensure that the two APIs
191212
/// are coherent.
@@ -238,11 +259,9 @@ pub trait TypeFolder<'tcx>: Sized {
238259
}
239260
}
240261

241-
/// The `FallibleTypeFolder` trait defines the actual *folding*. There is a
242-
/// method defined for every foldable type. Each of these has a
243-
/// default implementation that does an "identity" fold. Within each
244-
/// identity fold, it should invoke `foo.try_fold_with(self)` to fold each
245-
/// sub-item.
262+
/// This trait is implemented for every folding traversal. There is a fold
263+
/// method defined for every type of interest. Each such method has a default
264+
/// that does an "identity" fold.
246265
///
247266
/// A blanket implementation of this trait (that defers to the relevant
248267
/// method of [`TypeFolder`]) is provided for all infallible folders in
@@ -285,8 +304,8 @@ pub trait FallibleTypeFolder<'tcx>: TypeFolder<'tcx> {
285304
}
286305
}
287306

288-
// Blanket implementation of fallible trait for infallible folders
289-
// delegates to infallible methods to prevent incoherence
307+
// This blanket implementation of the fallible trait for infallible folders
308+
// delegates to infallible methods to ensure coherence.
290309
impl<'tcx, F> FallibleTypeFolder<'tcx> for F
291310
where
292311
F: TypeFolder<'tcx, Error = !>,
@@ -328,6 +347,9 @@ where
328347
}
329348
}
330349

350+
/// This trait is implemented for every visiting traversal. There is a visit
351+
/// method defined for every type of interest. Each such method has a default
352+
/// that does a non-custom visit.
331353
pub trait TypeVisitor<'tcx>: Sized {
332354
type BreakTy = !;
333355

compiler/rustc_middle/src/ty/structural_impls.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeVisitor};
88
use crate::ty::print::{with_no_trimmed_paths, FmtPrinter, Printer};
99
use crate::ty::{self, InferConst, Lift, Term, Ty, TyCtxt};
1010
use rustc_data_structures::functor::IdFunctor;
11+
use rustc_hir as hir;
1112
use rustc_hir::def::Namespace;
1213
use rustc_hir::def_id::CRATE_DEF_INDEX;
1314
use rustc_index::vec::{Idx, IndexVec};
@@ -668,14 +669,6 @@ impl<'a, 'tcx> Lift<'tcx> for ty::InstanceDef<'a> {
668669

669670
///////////////////////////////////////////////////////////////////////////
670671
// TypeFoldable implementations.
671-
//
672-
// Ideally, each type should invoke `folder.fold_foo(self)` and
673-
// nothing else. In some cases, though, we haven't gotten around to
674-
// adding methods on the `folder` yet, and thus the folding is
675-
// hard-coded here. This is less-flexible, because folders cannot
676-
// override the behavior, but there are a lot of random types and one
677-
// can easily refactor the folding into the TypeFolder trait as
678-
// needed.
679672

680673
/// AdtDefs are basically the same as a DefId.
681674
impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::AdtDef {
@@ -1275,3 +1268,13 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Unevaluated<'tcx, ()> {
12751268
self.substs.visit_with(visitor)
12761269
}
12771270
}
1271+
1272+
impl<'tcx> TypeFoldable<'tcx> for hir::Constness {
1273+
fn try_super_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
1274+
Ok(self)
1275+
}
1276+
1277+
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
1278+
ControlFlow::CONTINUE
1279+
}
1280+
}

0 commit comments

Comments
 (0)