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, fmt::Debug};
8
9use atomic_refcell::{AtomicRef, AtomicRefMut};
10use petgraph::{
11    graph::NodeIndex,
12    visit::{Bfs, EdgeFiltered, EdgeRef},
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 a reference to this type's extended data.
44    fn extensions(&self) -> &IrViewExtensions<Self>
45    where
46        Self: Extendable<'a> + Sized;
47
48    /// Returns a mutable reference to this type's extended data.
49    fn extensions_mut(&mut self) -> &mut IrViewExtensions<Self>
50    where
51        Self: Extendable<'a> + Sized;
52}
53
54impl<'a, T> View<'a> for T
55where
56    T: ViewNode<'a>,
57{
58    #[inline]
59    fn inlines(&self) -> impl Iterator<Item = InlineIrTypeView<'a>> {
60        let graph = self.graph();
61        // Exclude edges that reference other schemas.
62        let filtered = EdgeFiltered::from_fn(&graph.g, |r| {
63            !matches!(graph.g[r.target()], IrGraphNode::Schema(_))
64        });
65        let mut bfs = Bfs::new(&graph.g, self.index());
66        std::iter::from_fn(move || bfs.next(&filtered)).filter_map(|index| match graph.g[index] {
67            IrGraphNode::Inline(ty) => Some(InlineIrTypeView::new(graph, index, ty)),
68            _ => None,
69        })
70    }
71
72    #[inline]
73    fn used_by(&self) -> impl Iterator<Item = IrOperationView<'a>> {
74        self.graph()
75            .metadata
76            .get(&self.index())
77            .into_iter()
78            .flat_map(|meta| &meta.operations)
79            .map(|op| IrOperationView::new(self.graph(), op))
80    }
81
82    #[inline]
83    fn reachable(&self) -> impl Iterator<Item = IrTypeView<'a>> {
84        let graph = self.graph();
85        let mut bfs = Bfs::new(&graph.g, self.index());
86        std::iter::from_fn(move || bfs.next(&graph.g)).map(|index| IrTypeView::new(graph, index))
87    }
88
89    #[inline]
90    fn extensions(&self) -> &IrViewExtensions<Self> {
91        IrViewExtensions::new(self)
92    }
93
94    #[inline]
95    fn extensions_mut(&mut self) -> &mut IrViewExtensions<Self> {
96        IrViewExtensions::new_mut(self)
97    }
98}
99
100pub trait ViewNode<'a> {
101    fn graph(&self) -> &'a IrGraph<'a>;
102    fn index(&self) -> NodeIndex;
103}
104
105pub trait Extendable<'graph> {
106    // These lifetime requirements might look redundant, but they're not:
107    // we're shortening the lifetime of the `AtomicRef` from `'graph` to `'view`,
108    // to prevent overlapping mutable borrows of the underlying `AtomicRefCell`
109    // at compile time.
110    //
111    // (`AtomicRefCell` panics on these illegal borrows at runtime, which is
112    // always memory-safe; we just want some extra type safety).
113    //
114    // This approach handles the obvious case of overlapping borrows from
115    // `ext()` and `ext_mut()`, and the `AtomicRefCell` avoids plumbing
116    // mutable references to the graph through every IR layer.
117
118    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
119    where
120        'graph: 'view;
121
122    fn ext_mut<'view>(&'view mut self) -> AtomicRefMut<'view, ExtensionMap>
123    where
124        'graph: 'view;
125}
126
127impl<'graph, T> Extendable<'graph> for T
128where
129    T: ViewNode<'graph>,
130{
131    #[inline]
132    fn ext<'view>(&'view self) -> AtomicRef<'view, ExtensionMap>
133    where
134        'graph: 'view,
135    {
136        self.graph().metadata[&self.index()].extensions.borrow()
137    }
138
139    #[inline]
140    fn ext_mut<'b>(&'b mut self) -> AtomicRefMut<'b, ExtensionMap>
141    where
142        'graph: 'b,
143    {
144        self.graph().metadata[&self.index()].extensions.borrow_mut()
145    }
146}
147
148/// Extended data attached to a type in the graph.
149///
150/// Generators can use extended data to decorate types with extra information,
151/// like name mappings. For example, the Rust generator stores a normalized,
152/// deduplicated identifier name on every named schema type.
153#[derive(RefCastCustom)]
154#[repr(transparent)]
155pub struct IrViewExtensions<X>(X);
156
157impl<X> IrViewExtensions<X> {
158    #[ref_cast_custom]
159    fn new(view: &X) -> &Self;
160
161    #[ref_cast_custom]
162    fn new_mut(view: &mut X) -> &mut Self;
163}
164
165impl<'a, X: Extendable<'a>> IrViewExtensions<X> {
166    /// Returns a reference to a value of an arbitrary type that was
167    /// previously inserted into this extended data.
168    #[inline]
169    pub fn get<'b, T: Send + Sync + 'static>(&'b self) -> Option<AtomicRef<'b, T>>
170    where
171        'a: 'b,
172    {
173        AtomicRef::filter_map(self.0.ext(), |ext| {
174            Some(
175                ext.get(&TypeId::of::<T>())?
176                    .as_ref()
177                    .downcast_ref::<T>()
178                    .unwrap(),
179            )
180        })
181    }
182
183    /// Inserts a value of an arbitrary type into this extended data,
184    /// and returns the previous value for that type.
185    #[inline]
186    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) -> Option<T> {
187        self.0
188            .ext_mut()
189            .insert(TypeId::of::<T>(), Box::new(value))
190            .and_then(|old| *Extension::into_inner(old).downcast().unwrap())
191    }
192}
193
194impl<X> Debug for IrViewExtensions<X> {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.debug_tuple("IrViewExtensions").finish_non_exhaustive()
197    }
198}