scopegraphs_lib/scopegraph.rs
1use std::cell::RefCell;
2use std::{
3 collections::{HashMap, HashSet},
4 fmt::{Debug, Formatter},
5 hash::Hash,
6};
7
8use crate::completeness::{Completeness, UncheckedCompleteness};
9use crate::storage::Storage;
10
11/// Representation of scopes (nodes in the scope graph).
12#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct Scope(pub(crate) usize);
14
15impl Debug for Scope {
16 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17 write!(f, "#{}", self.0)
18 }
19}
20
21// Mutability: RefCell in Scope, not Scope in RefCell
22// Concurrency: RW-lock on edges
23
24#[derive(Debug)]
25pub struct InnerScopeGraph<'sg, LABEL, DATA> {
26 pub(crate) storage: &'sg Storage,
27 #[allow(clippy::type_complexity)]
28 pub(crate) edges: RefCell<Vec<&'sg RefCell<HashMap<LABEL, HashSet<Scope>>>>>, // FIXME: BTreeMap? Vectors? Whatever?
29 pub(crate) data: RefCell<Vec<&'sg DATA>>,
30}
31
32impl<'sg, LABEL, DATA> InnerScopeGraph<'sg, LABEL, DATA> {
33 fn new(storage: &'sg Storage) -> Self {
34 Self {
35 storage,
36 edges: RefCell::new(Vec::new()),
37 data: RefCell::new(Vec::new()),
38 }
39 }
40
41 /// Adds a new scope to the graph, with `data` as its associated data.
42 /// After this operation, all future calls to [`InnerScopeGraph::get_data`] on this scope will return the associated data.
43 pub(super) fn add_scope(&self, data: DATA) -> Scope {
44 let id = self.data.borrow().len();
45
46 self.data.borrow_mut().push(self.storage.0.alloc(data));
47 self.edges.borrow_mut().push(
48 self.storage
49 .0
50 .alloc(RefCell::new(HashMap::with_capacity(0))),
51 );
52 Scope(id)
53 }
54
55 /// Returns the data associated with the `scope` argument.
56 fn get_data(&self, scope: Scope) -> &'sg DATA {
57 // panics if src.0 is out of bounds
58 // the methods that update `ScopeGraphs` retain the invariant that each scope has an appropriate entry in this vector
59 // however, panics can still happen when a scope from a different scope graph is used
60 self.data.borrow()[scope.0]
61 }
62}
63
64impl<'sg, LABEL: Hash + Eq, DATA> InnerScopeGraph<'sg, LABEL, DATA> {
65 /// Adds a new edge from `src`, to `dst`, with label `lbl` to the scope graph.
66 /// After this operation, all future calls to [`InnerScopeGraph::get_edges`] on the source will contain the destination.
67 pub(crate) fn add_edge(&self, src: Scope, lbl: LABEL, dst: Scope) {
68 // panics if src.0 is out of bounds
69 // the methods that update `ScopeGraphs` retain the invariant that each scope has an appropriate entry in this vector
70 // however, panics can still happen when a scope from a different scope graph is used
71 self.edges.borrow()[src.0]
72 .borrow_mut()
73 .entry(lbl)
74 .or_default()
75 .insert(dst);
76 }
77
78 /// Returns the targets of the outgoing edges of `src` with label `lbl`.
79 pub(crate) fn get_edges(&self, scope: Scope, lbl: LABEL) -> Vec<Scope> {
80 // panics if scope.0 is out of bounds
81 // the methods that update `ScopeGraphs` retain the invariant that each scope has an appropriate entry in this vector
82 // however, panics can still happen when a scope from a different scope graph is used
83 self.edges.borrow()[scope.0]
84 .borrow()
85 .get(&lbl)
86 .into_iter()
87 .flatten()
88 .copied()
89 .collect()
90 }
91}
92
93/// Scope Graph data structure.
94///
95/// As a data structure, scope graphs are simple graphs with labeled nodes and labeled, directed edges.
96///
97/// This trait has three type parameters:
98/// - [`LABEL`]: the type of the edge labels.
99/// - [`DATA`]: the type of the scope/node labels.
100/// - [`CMPL`]: metadata that guarantees query stability (i.e., query results remain valid in the future).
101///
102/// The data structure has been designed for typical scope graph usage scenario's.
103/// For example, there is no support for _removing_ scopes or edges, as this usually does not happen in scope graphs.
104/// In addition, there is no data type for edges, as edges should only be traversed, but never leak outside the scope graph structure.
105/// Finally, although not made explicit, [`LABEL`] should be a finite, iterable set.
106#[derive(Debug)]
107pub struct ScopeGraph<'storage, LABEL, DATA, CMPL> {
108 pub(super) inner_scope_graph: InnerScopeGraph<'storage, LABEL, DATA>,
109 pub(super) completeness: CMPL,
110}
111
112impl<'storage, LABEL, DATA, CMPL> ScopeGraph<'storage, LABEL, DATA, CMPL> {
113 pub fn new(storage: &'storage Storage, completeness: CMPL) -> Self {
114 ScopeGraph {
115 inner_scope_graph: InnerScopeGraph::new(storage),
116 completeness,
117 }
118 }
119}
120
121impl<'sg, LABEL, DATA> ScopeGraph<'sg, LABEL, DATA, UncheckedCompleteness> {
122 /// Creates a new scope graph with [`UncheckedCompleteness`] as its completeness validation.
123 ///
124 /// # Safety
125 ///
126 /// Unsafe, because [`UncheckedCompleteness`] does not actually guarantee query stability.
127 pub unsafe fn raw(storage: &'sg Storage) -> Self {
128 Self::new(storage, UncheckedCompleteness::new())
129 }
130}
131
132impl<'sg, LABEL, DATA, CMPL> ScopeGraph<'sg, LABEL, DATA, CMPL>
133where
134 CMPL: Completeness<LABEL, DATA>,
135{
136 /// Add a new scope to the scope graph, with `data` as its label.
137 pub fn add_scope(&self, data: DATA) -> Scope {
138 let scope = self.inner_scope_graph.add_scope(data);
139 self.completeness
140 .cmpl_new_scope(&self.inner_scope_graph, scope);
141 scope
142 }
143
144 /// Add a new edge in the scope graph.
145 ///
146 /// Permission for this is checked by `CMPL`.
147 pub fn add_edge(&self, src: Scope, lbl: LABEL, dst: Scope) -> CMPL::NewEdgeResult {
148 self.completeness
149 .cmpl_new_edge(&self.inner_scope_graph, src, lbl, dst)
150 }
151
152 /// Get the data associated with a scope.
153 pub fn get_data(&self, scope: Scope) -> &DATA {
154 self.inner_scope_graph.get_data(scope)
155 }
156
157 /// Get the targets of the outgoing edges of a scope with some label.
158 ///
159 /// Permission for this operation is checked by `CMPL`.
160 pub fn get_edges(&self, src: Scope, lbl: LABEL) -> CMPL::GetEdgesResult<'_> {
161 self.completeness
162 .cmpl_get_edges(&self.inner_scope_graph, src, lbl)
163 }
164
165 /// Utility function to add declarations (i.e., scopes with data, without any outgoing edges).
166 ///
167 /// It performs (roughly) the following operation:
168 ///
169 /// ```ignore
170 /// fn add_decl(&self, src: Scope, lbl: LABEL, data: DATA) -> CMPL::NewEdgeResult {
171 /// let s_data = self.add_scope(data);
172 /// self.add_edge(src, lbl, s_data);
173 /// }
174 /// ```
175 pub fn add_decl(&self, src: Scope, lbl: LABEL, data: DATA) -> CMPL::NewEdgeResult {
176 // Create scope with no open edges.
177 let s_data = self.inner_scope_graph.add_scope(data);
178 self.completeness
179 .cmpl_new_complete_scope(&self.inner_scope_graph, s_data);
180 self.add_edge(src, lbl, s_data)
181 }
182}
183
184impl<'sg, LABEL, DATA, CMPL> ScopeGraph<'sg, LABEL, DATA, CMPL>
185where
186 DATA: Default,
187 CMPL: Completeness<LABEL, DATA>,
188{
189 /// Add a new scope to the scope graph, with default data.
190 pub fn add_scope_default(&self) -> Scope {
191 self.add_scope(DATA::default())
192 }
193}