1mod hash;
4#[cfg(test)]
5mod tests;
6
7use std::cmp::Ordering;
8use std::hash::Hash;
9use std::sync::Arc;
10
11use indexmap::IndexMap;
12use petgraph::prelude::DiGraphMap;
13use sha2::Digest;
14use sha2::Sha256;
15use url::Url;
16use wdl_ast::TreeNode;
17use wdl_ast::v1::Ast;
18use wdl_ast::v1::DocumentItem;
19use wdl_ast::v1::EnumDefinition;
20use wdl_ast::v1::ImportStatement;
21use wdl_ast::v1::StructDefinition;
22use wdl_grammar::Diagnostic;
23use wdl_grammar::Span;
24use wdl_grammar::SyntaxKind;
25
26use crate::AppliedEdit;
27use crate::Diagnostics;
28use crate::Exceptable;
29use crate::document::Enum;
30use crate::document::ImportedEnum;
31use crate::document::ImportedStruct;
32use crate::document::ImportedTask;
33use crate::document::ImportedWorkflow;
34use crate::document::Input;
35use crate::document::Namespace;
36use crate::document::Output;
37use crate::document::Struct;
38use crate::document::Task;
39use crate::document::Workflow;
40use crate::document::cache::hash::HashableCallable;
41use crate::document::cache::hash::HashableItem;
42use crate::types::Type;
43
44#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
46#[repr(u8)]
47pub enum ItemKind {
48 Struct,
50 Enum,
52 Task,
54 Workflow,
56 Import,
58}
59
60#[derive(Debug, Clone, Default, PartialEq)]
62pub(crate) struct MergingImport {
63 pub(in crate::document) imported_tasks: IndexMap<String, ImportedTask>,
65 pub(in crate::document) imported_workflows: IndexMap<String, ImportedWorkflow>,
67 pub(in crate::document) imported_structs: IndexMap<String, ImportedStruct>,
73 pub(in crate::document) imported_enums: IndexMap<String, ImportedEnum>,
75}
76
77impl MergingImport {
78 pub(crate) fn items(&self) -> impl Iterator<Item = ImportedItem<'_>> {
80 self.imported_tasks
81 .values()
82 .map(ImportedItem::Task)
83 .chain(self.imported_workflows.values().map(ImportedItem::Workflow))
84 .chain(self.imported_structs.values().map(ImportedItem::Struct))
85 .chain(self.imported_enums.values().map(ImportedItem::Enum))
86 }
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub(crate) enum Import {
92 Namespace(Namespace),
94 Merging(MergingImport),
96}
97
98impl Import {
99 pub(crate) fn merging(&self) -> Option<&MergingImport> {
102 match self {
103 Import::Merging(i) => Some(i),
104 _ => None,
105 }
106 }
107
108 pub(crate) fn namespace(&self) -> Option<&Namespace> {
110 match self {
111 Import::Namespace(n) => Some(n),
112 _ => None,
113 }
114 }
115
116 fn namespace_mut(&mut self) -> Option<&mut Namespace> {
118 match self {
119 Import::Namespace(n) => Some(n),
120 _ => None,
121 }
122 }
123
124 fn structs(&self) -> impl Iterator<Item = &ImportedStruct> {
126 match self {
127 Import::Namespace(n) => n.imported_structs.values(),
128 Import::Merging(m) => m.imported_structs.values(),
129 }
130 }
131
132 pub(in crate::document) fn add_struct(&mut self, s: ImportedStruct) {
134 let _ = match self {
135 Import::Namespace(n) => n.imported_structs.insert(s.local_name.clone(), s),
136 Import::Merging(m) => m.imported_structs.insert(s.local_name.clone(), s),
137 };
138 }
139
140 fn enums(&self) -> impl Iterator<Item = &ImportedEnum> {
142 match self {
143 Import::Namespace(n) => n.imported_enums.values(),
144 Import::Merging(m) => m.imported_enums.values(),
145 }
146 }
147
148 pub(in crate::document) fn add_enum(&mut self, e: ImportedEnum) {
150 let _ = match self {
151 Import::Namespace(n) => n.imported_enums.insert(e.local_name.clone(), e),
152 Import::Merging(m) => m.imported_enums.insert(e.local_name.clone(), e),
153 };
154 }
155}
156
157pub(crate) enum ImportedItem<'a> {
159 Struct(&'a ImportedStruct),
161 Enum(&'a ImportedEnum),
163 Task(&'a ImportedTask),
165 Workflow(&'a ImportedWorkflow),
167}
168
169impl<'a> ImportedItem<'a> {
170 fn aliased_name(&self) -> &'a str {
172 match self {
173 ImportedItem::Struct(s) => &s.local_name,
174 ImportedItem::Enum(e) => &e.local_name,
175 ImportedItem::Task(t) => &t.local_name,
176 ImportedItem::Workflow(w) => &w.local_name,
177 }
178 }
179}
180
181#[derive(Copy, Clone, Debug)]
183pub enum MaybeImported<Local, Imported> {
184 Local(Local),
186 Imported(Imported),
188}
189
190impl<L, I> MaybeImported<L, I> {
191 pub fn is_imported(&self) -> bool {
193 matches!(self, Self::Imported(_))
194 }
195
196 pub fn expect_imported(self) -> I {
202 match self {
203 Self::Imported(i) => i,
204 Self::Local(_) => panic!("expected an imported item"),
205 }
206 }
207
208 pub fn expect_local(self) -> L {
214 match self {
215 Self::Local(l) => l,
216 Self::Imported(_) => panic!("expected a locally defined item"),
217 }
218 }
219}
220
221pub(in crate::document) type Item<'a> = MaybeImported<CachedItemRef<'a>, ImportedItem<'a>>;
223
224pub type WorkflowRef<'a> = MaybeImported<&'a Workflow, &'a ImportedWorkflow>;
226impl<'a> WorkflowRef<'a> {
227 pub fn name(&self) -> &'a str {
229 match self {
230 WorkflowRef::Local(w) => w.name(),
231 WorkflowRef::Imported(i) => &i.local_name,
232 }
233 }
234
235 pub fn name_span(&self) -> Span {
237 match self {
238 WorkflowRef::Local(w) => w.name_span(),
239 WorkflowRef::Imported(i) => i.span,
240 }
241 }
242
243 pub fn inputs(&self) -> Arc<IndexMap<String, Input>> {
245 match self {
246 WorkflowRef::Local(w) => Arc::clone(&w.inputs),
247 WorkflowRef::Imported(i) => Arc::clone(&i.inputs),
248 }
249 }
250
251 pub fn outputs(&self) -> Arc<IndexMap<String, Output>> {
253 match self {
254 WorkflowRef::Local(w) => Arc::clone(&w.outputs),
255 WorkflowRef::Imported(i) => Arc::clone(&i.outputs),
256 }
257 }
258
259 pub fn source(&self) -> Option<Arc<Url>> {
261 match self {
262 WorkflowRef::Local(_) => None,
263 WorkflowRef::Imported(i) => Some(i.source()),
264 }
265 }
266}
267
268pub type TaskRef<'a> = MaybeImported<&'a Task, &'a ImportedTask>;
270impl<'a> TaskRef<'a> {
271 pub fn name(&self) -> &'a str {
273 match self {
274 TaskRef::Local(t) => t.name(),
275 TaskRef::Imported(i) => &i.local_name,
276 }
277 }
278
279 pub fn name_span(&self) -> Span {
281 match self {
282 TaskRef::Local(t) => t.name_span(),
283 TaskRef::Imported(i) => i.span,
284 }
285 }
286
287 pub fn inputs(&self) -> Arc<IndexMap<String, Input>> {
289 match self {
290 TaskRef::Local(t) => Arc::clone(&t.inputs),
291 TaskRef::Imported(i) => Arc::clone(&i.inputs),
292 }
293 }
294
295 pub fn outputs(&self) -> Arc<IndexMap<String, Output>> {
297 match self {
298 TaskRef::Local(t) => Arc::clone(&t.outputs),
299 TaskRef::Imported(i) => Arc::clone(&i.outputs),
300 }
301 }
302
303 pub fn source(&self) -> Option<Arc<Url>> {
305 match self {
306 TaskRef::Local(_) => None,
307 TaskRef::Imported(i) => Some(i.source()),
308 }
309 }
310}
311
312pub type StructRef<'a> = MaybeImported<&'a Struct, &'a ImportedStruct>;
314impl<'a> StructRef<'a> {
315 pub fn name(&self) -> &'a str {
317 match self {
318 StructRef::Local(s) => s.name(),
319 StructRef::Imported(i) => &i.local_name,
320 }
321 }
322
323 pub fn name_span(&self) -> Span {
325 match self {
326 StructRef::Local(s) => s.name_span(),
327 StructRef::Imported(i) => i.span,
328 }
329 }
330
331 pub fn ty(&self) -> Option<&'a Type> {
333 match self {
334 StructRef::Local(s) => s.ty(),
335 StructRef::Imported(i) => i.ty(),
336 }
337 }
338
339 pub fn source(&self) -> Option<Arc<Url>> {
341 match self {
342 StructRef::Local(_) => None,
343 StructRef::Imported(i) => Some(i.source()),
344 }
345 }
346
347 pub fn definition(&self) -> StructDefinition {
349 match self {
350 StructRef::Local(s) => s.definition(),
351 StructRef::Imported(i) => i.definition(),
352 }
353 }
354
355 pub fn offset(&self) -> usize {
357 match self {
358 StructRef::Local(s) => s.offset(),
359 StructRef::Imported(i) => i.offset(),
360 }
361 }
362
363 pub fn node(&self) -> &'a rowan::GreenNode {
365 match self {
366 StructRef::Local(s) => s.node(),
367 StructRef::Imported(i) => i.node(),
368 }
369 }
370}
371
372pub type EnumRef<'a> = MaybeImported<&'a Enum, &'a ImportedEnum>;
374impl<'a> EnumRef<'a> {
375 pub fn name(&self) -> &'a str {
377 match self {
378 EnumRef::Local(e) => e.name(),
379 EnumRef::Imported(i) => &i.local_name,
380 }
381 }
382
383 pub fn name_span(&self) -> Span {
385 match self {
386 EnumRef::Local(e) => e.name_span(),
387 EnumRef::Imported(i) => i.span,
388 }
389 }
390
391 pub fn ty(&self) -> Option<&'a Type> {
393 match self {
394 EnumRef::Local(e) => e.ty(),
395 EnumRef::Imported(i) => i.ty(),
396 }
397 }
398
399 pub fn source(&self) -> Option<Arc<Url>> {
401 match self {
402 EnumRef::Local(_) => None,
403 EnumRef::Imported(i) => Some(i.source()),
404 }
405 }
406
407 pub fn definition(&self) -> EnumDefinition {
409 match self {
410 EnumRef::Local(e) => e.definition(),
411 EnumRef::Imported(i) => i.definition(),
412 }
413 }
414
415 pub fn offset(&self) -> usize {
417 match self {
418 EnumRef::Local(e) => e.offset(),
419 EnumRef::Imported(i) => i.offset(),
420 }
421 }
422
423 pub fn node(&self) -> &'a rowan::GreenNode {
425 match self {
426 EnumRef::Local(e) => e.node(),
427 EnumRef::Imported(i) => i.node(),
428 }
429 }
430}
431
432impl<'a> Item<'a> {
433 fn name(&self) -> Option<&'a str> {
435 match self {
436 Item::Local(i) => i.name(),
437 Item::Imported(i) => Some(i.aliased_name()),
438 }
439 }
440
441 pub fn signature_hash(&self) -> Option<SignatureHash> {
443 match self {
444 Item::Local(i) => Some(i.signature_hash()),
445 Item::Imported(_) => None,
446 }
447 }
448}
449
450pub(in crate::document) type SignatureHash = [u8; 32];
455pub(in crate::document) type BodyHash = [u8; 32];
459
460#[derive(Debug, Clone, Default, PartialEq, Eq)]
462pub struct WithBodyHash<T> {
463 pub body_hash: BodyHash,
465 pub item: T,
467}
468
469#[derive(Debug, Clone, PartialEq)]
471pub struct CachedItem<T> {
472 signature_hash: SignatureHash,
474 offset: usize,
476 item: T,
478 diagnostics: Diagnostics,
480}
481
482impl<T> CachedItem<T> {
483 pub(in crate::document) fn new(
485 signature_hash: SignatureHash,
486 offset: usize,
487 item: T,
488 diagnostics: Diagnostics,
489 ) -> Self {
490 Self {
491 signature_hash,
492 offset,
493 item,
494 diagnostics,
495 }
496 }
497
498 pub fn item(&self) -> &T {
500 &self.item
501 }
502
503 pub fn item_mut(&mut self) -> &mut T {
505 &mut self.item
506 }
507
508 pub fn set_diagnostics(&mut self, diagnostics: Diagnostics) {
512 self.diagnostics = diagnostics;
513 self.shift_diagnostic_offsets();
514 }
515
516 pub(in crate::document) fn add_diagnostic(&mut self, mut diagnostic: Diagnostic) {
520 diagnostic.offset(-(self.offset as isize));
521 self.diagnostics.add(diagnostic);
522 }
523
524 pub(in crate::document) fn exceptable_add<N: TreeNode + Exceptable>(
528 &mut self,
529 mut diagnostic: Diagnostic,
530 element: &N,
531 exceptable_nodes: &Option<&'static [SyntaxKind]>,
532 ) {
533 diagnostic.offset(-(self.offset as isize));
534 self.diagnostics
535 .exceptable_add(diagnostic, element, exceptable_nodes);
536 }
537
538 fn shift_diagnostic_offsets(&mut self) {
541 for diagnostic in &mut self.diagnostics.diagnostics {
542 diagnostic.offset(-(self.offset as isize))
543 }
544 }
545}
546
547impl CachedItem<Struct> {
548 fn target(&self) -> &Struct {
550 &self.item
551 }
552}
553
554impl CachedItem<Enum> {
555 fn target(&self) -> &Enum {
557 &self.item
558 }
559}
560
561impl<T> CachedItem<WithBodyHash<T>> {
562 fn target(&self) -> &T {
564 &self.item.item
565 }
566}
567
568#[derive(Debug)]
570pub(in crate::document) enum CachedItemRefMut<'a> {
571 Struct(&'a mut CachedItem<Struct>),
573 Enum(&'a mut CachedItem<Enum>),
575 Task(&'a mut CachedItem<WithBodyHash<Task>>),
577 Workflow(&'a mut CachedItem<WithBodyHash<Workflow>>),
579 Import(&'a mut CachedItem<WithBodyHash<Import>>),
581}
582
583impl CachedItemRefMut<'_> {
584 fn offset(&self) -> usize {
586 match self {
587 Self::Struct(s) => s.offset,
588 Self::Enum(e) => e.offset,
589 Self::Task(t) => t.offset,
590 Self::Workflow(w) => w.offset,
591 Self::Import(i) => i.offset,
592 }
593 }
594
595 fn diagnostics_mut(&mut self) -> &mut Vec<Diagnostic> {
597 match self {
598 Self::Struct(s) => &mut s.diagnostics.diagnostics,
599 Self::Enum(e) => &mut e.diagnostics.diagnostics,
600 Self::Task(t) => &mut t.diagnostics.diagnostics,
601 Self::Workflow(w) => &mut w.diagnostics.diagnostics,
602 Self::Import(i) => &mut i.diagnostics.diagnostics,
603 }
604 }
605
606 fn shift_existing_diagnostics(&mut self, edits: &[AppliedEdit], new_item_offset: usize) {
649 fn shift_absolute_span(span: Span, edits: &[AppliedEdit]) -> Span {
651 let mut start = span.start();
652 let mut end = span.end();
653 for edit in edits {
654 let edit_start = edit.range.start;
655 let edit_end = edit.range.end;
656 let replacement_end = edit_start + edit.replacement_length;
657 let edit_diff = edit.replacement_length as isize - edit.range.len() as isize;
658
659 start = if start < edit_start {
660 start
661 } else if start <= edit_end {
662 replacement_end
663 } else {
664 start.saturating_add_signed(edit_diff)
665 };
666
667 end = if end < edit_start {
668 end
669 } else if end <= edit_end {
670 replacement_end
671 } else {
672 end.saturating_add_signed(edit_diff)
673 };
674 }
675 Span::new(start, end - start)
676 }
677
678 if edits.is_empty() {
679 return;
681 }
682
683 let original_item_offset = self.offset();
684 for diagnostic in self.diagnostics_mut() {
685 for label in diagnostic.labels_mut() {
686 let start_absolute = original_item_offset + label.span().start();
687 let end_absolute = original_item_offset + label.span().end();
688 let new_span = shift_absolute_span(
689 Span::new(start_absolute, end_absolute - start_absolute),
690 edits,
691 );
692
693 let new_relative_start = new_span.start().saturating_sub(new_item_offset);
695 label.set_span(Span::new(new_relative_start, new_span.len()));
696 }
697 }
698
699 match self {
701 Self::Struct(s) => {
702 let Struct {
703 name: _,
704 name_span,
705 offset: _,
706 node: _,
707 ty: _,
708 } = &mut s.item;
709
710 *name_span = shift_absolute_span(*name_span, edits)
711 }
712 Self::Enum(e) => {
713 let Enum {
714 name: _,
715 name_span,
716 offset: _,
717 node: _,
718 ty: _,
719 } = &mut e.item;
720
721 *name_span = shift_absolute_span(*name_span, edits)
722 }
723 Self::Task(t) => {
724 let Task {
725 name: _,
726 name_span,
727 span,
728 scopes,
729 inputs: _,
730 outputs,
731 } = &mut t.item.item;
732
733 *name_span = shift_absolute_span(*name_span, edits);
734 *span = shift_absolute_span(*span, edits);
735 for scope in scopes {
736 scope.span = shift_absolute_span(scope.span, edits);
737 for name in scope.names.values_mut() {
738 name.span = shift_absolute_span(name.span, edits);
739 }
740 }
741 for output in Arc::make_mut(outputs).values_mut() {
742 output.name_span = shift_absolute_span(output.name_span, edits);
743 }
744 }
745 Self::Workflow(wf) => {
746 let Workflow {
747 name: _,
748 name_span,
749 span,
750 scopes,
751 inputs: _,
752 outputs,
753 allows_nested_inputs: _,
754 calls: _,
755 } = &mut wf.item.item;
756
757 *name_span = shift_absolute_span(*name_span, edits);
758 *span = shift_absolute_span(*span, edits);
759 for scope in scopes {
760 scope.span = shift_absolute_span(scope.span, edits);
761 for name in scope.names.values_mut() {
762 name.span = shift_absolute_span(name.span, edits);
763 }
764 }
765 for output in Arc::make_mut(outputs).values_mut() {
766 output.name_span = shift_absolute_span(output.name_span, edits);
767 }
768 }
769 Self::Import(i) => match &mut i.item.item {
770 Import::Namespace(n) => {
771 let Namespace {
772 name: _,
773 span,
774 source: _,
775 document: _,
776 used: _,
777 imported_structs,
778 imported_enums,
779 } = n;
780
781 *span = shift_absolute_span(*span, edits);
782 for s in imported_structs.values_mut() {
783 s.span = shift_absolute_span(s.span, edits);
784 }
785 for e in imported_enums.values_mut() {
786 e.span = shift_absolute_span(e.span, edits);
787 }
788 }
789 Import::Merging(m) => {
790 let MergingImport {
791 imported_tasks,
792 imported_workflows,
793 imported_structs,
794 imported_enums,
795 } = m;
796
797 for t in imported_tasks.values_mut() {
798 t.span = shift_absolute_span(t.span, edits);
799 }
800 for w in imported_workflows.values_mut() {
801 w.span = shift_absolute_span(w.span, edits);
802 }
803 for s in imported_structs.values_mut() {
804 s.span = shift_absolute_span(s.span, edits);
805 }
806 for e in imported_enums.values_mut() {
807 e.span = shift_absolute_span(e.span, edits);
808 }
809 }
810 },
811 }
812 }
813
814 fn swap_offset(&mut self, offset: usize) {
816 match self {
817 Self::Struct(s) => s.offset = offset,
818 Self::Enum(e) => e.offset = offset,
819 Self::Task(t) => t.offset = offset,
820 Self::Workflow(w) => w.offset = offset,
821 Self::Import(i) => i.offset = offset,
822 }
823 }
824}
825
826#[derive(Copy, Clone, Debug, PartialEq)]
828pub(in crate::document) enum CachedItemRef<'a> {
829 Struct(&'a CachedItem<Struct>),
831 Enum(&'a CachedItem<Enum>),
833 Task(&'a CachedItem<WithBodyHash<Task>>),
835 Workflow(&'a CachedItem<WithBodyHash<Workflow>>),
837 Import(&'a CachedItem<WithBodyHash<Import>>),
839}
840
841impl<'a> CachedItemRef<'a> {
842 pub fn name(&self) -> Option<&'a str> {
844 match self {
845 Self::Struct(s) => Some(s.item.name()),
846 Self::Enum(e) => Some(e.item.name()),
847 Self::Task(t) => Some(t.item.item.name()),
848 Self::Workflow(w) => Some(w.item.item.name()),
849 Self::Import(i) => match &i.item.item {
850 Import::Merging { .. } => None,
851 Import::Namespace(ns) => Some(ns.name()),
852 },
853 }
854 }
855
856 pub fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + use<'a> {
858 let (offset, diagnostics) = match self {
859 Self::Struct(s) => (s.offset, s.diagnostics.iter()),
860 Self::Enum(e) => (e.offset, e.diagnostics.iter()),
861 Self::Task(t) => (t.offset, t.diagnostics.iter()),
862 Self::Workflow(w) => (w.offset, w.diagnostics.iter()),
863 Self::Import(i) => (i.offset, i.diagnostics.iter()),
864 };
865
866 diagnostics.cloned().map(move |mut d| {
870 d.offset(offset as isize);
871 d
872 })
873 }
874
875 pub fn signature_hash(&self) -> SignatureHash {
877 match self {
878 Self::Struct(s) => s.signature_hash,
879 Self::Enum(e) => e.signature_hash,
880 Self::Task(t) => t.signature_hash,
881 Self::Workflow(w) => w.signature_hash,
882 Self::Import(i) => i.signature_hash,
883 }
884 }
885
886 pub fn body_hash(&self) -> Option<BodyHash> {
888 match self {
889 Self::Import(i) => Some(i.item.body_hash),
890 Self::Task(t) => Some(t.item.body_hash),
891 Self::Workflow(w) => Some(w.item.body_hash),
892 _ => None,
893 }
894 }
895}
896
897#[cfg(test)]
899#[derive(Debug, Clone, Default)]
900struct TestCache {
901 invalidated_signatures: Vec<SignatureHash>,
903 invalidated_bodies: Vec<SignatureHash>,
905}
906
907#[derive(Debug, Clone, Default)]
909pub(crate) struct AnalysisCache {
910 pub structs: IndexMap<SignatureHash, CachedItem<Struct>>,
912 pub enums: IndexMap<SignatureHash, CachedItem<Enum>>,
914 pub tasks: IndexMap<SignatureHash, CachedItem<WithBodyHash<Task>>>,
916 pub workflow: Option<CachedItem<WithBodyHash<Workflow>>>,
918 pub imports: IndexMap<SignatureHash, CachedItem<WithBodyHash<Import>>>,
920 dependencies: DiGraphMap<SignatureHash, ()>,
922 #[cfg(test)]
924 tests: TestCache,
925}
926
927impl PartialEq for AnalysisCache {
928 fn eq(&self, other: &Self) -> bool {
929 self.structs == other.structs
930 && self.enums == other.enums
931 && self.tasks == other.tasks
932 && self.workflow == other.workflow
933 && self.imports == other.imports
934 && self
935 .dependencies
936 .all_edges()
937 .all(|(a, b, _)| other.dependencies.contains_edge(a, b))
938 }
939}
940
941macro_rules! item_getters {
943 (
944 $(
945 (
946 $item_ty:ident, $cache_field:ident, $all_by_name:ident, $local_fn:ident, $local_fn_by_name:ident, $import_fn:ident, $import_fn_by_name:ident
947 ) => ($ty:ty, $ref_ty:ident, $imported_ty:ty)
948 ),+ $(,)+
949 ) => {
950 $(
951 paste::paste! {
952 #[doc = "Gets the " $item_ty "s locally defined in the document."]
953 #[doc = "[`Self::" $item_ty "_by_index()`]."]
958 pub(crate) fn $local_fn(&self) -> impl Iterator<Item = (usize, SignatureHash, &$ty)> {
959 self.$cache_field
960 .iter()
961 .enumerate()
962 .map(|(idx, (hash, i))| (idx, *hash, i.target()))
963 }
964
965 #[doc = "Gets a locally defined " $item_ty " in the document by name."]
966 #[doc = "See: [`Self::" $local_fn "`]"]
968 pub(crate) fn $local_fn_by_name(&self, name: &str) -> Option<(usize, SignatureHash, &$ty)> {
969 self.$local_fn().find(|(_idx, _hash, i)| i.name() == name)
970 }
971
972 #[doc = "Gets the " $item_ty "s in the document."]
973 #[doc = "See: [`Self::" $local_fn "`], [`Self::" $import_fn "`]."]
977 pub(crate) fn $cache_field(&self) -> impl Iterator<Item = $ref_ty<'_>> {
978 self.$local_fn()
979 .map(|(_idx, _hash, t)| $ref_ty::Local(t))
980 .chain(self.$import_fn().map(|(_hash, t)| $ref_ty::Imported(t)))
981 }
982
983 #[doc = "Gets a " $item_ty " in the document by name."]
984 #[doc = "See: [`Self::" $local_fn_by_name "`], [`Self::" $import_fn_by_name "`]."]
986 pub(crate) fn $all_by_name(&self, name: &str) -> Option<(SignatureHash, $ref_ty<'_>)> {
987 self.$local_fn_by_name(name)
988 .map(|(_idx, hash, t)| (hash, $ref_ty::Local(t)))
989 .or_else(|| {
990 self.$import_fn_by_name(name)
991 .map(|(hash, t)| (hash, $ref_ty::Imported(t)))
992 })
993 }
994
995 #[doc = "Gets an imported " $item_ty " in the document by local name."]
996 pub(crate) fn $import_fn_by_name(
1000 &self,
1001 name: &str,
1002 ) -> Option<(SignatureHash, &$imported_ty)> {
1003 self.$import_fn()
1004 .find(|(_hash, t)| t.local_name == name)
1005 }
1006 }
1007 )+
1008 }
1009}
1010
1011impl AnalysisCache {
1013 item_getters!(
1014 (task, tasks, task_by_name, local_tasks, local_task_by_name, imported_tasks, imported_task_by_name) => (Task, TaskRef, ImportedTask),
1015 (struct, structs, struct_by_name, local_structs, local_struct_by_name, imported_structs, imported_struct_by_name) => (Struct, StructRef, ImportedStruct),
1016 (enum, enums, enum_by_name, local_enums, local_enum_by_name, imported_enums, imported_enum_by_name) => (Enum, EnumRef, ImportedEnum),
1017 );
1018
1019 pub fn len(&self) -> usize {
1021 let Self {
1022 structs,
1023 enums,
1024 tasks,
1025 workflow,
1026 imports,
1027 dependencies: _,
1028 #[cfg(test)]
1029 tests: _,
1030 } = self;
1031
1032 structs.len() + enums.len() + tasks.len() + workflow.is_some() as usize + imports.len()
1033 }
1034
1035 #[cfg(test)]
1037 pub fn is_empty(&self) -> bool {
1038 self.len() == 0
1039 }
1040
1041 pub(crate) fn imported_structs(
1045 &self,
1046 ) -> impl Iterator<Item = (SignatureHash, &ImportedStruct)> {
1047 self.imports()
1048 .flat_map(|(_idx, hash, i)| i.structs().map(move |s| (hash, s)))
1049 }
1050
1051 pub(crate) fn imported_enums(&self) -> impl Iterator<Item = (SignatureHash, &ImportedEnum)> {
1055 self.imports()
1056 .flat_map(|(_idx, hash, i)| i.enums().map(move |e| (hash, e)))
1057 }
1058
1059 pub(crate) fn imported_tasks(&self) -> impl Iterator<Item = (SignatureHash, &ImportedTask)> {
1064 self.imports()
1065 .filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
1066 .flat_map(|(hash, i)| i.imported_tasks.values().map(move |t| (hash, t)))
1067 }
1068
1069 pub(crate) fn imports(&self) -> impl Iterator<Item = (usize, SignatureHash, &Import)> {
1071 self.imports
1072 .iter()
1073 .enumerate()
1074 .map(|(idx, (hash, i))| (idx, *hash, &i.item.item))
1075 }
1076
1077 pub(crate) fn namespaces(&self) -> impl Iterator<Item = (SignatureHash, &Namespace)> {
1079 self.imports()
1080 .filter_map(|(_idx, hash, i)| i.namespace().map(|ns| (hash, ns)))
1081 }
1082
1083 pub fn namespace_by_name(&self, name: &str) -> Option<(SignatureHash, &Namespace)> {
1085 self.namespaces().find(|(_, ns)| ns.name == name)
1086 }
1087
1088 pub(crate) fn workflow(&self) -> Option<&Workflow> {
1092 self.workflow.as_ref().map(|i| &i.item.item)
1093 }
1094
1095 pub(crate) fn imported_workflow_by_name(
1100 &self,
1101 name: &str,
1102 ) -> Option<(SignatureHash, &ImportedWorkflow)> {
1103 self.imports()
1104 .filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
1105 .find_map(|(hash, i)| i.imported_workflows.get(name).map(|w| (hash, w)))
1106 }
1107
1108 pub(crate) fn imported_workflows(
1113 &self,
1114 ) -> impl Iterator<Item = (SignatureHash, &ImportedWorkflow)> {
1115 self.imports()
1116 .filter_map(|(_idx, hash, i)| i.merging().map(|m| (hash, m)))
1117 .flat_map(|(hash, i)| i.imported_workflows.values().map(move |w| (hash, w)))
1118 }
1119
1120 pub(crate) fn workflow_by_name(&self, name: &str) -> Option<(SignatureHash, WorkflowRef<'_>)> {
1124 self.workflow
1125 .as_ref()
1126 .filter(|wf| wf.item.item.name == name)
1127 .map(|item| (item.signature_hash, WorkflowRef::Local(&item.item.item)))
1128 .or_else(|| {
1129 self.imported_workflow_by_name(name)
1130 .map(|(hash, wf)| (hash, WorkflowRef::Imported(wf)))
1131 })
1132 }
1133}
1134
1135impl AnalysisCache {
1137 pub(in crate::document) fn dirty<'a>(
1146 &self,
1147 current_ast: &'a AstItems,
1148 ) -> impl Iterator<Item = (SignatureHash, Option<BodyHash>, &'a DocumentItem)> {
1149 current_ast.items.iter().filter_map(move |ast_item| {
1150 match self.get(&ast_item.signature_hash) {
1151 Some(cache_item) => {
1152 let Some(expected_body_hash) = cache_item.body_hash() else {
1153 return None; };
1155
1156 let Some(new_body_hash) = ast_item.body_hash else {
1157 return None;
1163 };
1164
1165 if expected_body_hash == new_body_hash {
1166 return None; }
1168
1169 Some((ast_item.signature_hash, ast_item.body_hash, &ast_item.item))
1170 }
1171 None => Some((ast_item.signature_hash, ast_item.body_hash, &ast_item.item)),
1173 }
1174 })
1175 }
1176
1177 fn items(&self) -> impl Iterator<Item = CachedItemRef<'_>> {
1179 self.structs
1180 .values()
1181 .map(CachedItemRef::Struct)
1182 .chain(self.enums.values().map(CachedItemRef::Enum))
1183 .chain(self.tasks.values().map(CachedItemRef::Task))
1184 .chain(
1185 self.workflow
1186 .as_ref()
1187 .into_iter()
1188 .map(CachedItemRef::Workflow),
1189 )
1190 .chain(self.imports.values().map(CachedItemRef::Import))
1191 }
1192
1193 pub(in crate::document) fn items_mut(&mut self) -> impl Iterator<Item = CachedItemRefMut<'_>> {
1195 self.structs
1196 .values_mut()
1197 .map(CachedItemRefMut::Struct)
1198 .chain(self.enums.values_mut().map(CachedItemRefMut::Enum))
1199 .chain(self.tasks.values_mut().map(CachedItemRefMut::Task))
1200 .chain(
1201 self.workflow
1202 .as_mut()
1203 .into_iter()
1204 .map(CachedItemRefMut::Workflow),
1205 )
1206 .chain(self.imports.values_mut().map(CachedItemRefMut::Import))
1207 }
1208
1209 pub(in crate::document) fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + use<'_> {
1211 self.items().flat_map(|i| i.diagnostics())
1212 }
1213
1214 pub(in crate::document) fn get(&self, hash: &SignatureHash) -> Option<CachedItemRef<'_>> {
1216 self.structs
1217 .get(hash)
1218 .map(CachedItemRef::Struct)
1219 .or_else(|| self.enums.get(hash).map(CachedItemRef::Enum))
1220 .or_else(|| self.tasks.get(hash).map(CachedItemRef::Task))
1221 .or_else(|| {
1222 if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
1223 self.workflow.as_ref().map(CachedItemRef::Workflow)
1224 } else {
1225 None
1226 }
1227 })
1228 .or_else(|| self.imports.get(hash).map(CachedItemRef::Import))
1229 }
1230
1231 fn get_mut(&mut self, hash: &SignatureHash) -> Option<CachedItemRefMut<'_>> {
1233 self.structs
1234 .get_mut(hash)
1235 .map(CachedItemRefMut::Struct)
1236 .or_else(|| self.enums.get_mut(hash).map(CachedItemRefMut::Enum))
1237 .or_else(|| self.tasks.get_mut(hash).map(CachedItemRefMut::Task))
1238 .or_else(|| {
1239 if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
1240 self.workflow.as_mut().map(CachedItemRefMut::Workflow)
1241 } else {
1242 None
1243 }
1244 })
1245 .or_else(|| self.imports.get_mut(hash).map(CachedItemRefMut::Import))
1246 }
1247
1248 pub(in crate::document) fn item_by_name(&self, name: &str) -> Option<Item<'_>> {
1250 self.items()
1251 .map(Item::Local)
1252 .chain(
1253 self.imports()
1254 .filter_map(|(_idx, _hash, i)| i.merging())
1255 .flat_map(|i| i.items().map(Item::Imported)),
1256 )
1257 .find(|i| i.name() == Some(name))
1258 }
1259
1260 pub(in crate::document) fn struct_by_index(&self, index: usize) -> Option<&Struct> {
1262 Some(&self.structs.get_index(index)?.1.item)
1263 }
1264
1265 pub(in crate::document) fn enum_by_index(&self, index: usize) -> Option<&Enum> {
1267 Some(&self.enums.get_index(index)?.1.item)
1268 }
1269
1270 fn keys(&self) -> impl Iterator<Item = &SignatureHash> {
1275 self.structs
1276 .keys()
1277 .chain(self.enums.keys())
1278 .chain(self.tasks.keys())
1279 .chain(self.workflow.as_ref().map(|w| &w.signature_hash))
1280 .chain(self.imports.keys())
1281 }
1282
1283 pub(in crate::document) fn exports_hash(&self) -> BodyHash {
1285 let mut hasher = Sha256::default();
1286
1287 let mut keys: Vec<_> = self.keys().collect();
1291 keys.sort_unstable();
1292
1293 for signature in keys {
1294 hasher.update(signature);
1295 }
1296 hasher.finalize().into()
1297 }
1298}
1299
1300#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1302pub(in crate::document) enum InvalidationStrategy {
1303 Signature,
1305 Body,
1307}
1308
1309impl AnalysisCache {
1311 pub(in crate::document) fn insert_import(
1313 &mut self,
1314 mut item: CachedItem<WithBodyHash<Import>>,
1315 ) {
1316 let hash = item.signature_hash;
1317 item.shift_diagnostic_offsets();
1318
1319 self.imports.insert(item.signature_hash, item);
1320 self.dependencies.add_node(hash);
1321 }
1322
1323 pub(in crate::document) fn insert_enum(&mut self, mut item: CachedItem<Enum>) {
1325 let hash = item.signature_hash;
1326 item.shift_diagnostic_offsets();
1327
1328 self.enums.insert(item.signature_hash, item);
1329 self.dependencies.add_node(hash);
1330 }
1331
1332 pub(in crate::document) fn insert_struct(&mut self, mut item: CachedItem<Struct>) {
1334 let hash = item.signature_hash;
1335 item.shift_diagnostic_offsets();
1336
1337 self.structs.insert(item.signature_hash, item);
1338 self.dependencies.add_node(hash);
1339 }
1340
1341 pub(in crate::document) fn insert_task(&mut self, mut item: CachedItem<WithBodyHash<Task>>) {
1343 let hash = item.signature_hash;
1344 item.shift_diagnostic_offsets();
1345
1346 self.tasks.insert(item.signature_hash, item);
1347 self.dependencies.add_node(hash);
1348 }
1349
1350 pub(in crate::document) fn set_workflow(&mut self, item: CachedItem<WithBodyHash<Workflow>>) {
1352 let hash = item.signature_hash;
1356 self.workflow = Some(item);
1357 self.dependencies.add_node(hash);
1358 }
1359
1360 pub(in crate::document) fn namespaces_mut(&mut self) -> impl Iterator<Item = &mut Namespace> {
1362 self.imports
1363 .values_mut()
1364 .flat_map(|i| i.item.item.namespace_mut())
1365 }
1366
1367 pub(in crate::document) fn workflow_item_mut(
1370 &mut self,
1371 ) -> Option<&mut CachedItem<WithBodyHash<Workflow>>> {
1372 self.workflow.as_mut()
1373 }
1374
1375 pub(in crate::document) fn struct_item_mut(
1377 &mut self,
1378 index: usize,
1379 ) -> Option<&mut CachedItem<Struct>> {
1380 Some(self.structs.get_index_mut(index)?.1)
1381 }
1382
1383 fn remove_item(&mut self, hash: &SignatureHash) {
1387 self.structs
1388 .shift_remove(hash)
1389 .map(|_| ())
1390 .or_else(|| self.enums.shift_remove(hash).map(|_| ()))
1391 .or_else(|| self.tasks.shift_remove(hash).map(|_| ()))
1392 .or_else(|| self.imports.shift_remove(hash).map(|_| ()));
1393
1394 if self.workflow.as_ref().map(|w| &w.signature_hash) == Some(hash) {
1395 self.workflow = None;
1396 }
1397 }
1398
1399 pub(in crate::document) fn invalidate(
1401 &mut self,
1402 ast_items: &AstItems,
1403 edits: &[AppliedEdit],
1404 hashes: impl IntoIterator<Item = (InvalidationStrategy, SignatureHash)>,
1405 ) {
1406 let mut dirty_set = std::collections::HashSet::new();
1407 for (strategy, hash) in hashes {
1408 match strategy {
1409 InvalidationStrategy::Signature => {
1411 let mut stack = vec![hash];
1412 while let Some(node) = stack.pop() {
1413 if dirty_set.insert(node) {
1414 #[cfg(test)]
1415 self.tests.invalidated_signatures.push(node);
1416
1417 for dependent in self
1418 .dependencies
1419 .neighbors_directed(node, petgraph::Direction::Incoming)
1420 {
1421 stack.push(dependent);
1422 }
1423 }
1424 }
1425 }
1426 InvalidationStrategy::Body => {
1428 #[cfg(test)]
1429 self.tests.invalidated_bodies.push(hash);
1430
1431 self.remove_item(&hash);
1432 let outgoing: Vec<_> = self
1433 .dependencies
1434 .neighbors_directed(hash, petgraph::Direction::Outgoing)
1435 .collect();
1436 for dependency in outgoing {
1437 self.dependencies.remove_edge(hash, dependency);
1438 }
1439 }
1440 }
1441 }
1442
1443 for hash in dirty_set {
1444 self.remove_item(&hash);
1445 self.dependencies.remove_node(hash);
1446 }
1447
1448 for ast_item in &ast_items.items {
1449 let hash = ast_item.signature_hash;
1450 let Some(mut item) = self.get_mut(&hash) else {
1451 continue;
1452 };
1453
1454 item.shift_existing_diagnostics(edits, ast_item.offset);
1456 item.swap_offset(ast_item.offset);
1457 }
1458 }
1459
1460 pub(in crate::document) fn intersect(
1463 &self,
1464 current_ast: &AstItems,
1465 mut resolve_import_body_hash: impl FnMut(&ImportStatement) -> Option<BodyHash>,
1466 ) -> Vec<(InvalidationStrategy, SignatureHash)> {
1467 let mut to_remove = Vec::new();
1468 for cache_item in self.items() {
1469 let signature_hash = cache_item.signature_hash();
1470 if !current_ast.contains(&signature_hash) {
1471 to_remove.push((InvalidationStrategy::Signature, signature_hash));
1472 continue;
1473 }
1474
1475 let new_body_hash = match cache_item {
1476 CachedItemRef::Import(_) => {
1477 let import_stmt = current_ast
1478 .imports()
1479 .find(|(h, _)| **h == signature_hash)
1480 .map(|(_, i)| i)
1481 .expect("should exist because current_ast contains hash");
1482 resolve_import_body_hash(import_stmt)
1483 }
1484 _ => current_ast.get_body_hash(&signature_hash),
1485 };
1486
1487 if cache_item.body_hash() != new_body_hash {
1488 if matches!(cache_item, CachedItemRef::Import(_)) {
1489 to_remove.push((InvalidationStrategy::Signature, signature_hash));
1490 } else {
1491 to_remove.push((InvalidationStrategy::Body, signature_hash));
1492 }
1493 }
1494 }
1495
1496 to_remove
1497 }
1498
1499 pub(in crate::document) fn enum_item_mut(
1501 &mut self,
1502 index: usize,
1503 ) -> Option<&mut CachedItem<Enum>> {
1504 Some(self.enums.get_index_mut(index)?.1)
1505 }
1506
1507 pub(in crate::document) fn add_dependency(
1509 &mut self,
1510 dependent: SignatureHash,
1511 dependency: SignatureHash,
1512 ) {
1513 self.dependencies.add_edge(dependent, dependency, ());
1514 }
1515}
1516
1517struct AstItem {
1519 signature_hash: SignatureHash,
1521 body_hash: Option<BodyHash>,
1523 offset: usize,
1525 item: DocumentItem,
1527}
1528
1529pub(in crate::document) struct AstItems {
1531 items: Vec<AstItem>,
1533}
1534
1535impl AstItems {
1536 pub fn contains(&self, signature_hash: &SignatureHash) -> bool {
1538 self.items
1539 .iter()
1540 .any(|item| &item.signature_hash == signature_hash)
1541 }
1542
1543 pub fn get_body_hash(&self, signature_hash: &SignatureHash) -> Option<BodyHash> {
1545 self.items.iter().find_map(|item| {
1546 if &item.signature_hash == signature_hash {
1547 item.body_hash
1548 } else {
1549 None
1550 }
1551 })
1552 }
1553
1554 pub fn len(&self) -> usize {
1556 self.items.len()
1557 }
1558
1559 fn imports(&self) -> impl Iterator<Item = (&SignatureHash, &ImportStatement)> {
1561 self.items.iter().filter_map(|item| match &item.item {
1562 DocumentItem::Import(i) => Some((&item.signature_hash, i)),
1563 _ => None,
1564 })
1565 }
1566
1567 pub fn new(ast: &Ast) -> Self {
1569 #[derive(PartialEq, Eq)]
1570 struct DocumentItemOrd<'a>(&'a DocumentItem);
1571
1572 impl DocumentItemOrd<'_> {
1573 fn ord(&self) -> u8 {
1574 match self.0 {
1575 DocumentItem::Import(_) => 0,
1576 DocumentItem::Struct(_) | DocumentItem::Enum(_) => 1,
1577 DocumentItem::Task(_) | DocumentItem::Workflow(_) => 2,
1578 }
1579 }
1580 }
1581
1582 impl PartialOrd for DocumentItemOrd<'_> {
1583 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1584 Some(self.cmp(other))
1585 }
1586 }
1587
1588 impl Ord for DocumentItemOrd<'_> {
1589 fn cmp(&self, other: &Self) -> Ordering {
1590 self.ord().cmp(&other.ord())
1591 }
1592 }
1593
1594 let mut items = Vec::new();
1595
1596 for item in ast.items() {
1597 let offset = usize::from(item.inner().text_range().start());
1598 let (signature_hash, body_hash) = match &item {
1599 DocumentItem::Import(i) => (HashableItem::hash(i), None),
1600 DocumentItem::Struct(s) => (HashableItem::hash(s), None),
1601 DocumentItem::Enum(e) => (HashableItem::hash(e), None),
1602 DocumentItem::Task(t) => {
1603 let (signature_hash, body_hash) = t.hash_callable();
1604 (signature_hash, Some(body_hash))
1605 }
1606 DocumentItem::Workflow(w) => {
1607 let (signature_hash, body_hash) = w.hash_callable();
1608 (signature_hash, Some(body_hash))
1609 }
1610 };
1611
1612 items.push(AstItem {
1613 signature_hash,
1614 body_hash,
1615 offset,
1616 item,
1617 });
1618 }
1619
1620 items.sort_by(|a, b| DocumentItemOrd(&a.item).cmp(&DocumentItemOrd(&b.item)));
1621
1622 Self { items }
1623 }
1624}