Skip to content

Commit 79ca462

Browse files
authored
Rollup merge of rust-lang#93758 - nnethercote:improve-folding-comments, r=BoxyUwU
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. r? `@BoxyUwU`
2 parents 39aef92 + 2e56c02 commit 79ca462

File tree

2 files changed

+113
-77
lines changed

2 files changed

+113
-77
lines changed

compiler/rustc_middle/src/ty/fold.rs

+102-69
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,56 @@
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.
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.
2641
//!
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.
42+
//! For example, if you have `struct S(Ty, U)` where `S: TypeFoldable` and `U:
43+
//! TypeFoldable`, and an instance `S(ty, u)`, it would be visited like so:
44+
//! ```
45+
//! s.visit_with(visitor) calls
46+
//! - s.super_visit_with(visitor) calls
47+
//! - ty.visit_with(visitor) calls
48+
//! - visitor.visit_ty(ty) may call
49+
//! - ty.super_visit_with(visitor)
50+
//! - u.visit_with(visitor)
51+
//! ```
3352
use crate::mir;
3453
use crate::ty::{self, flags::FlagComputation, Binder, Ty, TyCtxt, TypeFlags};
35-
use rustc_hir as hir;
3654
use rustc_hir::def_id::DefId;
3755

3856
use rustc_data_structures::fx::FxHashSet;
@@ -41,42 +59,67 @@ use std::collections::BTreeMap;
4159
use std::fmt;
4260
use std::ops::ControlFlow;
4361

44-
/// This trait is implemented for every type that can be folded.
45-
/// Basically, every type that has a corresponding method in `TypeFolder`.
62+
/// This trait is implemented for every type that can be folded/visited,
63+
/// providing the skeleton of the traversal.
4664
///
47-
/// To implement this conveniently, use the derive macro located in `rustc_macros`.
65+
/// To implement this conveniently, use the derive macro located in
66+
/// `rustc_macros`.
4867
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()
68+
/// The main entry point for folding. To fold a value `t` with a folder `f`
69+
/// call: `t.try_fold_with(f)`.
70+
///
71+
/// For types of interest (such as `Ty`), this default is overridden with a
72+
/// method that calls a folder method specifically for that type (such as
73+
/// `F::try_fold_ty`). This is where control transfers from `TypeFoldable`
74+
/// to `TypeFolder`.
75+
///
76+
/// For other types, this default is used.
77+
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
78+
self.try_super_fold_with(folder)
5679
}
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).
80+
81+
/// A convenient alternative to [`try_fold_with`] for use with infallible
82+
/// folders. Do not override this method, to ensure coherence with
83+
/// `try_fold_with`.
6284
fn fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
6385
self.try_fold_with(folder).into_ok()
6486
}
6587

88+
/// Traverses the type in question, typically by calling `try_fold_with` on
89+
/// each field/element. This is true even for types of interest such as
90+
/// `Ty`. This should only be called within `TypeFolder` methods, when
91+
/// non-custom traversals are desired for types of interest.
6692
fn try_super_fold_with<F: FallibleTypeFolder<'tcx>>(
6793
self,
6894
folder: &mut F,
6995
) -> Result<Self, F::Error>;
7096

71-
fn try_fold_with<F: FallibleTypeFolder<'tcx>>(self, folder: &mut F) -> Result<Self, F::Error> {
72-
self.try_super_fold_with(folder)
97+
/// A convenient alternative to [`try_super_fold_with`] for use with
98+
/// infallible folders. Do not override this method, to ensure coherence
99+
/// with `try_super_fold_with`.
100+
fn super_fold_with<F: TypeFolder<'tcx, Error = !>>(self, folder: &mut F) -> Self {
101+
self.try_super_fold_with(folder).into_ok()
73102
}
74103

