wdl_analysis/document.rs
1//! Representation of analyzed WDL documents.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::collections::HashSet;
6use std::collections::hash_map::Entry;
7use std::path::Path;
8use std::sync::Arc;
9
10use arrayvec::ArrayString;
11use indexmap::IndexMap;
12use indexmap::IndexSet;
13use petgraph::graph::NodeIndex;
14use rowan::GreenNode;
15use rowan::TextRange;
16use rowan::TextSize;
17use url::Url;
18use uuid::Uuid;
19use wdl_ast::Ast;
20use wdl_ast::AstNode;
21use wdl_ast::Diagnostic;
22use wdl_ast::Severity;
23use wdl_ast::Span;
24use wdl_ast::SupportedVersion;
25use wdl_ast::SyntaxNode;
26
27use crate::Diagnostics;
28use crate::config::Config;
29use crate::diagnostics::Context;
30use crate::diagnostics::no_common_type;
31use crate::graph::DocumentGraph;
32use crate::graph::ParseState;
33use crate::types::CallType;
34use crate::types::EnumChoiceCacheKey;
35use crate::types::Optional;
36use crate::types::Type;
37
38pub mod v1;
39
40/// The `task` variable name available in task command sections and outputs in
41/// WDL 1.2.
42pub const TASK_VAR_NAME: &str = "task";
43
44/// A successfully resolved namespace introduced by an import.
45#[derive(Debug)]
46pub struct Namespace {
47 /// The span of the import that introduced the namespace.
48 pub(crate) span: Span,
49 /// The URI of the imported document that introduced the namespace.
50 source: Arc<Url>,
51 /// The namespace's document.
52 document: Document,
53 /// Whether or not the namespace is used (i.e. referenced) in the document.
54 pub(crate) used: bool,
55}
56
57impl Namespace {
58 /// Gets the span of the import that introduced the namespace.
59 pub fn span(&self) -> Span {
60 self.span
61 }
62
63 /// Gets the URI of the imported document that introduced the namespace.
64 pub fn source(&self) -> &Arc<Url> {
65 &self.source
66 }
67
68 /// Gets the imported document.
69 pub fn document(&self) -> &Document {
70 &self.document
71 }
72}
73
74/// Represents a struct in a document.
75#[derive(Debug, Clone)]
76pub struct Struct {
77 /// The name of the struct.
78 name: String,
79 /// The span that introduced the struct.
80 ///
81 /// This is either the name of a struct definition (local) or an import's
82 /// URI or alias (imported).
83 name_span: Span,
84 /// The offset of the CST node from the start of the document.
85 ///
86 /// This is used to adjust diagnostics resulting from traversing the struct
87 /// node as if it were the root of the CST.
88 offset: usize,
89 /// Stores the CST node of the struct.
90 ///
91 /// This is used to calculate type equivalence for imports.
92 node: rowan::GreenNode,
93 /// The source of this struct.
94 ///
95 /// `Some(uri)` means the struct was imported (by a namespaced, wildcard, or
96 /// selective import) from the document at `uri`. Imported structs carry
97 /// their resolved type from that document, so they are not type-checked
98 /// again here, and `uri` locates the original definition for
99 /// go-to-definition.
100 ///
101 /// `None` means the struct is defined locally in the containing document.
102 source: Option<Arc<Url>>,
103 /// The type of the struct.
104 ///
105 /// Initially this is `None` until a type check occurs.
106 ty: Option<Type>,
107}
108
109impl Struct {
110 /// Gets the name of the struct.
111 pub fn name(&self) -> &str {
112 &self.name
113 }
114
115 /// Gets the span of the name.
116 pub fn name_span(&self) -> Span {
117 self.name_span
118 }
119
120 /// Gets the offset of the struct
121 pub fn offset(&self) -> usize {
122 self.offset
123 }
124
125 /// Gets the node of the struct.
126 pub fn node(&self) -> &rowan::GreenNode {
127 &self.node
128 }
129
130 /// Gets the URI of the document this struct was imported from.
131 ///
132 /// Returns `Some(uri)` for an imported struct (whether namespaced,
133 /// wildcard, or selective), where `uri` is the document the struct was
134 /// imported from. Returns `None` for a struct defined locally in the
135 /// containing document.
136 pub fn source(&self) -> Option<&Arc<Url>> {
137 self.source.as_ref()
138 }
139
140 /// Gets the type of the struct.
141 ///
142 /// A value of `None` indicates that the type could not be determined for
143 /// the struct; this may happen if the struct definition is recursive.
144 pub fn ty(&self) -> Option<&Type> {
145 self.ty.as_ref()
146 }
147}
148
149/// Represents an enum in a document.
150#[derive(Debug, Clone)]
151pub struct Enum {
152 /// The name of the enum.
153 name: String,
154 /// The span that introduced the enum.
155 ///
156 /// This is either the name of an enum definition (local) or an import's
157 /// URI or alias (imported).
158 name_span: Span,
159 /// The offset of the CST node from the start of the document.
160 ///
161 /// This is used to adjust diagnostics resulting from traversing the enum
162 /// node as if it were the root of the CST.
163 offset: usize,
164 /// Stores the CST node of the enum.
165 ///
166 /// This is used to calculate type equivalence for imports and can be
167 /// reconstructed into an AST node to access choice expressions.
168 node: rowan::GreenNode,
169 /// The source of this enum.
170 ///
171 /// `Some(uri)` means the enum was imported (by a namespaced, wildcard, or
172 /// selective import) from the document at `uri`. Imported enums carry their
173 /// resolved type from that document, so they are not type-checked again
174 /// here, and `uri` locates the original definition for go-to-definition.
175 ///
176 /// `None` means the enum is defined locally in the containing document.
177 source: Option<Arc<Url>>,
178 /// The type of the enum.
179 ///
180 /// Initially this is `None` until types are populated for the document.
181 ty: Option<Type>,
182}
183
184impl Enum {
185 /// Gets the name of the enum.
186 pub fn name(&self) -> &str {
187 &self.name
188 }
189
190 /// Gets the span of the name.
191 pub fn name_span(&self) -> Span {
192 self.name_span
193 }
194
195 /// Gets the offset of the enum.
196 pub fn offset(&self) -> usize {
197 self.offset
198 }
199
200 /// Gets the green node of the enum.
201 pub fn node(&self) -> &rowan::GreenNode {
202 &self.node
203 }
204
205 /// Reconstructs the AST definition from the stored green node.
206 ///
207 /// This provides access to choice expressions and other AST details.
208 pub fn definition(&self) -> wdl_ast::v1::EnumDefinition {
209 wdl_ast::v1::EnumDefinition::cast(wdl_ast::SyntaxNode::new_root(self.node.clone()))
210 .expect("stored node should be a valid enum definition")
211 }
212
213 /// Gets the URI of the document this enum was imported from.
214 ///
215 /// Returns `Some(uri)` for an imported enum (whether namespaced, wildcard,
216 /// or selective), where `uri` is the document the enum was imported from.
217 /// Returns `None` for an enum defined locally in the containing document.
218 pub fn source(&self) -> Option<&Arc<Url>> {
219 self.source.as_ref()
220 }
221
222 /// Gets the type of the enum.
223 pub fn ty(&self) -> Option<&Type> {
224 self.ty.as_ref()
225 }
226}
227
228/// Represents information about a name in a scope.
229#[derive(Debug, Clone)]
230pub struct Name {
231 /// The span of the name.
232 span: Span,
233 /// The type of the name.
234 ty: Type,
235}
236
237impl Name {
238 /// Gets the span of the name.
239 pub fn span(&self) -> Span {
240 self.span
241 }
242
243 /// Gets the type of the name.
244 pub fn ty(&self) -> &Type {
245 &self.ty
246 }
247}
248
249/// Represents an index of a scope in a collection of scopes.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
251pub struct ScopeIndex(usize);
252
253/// Represents a scope in a WDL document.
254#[derive(Debug)]
255pub struct Scope {
256 /// The index of the parent scope.
257 ///
258 /// This is `None` for task and workflow scopes.
259 parent: Option<ScopeIndex>,
260 /// The span in the document where the names of the scope are visible.
261 span: Span,
262 /// The map of names in scope to their span and types.
263 names: IndexMap<String, Name>,
264}
265
266impl Scope {
267 /// Creates a new scope given the parent scope and span.
268 fn new(parent: Option<ScopeIndex>, span: Span) -> Self {
269 Self {
270 parent,
271 span,
272 names: Default::default(),
273 }
274 }
275
276 /// Inserts a name into the scope.
277 pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
278 self.names.insert(name.into(), Name { span, ty });
279 }
280}
281
282/// Represents a reference to a scope.
283#[derive(Debug, Clone, Copy)]
284pub struct ScopeRef<'a> {
285 /// The reference to the scopes collection.
286 scopes: &'a [Scope],
287 /// The index of the scope in the collection.
288 index: ScopeIndex,
289}
290
291impl<'a> ScopeRef<'a> {
292 /// Creates a new scope reference given the scope index.
293 fn new(scopes: &'a [Scope], index: ScopeIndex) -> Self {
294 Self { scopes, index }
295 }
296
297 /// Gets the span of the scope.
298 pub fn span(&self) -> Span {
299 self.scopes[self.index.0].span
300 }
301
302 /// Gets the parent scope.
303 ///
304 /// Returns `None` if there is no parent scope.
305 pub fn parent(&self) -> Option<Self> {
306 self.scopes[self.index.0].parent.map(|p| Self {
307 scopes: self.scopes,
308 index: p,
309 })
310 }
311
312 /// Gets all of the names available at this scope.
313 pub fn names(&self) -> impl Iterator<Item = (&str, &Name)> + use<'_> {
314 self.scopes[self.index.0]
315 .names
316 .iter()
317 .map(|(name, n)| (name.as_str(), n))
318 }
319
320 /// Gets a name local to this scope.
321 ///
322 /// Returns `None` if a name local to this scope was not found.
323 pub fn local(&self, name: &str) -> Option<&Name> {
324 self.scopes[self.index.0].names.get(name)
325 }
326
327 /// Lookups a name in the scope.
328 ///
329 /// Returns `None` if the name is not available in the scope.
330 pub fn lookup(&self, name: &str) -> Option<&Name> {
331 let mut current = Some(self.index);
332
333 while let Some(index) = current {
334 if let Some(name) = self.scopes[index.0].names.get(name) {
335 return Some(name);
336 }
337
338 current = self.scopes[index.0].parent;
339 }
340
341 None
342 }
343}
344
345/// Represents a mutable reference to a scope.
346#[derive(Debug)]
347struct ScopeRefMut<'a> {
348 /// The reference to all scopes.
349 scopes: &'a mut [Scope],
350 /// The index to the scope.
351 index: ScopeIndex,
352}
353
354impl<'a> ScopeRefMut<'a> {
355 /// Creates a new mutable scope reference given the scope index.
356 fn new(scopes: &'a mut [Scope], index: ScopeIndex) -> Self {
357 Self { scopes, index }
358 }
359
360 /// Lookups a name in the scope.
361 ///
362 /// Returns `None` if the name is not available in the scope.
363 pub fn lookup(&self, name: &str) -> Option<&Name> {
364 let mut current = Some(self.index);
365
366 while let Some(index) = current {
367 if let Some(name) = self.scopes[index.0].names.get(name) {
368 return Some(name);
369 }
370
371 current = self.scopes[index.0].parent;
372 }
373
374 None
375 }
376
377 /// Inserts a name into the scope.
378 pub fn insert(&mut self, name: impl Into<String>, span: Span, ty: Type) {
379 self.scopes[self.index.0]
380 .names
381 .insert(name.into(), Name { span, ty });
382 }
383
384 /// Converts the mutable scope reference to an immutable scope reference.
385 pub fn as_scope_ref(&'a self) -> ScopeRef<'a> {
386 ScopeRef {
387 scopes: self.scopes,
388 index: self.index,
389 }
390 }
391}
392
393/// A scope union takes the union of names within a number of given scopes and
394/// computes a set of common output names for a presumed parent scope. This is
395/// useful when calculating common elements from, for example, an `if`
396/// statement within a workflow.
397#[derive(Debug)]
398pub struct ScopeUnion<'a> {
399 /// The scope references to process.
400 scope_refs: Vec<(ScopeRef<'a>, bool)>,
401}
402
403impl<'a> ScopeUnion<'a> {
404 /// Creates a new scope union.
405 pub fn new() -> Self {
406 Self {
407 scope_refs: Vec::new(),
408 }
409 }
410
411 /// Adds a scope to the union.
412 pub fn insert(&mut self, scope_ref: ScopeRef<'a>, exhaustive: bool) {
413 self.scope_refs.push((scope_ref, exhaustive));
414 }
415
416 /// Resolves the scope union to names and types that should be accessible
417 /// from the parent scope.
418 ///
419 /// Returns an error if any issues are encountered during resolving.
420 pub fn resolve(self) -> Result<HashMap<String, Name>, Vec<Diagnostic>> {
421 let mut errors = Vec::new();
422 let mut ignored: HashSet<String> = HashSet::new();
423
424 // Gather all declaration names and reconcile types
425 let mut names: HashMap<String, Name> = HashMap::new();
426 for (scope_ref, _) in &self.scope_refs {
427 for (name, info) in scope_ref.names() {
428 if ignored.contains(name) {
429 continue;
430 }
431
432 match names.entry(name.to_string()) {
433 Entry::Vacant(entry) => {
434 entry.insert(info.clone());
435 }
436 Entry::Occupied(mut entry) => {
437 let Some(ty) = entry.get().ty.common_type(&info.ty) else {
438 errors.push(no_common_type(
439 &entry.get().ty,
440 entry.get().span,
441 &info.ty,
442 info.span,
443 ));
444 names.remove(name);
445 ignored.insert(name.to_string());
446 continue;
447 };
448
449 entry.get_mut().ty = ty;
450 }
451 }
452 }
453 }
454
455 // Mark types as optional if not present in all clauses
456 for (scope_ref, _) in &self.scope_refs {
457 for (name, info) in &mut names {
458 if ignored.contains(name) {
459 continue;
460 }
461
462 // If this name is not in the current clause's scope, mark as optional
463 if scope_ref.local(name).is_none() {
464 info.ty = info.ty.optional();
465 }
466 }
467 }
468
469 // If there's no `else` clause, mark all types as optional
470 let has_exhaustive = self.scope_refs.iter().any(|(_, exhaustive)| *exhaustive);
471 if !has_exhaustive {
472 for info in names.values_mut() {
473 info.ty = info.ty.optional();
474 }
475 }
476
477 if !errors.is_empty() {
478 return Err(errors);
479 }
480
481 Ok(names)
482 }
483}
484
485/// Represents a task or workflow input.
486#[derive(Debug, Clone, PartialEq, Eq)]
487pub struct Input {
488 /// The type of the input.
489 ty: Type,
490 /// Whether or not the input is required.
491 ///
492 /// A required input is one that has a non-optional type and no default
493 /// expression.
494 required: bool,
495}
496
497impl Input {
498 /// Gets the type of the input.
499 pub fn ty(&self) -> &Type {
500 &self.ty
501 }
502
503 /// Whether or not the input is required.
504 pub fn required(&self) -> bool {
505 self.required
506 }
507}
508
509/// Represents a task or workflow output.
510#[derive(Debug, Clone, PartialEq, Eq)]
511pub struct Output {
512 /// The type of the output.
513 ty: Type,
514 /// The span of the output name.
515 name_span: Span,
516}
517
518impl Output {
519 /// Creates a new output with the given type.
520 pub(crate) fn new(ty: Type, name_span: Span) -> Self {
521 Self { ty, name_span }
522 }
523
524 /// Gets the type of the output.
525 pub fn ty(&self) -> &Type {
526 &self.ty
527 }
528
529 /// Gets the span of output's name.
530 pub fn name_span(&self) -> Span {
531 self.name_span
532 }
533}
534
535/// Represents a task in a document.
536#[derive(Debug)]
537pub struct Task {
538 /// The span of the task name.
539 name_span: Span,
540 /// The name of the task.
541 name: String,
542 /// The span of the task definition.
543 span: Span,
544 /// The scopes contained in the task.
545 ///
546 /// The first scope will always be the task's scope.
547 ///
548 /// The scopes will be in sorted order by span start.
549 scopes: Vec<Scope>,
550 /// The inputs of the task.
551 inputs: Arc<IndexMap<String, Input>>,
552 /// The outputs of the task.
553 outputs: Arc<IndexMap<String, Output>>,
554}
555
556impl Task {
557 /// Gets the name of the task.
558 pub fn name(&self) -> &str {
559 &self.name
560 }
561
562 /// Gets the span of the name.
563 pub fn name_span(&self) -> Span {
564 self.name_span
565 }
566
567 /// Gets the span of the workflow definition.
568 pub fn span(&self) -> Span {
569 self.span
570 }
571
572 /// Gets the scope of the task.
573 pub fn scope(&self) -> ScopeRef<'_> {
574 ScopeRef::new(&self.scopes, ScopeIndex(0))
575 }
576
577 /// Gets the inputs of the task.
578 pub fn inputs(&self) -> &IndexMap<String, Input> {
579 &self.inputs
580 }
581
582 /// Gets the outputs of the task.
583 pub fn outputs(&self) -> &IndexMap<String, Output> {
584 &self.outputs
585 }
586}
587
588/// Represents a workflow in a document.
589#[derive(Debug)]
590pub struct Workflow {
591 /// The span of the workflow name.
592 name_span: Span,
593 /// The name of the workflow.
594 name: String,
595 /// The span of the workflow definition.
596 span: Span,
597 /// The scopes contained in the workflow.
598 ///
599 /// The first scope will always be the workflow's scope.
600 ///
601 /// The scopes will be in sorted order by span start.
602 scopes: Vec<Scope>,
603 /// The inputs of the workflow.
604 inputs: Arc<IndexMap<String, Input>>,
605 /// The outputs of the workflow.
606 outputs: Arc<IndexMap<String, Output>>,
607 /// The calls made by the workflow.
608 calls: HashMap<String, CallType>,
609 /// Whether or not nested inputs are allowed for the workflow.
610 allows_nested_inputs: bool,
611}
612
613impl Workflow {
614 /// Gets the name of the workflow.
615 pub fn name(&self) -> &str {
616 &self.name
617 }
618
619 /// Gets the span of the name.
620 pub fn name_span(&self) -> Span {
621 self.name_span
622 }
623
624 /// Gets the span of the workflow definition.
625 pub fn span(&self) -> Span {
626 self.span
627 }
628
629 /// Gets the scope of the workflow.
630 pub fn scope(&self) -> ScopeRef<'_> {
631 ScopeRef::new(&self.scopes, ScopeIndex(0))
632 }
633
634 /// Gets the inputs of the workflow.
635 pub fn inputs(&self) -> &IndexMap<String, Input> {
636 &self.inputs
637 }
638
639 /// Gets the outputs of the workflow.
640 pub fn outputs(&self) -> &IndexMap<String, Output> {
641 &self.outputs
642 }
643
644 /// Gets the calls made by the workflow.
645 pub fn calls(&self) -> &HashMap<String, CallType> {
646 &self.calls
647 }
648
649 /// Determines if the workflow allows nested inputs.
650 pub fn allows_nested_inputs(&self) -> bool {
651 self.allows_nested_inputs
652 }
653}
654
655/// A task imported into scope by a wildcard or selected-member import.
656#[derive(Debug)]
657pub struct ImportedTask {
658 /// The task name in the source document.
659 name: String,
660 /// The span of the import statement that introduced this task.
661 span: Span,
662 /// The source URI the task came from.
663 source: Arc<Url>,
664 /// The source document that defines the task.
665 document: Document,
666 /// The inputs of the task.
667 inputs: Arc<IndexMap<String, Input>>,
668 /// The outputs of the task.
669 outputs: Arc<IndexMap<String, Output>>,
670}
671
672impl ImportedTask {
673 /// Gets the task name in its source document.
674 pub fn name(&self) -> &str {
675 &self.name
676 }
677
678 /// Gets the source document that defines the task.
679 pub fn document(&self) -> &Document {
680 &self.document
681 }
682
683 /// Gets the source URI the task came from.
684 pub(crate) fn source(&self) -> &Url {
685 &self.source
686 }
687}
688
689/// A workflow imported into scope by a wildcard or selected-member import.
690#[derive(Debug)]
691pub struct ImportedWorkflow {
692 /// The workflow name in the source document.
693 name: String,
694 /// The span of the import statement.
695 span: Span,
696 /// The source URI.
697 source: Arc<Url>,
698 /// The source document that defines the workflow.
699 document: Document,
700 /// The inputs of the workflow.
701 inputs: Arc<IndexMap<String, Input>>,
702 /// The outputs of the workflow.
703 outputs: Arc<IndexMap<String, Output>>,
704}
705
706impl ImportedWorkflow {
707 /// Gets the workflow name in its source document.
708 pub fn name(&self) -> &str {
709 &self.name
710 }
711
712 /// Gets the source document that defines the workflow.
713 pub fn document(&self) -> &Document {
714 &self.document
715 }
716
717 /// Gets the source URI the workflow came from.
718 pub(crate) fn source(&self) -> &Url {
719 &self.source
720 }
721}
722
723/// A callable item.
724#[derive(Copy, Clone, Debug)]
725pub enum Callable<'a> {
726 /// A workflow.
727 Workflow(&'a Workflow),
728 /// A task.
729 Task(&'a Task),
730}
731
732impl Callable<'_> {
733 /// Get the name of this callable.
734 pub fn name(&self) -> &str {
735 match self {
736 Callable::Workflow(w) => w.name(),
737 Callable::Task(t) => t.name(),
738 }
739 }
740
741 /// Get the [`Span`] of the callable's name.
742 pub fn name_span(&self) -> Span {
743 match self {
744 Callable::Workflow(w) => w.name_span(),
745 Callable::Task(t) => t.name_span(),
746 }
747 }
748
749 /// Get the [`Span`] of the callable's full definition.
750 pub fn span(&self) -> Span {
751 match self {
752 Callable::Workflow(w) => w.span(),
753 Callable::Task(t) => t.span(),
754 }
755 }
756
757 /// Whether this callable represents a workflow.
758 pub fn is_workflow(&self) -> bool {
759 matches!(self, Callable::Workflow(_))
760 }
761
762 /// Whether this callable represents a task.
763 pub fn is_task(&self) -> bool {
764 matches!(self, Callable::Task(_))
765 }
766
767 /// Get the inputs of the callable.
768 pub fn inputs(&self) -> &IndexMap<String, Input> {
769 match self {
770 Callable::Workflow(w) => w.inputs(),
771 Callable::Task(t) => t.inputs(),
772 }
773 }
774
775 /// Get the defined outputs of the callable.
776 pub fn outputs(&self) -> &IndexMap<String, Output> {
777 match self {
778 Callable::Workflow(w) => w.outputs(),
779 Callable::Task(t) => t.outputs(),
780 }
781 }
782}
783
784/// Represents analysis data about a WDL document.
785#[derive(Debug)]
786pub(crate) struct DocumentData {
787 /// The configuration under which this document was analyzed.
788 config: Config,
789 /// The root CST node of the document.
790 ///
791 /// This is `None` when the document could not be parsed.
792 root: Option<GreenNode>,
793 /// The document identifier.
794 ///
795 /// The identifier changes every time the document is analyzed.
796 id: Arc<String>,
797 /// The URI of the analyzed document.
798 uri: Arc<Url>,
799 /// The version of the document.
800 version: Option<SupportedVersion>,
801 /// The successfully resolved namespaces in the document, keyed by name.
802 namespaces: IndexMap<String, Namespace>,
803 /// The names of imports that failed to resolve, keyed by name, each with
804 /// the span of the failing import. Kept so that downstream references to
805 /// the imported name (e.g., `import spellbook` followed by
806 /// `call spellbook.fireball`) don't produce cascading "unknown namespace"
807 /// diagnostics.
808 failed_imports: IndexMap<String, Span>,
809 /// The tasks in the document.
810 tasks: IndexMap<String, Task>,
811 /// The singular workflow in the document.
812 workflow: Option<Workflow>,
813 /// The structs in the document.
814 structs: IndexMap<String, Struct>,
815 /// The enums in the document.
816 enums: IndexMap<String, Enum>,
817 /// Tasks imported via wildcard or selected-member imports.
818 imported_tasks: IndexMap<String, ImportedTask>,
819 /// Workflows imported via wildcard or selected-member imports.
820 imported_workflows: IndexMap<String, ImportedWorkflow>,
821 /// Whether a wildcard import failed to resolve.
822 ///
823 /// Unknown unqualified calls are suppressed in this case because they may
824 /// have come from the missing import.
825 failed_wildcard_import: bool,
826 /// Selected task or workflow imports that failed to resolve.
827 failed_selected_imports: IndexSet<String>,
828 /// The diagnostics from parsing.
829 parse_diagnostics: Vec<Diagnostic>,
830 /// The diagnostics from analysis.
831 analysis_diagnostics: Diagnostics,
832}
833
834impl DocumentData {
835 /// Constructs a new analysis document data.
836 fn new(
837 config: Config,
838 uri: Arc<Url>,
839 root: Option<GreenNode>,
840 version: Option<SupportedVersion>,
841 diagnostics: Vec<Diagnostic>,
842 ) -> Self {
843 Self {
844 config,
845 root,
846 id: Uuid::new_v4().to_string().into(),
847 uri,
848 version,
849 namespaces: Default::default(),
850 failed_imports: Default::default(),
851 tasks: Default::default(),
852 workflow: Default::default(),
853 structs: Default::default(),
854 enums: Default::default(),
855 imported_tasks: Default::default(),
856 imported_workflows: Default::default(),
857 failed_wildcard_import: false,
858 failed_selected_imports: Default::default(),
859 parse_diagnostics: diagnostics,
860 analysis_diagnostics: Default::default(),
861 }
862 }
863
864 /// Gets the context of the given name.
865 ///
866 /// The name may be for a namespace, task, workflow, struct, or enum.
867 ///
868 /// Returns `None` if there is no context for the given name.
869 pub fn context(&self, name: &str) -> Option<Context> {
870 // Look through the various data structures for the name
871 if let Some(ns) = self.namespaces.get(name) {
872 Some(Context::Namespace(ns.span))
873 } else if let Some(span) = self.failed_imports.get(name) {
874 Some(Context::Namespace(*span))
875 } else if let Some(task) = self.tasks.get(name) {
876 Some(Context::Task(task.name_span()))
877 } else if let Some(wf) = &self.workflow
878 && wf.name == name
879 {
880 Some(Context::Workflow(wf.name_span()))
881 } else if let Some(s) = self.structs.get(name) {
882 Some(Context::Struct(s.name_span()))
883 } else {
884 // Finally, check the enums and failing that return `None`
885 self.enums.get(name).map(|e| Context::Enum(e.name_span()))
886 }
887 }
888}
889
890/// Represents an analyzed WDL document.
891///
892/// This type is cheaply cloned.
893#[derive(Debug, Clone)]
894pub struct Document {
895 /// The document data for the document.
896 data: Arc<DocumentData>,
897}
898
899impl Document {
900 /// Creates a new default document from a URI.
901 pub(crate) fn default_from_uri(uri: Arc<Url>) -> Self {
902 Self {
903 data: Arc::new(DocumentData::new(
904 Default::default(),
905 uri,
906 None,
907 None,
908 Default::default(),
909 )),
910 }
911 }
912
913 /// Creates a new analyzed document from a document graph node.
914 pub(crate) fn from_graph_node(
915 config: &Config,
916 graph: &DocumentGraph,
917 index: NodeIndex,
918 ) -> Self {
919 let node = graph.get(index);
920
921 let (wdl_version, diagnostics) = match node.parse_state() {
922 ParseState::NotParsed => panic!("node should have been parsed"),
923 ParseState::Error(_) => return Self::default_from_uri(node.uri().clone()),
924 ParseState::Parsed {
925 wdl_version,
926 diagnostics,
927 ..
928 } => (wdl_version, diagnostics),
929 };
930
931 let root = node.root().expect("node should have been parsed");
932 let (config, wdl_version) = match (root.version_statement(), wdl_version) {
933 (Some(stmt), Some(wdl_version)) => (
934 config.with_diagnostics_config(
935 config.diagnostics_config().excepted_for_node(stmt.inner()),
936 ),
937 *wdl_version,
938 ),
939 _ => {
940 // Don't process a document with a missing version statement or an unsupported
941 // version unless a fallback version is configured
942 return Self {
943 data: Arc::new(DocumentData::new(
944 config.clone(),
945 node.uri().clone(),
946 Some(root.inner().green().into()),
947 None,
948 diagnostics.to_vec(),
949 )),
950 };
951 }
952 };
953
954 let mut data = DocumentData::new(
955 config.clone(),
956 node.uri().clone(),
957 Some(root.inner().green().into()),
958 Some(wdl_version),
959 diagnostics.to_vec(),
960 );
961 match root.ast_with_version_fallback(config.fallback_version()) {
962 Ast::Unsupported => {}
963 Ast::V1(ast) => v1::populate_document(&mut data, &config, graph, index, &ast),
964 }
965
966 Self {
967 data: Arc::new(data),
968 }
969 }
970
971 /// Gets the analysis configuration.
972 pub fn config(&self) -> &Config {
973 &self.data.config
974 }
975
976 /// Gets the root AST document node.
977 ///
978 /// # Panics
979 ///
980 /// Panics if the document was not parsed.
981 pub fn root(&self) -> wdl_ast::Document {
982 wdl_ast::Document::cast(SyntaxNode::new_root(
983 self.data.root.clone().expect("should have a root"),
984 ))
985 .expect("should cast")
986 }
987
988 /// Gets the identifier of the document.
989 ///
990 /// This value changes when a document is reanalyzed.
991 pub fn id(&self) -> &Arc<String> {
992 &self.data.id
993 }
994
995 /// Gets the URI of the document.
996 pub fn uri(&self) -> &Arc<Url> {
997 &self.data.uri
998 }
999
1000 /// Gets the path to the document.
1001 ///
1002 /// If the scheme of the document's URI is not `file`, this will return the
1003 /// URI as a string. Otherwise, this will attempt to return the path
1004 /// relative to the current working directory, or the absolute path
1005 /// failing that.
1006 pub fn path(&self) -> Cow<'_, str> {
1007 if let Ok(path) = self.data.uri.to_file_path() {
1008 if let Some(path) = std::env::current_dir()
1009 .ok()
1010 .and_then(|cwd| path.strip_prefix(cwd).ok().and_then(Path::to_str))
1011 {
1012 return path.to_string().into();
1013 }
1014
1015 if let Ok(path) = path.into_os_string().into_string() {
1016 return path.into();
1017 }
1018 }
1019
1020 self.data.uri.as_str().into()
1021 }
1022
1023 /// Computes the `blake3` hash of the document's source text over the
1024 /// given span and returns the hex form.
1025 ///
1026 /// Uses `rowan::SyntaxText::for_each_chunk` so the span's text is never
1027 /// materialized as a `String`.
1028 ///
1029 /// Returns `None` if `span` falls outside the document's source text.
1030 pub fn hash_span(&self, span: Span) -> Option<ArrayString<64>> {
1031 let text = self.root().inner().text();
1032 let text_len = usize::from(text.len());
1033 if span.end() > text_len {
1034 return None;
1035 }
1036 let range = TextRange::new(
1037 TextSize::new(span.start() as u32),
1038 TextSize::new(span.end() as u32),
1039 );
1040 let slice = text.slice(range);
1041 let mut hasher = blake3::Hasher::new();
1042 slice.for_each_chunk(|chunk| {
1043 hasher.update(chunk.as_bytes());
1044 });
1045 Some(hasher.finalize().to_hex())
1046 }
1047
1048 /// Gets the supported version of the document.
1049 ///
1050 /// Returns `None` if the document could not be parsed or contains an
1051 /// unsupported version.
1052 pub fn version(&self) -> Option<SupportedVersion> {
1053 self.data.version
1054 }
1055
1056 /// Gets the successfully resolved namespaces in the document.
1057 pub fn namespaces(&self) -> impl Iterator<Item = (&str, &Namespace)> {
1058 self.data.namespaces.iter().map(|(n, ns)| (n.as_str(), ns))
1059 }
1060
1061 /// Gets a successfully resolved namespace in the document by name.
1062 pub fn namespace(&self, name: &str) -> Option<&Namespace> {
1063 self.data.namespaces.get(name)
1064 }
1065
1066 /// Gets the tasks in the document.
1067 pub fn tasks(&self) -> impl Iterator<Item = &Task> {
1068 self.data.tasks.iter().map(|(_, t)| t)
1069 }
1070
1071 /// Gets a task in the document by name.
1072 pub fn task_by_name(&self, name: &str) -> Option<&Task> {
1073 self.data.tasks.get(name)
1074 }
1075
1076 /// Gets an imported task in the document by local name.
1077 pub fn imported_task_by_name(&self, name: &str) -> Option<&ImportedTask> {
1078 self.data.imported_tasks.get(name)
1079 }
1080
1081 /// Gets a workflow in the document.
1082 ///
1083 /// Returns `None` if the document did not contain a workflow.
1084 pub fn workflow(&self) -> Option<&Workflow> {
1085 self.data.workflow.as_ref()
1086 }
1087
1088 /// Gets an imported workflow in the document by local name.
1089 pub fn imported_workflow_by_name(&self, name: &str) -> Option<&ImportedWorkflow> {
1090 self.data.imported_workflows.get(name)
1091 }
1092
1093 /// Gets a [`Callable`] in the document by name.
1094 ///
1095 /// Returns `None` if the document did not contain a callable definition
1096 /// with the given name.
1097 pub fn callable_by_name(&self, name: &str) -> Option<Callable<'_>> {
1098 if let Some(workflow) = self.workflow()
1099 && workflow.name() == name
1100 {
1101 return Some(Callable::Workflow(workflow));
1102 }
1103
1104 if let Some(task) = self.task_by_name(name) {
1105 return Some(Callable::Task(task));
1106 }
1107
1108 None
1109 }
1110
1111 /// Get all callable targets in the document.
1112 pub fn callables(&self) -> impl Iterator<Item = Callable<'_>> {
1113 self.workflow()
1114 .map(Callable::Workflow)
1115 .into_iter()
1116 .chain(self.tasks().map(Callable::Task))
1117 }
1118
1119 /// Gets the structs in the document.
1120 pub fn structs(&self) -> impl Iterator<Item = (&str, &Struct)> {
1121 self.data.structs.iter().map(|(n, s)| (n.as_str(), s))
1122 }
1123
1124 /// Gets a struct in the document by name.
1125 pub fn struct_by_name(&self, name: &str) -> Option<&Struct> {
1126 self.data.structs.get(name)
1127 }
1128
1129 /// Gets the enums in the document.
1130 pub fn enums(&self) -> impl Iterator<Item = (&str, &Enum)> {
1131 self.data.enums.iter().map(|(n, e)| (n.as_str(), e))
1132 }
1133
1134 /// Gets an enum in the document by name.
1135 pub fn enum_by_name(&self, name: &str) -> Option<&Enum> {
1136 self.data.enums.get(name)
1137 }
1138
1139 /// Gets the custom type by name.
1140 pub fn get_custom_type(&self, name: &str) -> Option<&Type> {
1141 if let Some(s) = self.struct_by_name(name) {
1142 return s.ty();
1143 }
1144
1145 if let Some(e) = self.enum_by_name(name) {
1146 return e.ty();
1147 }
1148
1149 None
1150 }
1151
1152 /// Gets a cache key for an enum choice lookup.
1153 pub fn get_choice_cache_key(&self, name: &str, choice: &str) -> Option<EnumChoiceCacheKey> {
1154 let (enum_index, _, r#enum) = self.data.enums.get_full(name)?;
1155 let enum_ty = r#enum.ty()?.as_enum()?;
1156 let choice_index = enum_ty.choices().iter().position(|v| v == choice)?;
1157 Some(EnumChoiceCacheKey::new(
1158 self.data.uri.clone(),
1159 enum_index,
1160 choice_index,
1161 ))
1162 }
1163
1164 /// Gets the parse diagnostics for the document.
1165 pub fn parse_diagnostics(&self) -> &[Diagnostic] {
1166 &self.data.parse_diagnostics
1167 }
1168
1169 /// Gets the analysis diagnostics for the document.
1170 pub fn analysis_diagnostics(&self) -> &Diagnostics {
1171 &self.data.analysis_diagnostics
1172 }
1173
1174 /// Gets all diagnostics for the document (both from parsing and analysis).
1175 pub fn diagnostics(&self) -> impl Iterator<Item = &Diagnostic> {
1176 self.data
1177 .parse_diagnostics
1178 .iter()
1179 .chain(self.data.analysis_diagnostics.diagnostics.iter())
1180 }
1181
1182 /// Sorts the diagnostics for the document.
1183 ///
1184 /// # Panics
1185 ///
1186 /// Panics if there is more than one reference to the document.
1187 pub fn sort_diagnostics(&mut self) -> Self {
1188 let data = &mut self.data;
1189 let inner = Arc::get_mut(data).expect("should only have one reference");
1190 inner.parse_diagnostics.sort();
1191 inner.analysis_diagnostics.sort();
1192 Self { data: data.clone() }
1193 }
1194
1195 /// Extends the analysis diagnostics for the document.
1196 ///
1197 /// # Panics
1198 ///
1199 /// Panics if there is more than one reference to the document.
1200 pub fn extend_diagnostics(&mut self, diagnostics: Diagnostics) -> Self {
1201 let data = &mut self.data;
1202 let inner = Arc::get_mut(data).expect("should only have one reference");
1203 inner.analysis_diagnostics.extend(diagnostics.diagnostics);
1204 Self { data: data.clone() }
1205 }
1206
1207 /// Finds a scope based on a position within the document.
1208 pub fn find_scope_by_position(&self, position: usize) -> Option<ScopeRef<'_>> {
1209 /// Finds a scope within a collection of sorted scopes by position.
1210 fn find_scope(scopes: &[Scope], position: usize) -> Option<ScopeRef<'_>> {
1211 let mut index = match scopes.binary_search_by_key(&position, |s| s.span.start()) {
1212 Ok(index) => index,
1213 Err(index) => {
1214 // This indicates that we couldn't find a match and the match would go _before_
1215 // the first scope, so there is no containing scope.
1216 if index == 0 {
1217 return None;
1218 }
1219
1220 index - 1
1221 }
1222 };
1223
1224 // We now have the index to start looking up the list of scopes
1225 // We walk up the list to try to find a span that contains the position
1226 loop {
1227 let scope = &scopes[index];
1228 if scope.span.contains(position) {
1229 return Some(ScopeRef::new(scopes, ScopeIndex(index)));
1230 }
1231
1232 if index == 0 {
1233 return None;
1234 }
1235
1236 index -= 1;
1237 }
1238 }
1239
1240 // Check to see if the position is contained in the workflow
1241 if let Some(workflow) = &self.data.workflow
1242 && workflow.scope().span().contains(position)
1243 {
1244 return find_scope(&workflow.scopes, position);
1245 }
1246
1247 // Search for a task that might contain the position
1248 let task = match self
1249 .data
1250 .tasks
1251 .binary_search_by_key(&position, |_, t| t.scope().span().start())
1252 {
1253 Ok(index) => &self.data.tasks[index],
1254 Err(index) => {
1255 // This indicates that we couldn't find a match and the match would go _before_
1256 // the first task, so there is no containing task.
1257 if index == 0 {
1258 return None;
1259 }
1260
1261 &self.data.tasks[index - 1]
1262 }
1263 };
1264
1265 if task.scope().span().contains(position) {
1266 return find_scope(&task.scopes, position);
1267 }
1268
1269 None
1270 }
1271
1272 /// Determines if the document, or any documents transitively imported by
1273 /// this document, has errors.
1274 ///
1275 /// Returns `true` if the document, or one of its transitive imports, has at
1276 /// least one error diagnostic.
1277 ///
1278 /// Returns `false` if the document, and all of its transitive imports, have
1279 /// no error diagnostics.
1280 pub fn has_errors(&self) -> bool {
1281 // Check this document for errors
1282 if self.diagnostics().any(|d| d.severity() == Severity::Error) {
1283 return true;
1284 }
1285
1286 // Check every imported document for errors
1287 for (_, ns) in self.namespaces() {
1288 if ns.document().has_errors() {
1289 return true;
1290 }
1291 }
1292
1293 false
1294 }
1295
1296 /// Visits the document with a pre-order traversal using the provided
1297 /// visitor to visit each element in the document.
1298 pub fn visit<V: crate::Visitor>(&self, diagnostics: &mut crate::Diagnostics, visitor: &mut V) {
1299 crate::visit(self, diagnostics, visitor)
1300 }
1301}