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}