75-
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
104+
/// The entry point for visiting. To visit a value `t` with a visitor `v`
105+
/// call: `t.visit_with(v)`.
106+
///
107+
/// For types of interest (such as `Ty`), this default is overridden with a
108+
/// method that calls a visitor method specifically for that type (such as
109+
/// `V::visit_ty`). This is where control transfers from `TypeFoldable` to
110+
/// `TypeVisitor`.
111+
///
112+
/// For other types, this default is used.
76113
fn visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy> {
77114
self.super_visit_with(visitor)
78115
}
79116

117+
/// Traverses the type in question, typically by calling `visit_with` on
118+
/// each field/element. This is true even for types of interest such as
119+
/// `Ty`. This should only be called within `TypeVisitor` methods, when
120+
/// non-custom traversals are desired for types of interest.
121+
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, visitor: &mut V) -> ControlFlow<V::BreakTy>;
122+
80123
/// Returns `true` if `self` has any late-bound regions that are either
81124
/// bound by `binder` or bound by some binder outside of `binder`.
82125
/// If `binder` is `ty::INNERMOST`, this indicates whether
@@ -168,24 +211,13 @@ pub trait TypeFoldable<'tcx>: fmt::Debug + Clone {
168211
}
169212
}
170213

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.
214+
/// This trait is implemented for every folding traversal. There is a fold
215+
/// method defined for every type of interest. Each such method has a default
216+
/// that does an "identity" fold.
185217
///
186218
/// 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,
219+
/// associated type is something other than the default `!`) then
220+
/// [`FallibleTypeFolder`] should be implemented manually. Otherwise,
189221
/// a blanket implementation of [`FallibleTypeFolder`] will defer to
190222
/// the infallible methods of this trait to ensure that the two APIs
191223
/// are coherent.
@@ -238,11 +270,9 @@ pub trait TypeFolder<'tcx>: Sized {
238270
}
239271
}
240272

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.
273+
/// This trait is implemented for every folding traversal. There is a fold
274+
/// method defined for every type of interest. Each such method has a default
275+
/// that does an "identity" fold.
246276
///
247277
/// A blanket implementation of this trait (that defers to the relevant
248278
/// method of [`TypeFolder`]) is provided for all infallible folders in
@@ -282,8 +312,8 @@ pub trait FallibleTypeFolder<'tcx>: TypeFolder<'tcx> {
282312
}
283313
}
284314

285-
// Blanket implementation of fallible trait for infallible folders
286-
// delegates to infallible methods to prevent incoherence
315+
// This blanket implementation of the fallible trait for infallible folders
316+
// delegates to infallible methods to ensure coherence.
287317
impl<'tcx, F> FallibleTypeFolder<'tcx> for F
288318
where
289319
F: TypeFolder<'tcx, Error = !>,
@@ -322,6 +352,9 @@ where
322352
}
323353
}
324354

355+
/// This trait is implemented for every visiting traversal. There is a visit
356+
/// method defined for every type of interest. Each such method has a default
357+
/// that recurses into the type's fields in a non-custom fashion.
325358
pub trait TypeVisitor<'tcx>: Sized {
326359
type BreakTy = !;
327360

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};
@@ -663,14 +664,6 @@ impl<'a, 'tcx> Lift<'tcx> for ty::InstanceDef<'a> {
663664

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

675668
/// AdtDefs are basically the same as a DefId.
676669
impl<'tcx> TypeFoldable<'tcx> for &'tcx ty::AdtDef {
@@ -1270,3 +1263,13 @@ impl<'tcx> TypeFoldable<'tcx> for ty::Unevaluated<'tcx, ()> {
12701263
self.substs.visit_with(visitor)
12711264
}
12721265
}
1266+
1267+
impl<'tcx> TypeFoldable<'tcx> for hir::Constness {
1268+
fn try_super_fold_with<F: FallibleTypeFolder<'tcx>>(self, _: &mut F) -> Result<Self, F::Error> {
1269+
Ok(self)
1270+
}
1271+
1272+
fn super_visit_with<V: TypeVisitor<'tcx>>(&self, _: &mut V) -> ControlFlow<V::BreakTy> {
1273+
ControlFlow::CONTINUE
1274+
}
1275+
}

0 commit comments

Comments
 (0)