Skip to main content

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}