scopegraphs/completeness/mod.rs
1//! This module contains several utilities to guarantee query stability.
2//!
3//! Query stability means that the result of a query, once resolved, will remain valid after future
4//! additions to the scope graph.
5//! This allows safe interleaving of name resolution and scope graph construction.
6//!
7//! The main trait of this module is [`Completeness`]. An implementation of this trait should be passed
8//! to [`ScopeGraph::new`](super::ScopeGraph::new) to obtain a scope graph instance.
9//!
10//! Currently, the module contains three safe implementations.
11//! * [`ImplicitClose`] is the easiest to use, and most likely the preferred choice for simple
12//! sequential type checkers.
13//! * [`ExplicitClose`] requires some manual bookkeeping, but allows more flexible handling of
14//! queries. This is the most suitable choice for type checkers that need to do dynamic scheduling.
15//! Running queries can return an error, because scopes relevant to the query weren't closed yet.
16//! * [`FutureCompleteness`] is like [`ExplicitClose`], except queries can no longer error. Instead,
17//! queries return a [`Future`](std::future::Future) that resolves when all scopes related to the query are closed.
18
19mod future;
20
21pub use future::*;
22
23mod critical_edge;
24pub use critical_edge::*;
25mod explicit;
26pub use explicit::*;
27mod implicit;
28pub use implicit::*;
29mod unchecked;
30pub use unchecked::*;
31
32mod private {
33 pub trait Sealed {}
34}
35
36use crate::scopegraph::{InnerScopeGraph, Scope};
37use crate::{Label, ScopeGraph};
38use private::Sealed;
39use std::rc::Rc;
40use std::{fmt::Debug, hash::Hash, marker::PhantomData};
41
42/*** Completeness trait ***/
43
44/// Types implementing this trait have the responsibility to guarantee query stability.
45///
46/// This means that the result of a query, once computed, may not be invalidated by later extensions
47/// of the scope graph. See [`crate::concepts::completeness`]
48///
49/// A [`super::ScopeGraph`] will call the handlers of its completeness on each operation.
50/// For scope addition and data access, it can update some internal state.
51/// For edge addition and retrieval, it can override/augment the default result as well.
52///
53/// The way query stability is guaranteed is very flexible. Current implementations use
54/// - critical edges, explicitly closed by the client ([`ExplicitClose`]).
55/// - critical edges, implicitly closed when retrieved ([`ImplicitClose`]).
56///
57/// Future implementations may also:
58/// - delay retrieval of edges until it is closed (by returning a future). TODO: [this is implemented now](`FutureCompleteness`)
59/// - guarantee stability by retaining residual queries, and checking those do not return any
60/// results for later additions.
61/// - ...
62///
63/// This trait is sealed.
64/// You cannot define your own completeness strategies to ensure that only verified implementations are available.
65// TODO: @Aron could you document this?
66#[allow(missing_docs)]
67pub trait Completeness<LABEL: Label, DATA>: Sealed {
68 fn cmpl_new_scope(&self, inner_scope_graph: &InnerScopeGraph<LABEL, DATA>, scope: Scope);
69
70 /// Should initialize a scope without possibility to extend it with edges
71 fn cmpl_new_complete_scope(
72 &self,
73 inner_scope_graph: &InnerScopeGraph<LABEL, DATA>,
74 scope: Scope,
75 );
76
77 type NewEdgeResult;
78 fn cmpl_new_edge(
79 &self,
80 inner_scope_graph: &InnerScopeGraph<LABEL, DATA>,
81 src: Scope,
82 lbl: LABEL,
83 dst: Scope,
84 ) -> Self::NewEdgeResult;
85
86 // type GetDataResult;
87 // fn get_data(&mut self, inner_scope_graph: &InnerScopeGraph<LABEL, DATA>, scope: Scope) -> Self::GetDataResult;
88
89 type GetEdgesResult<'rslv>
90 where
91 Self: 'rslv,
92 LABEL: 'rslv,
93 DATA: 'rslv;
94 fn cmpl_get_edges<'rslv>(
95 &'rslv self,
96 inner_scope_graph: &'rslv InnerScopeGraph<LABEL, DATA>,
97 src: Scope,
98 lbl: LABEL,
99 ) -> Self::GetEdgesResult<'rslv>
100 where
101 LABEL: 'rslv,
102 DATA: 'rslv;
103}
104
105// There are two main ways of creating edges, which depend on the completeness mode:
106// - user-closed: requires the user to close each open edge exactly once; extension based on `ScopeExt`
107// - implicit: user does not have explicit control over closing edges
108// These are marker traits, that are used in impl block that provide different APIs to different
109
110#[doc(hidden)]
111pub struct Witness(pub(crate) ());
112
113/// Marker trait for completeness strategies that require edges to be closed explicitly.
114pub trait UserClosed<LABEL: Label, DATA>: Completeness<LABEL, DATA> {
115 #[doc(hidden)]
116 fn close(&self, scope: Scope, label: &LABEL, witness: Witness);
117}
118
119/// Marker trait for completeness strategies that do not require edges to be closed explicitly.
120///
121/// Used to make the correct API's available on scope graphs.
122pub trait Implicit<LABEL: Label, DATA>: Completeness<LABEL, DATA> {}
123
124// Edge extension permissions for UserClosed Completenesses
125#[derive(Debug)]
126struct ScopeExtPermInner<
127 'ext,
128 LABEL: Hash + Label + Debug, /* <= Bound here required for Drop implementation */
129 DATA,
130 CMPL: UserClosed<LABEL, DATA>, // Bound required for Drop implementation
131> {
132 /// Scope for which this object witnesses the permission to extend.
133 scope: Scope,
134 /// Label with which [scope] may be extended
135 label: LABEL,
136 /// Scope graph in which the scope may be extended.
137 sg: &'ext CMPL,
138 _data: PhantomData<DATA>, // FIXME: required for using `where CMPL: UserClosed<LABEL, DATA>` in impl blocks. Can it be removed some way?
139}
140
141impl<LABEL: Hash + Label + Debug, DATA, CMPL> Drop for ScopeExtPermInner<'_, LABEL, DATA, CMPL>
142where
143 CMPL: UserClosed<LABEL, DATA>,
144{
145 /// This is the trick! When the permission is dropped, we know for sure that no future extensions will be made (otherwise, [self] should have been kept alive)
146 /// Thus, we can close the critical edge.
147 fn drop(&mut self) {
148 self.sg.close(self.scope, &self.label, Witness(()))
149 }
150}
151
152/// Represents the permission to extend a scope with a particular label.
153///
154/// Do not instantiate this struct manually, but use the [add_scope] macro instead.
155#[derive(Debug)]
156pub struct ScopeExtPerm<'ext, LABEL: Hash + Label + Debug, DATA, CMPL: UserClosed<LABEL, DATA>>(
157 Rc<ScopeExtPermInner<'ext, LABEL, DATA, CMPL>>,
158);
159
160impl<LABEL: Hash + Label + Debug, DATA, CMPL: UserClosed<LABEL, DATA>> Clone
161 for ScopeExtPerm<'_, LABEL, DATA, CMPL>
162{
163 fn clone(&self) -> Self {
164 Self(self.0.clone())
165 }
166}
167
168impl<'ext, LABEL: Hash + Label + Debug, DATA, CMPL: UserClosed<LABEL, DATA>>
169 ScopeExtPerm<'ext, LABEL, DATA, CMPL>
170where
171 CMPL: UserClosed<LABEL, DATA>,
172{
173 pub(super) fn scope(&self) -> &Scope {
174 &self.0.scope
175 }
176
177 pub(super) fn label(&self) -> &LABEL {
178 &self.0.label
179 }
180
181 /// This is an implementation detail of the [add_scope!] macro and should not be called directly!
182 #[doc(hidden)]
183 pub unsafe fn init<'storage>(
184 scope: Scope,
185 label: LABEL,
186 sg: &'ext ScopeGraph<'storage, LABEL, DATA, CMPL>,
187 ) -> ScopeExtPerm<'ext, LABEL, DATA, CMPL> {
188 ScopeExtPerm(Rc::new(ScopeExtPermInner {
189 scope,
190 label,
191 sg: &sg.completeness,
192 _data: PhantomData,
193 }))
194 }
195
196 /// Closes the edge, meaning that it cannot be extended anymore.
197 /// // TODO: fix this sentence
198 /// Closes an edge, (i.e., prohibit future new extensions, by losing ownership of the extends permission).
199 ///
200 /// For example, the following program will return an error.
201 /// ```compile_fail
202 /// # use scopegraphs::completeness::ExplicitClose;
203 /// # use scopegraphs::Label;
204 /// # use scopegraphs::Storage;
205 /// # use scopegraphs::ScopeGraph;
206 /// # use scopegraphs::add_scope;
207 ///
208 /// # #[derive(Eq, Hash, PartialEq, Label, Clone, Copy)] enum Lbl { Def }
209 /// # use Lbl::*;
210 /// let storage = Storage::new();
211 /// let mut sg = ScopeGraph::<Lbl, usize, _>::new(&storage, ExplicitClose::default());
212 ///
213 /// let (s1, s1_def) = add_scope!(&sg, 0, [Def]);
214 /// let s2 = sg.add_scope_closed(42);
215 ///
216 /// s1_def.close();
217 /// sg.ext_edge(&s1_def, s2).expect_err("cannot add edge after closing edge");
218 /// ```
219 ///
220 /// Closing is required to permit queries to traverse these edges:
221 /// ```
222 ///
223 /// # use scopegraphs::completeness::ExplicitClose;
224 /// # use scopegraphs::ScopeGraph;
225 /// # use scopegraphs::resolve::{DefaultDataEquivalence, DefaultLabelOrder, EdgeOrData, Resolve};
226 /// # use scopegraphs_macros::{compile_regex, Label};
227 /// # use scopegraphs::Storage;
228 /// # use scopegraphs::add_scope;
229 /// #
230 /// # #[derive(Eq, Hash, PartialEq, Label, Debug, Copy, Clone)]
231 /// # enum Lbl { Def }
232 /// # use Lbl::*;
233 /// # type LblD = EdgeOrData<Lbl>;
234 /// #
235 /// # compile_regex!(type Regex<Lbl> = Def);
236 /// let storage = Storage::new();
237 /// let mut sg = ScopeGraph::<Lbl, usize, _>::new(&storage, ExplicitClose::default());
238 ///
239 /// let (s1, s1_def) = add_scope!(&sg, 0, [Def]);
240 /// let s2 = sg.add_scope_closed(42);
241 ///
242 /// // Note: not calling `s1_def.close()`
243 ///
244 /// let query_result = sg.query()
245 /// .with_path_wellformedness(Regex::new()) // regex: `Def`
246 /// .with_data_wellformedness(|x: &usize| *x == 42) // match `42`
247 /// .resolve(s1);
248 ///
249 /// query_result.expect_err("require s1/Def to be closed");
250 /// ```
251 ///
252 /// Closing allows queries to resolve:
253 /// ```
254 ///
255 /// # use scopegraphs::completeness::ExplicitClose;
256 /// # use scopegraphs::ScopeGraph;
257 /// # use scopegraphs::resolve::{DefaultDataEquivalence, DefaultLabelOrder, EdgeOrData, Resolve};
258 /// # use scopegraphs_macros::{compile_regex, Label};
259 /// # use scopegraphs::Storage;
260 /// # use scopegraphs::add_scope;
261 /// #
262 /// # #[derive(Eq, Hash, PartialEq, Label, Debug, Copy, Clone)]
263 /// # enum Lbl { Def }
264 /// # use Lbl::*;
265 /// # type LblD = EdgeOrData<Lbl>;
266 /// #
267 /// # compile_regex!(type Regex<Lbl> = Def);
268 /// let storage = Storage::new();
269 /// let mut sg = ScopeGraph::<Lbl, usize, _>::new(&storage, ExplicitClose::default());
270 ///
271 /// let (s1, s1_def) = add_scope!(&sg, [Def]);
272 /// let s2 = sg.add_scope_closed(42);
273 ///
274 /// // Note: closing the edge *after* creating all edges, *before* doing the query
275 /// s1_def.close();
276 ///
277 /// let query_result = sg.query()
278 /// .with_path_wellformedness(Regex::new()) // regex: `Def`
279 /// .with_data_wellformedness(|x: &usize| *x == 42) // match `42`
280 /// .resolve(s1);
281 ///
282 /// query_result.expect("query should return result");
283 /// ```
284 pub fn close(self) {
285 // self dropped at the end of this block
286 }
287}
288
289/// Creates a scope (with some data if specified), and permission to extend it for each label specified in the label list argument.
290///
291/// TODO: Better documentation, examples.
292#[macro_export]
293macro_rules! add_scope {
294 ($sg:expr, $data:expr, [ $($lbl:expr),* ]) => {
295 {
296 // put initialized code in block
297 let sg = $sg; // evaluate scope graph expression
298 let data = $data; // evaluate data expression
299
300 // create new scope
301 let scope = sg.add_scope_with(data, [$($lbl),*]);
302
303 // return the scope, and the extension permissions
304 (scope, $(unsafe { $crate::completeness::ScopeExtPerm::init(scope, $lbl, sg) }),*)
305 }
306 };
307
308 // case when no data is given: falls back on [Default] value for data
309 ($sg:expr, [$($lbl:expr),* ]) => { add_scope!($sg, Default::default(), [$($lbl),*]) };
310
311 // case when no data nor labels are given
312 ($sg:expr) => { add_scope!($sg, Default::default(), []).0 };
313}
314
315/// Adds an edge to the scope graph
316// TODO: better explanation, (link to) examples.
317#[macro_export]
318macro_rules! add_edge {
319 // case for call with extension permission
320 ($sg:expr, $ext:expr, $tgt:expr) => {
321 sg.ext_edge($ext, $tgt)
322 };
323
324 // case when giving source and target scopes explicitly
325 ($sg:expr, $src:expr, $lbl:expr, $tgt:expr) => {
326 sg.add_edge($src, $lbl, $tgt)
327 };
328}
329
330// TODO: Residual-query-based Completeness requires access to query resolution