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}