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