wdl_ast/v1/
workflow.rs

1//! V1 AST representation for workflows.
2
3use wdl_grammar::SupportedVersion;
4use wdl_grammar::version::V1;
5
6use super::BoundDecl;
7use super::Expr;
8use super::InputSection;
9use super::LiteralBoolean;
10use super::LiteralFloat;
11use super::LiteralInteger;
12use super::LiteralString;
13use super::MetadataSection;
14use super::MetadataValue;
15use super::OutputSection;
16use super::ParameterMetadataSection;
17use crate::AstChildren;
18use crate::AstNode;
19use crate::AstToken;
20use crate::Ident;
21use crate::SyntaxElement;
22use crate::SyntaxKind;
23use crate::SyntaxNode;
24use crate::WorkflowDescriptionLanguage;
25use crate::support::child;
26use crate::support::children;
27use crate::token;
28
29/// The name of the `allow_nested_inputs` workflow hint.
30pub const WORKFLOW_HINT_ALLOW_NESTED_INPUTS: &str = "allow_nested_inputs";
31
32/// The alias of the `allow_nested_inputs` workflow hint (e.g.
33/// `allowNestedInputs`).
34pub const WORKFLOW_HINT_ALLOW_NESTED_INPUTS_ALIAS: &str = "allowNestedInputs";
35
36/// Represents a workflow definition.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct WorkflowDefinition(pub(crate) SyntaxNode);
39
40impl WorkflowDefinition {
41    /// Gets the name of the workflow.
42    pub fn name(&self) -> Ident {
43        token(&self.0).expect("workflow should have a name")
44    }
45
46    /// Gets the items of the workflow.
47    pub fn items(&self) -> impl Iterator<Item = WorkflowItem> + use<> {
48        WorkflowItem::children(&self.0)
49    }
50
51    /// Gets the input section of the workflow.
52    pub fn input(&self) -> Option<InputSection> {
53        child(&self.0)
54    }
55
56    /// Gets the output section of the workflow.
57    pub fn output(&self) -> Option<OutputSection> {
58        child(&self.0)
59    }
60
61    /// Gets the statements of the workflow.
62    pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement> + use<> {
63        WorkflowStatement::children(&self.0)
64    }
65
66    /// Gets the metadata section of the workflow.
67    pub fn metadata(&self) -> Option<MetadataSection> {
68        child(&self.0)
69    }
70
71    /// Gets the parameter section of the workflow.
72    pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection> {
73        child(&self.0)
74    }
75
76    /// Gets the hints section of the workflow.
77    pub fn hints(&self) -> Option<WorkflowHintsSection> {
78        child(&self.0)
79    }
80
81    /// Gets the private declarations of the workflow.
82    pub fn declarations(&self) -> AstChildren<BoundDecl> {
83        children(&self.0)
84    }
85
86    /// Determines if the workflow definition allows nested inputs.
87    pub fn allows_nested_inputs(&self, version: SupportedVersion) -> bool {
88        match version {
89            SupportedVersion::V1(V1::Zero) => return true,
90            SupportedVersion::V1(V1::One) => {
91                // Fall through to below
92            }
93            SupportedVersion::V1(V1::Two) => {
94                // Check the hints section
95                let allow = self.hints().and_then(|s| {
96                    s.items().find_map(|i| {
97                        let name = i.name();
98                        if name.as_str() == WORKFLOW_HINT_ALLOW_NESTED_INPUTS
99                            || name.as_str() == WORKFLOW_HINT_ALLOW_NESTED_INPUTS_ALIAS
100                        {
101                            match i.value() {
102                                WorkflowHintsItemValue::Boolean(v) => Some(v.value()),
103                                _ => Some(false),
104                            }
105                        } else {
106                            None
107                        }
108                    })
109                });
110
111                if let Some(allow) = allow {
112                    return allow;
113                }
114
115                // Fall through to below
116            }
117            _ => return false,
118        }
119
120        // Check the metadata section
121        self.metadata()
122            .and_then(|s| {
123                s.items().find_map(|i| {
124                    if i.name().as_str() == "allowNestedInputs" {
125                        match i.value() {
126                            MetadataValue::Boolean(v) => Some(v.value()),
127                            _ => Some(false),
128                        }
129                    } else {
130                        None
131                    }
132                })
133            })
134            .unwrap_or(false)
135    }
136}
137
138impl AstNode for WorkflowDefinition {
139    type Language = WorkflowDescriptionLanguage;
140
141    fn can_cast(kind: SyntaxKind) -> bool
142    where
143        Self: Sized,
144    {
145        kind == SyntaxKind::WorkflowDefinitionNode
146    }
147
148    fn cast(syntax: SyntaxNode) -> Option<Self>
149    where
150        Self: Sized,
151    {
152        match syntax.kind() {
153            SyntaxKind::WorkflowDefinitionNode => Some(Self(syntax)),
154            _ => None,
155        }
156    }
157
158    fn syntax(&self) -> &SyntaxNode {
159        &self.0
160    }
161}
162
163/// Represents an item in a workflow definition.
164#[derive(Clone, Debug, PartialEq, Eq)]
165pub enum WorkflowItem {
166    /// The item is an input section.
167    Input(InputSection),
168    /// The item is an output section.
169    Output(OutputSection),
170    /// The item is a conditional statement.
171    Conditional(ConditionalStatement),
172    /// The item is a scatter statement.
173    Scatter(ScatterStatement),
174    /// The item is a call statement.
175    Call(CallStatement),
176    /// The item is a metadata section.
177    Metadata(MetadataSection),
178    /// The item is a parameter meta section.
179    ParameterMetadata(ParameterMetadataSection),
180    /// The item is a workflow hints section.
181    Hints(WorkflowHintsSection),
182    /// The item is a private bound declaration.
183    Declaration(BoundDecl),
184}
185
186impl WorkflowItem {
187    /// Returns whether or not a [`SyntaxKind`] is able to be cast to any of the
188    /// underlying members within the [`WorkflowItem`].
189    pub fn can_cast(kind: SyntaxKind) -> bool
190    where
191        Self: Sized,
192    {
193        matches!(
194            kind,
195            SyntaxKind::InputSectionNode
196                | SyntaxKind::OutputSectionNode
197                | SyntaxKind::ConditionalStatementNode
198                | SyntaxKind::ScatterStatementNode
199                | SyntaxKind::CallStatementNode
200                | SyntaxKind::MetadataSectionNode
201                | SyntaxKind::ParameterMetadataSectionNode
202                | SyntaxKind::WorkflowHintsSectionNode
203                | SyntaxKind::BoundDeclNode
204        )
205    }
206
207    /// Attempts to cast the [`SyntaxNode`] to any of the underlying members
208    /// within the [`WorkflowItem`].
209    pub fn cast(syntax: SyntaxNode) -> Option<Self>
210    where
211        Self: Sized,
212    {
213        match syntax.kind() {
214            SyntaxKind::InputSectionNode => Some(Self::Input(
215                InputSection::cast(syntax).expect("input section to cast"),
216            )),
217            SyntaxKind::OutputSectionNode => Some(Self::Output(
218                OutputSection::cast(syntax).expect("output section to cast"),
219            )),
220            SyntaxKind::ConditionalStatementNode => Some(Self::Conditional(
221                ConditionalStatement::cast(syntax).expect("conditional statement to cast"),
222            )),
223            SyntaxKind::ScatterStatementNode => Some(Self::Scatter(
224                ScatterStatement::cast(syntax).expect("scatter statement to cast"),
225            )),
226            SyntaxKind::CallStatementNode => Some(Self::Call(
227                CallStatement::cast(syntax).expect("call statement to cast"),
228            )),
229            SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
230                MetadataSection::cast(syntax).expect("metadata section to cast"),
231            )),
232            SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
233                ParameterMetadataSection::cast(syntax).expect("parameter metadata section to cast"),
234            )),
235            SyntaxKind::WorkflowHintsSectionNode => Some(Self::Hints(
236                WorkflowHintsSection::cast(syntax).expect("workflow hints section to cast"),
237            )),
238            SyntaxKind::BoundDeclNode => Some(Self::Declaration(
239                BoundDecl::cast(syntax).expect("bound decl to cast"),
240            )),
241            _ => None,
242        }
243    }
244
245    /// Gets a reference to the underlying [`SyntaxNode`].
246    pub fn syntax(&self) -> &SyntaxNode {
247        match self {
248            Self::Input(element) => element.syntax(),
249            Self::Output(element) => element.syntax(),
250            Self::Conditional(element) => element.syntax(),
251            Self::Scatter(element) => element.syntax(),
252            Self::Call(element) => element.syntax(),
253            Self::Metadata(element) => element.syntax(),
254            Self::ParameterMetadata(element) => element.syntax(),
255            Self::Hints(element) => element.syntax(),
256            Self::Declaration(element) => element.syntax(),
257        }
258    }
259
260    /// Attempts to get a reference to the inner [`InputSection`].
261    ///
262    /// * If `self` is a [`WorkflowItem::Input`], then a reference to the inner
263    ///   [`InputSection`] is returned wrapped in [`Some`].
264    /// * Else, [`None`] is returned.
265    pub fn as_input_section(&self) -> Option<&InputSection> {
266        match self {
267            Self::Input(input_section) => Some(input_section),
268            _ => None,
269        }
270    }
271
272    /// Consumes `self` and attempts to return the inner [`InputSection`].
273    ///
274    /// * If `self` is a [`WorkflowItem::Input`], then the inner
275    ///   [`InputSection`] is returned wrapped in [`Some`].
276    /// * Else, [`None`] is returned.
277    pub fn into_input_section(self) -> Option<InputSection> {
278        match self {
279            Self::Input(input_section) => Some(input_section),
280            _ => None,
281        }
282    }
283
284    /// Attempts to get a reference to the inner [`OutputSection`].
285    ///
286    /// * If `self` is a [`WorkflowItem::Output`], then a reference to the inner
287    ///   [`OutputSection`] is returned wrapped in [`Some`].
288    /// * Else, [`None`] is returned.
289    pub fn as_output_section(&self) -> Option<&OutputSection> {
290        match self {
291            Self::Output(output_section) => Some(output_section),
292            _ => None,
293        }
294    }
295
296    /// Consumes `self` and attempts to return the inner [`OutputSection`].
297    ///
298    /// * If `self` is a [`WorkflowItem::Output`], then the inner
299    ///   [`OutputSection`] is returned wrapped in [`Some`].
300    /// * Else, [`None`] is returned.
301    pub fn into_output_section(self) -> Option<OutputSection> {
302        match self {
303            Self::Output(output_section) => Some(output_section),
304            _ => None,
305        }
306    }
307
308    /// Attempts to get a reference to the inner [`ConditionalStatement`].
309    ///
310    /// * If `self` is a [`WorkflowItem::Conditional`], then a reference to the
311    ///   inner [`ConditionalStatement`] is returned wrapped in [`Some`].
312    /// * Else, [`None`] is returned.
313    pub fn as_conditional(&self) -> Option<&ConditionalStatement> {
314        match self {
315            Self::Conditional(conditional) => Some(conditional),
316            _ => None,
317        }
318    }
319
320    /// Consumes `self` and attempts to return the inner
321    /// [`ConditionalStatement`].
322    ///
323    /// * If `self` is a [`WorkflowItem::Conditional`], then the inner
324    ///   [`ConditionalStatement`] is returned wrapped in [`Some`].
325    /// * Else, [`None`] is returned.
326    pub fn into_conditional(self) -> Option<ConditionalStatement> {
327        match self {
328            Self::Conditional(conditional) => Some(conditional),
329            _ => None,
330        }
331    }
332
333    /// Attempts to get a reference to the inner [`ScatterStatement`].
334    ///
335    /// * If `self` is a [`WorkflowItem::Scatter`], then a reference to the
336    ///   inner [`ScatterStatement`] is returned wrapped in [`Some`].
337    /// * Else, [`None`] is returned.
338    pub fn as_scatter(&self) -> Option<&ScatterStatement> {
339        match self {
340            Self::Scatter(scatter) => Some(scatter),
341            _ => None,
342        }
343    }
344
345    /// Consumes `self` and attempts to return the inner
346    /// [`ScatterStatement`].
347    ///
348    /// * If `self` is a [`WorkflowItem::Scatter`], then the inner
349    ///   [`ScatterStatement`] is returned wrapped in [`Some`].
350    /// * Else, [`None`] is returned.
351    pub fn into_scatter(self) -> Option<ScatterStatement> {
352        match self {
353            Self::Scatter(scatter) => Some(scatter),
354            _ => None,
355        }
356    }
357
358    /// Attempts to get a reference to the inner [`CallStatement`].
359    ///
360    /// * If `self` is a [`WorkflowItem::Call`], then a reference to the inner
361    ///   [`CallStatement`] is returned wrapped in [`Some`].
362    /// * Else, [`None`] is returned.
363    pub fn as_call(&self) -> Option<&CallStatement> {
364        match self {
365            Self::Call(call) => Some(call),
366            _ => None,
367        }
368    }
369
370    /// Consumes `self` and attempts to return the inner [`CallStatement`].
371    ///
372    /// * If `self` is a [`WorkflowItem::Call`], then the inner
373    ///   [`CallStatement`] is returned wrapped in [`Some`].
374    /// * Else, [`None`] is returned.
375    pub fn into_call(self) -> Option<CallStatement> {
376        match self {
377            Self::Call(call) => Some(call),
378            _ => None,
379        }
380    }
381
382    /// Attempts to get a reference to the inner [`MetadataSection`].
383    ///
384    /// * If `self` is a [`WorkflowItem::Metadata`], then a reference to the
385    ///   inner [`MetadataSection`] is returned wrapped in [`Some`].
386    /// * Else, [`None`] is returned.
387    pub fn as_metadata_section(&self) -> Option<&MetadataSection> {
388        match self {
389            Self::Metadata(metadata_section) => Some(metadata_section),
390            _ => None,
391        }
392    }
393
394    /// Consumes `self` and attempts to return the inner [`MetadataSection`].
395    ///
396    /// * If `self` is a [`WorkflowItem::Metadata`], then the inner
397    ///   [`MetadataSection`] is returned wrapped in [`Some`].
398    /// * Else, [`None`] is returned.
399    pub fn into_metadata_section(self) -> Option<MetadataSection> {
400        match self {
401            Self::Metadata(metadata_section) => Some(metadata_section),
402            _ => None,
403        }
404    }
405
406    /// Attempts to get a reference to the inner [`ParameterMetadataSection`].
407    ///
408    /// * If `self` is a [`WorkflowItem::ParameterMetadata`], then a reference
409    ///   to the inner [`ParameterMetadataSection`] is returned wrapped in
410    ///   [`Some`].
411    /// * Else, [`None`] is returned.
412    pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection> {
413        match self {
414            Self::ParameterMetadata(parameter_metadata_section) => Some(parameter_metadata_section),
415            _ => None,
416        }
417    }
418
419    /// Consumes `self` and attempts to return the inner
420    /// [`ParameterMetadataSection`].
421    ///
422    /// * If `self` is a [`WorkflowItem::ParameterMetadata`], then the inner
423    ///   [`ParameterMetadataSection`] is returned wrapped in [`Some`].
424    /// * Else, [`None`] is returned.
425    pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection> {
426        match self {
427            Self::ParameterMetadata(parameter_metadata_section) => Some(parameter_metadata_section),
428            _ => None,
429        }
430    }
431
432    /// Attempts to get a reference to the inner [`WorkflowHintsSection`].
433    ///
434    /// * If `self` is a [`WorkflowItem::Hints`], then a reference to the inner
435    ///   [`WorkflowHintsSection`] is returned wrapped in [`Some`].
436    /// * Else, [`None`] is returned.
437    pub fn as_hints_section(&self) -> Option<&WorkflowHintsSection> {
438        match self {
439            Self::Hints(hints_section) => Some(hints_section),
440            _ => None,
441        }
442    }
443
444    /// Consumes `self` and attempts to return the inner
445    /// [`WorkflowHintsSection`].
446    ///
447    /// * If `self` is a [`WorkflowItem::Hints`], then the inner
448    ///   [`WorkflowHintsSection`] is returned wrapped in [`Some`].
449    /// * Else, [`None`] is returned.
450    pub fn into_hints_section(self) -> Option<WorkflowHintsSection> {
451        match self {
452            Self::Hints(hints_section) => Some(hints_section),
453            _ => None,
454        }
455    }
456
457    /// Attempts to get a reference to the inner [`BoundDecl`].
458    ///
459    /// * If `self` is a [`WorkflowItem::Declaration`], then a reference to the
460    ///   inner [`BoundDecl`] is returned wrapped in [`Some`].
461    /// * Else, [`None`] is returned.
462    pub fn as_declaration(&self) -> Option<&BoundDecl> {
463        match self {
464            Self::Declaration(declaration) => Some(declaration),
465            _ => None,
466        }
467    }
468
469    /// Consumes `self` and attempts to return the inner [`BoundDecl`].
470    ///
471    /// * If `self` is a [`WorkflowItem::Declaration`], then the inner
472    ///   [`BoundDecl`] is returned wrapped in [`Some`].
473    /// * Else, [`None`] is returned.
474    pub fn into_declaration(self) -> Option<BoundDecl> {
475        match self {
476            Self::Declaration(declaration) => Some(declaration),
477            _ => None,
478        }
479    }
480
481    /// Finds the first child that can be cast to an [`WorkflowItem`].
482    ///
483    /// This is meant to emulate the functionality of
484    /// [`rowan::ast::support::child`] without requiring [`WorkflowItem`] to
485    /// implement the `AstNode` trait.
486    pub fn child(syntax: &SyntaxNode) -> Option<Self> {
487        syntax.children().find_map(Self::cast)
488    }
489
490    /// Finds all children that can be cast to an [`WorkflowItem`].
491    ///
492    /// This is meant to emulate the functionality of
493    /// [`rowan::ast::support::children`] without requiring [`WorkflowItem`] to
494    /// implement the `AstNode` trait.
495    pub fn children(syntax: &SyntaxNode) -> impl Iterator<Item = WorkflowItem> + use<> {
496        syntax.children().filter_map(Self::cast)
497    }
498}
499
500/// Represents a statement in a workflow definition.
501#[derive(Clone, Debug, PartialEq, Eq)]
502pub enum WorkflowStatement {
503    /// The statement is a conditional statement.
504    Conditional(ConditionalStatement),
505    /// The statement is a scatter statement.
506    Scatter(ScatterStatement),
507    /// The statement is a call statement.
508    Call(CallStatement),
509    /// The statement is a private bound declaration.
510    Declaration(BoundDecl),
511}
512
513impl WorkflowStatement {
514    /// Returns whether or not a [`SyntaxKind`] is able to be cast to any of the
515    /// underlying members within the [`WorkflowStatement`].
516    pub fn can_cast(kind: SyntaxKind) -> bool
517    where
518        Self: Sized,
519    {
520        matches!(
521            kind,
522            SyntaxKind::ConditionalStatementNode
523                | SyntaxKind::ScatterStatementNode
524                | SyntaxKind::CallStatementNode
525                | SyntaxKind::BoundDeclNode
526        )
527    }
528
529    /// Attempts to cast the [`SyntaxNode`] to any of the underlying members
530    /// within the [`WorkflowStatement`].
531    pub fn cast(syntax: SyntaxNode) -> Option<Self>
532    where
533        Self: Sized,
534    {
535        match syntax.kind() {
536            SyntaxKind::ConditionalStatementNode => Some(Self::Conditional(
537                ConditionalStatement::cast(syntax).expect("conditional statement to cast"),
538            )),
539            SyntaxKind::ScatterStatementNode => Some(Self::Scatter(
540                ScatterStatement::cast(syntax).expect("scatter statement to cast"),
541            )),
542            SyntaxKind::CallStatementNode => Some(Self::Call(
543                CallStatement::cast(syntax).expect("call statement to cast"),
544            )),
545            SyntaxKind::BoundDeclNode => Some(Self::Declaration(
546                BoundDecl::cast(syntax).expect("bound decl to cast"),
547            )),
548            _ => None,
549        }
550    }
551
552    /// Gets a reference to the underlying [`SyntaxNode`].
553    pub fn syntax(&self) -> &SyntaxNode {
554        match self {
555            Self::Conditional(element) => element.syntax(),
556            Self::Scatter(element) => element.syntax(),
557            Self::Call(element) => element.syntax(),
558            Self::Declaration(element) => element.syntax(),
559        }
560    }
561
562    /// Attempts to get a reference to the inner [`ConditionalStatement`].
563    ///
564    /// * If `self` is a [`WorkflowStatement::Conditional`], then a reference to
565    ///   the inner [`ConditionalStatement`] is returned wrapped in [`Some`].
566    /// * Else, [`None`] is returned.
567    pub fn as_conditional(&self) -> Option<&ConditionalStatement> {
568        match self {
569            Self::Conditional(conditional) => Some(conditional),
570            _ => None,
571        }
572    }
573
574    /// Consumes `self` and attempts to return the inner
575    /// [`ConditionalStatement`].
576    ///
577    /// * If `self` is a [`WorkflowStatement::Conditional`], then the inner
578    ///   [`ConditionalStatement`] is returned wrapped in [`Some`].
579    /// * Else, [`None`] is returned.
580    pub fn into_conditional(self) -> Option<ConditionalStatement> {
581        match self {
582            Self::Conditional(conditional) => Some(conditional),
583            _ => None,
584        }
585    }
586
587    /// Unwraps the statement into a conditional statement.
588    ///
589    /// # Panics
590    ///
591    /// Panics if the statement is not a conditional statement.
592    pub fn unwrap_conditional(self) -> ConditionalStatement {
593        match self {
594            Self::Conditional(stmt) => stmt,
595            _ => panic!("not a conditional statement"),
596        }
597    }
598
599    /// Attempts to get a reference to the inner [`ScatterStatement`].
600    ///
601    /// * If `self` is a [`WorkflowStatement::Scatter`], then a reference to the
602    ///   inner [`ScatterStatement`] is returned wrapped in [`Some`].
603    /// * Else, [`None`] is returned.
604    pub fn as_scatter(&self) -> Option<&ScatterStatement> {
605        match self {
606            Self::Scatter(scatter) => Some(scatter),
607            _ => None,
608        }
609    }
610
611    /// Consumes `self` and attempts to return the inner
612    /// [`ScatterStatement`].
613    ///
614    /// * If `self` is a [`WorkflowStatement::Scatter`], then the inner
615    ///   [`ScatterStatement`] is returned wrapped in [`Some`].
616    /// * Else, [`None`] is returned.
617    pub fn into_scatter(self) -> Option<ScatterStatement> {
618        match self {
619            Self::Scatter(scatter) => Some(scatter),
620            _ => None,
621        }
622    }
623
624    /// Unwraps the statement into a scatter statement.
625    ///
626    /// # Panics
627    ///
628    /// Panics if the statement is not a scatter statement.
629    pub fn unwrap_scatter(self) -> ScatterStatement {
630        match self {
631            Self::Scatter(stmt) => stmt,
632            _ => panic!("not a scatter statement"),
633        }
634    }
635
636    /// Attempts to get a reference to the inner [`CallStatement`].
637    ///
638    /// * If `self` is a [`WorkflowStatement::Call`], then a reference to the
639    ///   inner [`CallStatement`] is returned wrapped in [`Some`].
640    /// * Else, [`None`] is returned.
641    pub fn as_call(&self) -> Option<&CallStatement> {
642        match self {
643            Self::Call(call) => Some(call),
644            _ => None,
645        }
646    }
647
648    /// Consumes `self` and attempts to return the inner
649    /// [`CallStatement`].
650    ///
651    /// * If `self` is a [`WorkflowStatement::Call`], then the inner
652    ///   [`CallStatement`] is returned wrapped in [`Some`].
653    /// * Else, [`None`] is returned.
654    pub fn into_call(self) -> Option<CallStatement> {
655        match self {
656            Self::Call(call) => Some(call),
657            _ => None,
658        }
659    }
660
661    /// Unwraps the statement into a call statement.
662    ///
663    /// # Panics
664    ///
665    /// Panics if the statement is not a call statement.
666    pub fn unwrap_call(self) -> CallStatement {
667        match self {
668            Self::Call(stmt) => stmt,
669            _ => panic!("not a call statement"),
670        }
671    }
672
673    /// Attempts to get a reference to the inner [`BoundDecl`].
674    ///
675    /// * If `self` is a [`WorkflowStatement::Declaration`], then a reference to
676    ///   the inner [`BoundDecl`] is returned wrapped in [`Some`].
677    /// * Else, [`None`] is returned.
678    pub fn as_declaration(&self) -> Option<&BoundDecl> {
679        match self {
680            Self::Declaration(declaration) => Some(declaration),
681            _ => None,
682        }
683    }
684
685    /// Consumes `self` and attempts to return the inner
686    /// [`BoundDecl`].
687    ///
688    /// * If `self` is a [`WorkflowStatement::Declaration`], then the inner
689    ///   [`BoundDecl`] is returned wrapped in [`Some`].
690    /// * Else, [`None`] is returned.
691    pub fn into_declaration(self) -> Option<BoundDecl> {
692        match self {
693            Self::Declaration(declaration) => Some(declaration),
694            _ => None,
695        }
696    }
697
698    /// Unwraps the statement into a bound declaration.
699    ///
700    /// # Panics
701    ///
702    /// Panics if the statement is not a bound declaration.
703    pub fn unwrap_declaration(self) -> BoundDecl {
704        match self {
705            Self::Declaration(declaration) => declaration,
706            _ => panic!("not a bound declaration"),
707        }
708    }
709
710    /// Finds the first child that can be cast to an [`WorkflowStatement`].
711    ///
712    /// This is meant to emulate the functionality of
713    /// [`rowan::ast::support::child`] without requiring [`WorkflowStatement`]
714    /// to implement the `AstNode` trait.
715    pub fn child(syntax: &SyntaxNode) -> Option<Self> {
716        syntax.children().find_map(Self::cast)
717    }
718
719    /// Finds all children that can be cast to an [`WorkflowStatement`].
720    ///
721    /// This is meant to emulate the functionality of
722    /// [`rowan::ast::support::children`] without requiring
723    /// [`WorkflowStatement`] to implement the `AstNode` trait.
724    pub fn children(syntax: &SyntaxNode) -> impl Iterator<Item = WorkflowStatement> + use<> {
725        syntax.children().filter_map(Self::cast)
726    }
727}
728
729/// Represents a workflow conditional statement.
730#[derive(Clone, Debug, PartialEq, Eq)]
731pub struct ConditionalStatement(pub(crate) SyntaxNode);
732
733impl ConditionalStatement {
734    /// Gets the expression of the conditional statement
735    pub fn expr(&self) -> Expr {
736        Expr::child(&self.0).expect("expected a conditional expression")
737    }
738
739    /// Gets the statements of the conditional body.
740    pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement> + use<> {
741        WorkflowStatement::children(&self.0)
742    }
743}
744
745impl AstNode for ConditionalStatement {
746    type Language = WorkflowDescriptionLanguage;
747
748    fn can_cast(kind: SyntaxKind) -> bool
749    where
750        Self: Sized,
751    {
752        kind == SyntaxKind::ConditionalStatementNode
753    }
754
755    fn cast(syntax: SyntaxNode) -> Option<Self>
756    where
757        Self: Sized,
758    {
759        match syntax.kind() {
760            SyntaxKind::ConditionalStatementNode => Some(Self(syntax)),
761            _ => None,
762        }
763    }
764
765    fn syntax(&self) -> &SyntaxNode {
766        &self.0
767    }
768}
769
770/// Represents a workflow scatter statement.
771#[derive(Clone, Debug, PartialEq, Eq)]
772pub struct ScatterStatement(pub(crate) SyntaxNode);
773
774impl ScatterStatement {
775    /// Gets the scatter variable identifier.
776    pub fn variable(&self) -> Ident {
777        token(&self.0).expect("expected a scatter variable identifier")
778    }
779
780    /// Gets the scatter expression.
781    pub fn expr(&self) -> Expr {
782        Expr::child(&self.0).expect("expected a scatter expression")
783    }
784
785    /// Gets the statements of the scatter body.
786    pub fn statements(&self) -> impl Iterator<Item = WorkflowStatement> + use<> {
787        WorkflowStatement::children(&self.0)
788    }
789}
790
791impl AstNode for ScatterStatement {
792    type Language = WorkflowDescriptionLanguage;
793
794    fn can_cast(kind: SyntaxKind) -> bool
795    where
796        Self: Sized,
797    {
798        kind == SyntaxKind::ScatterStatementNode
799    }
800
801    fn cast(syntax: SyntaxNode) -> Option<Self>
802    where
803        Self: Sized,
804    {
805        match syntax.kind() {
806            SyntaxKind::ScatterStatementNode => Some(Self(syntax)),
807            _ => None,
808        }
809    }
810
811    fn syntax(&self) -> &SyntaxNode {
812        &self.0
813    }
814}
815
816/// Represents a workflow call statement.
817#[derive(Clone, Debug, PartialEq, Eq)]
818pub struct CallStatement(pub(crate) SyntaxNode);
819
820impl CallStatement {
821    /// Gets the target of the call.
822    pub fn target(&self) -> CallTarget {
823        child(&self.0).expect("expected a call target")
824    }
825
826    /// Gets the optional alias for the call.
827    pub fn alias(&self) -> Option<CallAlias> {
828        child(&self.0)
829    }
830
831    /// Gets the after clauses for the call statement.
832    pub fn after(&self) -> AstChildren<CallAfter> {
833        children(&self.0)
834    }
835
836    /// Gets the inputs for the call statement.
837    pub fn inputs(&self) -> AstChildren<CallInputItem> {
838        children(&self.0)
839    }
840}
841
842impl AstNode for CallStatement {
843    type Language = WorkflowDescriptionLanguage;
844
845    fn can_cast(kind: SyntaxKind) -> bool
846    where
847        Self: Sized,
848    {
849        kind == SyntaxKind::CallStatementNode
850    }
851
852    fn cast(syntax: SyntaxNode) -> Option<Self>
853    where
854        Self: Sized,
855    {
856        match syntax.kind() {
857            SyntaxKind::CallStatementNode => Some(Self(syntax)),
858            _ => None,
859        }
860    }
861
862    fn syntax(&self) -> &SyntaxNode {
863        &self.0
864    }
865}
866
867/// Represents a target in a call statement.
868#[derive(Clone, Debug, PartialEq, Eq)]
869pub struct CallTarget(SyntaxNode);
870
871impl CallTarget {
872    /// Gets an iterator of the names of the call target.
873    ///
874    /// The last name in the iteration is considered to be the task or workflow
875    /// being called.
876    pub fn names(&self) -> impl Iterator<Item = Ident> + use<> {
877        self.0
878            .children_with_tokens()
879            .filter_map(SyntaxElement::into_token)
880            .filter_map(Ident::cast)
881    }
882}
883
884impl AstNode for CallTarget {
885    type Language = WorkflowDescriptionLanguage;
886
887    fn can_cast(kind: SyntaxKind) -> bool
888    where
889        Self: Sized,
890    {
891        kind == SyntaxKind::CallTargetNode
892    }
893
894    fn cast(syntax: SyntaxNode) -> Option<Self>
895    where
896        Self: Sized,
897    {
898        match syntax.kind() {
899            SyntaxKind::CallTargetNode => Some(Self(syntax)),
900            _ => None,
901        }
902    }
903
904    fn syntax(&self) -> &SyntaxNode {
905        &self.0
906    }
907}
908
909/// Represents an alias in a call statement.
910#[derive(Clone, Debug, PartialEq, Eq)]
911pub struct CallAlias(SyntaxNode);
912
913impl CallAlias {
914    /// Gets the alias name.
915    pub fn name(&self) -> Ident {
916        token(&self.0).expect("expected a alias identifier")
917    }
918}
919
920impl AstNode for CallAlias {
921    type Language = WorkflowDescriptionLanguage;
922
923    fn can_cast(kind: SyntaxKind) -> bool
924    where
925        Self: Sized,
926    {
927        kind == SyntaxKind::CallAliasNode
928    }
929
930    fn cast(syntax: SyntaxNode) -> Option<Self>
931    where
932        Self: Sized,
933    {
934        match syntax.kind() {
935            SyntaxKind::CallAliasNode => Some(Self(syntax)),
936            _ => None,
937        }
938    }
939
940    fn syntax(&self) -> &SyntaxNode {
941        &self.0
942    }
943}
944
945/// Represents an after clause in a call statement.
946#[derive(Clone, Debug, PartialEq, Eq)]
947pub struct CallAfter(SyntaxNode);
948
949impl CallAfter {
950    /// Gets the name from the `after` clause.
951    pub fn name(&self) -> Ident {
952        token(&self.0).expect("expected an after identifier")
953    }
954}
955
956impl AstNode for CallAfter {
957    type Language = WorkflowDescriptionLanguage;
958
959    fn can_cast(kind: SyntaxKind) -> bool
960    where
961        Self: Sized,
962    {
963        kind == SyntaxKind::CallAfterNode
964    }
965
966    fn cast(syntax: SyntaxNode) -> Option<Self>
967    where
968        Self: Sized,
969    {
970        match syntax.kind() {
971            SyntaxKind::CallAfterNode => Some(Self(syntax)),
972            _ => None,
973        }
974    }
975
976    fn syntax(&self) -> &SyntaxNode {
977        &self.0
978    }
979}
980
981/// Represents an input item in a call statement.
982#[derive(Clone, Debug, PartialEq, Eq)]
983pub struct CallInputItem(SyntaxNode);
984
985impl CallInputItem {
986    /// Gets the name of the input.
987    pub fn name(&self) -> Ident {
988        token(&self.0).expect("expected an input name")
989    }
990
991    /// The optional expression for the input.
992    pub fn expr(&self) -> Option<Expr> {
993        Expr::child(&self.0)
994    }
995
996    /// Gets the call statement for the call input item.
997    pub fn parent(&self) -> CallStatement {
998        CallStatement::cast(self.0.parent().expect("should have parent")).expect("node should cast")
999    }
1000}
1001
1002impl AstNode for CallInputItem {
1003    type Language = WorkflowDescriptionLanguage;
1004
1005    fn can_cast(kind: SyntaxKind) -> bool
1006    where
1007        Self: Sized,
1008    {
1009        kind == SyntaxKind::CallInputItemNode
1010    }
1011
1012    fn cast(syntax: SyntaxNode) -> Option<Self>
1013    where
1014        Self: Sized,
1015    {
1016        match syntax.kind() {
1017            SyntaxKind::CallInputItemNode => Some(Self(syntax)),
1018            _ => None,
1019        }
1020    }
1021
1022    fn syntax(&self) -> &SyntaxNode {
1023        &self.0
1024    }
1025}
1026
1027/// Represents a hints section in a workflow definition.
1028#[derive(Clone, Debug, PartialEq, Eq)]
1029pub struct WorkflowHintsSection(pub(crate) SyntaxNode);
1030
1031impl WorkflowHintsSection {
1032    /// Gets the items in the hints section.
1033    pub fn items(&self) -> AstChildren<WorkflowHintsItem> {
1034        children(&self.0)
1035    }
1036
1037    /// Gets the parent of the hints section.
1038    pub fn parent(&self) -> WorkflowDefinition {
1039        WorkflowDefinition::cast(self.0.parent().expect("should have a parent"))
1040            .expect("parent should cast")
1041    }
1042}
1043
1044impl AstNode for WorkflowHintsSection {
1045    type Language = WorkflowDescriptionLanguage;
1046
1047    fn can_cast(kind: SyntaxKind) -> bool
1048    where
1049        Self: Sized,
1050    {
1051        kind == SyntaxKind::WorkflowHintsSectionNode
1052    }
1053
1054    fn cast(syntax: SyntaxNode) -> Option<Self>
1055    where
1056        Self: Sized,
1057    {
1058        match syntax.kind() {
1059            SyntaxKind::WorkflowHintsSectionNode => Some(Self(syntax)),
1060            _ => None,
1061        }
1062    }
1063
1064    fn syntax(&self) -> &SyntaxNode {
1065        &self.0
1066    }
1067}
1068
1069/// Represents an item in a workflow hints section.
1070#[derive(Clone, Debug, PartialEq, Eq)]
1071pub struct WorkflowHintsItem(SyntaxNode);
1072
1073impl WorkflowHintsItem {
1074    /// Gets the name of the hints item.
1075    pub fn name(&self) -> Ident {
1076        token(&self.0).expect("expected an item name")
1077    }
1078
1079    /// Gets the value of the hints item.
1080    pub fn value(&self) -> WorkflowHintsItemValue {
1081        child(&self.0).expect("expected an item value")
1082    }
1083}
1084
1085impl AstNode for WorkflowHintsItem {
1086    type Language = WorkflowDescriptionLanguage;
1087
1088    fn can_cast(kind: SyntaxKind) -> bool
1089    where
1090        Self: Sized,
1091    {
1092        kind == SyntaxKind::WorkflowHintsItemNode
1093    }
1094
1095    fn cast(syntax: SyntaxNode) -> Option<Self>
1096    where
1097        Self: Sized,
1098    {
1099        match syntax.kind() {
1100            SyntaxKind::WorkflowHintsItemNode => Some(Self(syntax)),
1101            _ => None,
1102        }
1103    }
1104
1105    fn syntax(&self) -> &SyntaxNode {
1106        &self.0
1107    }
1108}
1109
1110/// Represents a workflow hints item value.
1111#[derive(Clone, Debug, PartialEq, Eq)]
1112pub enum WorkflowHintsItemValue {
1113    /// The value is a literal boolean.
1114    Boolean(LiteralBoolean),
1115    /// The value is a literal integer.
1116    Integer(LiteralInteger),
1117    /// The value is a literal float.
1118    Float(LiteralFloat),
1119    /// The value is a literal string.
1120    String(LiteralString),
1121    /// The value is a literal object.
1122    Object(WorkflowHintsObject),
1123    /// The value is a literal array.
1124    Array(WorkflowHintsArray),
1125}
1126
1127impl WorkflowHintsItemValue {
1128    /// Unwraps the value into a boolean.
1129    ///
1130    /// # Panics
1131    ///
1132    /// Panics if the value is not a boolean.
1133    pub fn unwrap_boolean(self) -> LiteralBoolean {
1134        match self {
1135            Self::Boolean(b) => b,
1136            _ => panic!("not a boolean"),
1137        }
1138    }
1139
1140    /// Unwraps the value into an integer.
1141    ///
1142    /// # Panics
1143    ///
1144    /// Panics if the value is not an integer.
1145    pub fn unwrap_integer(self) -> LiteralInteger {
1146        match self {
1147            Self::Integer(i) => i,
1148            _ => panic!("not an integer"),
1149        }
1150    }
1151
1152    /// Unwraps the value into a float.
1153    ///
1154    /// # Panics
1155    ///
1156    /// Panics if the value is not a float.
1157    pub fn unwrap_float(self) -> LiteralFloat {
1158        match self {
1159            Self::Float(f) => f,
1160            _ => panic!("not a float"),
1161        }
1162    }
1163
1164    /// Unwraps the value into a string.
1165    ///
1166    /// # Panics
1167    ///
1168    /// Panics if the value is not a string.
1169    pub fn unwrap_string(self) -> LiteralString {
1170        match self {
1171            Self::String(s) => s,
1172            _ => panic!("not a string"),
1173        }
1174    }
1175
1176    /// Unwraps the value into an object.
1177    ///
1178    /// # Panics
1179    ///
1180    /// Panics if the value is not an object.
1181    pub fn unwrap_object(self) -> WorkflowHintsObject {
1182        match self {
1183            Self::Object(o) => o,
1184            _ => panic!("not an object"),
1185        }
1186    }
1187
1188    /// Unwraps the value into an array.
1189    ///
1190    /// # Panics
1191    ///
1192    /// Panics if the value is not an array.
1193    pub fn unwrap_array(self) -> WorkflowHintsArray {
1194        match self {
1195            Self::Array(a) => a,
1196            _ => panic!("not an array"),
1197        }
1198    }
1199}
1200
1201impl AstNode for WorkflowHintsItemValue {
1202    type Language = WorkflowDescriptionLanguage;
1203
1204    fn can_cast(kind: SyntaxKind) -> bool
1205    where
1206        Self: Sized,
1207    {
1208        matches!(
1209            kind,
1210            SyntaxKind::LiteralBooleanNode
1211                | SyntaxKind::LiteralIntegerNode
1212                | SyntaxKind::LiteralFloatNode
1213                | SyntaxKind::LiteralStringNode
1214                | SyntaxKind::WorkflowHintsObjectNode
1215                | SyntaxKind::WorkflowHintsArrayNode
1216        )
1217    }
1218
1219    fn cast(syntax: SyntaxNode) -> Option<Self>
1220    where
1221        Self: Sized,
1222    {
1223        match syntax.kind() {
1224            SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(syntax))),
1225            SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(syntax))),
1226            SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(syntax))),
1227            SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(syntax))),
1228            SyntaxKind::WorkflowHintsObjectNode => Some(Self::Object(WorkflowHintsObject(syntax))),
1229            SyntaxKind::WorkflowHintsArrayNode => Some(Self::Array(WorkflowHintsArray(syntax))),
1230            _ => None,
1231        }
1232    }
1233
1234    fn syntax(&self) -> &SyntaxNode {
1235        match self {
1236            Self::Boolean(b) => &b.0,
1237            Self::Integer(i) => &i.0,
1238            Self::Float(f) => &f.0,
1239            Self::String(s) => &s.0,
1240            Self::Object(o) => &o.0,
1241            Self::Array(a) => &a.0,
1242        }
1243    }
1244}
1245
1246/// Represents a workflow hints object.
1247#[derive(Clone, Debug, PartialEq, Eq)]
1248pub struct WorkflowHintsObject(pub(crate) SyntaxNode);
1249
1250impl WorkflowHintsObject {
1251    /// Gets the items of the workflow hints object.
1252    pub fn items(&self) -> AstChildren<WorkflowHintsObjectItem> {
1253        children(&self.0)
1254    }
1255}
1256
1257impl AstNode for WorkflowHintsObject {
1258    type Language = WorkflowDescriptionLanguage;
1259
1260    fn can_cast(kind: SyntaxKind) -> bool
1261    where
1262        Self: Sized,
1263    {
1264        kind == SyntaxKind::WorkflowHintsObjectNode
1265    }
1266
1267    fn cast(syntax: SyntaxNode) -> Option<Self>
1268    where
1269        Self: Sized,
1270    {
1271        match syntax.kind() {
1272            SyntaxKind::WorkflowHintsObjectNode => Some(Self(syntax)),
1273            _ => None,
1274        }
1275    }
1276
1277    fn syntax(&self) -> &SyntaxNode {
1278        &self.0
1279    }
1280}
1281
1282/// Represents a workflow hints object item.
1283#[derive(Clone, Debug, PartialEq, Eq)]
1284pub struct WorkflowHintsObjectItem(pub(crate) SyntaxNode);
1285
1286impl WorkflowHintsObjectItem {
1287    /// Gets the name of the item.
1288    pub fn name(&self) -> Ident {
1289        token(&self.0).expect("expected a name")
1290    }
1291
1292    /// Gets the value of the item.
1293    pub fn value(&self) -> WorkflowHintsItemValue {
1294        child(&self.0).expect("expected a value")
1295    }
1296}
1297
1298impl AstNode for WorkflowHintsObjectItem {
1299    type Language = WorkflowDescriptionLanguage;
1300
1301    fn can_cast(kind: SyntaxKind) -> bool
1302    where
1303        Self: Sized,
1304    {
1305        kind == SyntaxKind::WorkflowHintsObjectItemNode
1306    }
1307
1308    fn cast(syntax: SyntaxNode) -> Option<Self>
1309    where
1310        Self: Sized,
1311    {
1312        match syntax.kind() {
1313            SyntaxKind::WorkflowHintsObjectItemNode => Some(Self(syntax)),
1314            _ => None,
1315        }
1316    }
1317
1318    fn syntax(&self) -> &SyntaxNode {
1319        &self.0
1320    }
1321}
1322
1323/// Represents a workflow hints array.
1324#[derive(Clone, Debug, PartialEq, Eq)]
1325pub struct WorkflowHintsArray(pub(crate) SyntaxNode);
1326
1327impl WorkflowHintsArray {
1328    /// Gets the elements of the workflow hints array.
1329    pub fn elements(&self) -> AstChildren<WorkflowHintsItemValue> {
1330        children(&self.0)
1331    }
1332}
1333
1334impl AstNode for WorkflowHintsArray {
1335    type Language = WorkflowDescriptionLanguage;
1336
1337    fn can_cast(kind: SyntaxKind) -> bool
1338    where
1339        Self: Sized,
1340    {
1341        kind == SyntaxKind::WorkflowHintsArrayNode
1342    }
1343
1344    fn cast(syntax: SyntaxNode) -> Option<Self>
1345    where
1346        Self: Sized,
1347    {
1348        match syntax.kind() {
1349            SyntaxKind::WorkflowHintsArrayNode => Some(Self(syntax)),
1350            _ => None,
1351        }
1352    }
1353
1354    fn syntax(&self) -> &SyntaxNode {
1355        &self.0
1356    }
1357}
1358
1359#[cfg(test)]
1360mod test {
1361    use super::*;
1362    use crate::Document;
1363    use crate::SupportedVersion;
1364    use crate::VisitReason;
1365    use crate::Visitor;
1366    use crate::v1::UnboundDecl;
1367
1368    #[test]
1369    fn workflows() {
1370        let (document, diagnostics) = Document::parse(
1371            r#"
1372version 1.1
1373
1374workflow test {
1375    input {
1376        String name
1377        Boolean do_thing
1378    }
1379
1380    output {
1381        String output = "hello, ~{name}!"
1382    }
1383
1384    if (do_thing) {
1385        call foo.my_task
1386
1387        scatter (a in [1, 2, 3]) {
1388            call my_task as my_task2 { input: a }
1389        }
1390    }
1391
1392    call my_task as my_task3 after my_task2 after my_task { input: a = 1 }
1393
1394    scatter (a in ["1", "2", "3"]) {
1395        # Do nothing
1396    }
1397
1398    meta {
1399        description: "a test"
1400        foo: null
1401    }
1402
1403    parameter_meta {
1404        name: {
1405            help: "a name to greet"
1406        }
1407    }
1408
1409    hints {
1410        foo: "bar"
1411    }
1412
1413    String x = "private"
1414}
1415"#,
1416        );
1417
1418        assert!(diagnostics.is_empty());
1419        let ast = document.ast();
1420        let ast = ast.as_v1().expect("should be a V1 AST");
1421        let workflows: Vec<_> = ast.workflows().collect();
1422        assert_eq!(workflows.len(), 1);
1423        assert_eq!(workflows[0].name().as_str(), "test");
1424
1425        // Workflow inputs
1426        let input = workflows[0]
1427            .input()
1428            .expect("workflow should have an input section");
1429        assert_eq!(input.parent().unwrap_workflow().name().as_str(), "test");
1430        let decls: Vec<_> = input.declarations().collect();
1431        assert_eq!(decls.len(), 2);
1432
1433        // First declaration
1434        assert_eq!(
1435            decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1436            "String"
1437        );
1438        assert_eq!(
1439            decls[0].clone().unwrap_unbound_decl().name().as_str(),
1440            "name"
1441        );
1442
1443        // Second declaration
1444        assert_eq!(
1445            decls[1].clone().unwrap_unbound_decl().ty().to_string(),
1446            "Boolean"
1447        );
1448        assert_eq!(
1449            decls[1].clone().unwrap_unbound_decl().name().as_str(),
1450            "do_thing"
1451        );
1452
1453        // Workflow outputs
1454        let output = workflows[0]
1455            .output()
1456            .expect("workflow should have an output section");
1457        assert_eq!(output.parent().unwrap_workflow().name().as_str(), "test");
1458        let decls: Vec<_> = output.declarations().collect();
1459        assert_eq!(decls.len(), 1);
1460
1461        // First declaration
1462        assert_eq!(decls[0].ty().to_string(), "String");
1463        assert_eq!(decls[0].name().as_str(), "output");
1464        let parts: Vec<_> = decls[0]
1465            .expr()
1466            .unwrap_literal()
1467            .unwrap_string()
1468            .parts()
1469            .collect();
1470        assert_eq!(parts.len(), 3);
1471        assert_eq!(parts[0].clone().unwrap_text().as_str(), "hello, ");
1472        assert_eq!(
1473            parts[1]
1474                .clone()
1475                .unwrap_placeholder()
1476                .expr()
1477                .unwrap_name_ref()
1478                .name()
1479                .as_str(),
1480            "name"
1481        );
1482        assert_eq!(parts[2].clone().unwrap_text().as_str(), "!");
1483
1484        // Workflow statements
1485        let statements: Vec<_> = workflows[0].statements().collect();
1486        assert_eq!(statements.len(), 4);
1487
1488        // First workflow statement
1489        let conditional = statements[0].clone().unwrap_conditional();
1490        assert_eq!(
1491            conditional.expr().unwrap_name_ref().name().as_str(),
1492            "do_thing"
1493        );
1494
1495        // Inner statements
1496        let inner: Vec<_> = conditional.statements().collect();
1497        assert_eq!(inner.len(), 2);
1498
1499        // First inner statement
1500        let call = inner[0].clone().unwrap_call();
1501        let names = call.target().names().collect::<Vec<_>>();
1502        assert_eq!(names.len(), 2);
1503        assert_eq!(names[0].as_str(), "foo");
1504        assert_eq!(names[1].as_str(), "my_task");
1505        assert!(call.alias().is_none());
1506        assert_eq!(call.after().count(), 0);
1507        assert_eq!(call.inputs().count(), 0);
1508
1509        // Second inner statement
1510        let scatter = inner[1].clone().unwrap_scatter();
1511        assert_eq!(scatter.variable().as_str(), "a");
1512        let elements: Vec<_> = scatter
1513            .expr()
1514            .unwrap_literal()
1515            .unwrap_array()
1516            .elements()
1517            .collect();
1518        assert_eq!(elements.len(), 3);
1519        assert_eq!(
1520            elements[0]
1521                .clone()
1522                .unwrap_literal()
1523                .unwrap_integer()
1524                .value()
1525                .unwrap(),
1526            1
1527        );
1528        assert_eq!(
1529            elements[1]
1530                .clone()
1531                .unwrap_literal()
1532                .unwrap_integer()
1533                .value()
1534                .unwrap(),
1535            2
1536        );
1537        assert_eq!(
1538            elements[2]
1539                .clone()
1540                .unwrap_literal()
1541                .unwrap_integer()
1542                .value()
1543                .unwrap(),
1544            3
1545        );
1546
1547        // Inner statements
1548        let inner: Vec<_> = scatter.statements().collect();
1549        assert_eq!(inner.len(), 1);
1550
1551        // First inner statement
1552        let call = inner[0].clone().unwrap_call();
1553        let names = call.target().names().collect::<Vec<_>>();
1554        assert_eq!(names.len(), 1);
1555        assert_eq!(names[0].as_str(), "my_task");
1556        assert_eq!(call.alias().unwrap().name().as_str(), "my_task2");
1557        assert_eq!(call.after().count(), 0);
1558        let inputs: Vec<_> = call.inputs().collect();
1559        assert_eq!(inputs.len(), 1);
1560        assert_eq!(inputs[0].name().as_str(), "a");
1561        assert!(inputs[0].expr().is_none());
1562
1563        // Second workflow statement
1564        let call = statements[1].clone().unwrap_call();
1565        assert_eq!(names.len(), 1);
1566        assert_eq!(names[0].as_str(), "my_task");
1567        assert_eq!(call.alias().unwrap().name().as_str(), "my_task3");
1568        let after: Vec<_> = call.after().collect();
1569        assert_eq!(after.len(), 2);
1570        assert_eq!(after[0].name().as_str(), "my_task2");
1571        assert_eq!(after[1].name().as_str(), "my_task");
1572        let inputs: Vec<_> = call.inputs().collect();
1573        assert_eq!(inputs.len(), 1);
1574        assert_eq!(inputs[0].name().as_str(), "a");
1575        assert_eq!(
1576            inputs[0]
1577                .expr()
1578                .unwrap()
1579                .unwrap_literal()
1580                .unwrap_integer()
1581                .value()
1582                .unwrap(),
1583            1
1584        );
1585
1586        // Third workflow statement
1587        let scatter = statements[2].clone().unwrap_scatter();
1588        assert_eq!(scatter.variable().as_str(), "a");
1589        let elements: Vec<_> = scatter
1590            .expr()
1591            .unwrap_literal()
1592            .unwrap_array()
1593            .elements()
1594            .collect();
1595        assert_eq!(elements.len(), 3);
1596        assert_eq!(
1597            elements[0]
1598                .clone()
1599                .unwrap_literal()
1600                .unwrap_string()
1601                .text()
1602                .unwrap()
1603                .as_str(),
1604            "1"
1605        );
1606        assert_eq!(
1607            elements[1]
1608                .clone()
1609                .unwrap_literal()
1610                .unwrap_string()
1611                .text()
1612                .unwrap()
1613                .as_str(),
1614            "2"
1615        );
1616        assert_eq!(
1617            elements[2]
1618                .clone()
1619                .unwrap_literal()
1620                .unwrap_string()
1621                .text()
1622                .unwrap()
1623                .as_str(),
1624            "3"
1625        );
1626
1627        // Inner statements
1628        let inner: Vec<_> = scatter.statements().collect();
1629        assert_eq!(inner.len(), 0);
1630
1631        // Workflow metadata
1632        let metadata = workflows[0]
1633            .metadata()
1634            .expect("workflow should have a metadata section");
1635        assert_eq!(metadata.parent().unwrap_workflow().name().as_str(), "test");
1636        let items: Vec<_> = metadata.items().collect();
1637        assert_eq!(items.len(), 2);
1638        assert_eq!(items[0].name().as_str(), "description");
1639        assert_eq!(
1640            items[0].value().unwrap_string().text().unwrap().as_str(),
1641            "a test"
1642        );
1643        assert_eq!(items[1].name().as_str(), "foo");
1644        items[1].value().unwrap_null();
1645
1646        // Workflow parameter metadata
1647        let param_meta = workflows[0]
1648            .parameter_metadata()
1649            .expect("workflow should have a parameter metadata section");
1650        assert_eq!(
1651            param_meta.parent().unwrap_workflow().name().as_str(),
1652            "test"
1653        );
1654        let items: Vec<_> = param_meta.items().collect();
1655        assert_eq!(items.len(), 1);
1656        assert_eq!(items[0].name().as_str(), "name");
1657        let items: Vec<_> = items[0].value().unwrap_object().items().collect();
1658        assert_eq!(items.len(), 1);
1659        assert_eq!(items[0].name().as_str(), "help");
1660        assert_eq!(
1661            items[0].value().unwrap_string().text().unwrap().as_str(),
1662            "a name to greet"
1663        );
1664
1665        // Workflow hints
1666        let hints = workflows[0]
1667            .hints()
1668            .expect("workflow should have a hints section");
1669        assert_eq!(hints.parent().name().as_str(), "test");
1670        let items: Vec<_> = hints.items().collect();
1671        assert_eq!(items.len(), 1);
1672        assert_eq!(items[0].name().as_str(), "foo");
1673        assert_eq!(
1674            items[0].value().unwrap_string().text().unwrap().as_str(),
1675            "bar"
1676        );
1677
1678        // Workflow declarations
1679        let decls: Vec<_> = workflows[0].declarations().collect();
1680        assert_eq!(decls.len(), 1);
1681
1682        // First declaration
1683        assert_eq!(decls[0].ty().to_string(), "String");
1684        assert_eq!(decls[0].name().as_str(), "x");
1685        assert_eq!(
1686            decls[0]
1687                .expr()
1688                .unwrap_literal()
1689                .unwrap_string()
1690                .text()
1691                .unwrap()
1692                .as_str(),
1693            "private"
1694        );
1695
1696        #[derive(Default)]
1697        struct MyVisitor {
1698            workflows: usize,
1699            inputs: usize,
1700            outputs: usize,
1701            conditionals: usize,
1702            scatters: usize,
1703            calls: usize,
1704            metadata: usize,
1705            param_metadata: usize,
1706            unbound_decls: usize,
1707            bound_decls: usize,
1708        }
1709
1710        impl Visitor for MyVisitor {
1711            type State = ();
1712
1713            fn document(
1714                &mut self,
1715                _: &mut Self::State,
1716                _: VisitReason,
1717                _: &Document,
1718                _: SupportedVersion,
1719            ) {
1720            }
1721
1722            fn workflow_definition(
1723                &mut self,
1724                _: &mut Self::State,
1725                reason: VisitReason,
1726                _: &WorkflowDefinition,
1727            ) {
1728                if reason == VisitReason::Enter {
1729                    self.workflows += 1;
1730                }
1731            }
1732
1733            fn input_section(
1734                &mut self,
1735                _: &mut Self::State,
1736                reason: VisitReason,
1737                _: &InputSection,
1738            ) {
1739                if reason == VisitReason::Enter {
1740                    self.inputs += 1;
1741                }
1742            }
1743
1744            fn output_section(
1745                &mut self,
1746                _: &mut Self::State,
1747                reason: VisitReason,
1748                _: &OutputSection,
1749            ) {
1750                if reason == VisitReason::Enter {
1751                    self.outputs += 1;
1752                }
1753            }
1754
1755            fn conditional_statement(
1756                &mut self,
1757                _: &mut Self::State,
1758                reason: VisitReason,
1759                _: &ConditionalStatement,
1760            ) {
1761                if reason == VisitReason::Enter {
1762                    self.conditionals += 1;
1763                }
1764            }
1765
1766            fn scatter_statement(
1767                &mut self,
1768                _: &mut Self::State,
1769                reason: VisitReason,
1770                _: &ScatterStatement,
1771            ) {
1772                if reason == VisitReason::Enter {
1773                    self.scatters += 1;
1774                }
1775            }
1776
1777            fn call_statement(
1778                &mut self,
1779                _: &mut Self::State,
1780                reason: VisitReason,
1781                _: &CallStatement,
1782            ) {
1783                if reason == VisitReason::Enter {
1784                    self.calls += 1;
1785                }
1786            }
1787
1788            fn metadata_section(
1789                &mut self,
1790                _: &mut Self::State,
1791                reason: VisitReason,
1792                _: &MetadataSection,
1793            ) {
1794                if reason == VisitReason::Enter {
1795                    self.metadata += 1;
1796                }
1797            }
1798
1799            fn parameter_metadata_section(
1800                &mut self,
1801                _: &mut Self::State,
1802                reason: VisitReason,
1803                _: &ParameterMetadataSection,
1804            ) {
1805                if reason == VisitReason::Enter {
1806                    self.param_metadata += 1;
1807                }
1808            }
1809
1810            fn bound_decl(&mut self, _: &mut Self::State, reason: VisitReason, _: &BoundDecl) {
1811                if reason == VisitReason::Enter {
1812                    self.bound_decls += 1;
1813                }
1814            }
1815
1816            fn unbound_decl(&mut self, _: &mut Self::State, reason: VisitReason, _: &UnboundDecl) {
1817                if reason == VisitReason::Enter {
1818                    self.unbound_decls += 1;
1819                }
1820            }
1821        }
1822
1823        let mut visitor = MyVisitor::default();
1824        document.visit(&mut (), &mut visitor);
1825        assert_eq!(visitor.workflows, 1);
1826        assert_eq!(visitor.inputs, 1);
1827        assert_eq!(visitor.outputs, 1);
1828        assert_eq!(visitor.conditionals, 1);
1829        assert_eq!(visitor.scatters, 2);
1830        assert_eq!(visitor.calls, 3);
1831        assert_eq!(visitor.metadata, 1);
1832        assert_eq!(visitor.param_metadata, 1);
1833        assert_eq!(visitor.unbound_decls, 2);
1834        assert_eq!(visitor.bound_decls, 2);
1835    }
1836}