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 as StdTypeId, 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 path;
52pub mod primitive;
53pub mod schema;
54pub mod struct_;
55pub mod tagged;
56pub mod untagged;
57
58use self::{inline::InlineTypeView, ir::TypeView, operation::OperationView};
59
60/// A view of a type in the graph.
61pub trait View<'graph, 'a: 'graph> {
62    /// Returns an iterator over all the inline types that are
63    /// contained within this type.
64    fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'graph, 'a>> + use<'graph, 'a, Self>;
65
66    /// Returns an iterator over the operations that use this type.
67    ///
68    /// This is backward propagation: each operation depends on this type.
69    fn used_by(&self) -> impl Iterator<Item = OperationView<'graph, 'a>> + use<'graph, 'a, Self>;
70
71    /// Returns an iterator over all the types that this type transitively depends on.
72    /// This is forward propagation: this type depends on each reachable type.
73    ///
74    /// Complexity: O(n), where `n` is the number of dependency types.
75    fn dependencies(&self) -> impl Iterator<Item = TypeView<'graph, 'a>> + use<'graph, 'a, Self>;
76
77    /// Returns an iterator over all the types that transitively depend on this type.
78    /// This is backward propagation: each returned type depends on this type.
79    ///
80    /// Complexity: O(n), where `n` is the number of dependent types.
81    fn dependents(&self) -> impl Iterator<Item = TypeView<'graph, 'a>> + use<'graph, 'a, Self>;
82
83    /// Returns `true` if this type can implement `Eq` and `Hash`.
84    fn hashable(&self) -> bool;
85
86    /// Returns `true` if this type can implement `Default`.
87    fn defaultable(&self) -> bool;
88}
89
90/// A uniquely identifiable graph type view.
91pub trait HasTypeId {
92    /// Returns an opaque identity for this type.
93    fn id(&self) -> TypeId;
94}
95
96/// Opaque identity for a type in a [`CookedGraph`].
97#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
98pub struct TypeId(pub(in crate::ir) NodeIndex<usize>);
99
100/// A graph view that can belong to a resource.
101pub trait HasResource<'a> {
102    /// Returns the resource name, if this view belongs to one.
103    fn resource(&self) -> Option<&'a str>;
104}
105
106/// A view of a graph type with extended data.
107///
108/// Codegen backends use extended data to decorate types with extra information.
109/// For example, Rust codegen stores a unique identifier on each schema type,
110/// so that names never collide after case conversion.
111pub trait ExtendableView<'graph, 'a: 'graph>: View<'graph, 'a> {
112    /// Returns a reference to this type's extended data.
113    fn extensions(&self) -> &ViewExtensions<Self>
114    where
115        Self: Sized;
116
117    /// Returns a mutable reference to this type's extended data.
118    fn extensions_mut(&mut self) -> &mut ViewExtensions<Self>
119    where
120        Self: Sized;
121}
122
123impl<'graph, 'a: 'graph, T> HasTypeId for T
124where
125    T: ViewNode<'graph, 'a>,
126{
127    fn id(&self) -> TypeId {
128        TypeId(self.index())
129    }
130}
131
132impl<'graph, 'a: 'graph, T> View<'graph, 'a> for T
133where
134    T: ViewNode<'graph, 'a>,
135{
136    #[inline]
137    fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'graph, 'a>> + use<'graph, 'a, T> {
138        let cooked = self.cooked();
139        // Follow edges to inline schemas, skipping shadow edges.
140        // See `GraphEdge::shadow()` for an explanation.
141        let filtered = EdgeFiltered::from_fn(&cooked.graph, |e| {
142            !e.weight().shadow() && matches!(cooked.graph[e.target()], GraphType::Inline(_))
143        });
144        let mut bfs = Bfs::new(&cooked.graph, self.index());
145        std::iter::from_fn(move || bfs.next(&filtered))
146            .skip(1) // Skip the starting node.
147            .filter_map(|index| match cooked.graph[index] {
148                GraphType::Inline(ty) => Some(InlineTypeView::new(cooked, index, ty)),
149                _ => None,
150            })
151    }
152
153    #[inline]
154    fn used_by(&self) -> impl Iterator<Item = OperationView<'graph, 'a>> + use<'graph, 'a, T> {
155        let cooked = self.cooked();
156        cooked.metadata.used_by[self.index().index()]
157            .iter()
158            .map(|op| OperationView::new(cooked, op))
159    }
160
161    #[inline]
162    fn dependencies(&self) -> impl Iterator<Item = TypeView<'graph, 'a>> + use<'graph, 'a, T> {
163        let cooked = self.cooked();
164        let start = self.index();
165        cooked
166            .metadata
167            .closure
168            .dependencies_of(start)
169            .filter(move |&index| index != start)
170            .map(|index| TypeView::new(cooked, index))
171    }
172
173    #[inline]
174    fn dependents(&self) -> impl Iterator<Item = TypeView<'graph, 'a>> + use<'graph, 'a, T> {
175        let cooked = self.cooked();
176        let start = self.index();
177        cooked
178            .metadata
179            .closure
180            .dependents_of(start)
181            .filter(move |&index| index != start)
182            .map(|index| TypeView::new(cooked, index))
183    }
184
185    #[inline]
186    fn hashable(&self) -> bool {
187        self.cooked().metadata.hashable[self.index().index()]
188    }
189
190    #[inline]
191    fn defaultable(&self) -> bool {
192        self.cooked().metadata.defaultable[self.index().index()]
193    }
194}
195
196impl<'graph, 'a: 'graph, T> ExtendableView<'graph, 'a> for T
197where
198    T: ViewNode<'graph, 'a>,
199{
200    #[inline]
201    fn extensions(&self) -> &ViewExtensions<Self> {
202        ViewExtensions::new(self)
203    }
204
205    #[inline]
206    fn extensions_mut(&mut self) -> &mut ViewExtensions<Self> {
207        ViewExtensions::new_mut(self)
208    }
209}
210
211pub(crate) trait ViewNode<'graph, 'a: 'graph> {
212    fn cooked(&self) -> &'graph CookedGraph<'a>;
213    fn index(&self) -> NodeIndex<usize>;
214}
215
216impl<'graph, 'a: 'graph, T> internal::Extendable<'graph> for T
217where
218    T: ViewNode<'graph, 'a>,
219{
220    #[inline]
221    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
222    where
223        'graph: 'view,
224    {
225        self.cooked().metadata.extensions[self.index().index()].borrow()
226    }
227
228    #[inline]
229    fn ext_mut<'b>(&'b mut self) -> AtomicRefMut<'b, ExtensionMap>
230    where
231        'graph: 'b,
232    {
233        self.cooked().metadata.extensions[self.index().index()].borrow_mut()
234    }
235}
236
237/// Extended data attached to a graph type.
238#[derive(RefCastCustom)]
239#[repr(transparent)]
240pub struct ViewExtensions<X>(X);
241
242impl<X> ViewExtensions<X> {
243    #[ref_cast_custom]
244    fn new(view: &X) -> &Self;
245
246    #[ref_cast_custom]
247    fn new_mut(view: &mut X) -> &mut Self;
248}
249
250impl<'a, X: internal::Extendable<'a>> ViewExtensions<X> {
251    /// Returns a reference to a value of an arbitrary type that was
252    /// previously inserted into this extended data.
253    #[inline]
254    pub fn get<'b, T: Send + Sync + 'static>(&'b self) -> Option<AtomicRef<'b, T>>
255    where
256        'a: 'b,
257    {
258        AtomicRef::filter_map(self.0.ext(), |ext| {
259            Some(
260                ext.get(&StdTypeId::of::<T>())?
261                    .as_ref()
262                    .downcast_ref::<T>()
263                    .unwrap(),
264            )
265        })
266    }
267
268    /// Inserts a value of an arbitrary type into this extended data,
269    /// and returns the previous value for that type.
270    #[inline]
271    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
272        self.0
273            .ext_mut()
274            .insert(StdTypeId::of::<T>(), Box::new(value))
275            .and_then(|old| *Extension::into_inner(old).downcast().unwrap())
276    }
277}
278
279impl<X> Debug for ViewExtensions<X> {
280    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281        f.debug_tuple("ViewExtensions").finish_non_exhaustive()
282    }
283}
284
285mod internal {
286    use atomic_refcell::{AtomicRef, AtomicRefMut};
287
288    use super::ExtensionMap;
289
290    pub trait Extendable<'graph> {
291        // These lifetime requirements might look redundant, but they're not:
292        // we're shortening the lifetime of the `AtomicRef` from `'graph` to `'view`,
293        // to prevent overlapping mutable borrows of the underlying `AtomicRefCell`
294        // at compile time.
295        //
296        // (`AtomicRefCell` panics on these illegal borrows at runtime, which is
297        // always memory-safe; we just want some extra type safety).
298        //
299        // This approach handles the obvious case of overlapping borrows from
300        // `ext()` and `ext_mut()`, and the `AtomicRefCell` avoids plumbing
301        // mutable references to the graph through every IR layer.
302
303        fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
304        where
305            'graph: 'view;
306
307        fn ext_mut<'view>(&'view mut self) -> AtomicRefMut<'view, ExtensionMap>
308        where
309            'graph: 'view;
310    }
311}