Skip to main content

lady_deirdre/analysis/
tasks.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Lady Deirdre", a compiler front-end foundation       //
3// technology.                                                                //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/lady-deirdre/blob/master/EULA.md           //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{collections::HashSet, hash::RandomState};
36
37use crate::{
38    analysis::{
39        manager::TaskId,
40        AnalysisError,
41        AnalysisResult,
42        Analyzer,
43        Classifier,
44        DocumentReadGuard,
45        Event,
46        Grammar,
47        Revision,
48        TaskHandle,
49        TriggerHandle,
50    },
51    arena::Id,
52    lexis::{ToSpan, TokenBuffer},
53    sync::{Shared, SyncBuildHasher},
54    syntax::NodeRef,
55    units::{CompilationUnit, Document},
56};
57
58/// A task that grants access to the semantic features of the [Analyzer].
59///
60/// This kind of task implements a [SemanticAccess] trait through which you can
61/// read particular [attribute](crate::analysis::Attr) values, but you cannot
62/// change the content of the documents.
63///
64/// You may have as many instances of this task as needed at the same time, and
65/// you can use them from multiple threads to read the attributes. The analyzer
66/// is capable to manage the semantic graph concurrently.
67pub struct AnalysisTask<
68    'a,
69    N: Grammar,
70    H: TaskHandle = TriggerHandle,
71    S: SyncBuildHasher = RandomState,
72> {
73    id: TaskId,
74    analyzer: &'a Analyzer<N, H, S>,
75    revision: Revision,
76    handle: &'a H,
77}
78
79impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> SemanticAccess<N, H, S>
80    for AnalysisTask<'a, N, H, S>
81{
82}
83
84impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> AbstractTask<N, H, S>
85    for AnalysisTask<'a, N, H, S>
86{
87    #[inline(always)]
88    fn handle(&self) -> &H {
89        self.handle
90    }
91}
92
93impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> TaskSealed<N, H, S>
94    for AnalysisTask<'a, N, H, S>
95{
96    #[inline(always)]
97    fn analyzer(&self) -> &Analyzer<N, H, S> {
98        self.analyzer
99    }
100
101    #[inline(always)]
102    fn revision(&self) -> Revision {
103        self.revision
104    }
105}
106
107impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> Drop for AnalysisTask<'a, N, H, S> {
108    fn drop(&mut self) {
109        self.analyzer.tasks.release_task(self.id);
110    }
111}
112
113impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> AnalysisTask<'a, N, H, S> {
114    #[inline(always)]
115    pub(super) fn new(id: TaskId, analyzer: &'a Analyzer<N, H, S>, handle: &'a H) -> Self {
116        Self {
117            id,
118            analyzer,
119            revision: analyzer.db.load_revision(),
120            handle,
121        }
122    }
123}
124
125/// A task that grants access to change the content of the documents managed by
126/// the [Analyzer].
127///
128/// This kind of task implements a [MutationAccess] trait through which you can
129/// create, delete, or edit the existing documents' content, but you cannot
130/// read [attributes](crate::analysis::Attr) of the semantic graph.
131///
132/// You may have as many instances of this task as needed at the same time, and
133/// you can use them from multiple threads to manage distinct documents.
134///
135/// The analyzer allows you to edit independent documents concurrently, but
136/// if two independent threads would edit the same document, one of them will
137/// block until another one finishes its job.
138pub struct MutationTask<
139    'a,
140    N: Grammar,
141    H: TaskHandle = TriggerHandle,
142    S: SyncBuildHasher = RandomState,
143> {
144    id: TaskId,
145    analyzer: &'a Analyzer<N, H, S>,
146    handle: &'a H,
147}
148
149impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> MutationAccess<N, H, S>
150    for MutationTask<'a, N, H, S>
151{
152}
153
154impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> AbstractTask<N, H, S>
155    for MutationTask<'a, N, H, S>
156{
157    #[inline(always)]
158    fn handle(&self) -> &H {
159        self.handle
160    }
161}
162
163impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> TaskSealed<N, H, S>
164    for MutationTask<'a, N, H, S>
165{
166    #[inline(always)]
167    fn analyzer(&self) -> &Analyzer<N, H, S> {
168        self.analyzer
169    }
170
171    #[inline(always)]
172    fn revision(&self) -> Revision {
173        self.analyzer.db.load_revision()
174    }
175}
176
177impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> Drop for MutationTask<'a, N, H, S> {
178    fn drop(&mut self) {
179        self.analyzer.tasks.release_task(self.id);
180    }
181}
182
183impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> MutationTask<'a, N, H, S> {
184    #[inline(always)]
185    pub(super) fn new(id: TaskId, analyzer: &'a Analyzer<N, H, S>, handle: &'a H) -> Self {
186        Self {
187            id,
188            analyzer,
189            handle,
190        }
191    }
192}
193
194/// An exclusive task that grants access to change documents and to observe
195/// the semantic graph of the [Analyzer].
196///
197/// This task implements a [MutationAccess] trait through which you can
198/// create, delete, or edit the existing documents' content, and implements
199/// a [SemanticAccess] trait through which you can read particular
200/// [attribute](crate::analysis::Attr) values.
201///
202/// You can request both kinds of operations sequentially in a single thread,
203/// but the Analyzer does not allow you to have more than one active Exclusive
204/// task, and exclusive access is granted if and only if no other types
205/// of active tasks are granted.
206pub struct ExclusiveTask<
207    'a,
208    N: Grammar,
209    H: TaskHandle = TriggerHandle,
210    S: SyncBuildHasher = RandomState,
211> {
212    id: TaskId,
213    analyzer: &'a Analyzer<N, H, S>,
214    handle: &'a H,
215}
216
217impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> SemanticAccess<N, H, S>
218    for ExclusiveTask<'a, N, H, S>
219{
220}
221
222impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> MutationAccess<N, H, S>
223    for ExclusiveTask<'a, N, H, S>
224{
225}
226
227impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> AbstractTask<N, H, S>
228    for ExclusiveTask<'a, N, H, S>
229{
230    #[inline(always)]
231    fn handle(&self) -> &H {
232        self.handle
233    }
234}
235
236impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> TaskSealed<N, H, S>
237    for ExclusiveTask<'a, N, H, S>
238{
239    #[inline(always)]
240    fn analyzer(&self) -> &Analyzer<N, H, S> {
241        self.analyzer
242    }
243
244    #[inline(always)]
245    fn revision(&self) -> Revision {
246        self.analyzer.db.load_revision()
247    }
248}
249
250impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> Drop for ExclusiveTask<'a, N, H, S> {
251    fn drop(&mut self) {
252        self.analyzer.tasks.release_task(self.id);
253    }
254}
255
256impl<'a, N: Grammar, H: TaskHandle, S: SyncBuildHasher> ExclusiveTask<'a, N, H, S> {
257    #[inline(always)]
258    pub(super) fn new(id: TaskId, analyzer: &'a Analyzer<N, H, S>, handle: &'a H) -> Self {
259        Self {
260            id,
261            analyzer,
262            handle,
263        }
264    }
265}
266
267/// A trait that provides documents' mutation operations for the [MutationTask]
268/// and the [ExclusiveTask].
269///
270/// This trait is sealed and cannot be implemented outside of this crate.
271///
272/// The MutationAccess trait is a subtrait of the [AbstractTask] trait that
273/// provides general access to the [Analyzer]'s content.
274pub trait MutationAccess<N: Grammar, H: TaskHandle, S: SyncBuildHasher>:
275    AbstractTask<N, H, S>
276{
277    /// Creates a mutable [Document] inside the [Analyzer].
278    ///
279    /// This type of documents supports [write](Self::write_to_doc) operations.
280    ///
281    /// Returns a unique [identifier](Id) of the created document.
282    ///
283    /// The parameter could be a [TokenBuffer] or just an arbitrary string.
284    #[inline(always)]
285    fn add_mutable_doc(&mut self, text: impl Into<TokenBuffer<N::Token>>) -> Id {
286        self.analyzer().register_doc(Document::new_mutable(text))
287    }
288
289    /// Creates an immutable [Document] inside the [Analyzer].
290    ///
291    /// This type of documents does not support [write](Self::write_to_doc)
292    /// operations.
293    ///
294    /// Returns a unique [identifier](Id) of the created document.
295    ///
296    /// The parameter could be a [TokenBuffer] or just an arbitrary string.
297    #[inline(always)]
298    fn add_immutable_doc(&mut self, text: impl Into<TokenBuffer<N::Token>>) -> Id {
299        self.analyzer().register_doc(Document::new_immutable(text))
300    }
301
302    /// Writes user-input edit into the document managed by the [Analyzer].
303    ///
304    /// The `id` parameter specifies the document's [identifier](Id). If there is
305    /// no corresponding document managed by the analyzer
306    /// (e.g., if the document has been [removed](Self::remove_doc) or was not
307    /// created), the function returns
308    /// a [MissingDocument](AnalysisError::MissingDocument) error.
309    ///
310    /// If the document exists but is not [mutable](Self::add_mutable_doc),
311    /// the function returns
312    /// an [ImmutableDocument](AnalysisError::ImmutableDocument) error.
313    ///
314    /// The `span` parameter specifies a [span](ToSpan) of the text that needs
315    /// to be rewritten (e.g., an absolute chars range `10..30`, the entire text
316    /// cover `..`, or a single site inside the text `10..10`, or any other
317    /// type of span). If the span parameter is not
318    /// [valid](ToSpan::is_valid_span) for this document, the function returns
319    /// [InvalidSpan](AnalysisError::InvalidSpan) error.
320    ///
321    /// The `text` parameter is a string to be written in place of the spanned
322    /// source code text.
323    ///
324    /// This function instantly reparses a part of the underlying source code
325    /// relative to the edit, and it invalidates corresponding parts of the
326    /// analyzer's semantic graph, but it does not recompute invalid graph
327    /// [attributes](crate::analysis::Attr). The corresponding graph attributes
328    /// will be recomputed on demand later on when you try to read them
329    /// directly or indirectly through other related attributes.
330    ///
331    /// The reparsing process and the semantic graph invalidation usually take
332    /// a short time if the edit is short, and even if the entire source code is
333    /// big. Therefore, it is acceptable to call this function on every
334    /// user-input action. For instance, you can call this function on every
335    /// content change event from the text editor.
336    #[inline(always)]
337    fn write_to_doc(
338        &mut self,
339        id: Id,
340        span: impl ToSpan,
341        text: impl AsRef<str>,
342    ) -> AnalysisResult<()> {
343        self.analyzer().write_to_doc(self.handle(), id, span, text)
344    }
345
346    /// Removes a document managed by the [Analyzer].
347    ///
348    /// The `id` parameter specifies the document's [identifier](Id).
349    ///
350    /// If the document exists in the analyzer, the function returns true,
351    /// indicating that the document was successfully removed.
352    /// Otherwise, the function returns false.
353    #[inline(always)]
354    fn remove_doc(&mut self, id: Id) -> bool {
355        self.analyzer().remove_doc(id)
356    }
357
358    /// Invalidates semantic graph [attributes](crate::analysis::Attr)
359    /// associated with the corresponding `event` and the `id` parameters.
360    ///
361    /// This function will invalidate the attributes currently
362    /// [subscribed](crate::analysis::AttrContext::subscribe) on this event
363    /// with the [nil](Id::nil) identifier (passed to the subscribe function).
364    ///
365    /// Additionally, if the `id` parameter of this function is not
366    /// [nil](Id::nil), the function will invalidate attributes currently
367    /// subscribed on this event with this identifier.
368    ///
369    /// Note that the function does not recompute invalid attributes instantly.
370    /// The corresponding graph attributes will be recomputed on demand later on
371    /// when you try to read them directly or indirectly through other related
372    /// attributes.
373    #[inline(always)]
374    fn trigger_event(&mut self, id: Id, event: Event) {
375        let revision = self.analyzer().db.commit_revision();
376
377        self.analyzer().trigger_event(id, event, revision)
378    }
379}
380
381/// A marker trait of the [AnalysisTask] and the [ExclusiveTask] tasks indicating
382/// that these objects are allowed to read semantics information of
383/// the [Analyzer]'s semantic graph.
384///
385/// References to the object implementing this trait are passed to
386/// the corresponding semantics read functions such as
387/// the [Attr::snapshot](crate::analysis::Attr::snapshot) function to get
388/// a copy of the attribute's value.
389///
390/// This trait is sealed and cannot be implemented outside of this crate.
391///
392/// The SemanticAccess trait is a subtrait of the [AbstractTask] trait that
393/// provides general access to the [Analyzer]'s content.
394pub trait SemanticAccess<N: Grammar, H: TaskHandle, S: SyncBuildHasher>:
395    AbstractTask<N, H, S>
396{
397}
398
399/// A trait that provides general access to the [Analyzer]'s content.
400///
401/// This trait is a supertrait of the [MutationAccess] and the [SemanticAccess]
402/// trait, and therefore implemented for all three kinds of tasks:
403/// [AnalysisTask], [MutationTask], and [ExclusiveTask].
404///
405/// This trait is sealed and cannot be implemented outside of this crate.
406pub trait AbstractTask<N: Grammar, H: TaskHandle, S: SyncBuildHasher>: TaskSealed<N, H, S> {
407    /// Returns a [handle](TaskHandle) of the task through which you can
408    /// manually check if the task has been interrupted, and through which you
409    /// can interrupt the task manually.
410    fn handle(&self) -> &H;
411
412    /// A convenient function that checks if the task was interrupted.
413    ///
414    /// Returns Ok, if the task was not interrupted. Otherwise, returns
415    /// [Interrupted](AnalysisError::Interrupted) error.
416    #[inline(always)]
417    fn proceed(&self) -> AnalysisResult<()> {
418        if self.handle().is_triggered() {
419            return Err(AnalysisError::Interrupted);
420        }
421
422        Ok(())
423    }
424
425    /// Returns true, if the analyzer has a document with the `id` identifier.
426    #[inline(always)]
427    fn contains_doc(&self, id: Id) -> bool {
428        self.analyzer().docs.contains_key(&id)
429    }
430
431    /// Returns a RAII guard that provides read-only access to the analyzer's
432    /// document with specified `id`.
433    ///
434    /// Returns a [MissingDocument](AnalysisError::MissingDocument) error
435    /// if there is no document with the specified `id`.
436    ///
437    /// If the document exists but is currently locked for write (e.g., another
438    /// mutation task is performing a [write](MutationAccess::write_to_doc)
439    /// operation), the current thread will be blocked until the document is
440    /// unlocked.
441    #[inline(always)]
442    fn read_doc(&self, id: Id) -> AnalysisResult<DocumentReadGuard<N, S>> {
443        let Some(guard) = self.analyzer().docs.get(&id) else {
444            return Err(AnalysisError::MissingDocument);
445        };
446
447        Ok(DocumentReadGuard::from(guard))
448    }
449
450    /// Returns a RAII guard that provides read-only access to the analyzer's
451    /// document with specified `id`.
452    ///
453    /// This function is a non-blocking version of
454    /// the [read_doc](Self::read_doc) function.
455    ///
456    /// Returns None if there is no document with the specified `id`.
457    ///
458    /// Returns None if the document currently locked for write.
459    #[inline(always)]
460    fn try_read_doc(&self, id: Id) -> Option<DocumentReadGuard<N, S>> {
461        Some(DocumentReadGuard::from(self.analyzer().docs.try_get(&id)?))
462    }
463
464    /// Returns true if the document with the specified `id` exists in
465    /// the analyzer, and this document **allows**
466    /// [content edit](MutationAccess::write_to_doc) operations.
467    #[inline(always)]
468    fn is_doc_mutable(&self, id: Id) -> bool {
469        let Some(guard) = self.analyzer().docs.get(&id) else {
470            return false;
471        };
472
473        guard.doc.is_mutable()
474    }
475
476    /// Returns true if the document with the specified `id` exists in
477    /// the analyzer, and this document **does not allow**
478    /// [content edit](MutationAccess::write_to_doc) operations.
479    #[inline(always)]
480    fn is_doc_immutable(&self, id: Id) -> bool {
481        let Some(guard) = self.analyzer().docs.get(&id) else {
482            return false;
483        };
484
485        guard.doc.is_mutable()
486    }
487
488    /// Returns a snapshot of the [node references](NodeRef) set of the document
489    /// with `id` that refer to syntax tree nodes belonging to specified `class`.
490    ///
491    /// The returning object is a clone of the already precomputed [Shared] set.
492    /// Therefore, it is relatively cheap to call this function.
493    ///
494    /// If the document addressed by the `id` parameter does not exist in the
495    /// analyzer, the function returns
496    /// a [MissingDocument](AnalysisError::MissingDocument) error.
497    #[inline(always)]
498    fn snapshot_class(
499        &self,
500        id: Id,
501        class: &<N::Classifier as Classifier>::Class,
502    ) -> AnalysisResult<Shared<HashSet<NodeRef, S>>> {
503        let Some(guard) = self.analyzer().docs.get(&id) else {
504            return Err(AnalysisError::MissingDocument);
505        };
506
507        let Some(class_to_nodes) = guard.classes_to_nodes.get(class) else {
508            return Ok(Shared::default());
509        };
510
511        Ok(class_to_nodes.nodes.clone())
512    }
513
514    /// Provides access to the Analyzer's
515    /// [common semantics](Grammar::CommonSemantics), a special semantic
516    /// feature that is instantiated during the Analyzer's creation. It does
517    /// not belong to any specific document and is common across the entire
518    /// Analyzer.
519    ///
520    /// If the Analyzer's grammar does not specify common semantics, this
521    /// function returns a reference to the
522    /// [VoidFeature](crate::analysis::VoidFeature).
523    #[inline(always)]
524    fn common(&self) -> &N::CommonSemantics {
525        &self.analyzer().common
526    }
527}
528
529pub trait TaskSealed<N: Grammar, H: TaskHandle, S: SyncBuildHasher> {
530    fn analyzer(&self) -> &Analyzer<N, H, S>;
531
532    fn revision(&self) -> Revision;
533}