| //! A folding traversal mechanism for complex data structures that contain type |
| //! information. |
| //! |
| //! This is a modifying traversal. It consumes the data structure, producing a |
| //! (possibly) modified version of it. Both fallible and infallible versions are |
| //! available. The name is potentially confusing, because this traversal is more |
| //! like `Iterator::map` than `Iterator::fold`. |
| //! |
| //! This traversal has limited flexibility. Only a small number of "types of |
| //! interest" within the complex data structures can receive custom |
| //! modification. These are the ones containing the most important type-related |
| //! information, such as `Ty`, `Predicate`, `Region`, and `Const`. |
| //! |
| //! There are three traits involved in each traversal. |
| //! - `TypeFoldable`. This is implemented once for many types, including: |
| //! - Types of interest, for which the methods delegate to the folder. |
| //! - All other types, including generic containers like `Vec` and `Option`. |
| //! It defines a "skeleton" of how they should be folded. |
| //! - `TypeSuperFoldable`. This is implemented only for recursive types of |
| //! interest, and defines the folding "skeleton" for these types. (This |
| //! excludes `Region` because it is non-recursive, i.e. it never contains |
| //! other types of interest.) |
| //! - `TypeFolder`/`FallibleTypeFolder`. One of these is implemented for each |
| //! folder. This defines how types of interest are folded. |
| //! |
| //! This means each fold is a mixture of (a) generic folding operations, and (b) |
| //! custom fold operations that are specific to the folder. |
| //! - The `TypeFoldable` impls handle most of the traversal, and call into |
| //! `TypeFolder`/`FallibleTypeFolder` when they encounter a type of interest. |
| //! - A `TypeFolder`/`FallibleTypeFolder` may call into another `TypeFoldable` |
| //! impl, because some of the types of interest are recursive and can contain |
| //! other types of interest. |
| //! - A `TypeFolder`/`FallibleTypeFolder` may also call into a `TypeSuperFoldable` |
| //! impl, because each folder might provide custom handling only for some types |
| //! of interest, or only for some variants of each type of interest, and then |
| //! use default traversal for the remaining cases. |
| //! |
| //! For example, if you have `struct S(Ty, U)` where `S: TypeFoldable` and `U: |
| //! TypeFoldable`, and an instance `s = S(ty, u)`, it would be folded like so: |
| //! ```text |
| //! s.fold_with(folder) calls |
| //! - ty.fold_with(folder) calls |
| //! - folder.fold_ty(ty) may call |
| //! - ty.super_fold_with(folder) |
| //! - u.fold_with(folder) |
| //! ``` |
| |
| use rustc_data_structures::sync::Lrc; |
| use rustc_index::{Idx, IndexVec}; |
| use std::mem; |
| |
| use crate::{visit::TypeVisitable, Interner}; |
| |
| /// This trait is implemented for every type that can be folded, |
| /// providing the skeleton of the traversal. |
| /// |
| /// To implement this conveniently, use the derive macro located in |
| /// `rustc_macros`. |
| /// |
| /// This trait is a sub-trait of `TypeVisitable`. This is because many |
| /// `TypeFolder` instances use the methods in `TypeVisitableExt` while folding, |
| /// which means in practice almost every foldable type needs to also be |
| /// visitable. (However, there are some types that are visitable without being |
| /// foldable.) |
| pub trait TypeFoldable<I: Interner>: TypeVisitable<I> { |
| /// The entry point for folding. To fold a value `t` with a folder `f` |
| /// call: `t.try_fold_with(f)`. |
| /// |
| /// For most types, this just traverses the value, calling `try_fold_with` |
| /// on each field/element. |
| /// |
| /// For types of interest (such as `Ty`), the implementation of this method |
| /// calls a folder method specifically for that type (such as |
| /// `F::try_fold_ty`). This is where control transfers from `TypeFoldable` |
| /// to `TypeFolder`. |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<Self, F::Error>; |
| |
| /// A convenient alternative to `try_fold_with` for use with infallible |
| /// folders. Do not override this method, to ensure coherence with |
| /// `try_fold_with`. |
| fn fold_with<F: TypeFolder<I>>(self, folder: &mut F) -> Self { |
| self.try_fold_with(folder).into_ok() |
| } |
| } |
| |
| // This trait is implemented for types of interest. |
| pub trait TypeSuperFoldable<I: Interner>: TypeFoldable<I> { |
| /// Provides a default fold for a recursive type of interest. This should |
| /// only be called within `TypeFolder` methods, when a non-custom traversal |
| /// is desired for the value of the type of interest passed to that method. |
| /// For example, in `MyFolder::try_fold_ty(ty)`, it is valid to call |
| /// `ty.try_super_fold_with(self)`, but any other folding should be done |
| /// with `xyz.try_fold_with(self)`. |
| fn try_super_fold_with<F: FallibleTypeFolder<I>>( |
| self, |
| folder: &mut F, |
| ) -> Result<Self, F::Error>; |
| |
| /// A convenient alternative to `try_super_fold_with` for use with |
| /// infallible folders. Do not override this method, to ensure coherence |
| /// with `try_super_fold_with`. |
| fn super_fold_with<F: TypeFolder<I>>(self, folder: &mut F) -> Self { |
| self.try_super_fold_with(folder).into_ok() |
| } |
| } |
| |
| /// This trait is implemented for every infallible folding traversal. There is |
| /// a fold method defined for every type of interest. Each such method has a |
| /// default that does an "identity" fold. Implementations of these methods |
| /// often fall back to a `super_fold_with` method if the primary argument |
| /// doesn't satisfy a particular condition. |
| /// |
| /// A blanket implementation of [`FallibleTypeFolder`] will defer to |
| /// the infallible methods of this trait to ensure that the two APIs |
| /// are coherent. |
| pub trait TypeFolder<I: Interner>: FallibleTypeFolder<I, Error = !> { |
| fn interner(&self) -> I; |
| |
| fn fold_binder<T>(&mut self, t: I::Binder<T>) -> I::Binder<T> |
| where |
| T: TypeFoldable<I>, |
| I::Binder<T>: TypeSuperFoldable<I>, |
| { |
| t.super_fold_with(self) |
| } |
| |
| fn fold_ty(&mut self, t: I::Ty) -> I::Ty |
| where |
| I::Ty: TypeSuperFoldable<I>, |
| { |
| t.super_fold_with(self) |
| } |
| |
| // The default region folder is a no-op because `Region` is non-recursive |
| // and has no `super_fold_with` method to call. That also explains the |
| // lack of `I::Region: TypeSuperFoldable<I>` bound on this method. |
| fn fold_region(&mut self, r: I::Region) -> I::Region { |
| r |
| } |
| |
| fn fold_const(&mut self, c: I::Const) -> I::Const |
| where |
| I::Const: TypeSuperFoldable<I>, |
| { |
| c.super_fold_with(self) |
| } |
| |
| fn fold_predicate(&mut self, p: I::Predicate) -> I::Predicate |
| where |
| I::Predicate: TypeSuperFoldable<I>, |
| { |
| p.super_fold_with(self) |
| } |
| } |
| |
| /// This trait is implemented for every folding traversal. There is a fold |
| /// method defined for every type of interest. Each such method has a default |
| /// that does an "identity" fold. |
| /// |
| /// A blanket implementation of this trait (that defers to the relevant |
| /// method of [`TypeFolder`]) is provided for all infallible folders in |
| /// order to ensure the two APIs are coherent. |
| pub trait FallibleTypeFolder<I: Interner>: Sized { |
| type Error; |
| |
| fn interner(&self) -> I; |
| |
| fn try_fold_binder<T>(&mut self, t: I::Binder<T>) -> Result<I::Binder<T>, Self::Error> |
| where |
| T: TypeFoldable<I>, |
| I::Binder<T>: TypeSuperFoldable<I>, |
| { |
| t.try_super_fold_with(self) |
| } |
| |
| fn try_fold_ty(&mut self, t: I::Ty) -> Result<I::Ty, Self::Error> |
| where |
| I::Ty: TypeSuperFoldable<I>, |
| { |
| t.try_super_fold_with(self) |
| } |
| |
| // The default region folder is a no-op because `Region` is non-recursive |
| // and has no `super_fold_with` method to call. That also explains the |
| // lack of `I::Region: TypeSuperFoldable<I>` bound on this method. |
| fn try_fold_region(&mut self, r: I::Region) -> Result<I::Region, Self::Error> { |
| Ok(r) |
| } |
| |
| fn try_fold_const(&mut self, c: I::Const) -> Result<I::Const, Self::Error> |
| where |
| I::Const: TypeSuperFoldable<I>, |
| { |
| c.try_super_fold_with(self) |
| } |
| |
| fn try_fold_predicate(&mut self, p: I::Predicate) -> Result<I::Predicate, Self::Error> |
| where |
| I::Predicate: TypeSuperFoldable<I>, |
| { |
| p.try_super_fold_with(self) |
| } |
| } |
| |
| // This blanket implementation of the fallible trait for infallible folders |
| // delegates to infallible methods to ensure coherence. |
| impl<I: Interner, F> FallibleTypeFolder<I> for F |
| where |
| F: TypeFolder<I>, |
| { |
| type Error = !; |
| |
| fn interner(&self) -> I { |
| TypeFolder::interner(self) |
| } |
| |
| fn try_fold_binder<T>(&mut self, t: I::Binder<T>) -> Result<I::Binder<T>, !> |
| where |
| T: TypeFoldable<I>, |
| I::Binder<T>: TypeSuperFoldable<I>, |
| { |
| Ok(self.fold_binder(t)) |
| } |
| |
| fn try_fold_ty(&mut self, t: I::Ty) -> Result<I::Ty, !> |
| where |
| I::Ty: TypeSuperFoldable<I>, |
| { |
| Ok(self.fold_ty(t)) |
| } |
| |
| fn try_fold_region(&mut self, r: I::Region) -> Result<I::Region, !> { |
| Ok(self.fold_region(r)) |
| } |
| |
| fn try_fold_const(&mut self, c: I::Const) -> Result<I::Const, !> |
| where |
| I::Const: TypeSuperFoldable<I>, |
| { |
| Ok(self.fold_const(c)) |
| } |
| |
| fn try_fold_predicate(&mut self, p: I::Predicate) -> Result<I::Predicate, !> |
| where |
| I::Predicate: TypeSuperFoldable<I>, |
| { |
| Ok(self.fold_predicate(p)) |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////// |
| // Traversal implementations. |
| |
| impl<I: Interner, T: TypeFoldable<I>, U: TypeFoldable<I>> TypeFoldable<I> for (T, U) { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<(T, U), F::Error> { |
| Ok((self.0.try_fold_with(folder)?, self.1.try_fold_with(folder)?)) |
| } |
| } |
| |
| impl<I: Interner, A: TypeFoldable<I>, B: TypeFoldable<I>, C: TypeFoldable<I>> TypeFoldable<I> |
| for (A, B, C) |
| { |
| fn try_fold_with<F: FallibleTypeFolder<I>>( |
| self, |
| folder: &mut F, |
| ) -> Result<(A, B, C), F::Error> { |
| Ok(( |
| self.0.try_fold_with(folder)?, |
| self.1.try_fold_with(folder)?, |
| self.2.try_fold_with(folder)?, |
| )) |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>> TypeFoldable<I> for Option<T> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<Self, F::Error> { |
| Ok(match self { |
| Some(v) => Some(v.try_fold_with(folder)?), |
| None => None, |
| }) |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>, E: TypeFoldable<I>> TypeFoldable<I> for Result<T, E> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<Self, F::Error> { |
| Ok(match self { |
| Ok(v) => Ok(v.try_fold_with(folder)?), |
| Err(e) => Err(e.try_fold_with(folder)?), |
| }) |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>> TypeFoldable<I> for Lrc<T> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(mut self, folder: &mut F) -> Result<Self, F::Error> { |
| // We merely want to replace the contained `T`, if at all possible, |
| // so that we don't needlessly allocate a new `Lrc` or indeed clone |
| // the contained type. |
| unsafe { |
| // First step is to ensure that we have a unique reference to |
| // the contained type, which `Lrc::make_mut` will accomplish (by |
| // allocating a new `Lrc` and cloning the `T` only if required). |
| // This is done *before* casting to `Lrc<ManuallyDrop<T>>` so that |
| // panicking during `make_mut` does not leak the `T`. |
| Lrc::make_mut(&mut self); |
| |
| // Casting to `Lrc<ManuallyDrop<T>>` is safe because `ManuallyDrop` |
| // is `repr(transparent)`. |
| let ptr = Lrc::into_raw(self).cast::<mem::ManuallyDrop<T>>(); |
| let mut unique = Lrc::from_raw(ptr); |
| |
| // Call to `Lrc::make_mut` above guarantees that `unique` is the |
| // sole reference to the contained value, so we can avoid doing |
| // a checked `get_mut` here. |
| let slot = Lrc::get_mut_unchecked(&mut unique); |
| |
| // Semantically move the contained type out from `unique`, fold |
| // it, then move the folded value back into `unique`. Should |
| // folding fail, `ManuallyDrop` ensures that the "moved-out" |
| // value is not re-dropped. |
| let owned = mem::ManuallyDrop::take(slot); |
| let folded = owned.try_fold_with(folder)?; |
| *slot = mem::ManuallyDrop::new(folded); |
| |
| // Cast back to `Lrc<T>`. |
| Ok(Lrc::from_raw(Lrc::into_raw(unique).cast())) |
| } |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>> TypeFoldable<I> for Box<T> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(mut self, folder: &mut F) -> Result<Self, F::Error> { |
| *self = (*self).try_fold_with(folder)?; |
| Ok(self) |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>> TypeFoldable<I> for Vec<T> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<Self, F::Error> { |
| self.into_iter().map(|t| t.try_fold_with(folder)).collect() |
| } |
| } |
| |
| impl<I: Interner, T: TypeFoldable<I>, Ix: Idx> TypeFoldable<I> for IndexVec<Ix, T> { |
| fn try_fold_with<F: FallibleTypeFolder<I>>(self, folder: &mut F) -> Result<Self, F::Error> { |
| self.raw.try_fold_with(folder).map(IndexVec::from_raw) |
| } |
| } |