ploidy_core/ir/views/
mod.rs

1//! Graph-aware views of IR types.
2//!
3//! These views provide a representation of schema and inline types
4//! for code generation. Each view decorates an IR type with
5//! additional information from the type graph.
6
7use std::{any::TypeId, collections::VecDeque, fmt::Debug};
8
9use atomic_refcell::{AtomicRef, AtomicRefMut};
10use petgraph::{
11    graph::NodeIndex,
12    visit::{Bfs, EdgeFiltered, EdgeRef, VisitMap, Visitable},
13};
14use ref_cast::{RefCastCustom, ref_cast_custom};
15
16use super::graph::{Extension, ExtensionMap, IrGraph, IrGraphNode};
17
18pub mod enum_;
19pub mod inline;
20pub mod ir;
21pub mod operation;
22pub mod schema;
23pub mod struct_;
24pub mod tagged;
25pub mod untagged;
26pub mod wrappers;
27
28use self::{inline::InlineIrTypeView, ir::IrTypeView, operation::IrOperationView};
29
30/// A view of a type in the graph.
31pub trait View<'a> {
32    /// Returns an iterator over all the inline types that are
33    /// contained within this type.
34    fn inlines(&self) -> impl Iterator<Item = InlineIrTypeView<'a>>;
35
36    /// Returns an iterator over all the operations that directly or transitively
37    /// use this type.
38    fn used_by(&self) -> impl Iterator<Item = IrOperationView<'a>>;
39
40    /// Returns an iterator over all the types that are reachable from this type.
41    fn reachable(&self) -> impl Iterator<Item = IrTypeView<'a>>;
42
43    /// Returns an iterator over all reachable types, with a `filter` function
44    /// to control the traversal.
45    fn reachable_if<F>(&self, filter: F) -> impl Iterator<Item = IrTypeView<'a>>
46    where
47        F: Fn(&IrTypeView<'a>) -> Traversal;
48
49    /// Returns a reference to this type's extended data.
50    fn extensions(&self) -> &IrViewExtensions<Self>
51    where
52        Self: Extendable<'a> + Sized;
53
54    /// Returns a mutable reference to this type's extended data.
55    fn extensions_mut(&mut self) -> &mut IrViewExtensions<Self>
56    where
57        Self: Extendable<'a> + Sized;
58}
59
60impl<'a, T> View<'a> for T
61where
62    T: ViewNode<'a>,
63{
64    #[inline]
65    fn inlines(&self) -> impl Iterator<Item = InlineIrTypeView<'a>> {
66        let graph = self.graph();
67        // Exclude edges that reference other schemas.
68        let filtered = EdgeFiltered::from_fn(&graph.g, |r| {
69            !matches!(graph.g[r.target()], IrGraphNode::Schema(_))
70        });
71        let mut bfs = Bfs::new(&graph.g, self.index());
72        std::iter::from_fn(move || bfs.next(&filtered)).filter_map(|index| match graph.g[index] {
73            IrGraphNode::Inline(ty) => Some(InlineIrTypeView::new(graph, index, ty)),
74            _ => None,
75        })
76    }
77
78    #[inline]
79    fn used_by(&self) -> impl Iterator<Item = IrOperationView<'a>> {
80        self.graph()
81            .metadata
82            .get(&self.index())
83            .into_iter()
84            .flat_map(|meta| &meta.operations)
85            .map(|op| IrOperationView::new(self.graph(), op))
86    }
87
88    #[inline]
89    fn reachable(&self) -> impl Iterator<Item = IrTypeView<'a>> {
90        let graph = self.graph();
91        let mut bfs = Bfs::new(&graph.g, self.index());
92        std::iter::from_fn(move || bfs.next(&graph.g)).map(|index| IrTypeView::new(graph, index))
93    }
94
95    #[inline]
96    fn reachable_if<F>(&self, filter: F) -> impl Iterator<Item = IrTypeView<'a>>
97    where
98        F: Fn(&IrTypeView<'a>) -> Traversal,
99    {
100        let graph = self.graph();
101        let mut stack = VecDeque::new();
102        let mut discovered = graph.g.visit_map();
103
104        stack.push_back(self.index());
105        discovered.visit(self.index());
106
107        std::iter::from_fn(move || {
108            while let Some(index) = stack.pop_front() {
109                let view = IrTypeView::new(graph, index);
110                let traversal = filter(&view);
111
112                if matches!(traversal, Traversal::Visit | Traversal::Skip) {
113                    // Add the neighbors to the stack of nodes to visit.
114                    for neighbor in graph.g.neighbors(index) {
115                        if discovered.visit(neighbor) {
116                            stack.push_back(neighbor);
117                        }
118                    }
119                }
120
121                if matches!(traversal, Traversal::Visit | Traversal::Stop) {
122                    // Yield this node.
123                    return Some(view);
124                }
125
126                // (`Skip` and `Ignore` continue the loop without yielding).
127            }
128            None
129        })
130    }
131
132    #[inline]
133    fn extensions(&self) -> &IrViewExtensions<Self> {
134        IrViewExtensions::new(self)
135    }
136
137    #[inline]
138    fn extensions_mut(&mut self) -> &mut IrViewExtensions<Self> {
139        IrViewExtensions::new_mut(self)
140    }
141}
142
143pub trait ViewNode<'a> {
144    fn graph(&self) -> &'a IrGraph<'a>;
145    fn index(&self) -> NodeIndex;
146}
147
148pub trait Extendable<'graph> {
149    // These lifetime requirements might look redundant, but they're not:
150    // we're shortening the lifetime of the `AtomicRef` from `'graph` to `'view`,
151    // to prevent overlapping mutable borrows of the underlying `AtomicRefCell`
152    // at compile time.
153    //
154    // (`AtomicRefCell` panics on these illegal borrows at runtime, which is
155    // always memory-safe; we just want some extra type safety).
156    //
157    // This approach handles the obvious case of overlapping borrows from
158    // `ext()` and `ext_mut()`, and the `AtomicRefCell` avoids plumbing
159    // mutable references to the graph through every IR layer.
160
161    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
162    where
163        'graph: 'view;
164
165    fn ext_mut<'view>(&'view mut self) -> AtomicRefMut<'view, ExtensionMap>
166    where
167        'graph: 'view;
168}
169
170impl<'graph, T> Extendable<'graph> for T
171where
172    T: ViewNode<'graph>,
173{
174    #[inline]
175    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
176    where
177        'graph: 'view,
178    {
179        self.graph().metadata[&self.index()].extensions.borrow()
180    }
181
182    #[inline]
183    fn ext_mut<'b>(&'b mut self) -> AtomicRefMut<'b, ExtensionMap>
184    where
185        'graph: 'b,
186    {
187        self.graph().metadata[&self.index()].extensions.borrow_mut()
188    }
189}
190
191/// Extended data attached to a type in the graph.
192///
193/// Generators can use extended data to decorate types with extra information,
194/// like name mappings. For example, the Rust generator stores a normalized,
195/// deduplicated identifier name on every named schema type.
196#[derive(RefCastCustom)]
197#[repr(transparent)]
198pub struct IrViewExtensions<X>(X);
199
200impl<X> IrViewExtensions<X> {
201    #[ref_cast_custom]
202    fn new(view: &X) -> &Self;
203
204    #[ref_cast_custom]
205    fn new_mut(view: &mut X) -> &mut Self;
206}
207
208impl<'a, X: Extendable<'a>> IrViewExtensions<X> {
209    /// Returns a reference to a value of an arbitrary type that was
210    /// previously inserted into this extended data.
211    #[inline]
212    pub fn get<'b, T: Send + Sync + 'static>(&'b self) -> Option<AtomicRef<'b, T>>
213    where
214        'a: 'b,
215    {
216        AtomicRef::filter_map(self.0.ext(), |ext| {
217            Some(
218                ext.get(&TypeId::of::<T>())?
219                    .as_ref()
220                    .downcast_ref::<T>()
221                    .unwrap(),
222            )
223        })
224    }
225
226    /// Inserts a value of an arbitrary type into this extended data,
227    /// and returns the previous value for that type.
228    #[inline]
229    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
230        self.0
231            .ext_mut()
232            .insert(TypeId::of::<T>(), Box::new(value))
233            .and_then(|old| *Extension::into_inner(old).downcast().unwrap())
234    }
235}
236
237impl<X> Debug for IrViewExtensions<X> {
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        f.debug_tuple("IrViewExtensions").finish_non_exhaustive()
240    }
241}
242
243/// Controls how to continue traversing the graph when at a node.
244#[derive(Clone, Copy, Debug, Eq, PartialEq)]
245pub enum Traversal {
246    /// Yield this node, then continue into its neighbors.
247    Visit,
248    /// Yield this node, but don't continue into its neighbors.
249    Stop,
250    /// Don't yield this node, but continue into its neighbors.
251    Skip,
252    /// Don't yield this node, and don't continue into its neighbors.
253    Ignore,
254}