Skip to main content

ploidy_core/ir/views/
mod.rs

1//! Graph-aware views of IR types.
2//!
3//! Views are cheap, read-only types that pair a node with its cooked graph,
4//! so they can answer questions about an IR type and its relationships.
5//! The submodules document the OpenAPI concept that each view represents.
6//!
7//! # The `View` trait
8//!
9//! All view types implement [`View`], which provides graph traversal methods:
10//!
11//! * [`View::inlines()`] iterates over inline types nested within this type.
12//!   Use this to emit inline type definitions alongside their parent.
13//! * [`View::used_by()`] iterates over operations that reference this type.
14//!   Useful for generating per-operation imports or feature gates.
15//! * [`View::dependencies()`] iterates over all types that this type
16//!   transitively depends on. Use this for import lists, feature gates,
17//!   and topological ordering.
18//! * [`View::dependents()`] iterates over all types that transitively depend on
19//!   this type. Useful for impact analysis or invalidation.
20//!
21//! These methods answer Rust-specific questions:
22//!
23//! * [`View::hashable()`] returns whether this type can implement `Eq` and `Hash`.
24//! * [`View::defaultable()`] returns whether this type can implement `Default`.
25//!
26//! # Extensions
27//!
28//! [`ExtendableView`] attaches a type-erased extension map to each view node.
29//! Codegen backends use this to store and retrieve arbitrary metadata.
30
31use std::{any::TypeId, fmt::Debug};
32
33use atomic_refcell::{AtomicRef, AtomicRefMut};
34use petgraph::{
35    graph::NodeIndex,
36    visit::{Bfs, EdgeFiltered, EdgeRef},
37};
38use ref_cast::{RefCastCustom, ref_cast_custom};
39
40use super::{
41    graph::{CookedGraph, Extension, ExtensionMap},
42    types::GraphType,
43};
44
45pub mod any;
46pub mod container;
47pub mod enum_;
48pub mod inline;
49pub mod ir;
50pub mod operation;
51pub mod primitive;
52pub mod schema;
53pub mod struct_;
54pub mod tagged;
55pub mod untagged;
56
57use self::{inline::InlineTypeView, ir::TypeView, operation::OperationView};
58
59/// A view of a type in the graph.
60pub trait View<'a> {
61    /// Returns an iterator over all the inline types that are
62    /// contained within this type.
63    fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'a>> + use<'a, Self>;
64
65    /// Returns an iterator over the operations that use this type.
66    ///
67    /// This is backward propagation: each operation depends on this type.
68    fn used_by(&self) -> impl Iterator<Item = OperationView<'a>> + use<'a, Self>;
69
70    /// Returns an iterator over all the types that this type transitively depends on.
71    /// This is forward propagation: this type depends on each reachable type.
72    ///
73    /// Complexity: O(n), where `n` is the number of dependency types.
74    fn dependencies(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a, Self>;
75
76    /// Returns an iterator over all the types that transitively depend on this type.
77    /// This is backward propagation: each returned type depends on this type.
78    ///
79    /// Complexity: O(n), where `n` is the number of dependent types.
80    fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a, Self>;
81
82    /// Returns `true` if this type can implement `Eq` and `Hash`.
83    fn hashable(&self) -> bool;
84
85    /// Returns `true` if this type can implement `Default`.
86    fn defaultable(&self) -> bool;
87}
88
89/// A view of a graph type with extended data.
90///
91/// Codegen backends use extended data to decorate types with extra information.
92/// For example, Rust codegen stores a unique identifier on each schema type,
93/// so that names never collide after case conversion.
94pub trait ExtendableView<'a>: View<'a> {
95    /// Returns a reference to this type's extended data.
96    fn extensions(&self) -> &ViewExtensions<Self>
97    where
98        Self: Sized;
99
100    /// Returns a mutable reference to this type's extended data.
101    fn extensions_mut(&mut self) -> &mut ViewExtensions<Self>
102    where
103        Self: Sized;
104}
105
106impl<'a, T> View<'a> for T
107where
108    T: ViewNode<'a>,
109{
110    #[inline]
111    fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'a>> + use<'a, T> {
112        let cooked = self.cooked();
113        // Follow edges to inline schemas, skipping shadow edges.
114        // See `GraphEdge::shadow()` for an explanation.
115        let filtered = EdgeFiltered::from_fn(&cooked.graph, move |e| {
116            !e.weight().shadow() && matches!(cooked.graph[e.target()], GraphType::Inline(_))
117        });
118        let mut bfs = Bfs::new(&cooked.graph, self.index());
119        std::iter::from_fn(move || bfs.next(&filtered))
120            .skip(1) // Skip the starting node.
121            .filter_map(|index| match cooked.graph[index] {
122                GraphType::Inline(ty) => Some(InlineTypeView::new(cooked, index, ty)),
123                _ => None,
124            })
125    }
126
127    #[inline]
128    fn used_by(&self) -> impl Iterator<Item = OperationView<'a>> + use<'a, T> {
129        let cooked = self.cooked();
130        cooked.metadata.used_by[self.index().index()]
131            .iter()
132            .map(|op| OperationView::new(cooked, op))
133    }
134
135    #[inline]
136    fn dependencies(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a, T> {
137        let cooked = self.cooked();
138        let start = self.index();
139        cooked
140            .metadata
141            .closure
142            .dependencies_of(start)
143            .filter(move |&index| index != start)
144            .map(|index| TypeView::new(cooked, index))
145    }
146
147    #[inline]
148    fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a, T> {
149        let cooked = self.cooked();
150        let start = self.index();
151        cooked
152            .metadata
153            .closure
154            .dependents_of(start)
155            .filter(move |&index| index != start)
156            .map(|index| TypeView::new(cooked, index))
157    }
158
159    #[inline]
160    fn hashable(&self) -> bool {
161        self.cooked().metadata.hashable[self.index().index()]
162    }
163
164    #[inline]
165    fn defaultable(&self) -> bool {
166        self.cooked().metadata.defaultable[self.index().index()]
167    }
168}
169
170impl<'a, T> ExtendableView<'a> for T
171where
172    T: ViewNode<'a>,
173{
174    #[inline]
175    fn extensions(&self) -> &ViewExtensions<Self> {
176        ViewExtensions::new(self)
177    }
178
179    #[inline]
180    fn extensions_mut(&mut self) -> &mut ViewExtensions<Self> {
181        ViewExtensions::new_mut(self)
182    }
183}
184
185pub(crate) trait ViewNode<'a> {
186    fn cooked(&self) -> &'a CookedGraph<'a>;
187    fn index(&self) -> NodeIndex<usize>;
188}
189
190impl<'graph, T> internal::Extendable<'graph> for T
191where
192    T: ViewNode<'graph>,
193{
194    #[inline]
195    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
196    where
197        'graph: 'view,
198    {
199        self.cooked().metadata.extensions[self.index().index()].borrow()
200    }
201
202    #[inline]
203    fn ext_mut<'b>(&'b mut self) -> AtomicRefMut<'b, ExtensionMap>
204    where
205        'graph: 'b,
206    {
207        self.cooked().metadata.extensions[self.index().index()].borrow_mut()
208    }
209}
210
211/// Extended data attached to a graph type.
212#[derive(RefCastCustom)]
213#[repr(transparent)]
214pub struct ViewExtensions<X>(X);
215
216impl<X> ViewExtensions<X> {
217    #[ref_cast_custom]
218    fn new(view: &X) -> &Self;
219
220    #[ref_cast_custom]
221    fn new_mut(view: &mut X) -> &mut Self;
222}
223
224impl<'a, X: internal::Extendable<'a>> ViewExtensions<X> {
225    /// Returns a reference to a value of an arbitrary type that was
226    /// previously inserted into this extended data.
227    #[inline]
228    pub fn get<'b, T: Send + Sync + 'static>(&'b self) -> Option<AtomicRef<'b, T>>
229    where
230        'a: 'b,
231    {
232        AtomicRef::filter_map(self.0.ext(), |ext| {
233            Some(
234                ext.get(&TypeId::of::<T>())?
235                    .as_ref()
236                    .downcast_ref::<T>()
237                    .unwrap(),
238            )
239        })
240    }
241
242    /// Inserts a value of an arbitrary type into this extended data,
243    /// and returns the previous value for that type.
244    #[inline]
245    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
246        self.0
247            .ext_mut()
248            .insert(TypeId::of::<T>(), Box::new(value))
249            .and_then(|old| *Extension::into_inner(old).downcast().unwrap())
250    }
251}
252
253impl<X> Debug for ViewExtensions<X> {
254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255        f.debug_tuple("ViewExtensions").finish_non_exhaustive()
256    }
257}
258
259mod internal {
260    use atomic_refcell::{AtomicRef, AtomicRefMut};
261
262    use super::ExtensionMap;
263
264    pub trait Extendable<'graph> {
265        // These lifetime requirements might look redundant, but they're not:
266        // we're shortening the lifetime of the `AtomicRef` from `'graph` to `'view`,
267        // to prevent overlapping mutable borrows of the underlying `AtomicRefCell`
268        // at compile time.
269        //
270        // (`AtomicRefCell` panics on these illegal borrows at runtime, which is
271        // always memory-safe; we just want some extra type safety).
272        //
273        // This approach handles the obvious case of overlapping borrows from
274        // `ext()` and `ext_mut()`, and the `AtomicRefCell` avoids plumbing
275        // mutable references to the graph through every IR layer.
276
277        fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
278        where
279            'graph: 'view;
280
281        fn ext_mut<'view>(&'view mut self) -> AtomicRefMut<'view, ExtensionMap>
282        where
283            'graph: 'view;
284    }
285}