Skip to main content

wdl_engine/
value.rs

1//! Implementation of the WDL runtime and values.
2
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::fmt;
6use std::hash::Hash;
7use std::hash::Hasher;
8use std::path::Path;
9use std::path::PathBuf;
10use std::sync::Arc;
11use std::sync::LazyLock;
12
13use anyhow::Context;
14use anyhow::Result;
15use anyhow::anyhow;
16use anyhow::bail;
17use futures::FutureExt;
18use futures::StreamExt as _;
19use futures::TryStreamExt as _;
20use futures::future::BoxFuture;
21use indexmap::IndexMap;
22use ordered_float::OrderedFloat;
23use serde::ser::SerializeMap;
24use serde::ser::SerializeSeq;
25use url::Url;
26use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
27use wdl_analysis::types::ArrayType;
28use wdl_analysis::types::CallType;
29use wdl_analysis::types::Coercible as _;
30use wdl_analysis::types::CompoundType;
31use wdl_analysis::types::CustomType;
32use wdl_analysis::types::EnumType;
33use wdl_analysis::types::HiddenType;
34use wdl_analysis::types::MapType;
35use wdl_analysis::types::Optional;
36use wdl_analysis::types::PairType;
37use wdl_analysis::types::PrimitiveType;
38use wdl_analysis::types::StructType;
39use wdl_analysis::types::Type;
40use wdl_analysis::types::v1::task_member_type_post_evaluation;
41use wdl_ast::AstToken;
42use wdl_ast::SupportedVersion;
43use wdl_ast::TreeNode;
44use wdl_ast::v1;
45use wdl_ast::v1::TASK_FIELD_ATTEMPT;
46use wdl_ast::v1::TASK_FIELD_CONTAINER;
47use wdl_ast::v1::TASK_FIELD_CPU;
48use wdl_ast::v1::TASK_FIELD_DISKS;
49use wdl_ast::v1::TASK_FIELD_END_TIME;
50use wdl_ast::v1::TASK_FIELD_EXT;
51use wdl_ast::v1::TASK_FIELD_FPGA;
52use wdl_ast::v1::TASK_FIELD_GPU;
53use wdl_ast::v1::TASK_FIELD_ID;
54use wdl_ast::v1::TASK_FIELD_MAX_RETRIES;
55use wdl_ast::v1::TASK_FIELD_MEMORY;
56use wdl_ast::v1::TASK_FIELD_META;
57use wdl_ast::v1::TASK_FIELD_NAME;
58use wdl_ast::v1::TASK_FIELD_PARAMETER_META;
59use wdl_ast::v1::TASK_FIELD_PREVIOUS;
60use wdl_ast::v1::TASK_FIELD_RETURN_CODE;
61use wdl_ast::version::V1;
62
63use crate::EvaluationContext;
64use crate::EvaluationPath;
65use crate::Outputs;
66use crate::backend::TaskExecutionConstraints;
67use crate::http::Transferer;
68use crate::path;
69
70/// Represents a path to a file or directory on the host file system or a URL to
71/// a remote file.
72///
73/// The host in this context is where the WDL evaluation is taking place.
74#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
75pub struct HostPath(pub Arc<String>);
76
77impl HostPath {
78    /// Constructs a new host path from a string.
79    pub fn new(path: impl Into<String>) -> Self {
80        Self(Arc::new(path.into()))
81    }
82
83    /// Gets the string representation of the host path.
84    pub fn as_str(&self) -> &str {
85        &self.0
86    }
87
88    /// Shell-expands the path.
89    ///
90    /// The path is also joined with the provided base directory.
91    pub fn expand(&self, base_dir: &EvaluationPath) -> Result<Self> {
92        // Shell-expand both paths and URLs
93        let shell_expanded = shellexpand::full(self.as_str()).with_context(|| {
94            format!("failed to shell-expand path `{path}`", path = self.as_str())
95        })?;
96
97        // But don't join URLs
98        if path::is_supported_url(&shell_expanded) {
99            Ok(Self::new(shell_expanded))
100        } else {
101            // `join()` handles both relative and absolute paths
102            Ok(Self::new(base_dir.join(&shell_expanded)?.to_string()))
103        }
104    }
105
106    /// Determines if the host path is relative.
107    pub fn is_relative(&self) -> bool {
108        !path::is_supported_url(&self.0) && Path::new(self.0.as_str()).is_relative()
109    }
110}
111
112impl fmt::Display for HostPath {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        self.0.fmt(f)
115    }
116}
117
118/// Writes a string as the body of a double-quoted WDL literal.
119fn write_escaped_wdl_string(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
120    let mut chars = s.char_indices().peekable();
121    while let Some((_, c)) = chars.next() {
122        let next_is_brace = chars.peek().map(|(_, n)| *n == '{').unwrap_or(false);
123        match c {
124            '\\' => f.write_str(r"\\")?,
125            '\n' => f.write_str(r"\n")?,
126            '\r' => f.write_str(r"\r")?,
127            '\t' => f.write_str(r"\t")?,
128            '"' => f.write_str("\\\"")?,
129            '$' if next_is_brace => f.write_str(r"\$")?,
130            '~' if next_is_brace => f.write_str(r"\~")?,
131            c if c.is_control() => write!(f, "\\x{code:02X}", code = c as u32)?,
132            c => write!(f, "{c}")?,
133        }
134    }
135    Ok(())
136}
137
138impl From<Arc<String>> for HostPath {
139    fn from(path: Arc<String>) -> Self {
140        Self(path)
141    }
142}
143
144impl From<HostPath> for Arc<String> {
145    fn from(path: HostPath) -> Self {
146        path.0
147    }
148}
149
150impl From<String> for HostPath {
151    fn from(s: String) -> Self {
152        Arc::new(s).into()
153    }
154}
155
156impl<'a> From<&'a str> for HostPath {
157    fn from(s: &'a str) -> Self {
158        s.to_string().into()
159    }
160}
161
162impl From<url::Url> for HostPath {
163    fn from(url: url::Url) -> Self {
164        url.as_str().into()
165    }
166}
167
168impl From<HostPath> for PathBuf {
169    fn from(path: HostPath) -> Self {
170        PathBuf::from(path.0.as_str())
171    }
172}
173
174impl From<&HostPath> for PathBuf {
175    fn from(path: &HostPath) -> Self {
176        PathBuf::from(path.as_str())
177    }
178}
179
180/// Represents a path to a file or directory on the guest.
181///
182/// The guest in this context is the container where tasks are run.
183#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
184pub struct GuestPath(pub Arc<String>);
185
186impl GuestPath {
187    /// Constructs a new guest path from a string.
188    pub fn new(path: impl Into<String>) -> Self {
189        Self(Arc::new(path.into()))
190    }
191
192    /// Gets the string representation of the guest path.
193    pub fn as_str(&self) -> &str {
194        &self.0
195    }
196}
197
198impl fmt::Display for GuestPath {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        self.0.fmt(f)
201    }
202}
203
204impl From<Arc<String>> for GuestPath {
205    fn from(path: Arc<String>) -> Self {
206        Self(path)
207    }
208}
209
210impl From<GuestPath> for Arc<String> {
211    fn from(path: GuestPath) -> Self {
212        path.0
213    }
214}
215
216/// Implemented on coercible values.
217pub(crate) trait Coercible: Sized {
218    /// Coerces the value into the given type.
219    ///
220    /// If the provided evaluation context is `None`, host to guest and guest to
221    /// host translation is not performed; `File` and `Directory` values will
222    /// coerce directly to string.
223    ///
224    /// Returns an error if the coercion is not supported.
225    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self>;
226}
227
228/// Represents a none value with its associated optional type.
229///
230/// None values are cheap to clone.
231#[derive(Debug, Clone)]
232pub struct NoneValue(Arc<Type>);
233
234impl NoneValue {
235    /// Constructs a new `NoneValue` with the given type.
236    pub fn new(ty: Type) -> Self {
237        Self(Arc::new(ty))
238    }
239
240    /// Returns a cached [`NoneValue`] for `Type::None` (i.e., an untyped
241    /// none).
242    ///
243    /// This avoids repeated `Arc` allocations for the common case of
244    /// constructing sentinel none values (e.g., in function call argument
245    /// initialization).
246    pub fn untyped() -> Self {
247        static INSTANCE: LazyLock<NoneValue> = LazyLock::new(|| NoneValue::new(Type::None));
248        INSTANCE.clone()
249    }
250
251    /// Gets the type of the none value.
252    pub fn ty(&self) -> &Type {
253        &self.0
254    }
255}
256
257/// Represents a reference to a user-defined type name.
258///
259/// Type name reference values are cheap to clone.
260#[derive(Debug, Clone)]
261pub struct TypeNameRefValue(Arc<Type>);
262
263impl TypeNameRefValue {
264    /// Constructs a new `TypeNameRefValue` with the given type.
265    pub fn new(ty: Type) -> Self {
266        Self(Arc::new(ty))
267    }
268
269    /// Gets the referenced type.
270    pub fn ty(&self) -> &Type {
271        &self.0
272    }
273}
274
275/// Represents a WDL runtime value.
276///
277/// Values are cheap to clone.
278#[derive(Debug, Clone)]
279pub enum Value {
280    /// The value is a literal none value.
281    ///
282    /// The contained type is expected to be an optional type.
283    None(NoneValue),
284    /// The value is a primitive value.
285    Primitive(PrimitiveValue),
286    /// The value is a compound value.
287    Compound(CompoundValue),
288    /// The value is a hidden value.
289    ///
290    /// A hidden value is one that has a hidden (i.e. not expressible in WDL
291    /// source) type.
292    Hidden(HiddenValue),
293    /// The value is the outputs of a call.
294    Call(CallValue),
295    /// The value is a reference to a user-defined type.
296    TypeNameRef(TypeNameRefValue),
297}
298
299// NOTE: `Value` was optimized to `24` bytes in PR #727. Any attempts to raise
300// this limit should be carefully considered from a performance perspective.
301const _: () = {
302    assert!(std::mem::size_of::<Value>() <= 24);
303};
304
305impl Value {
306    /// Creates an object from an iterator of V1 AST metadata items.
307    ///
308    /// # Panics
309    ///
310    /// Panics if the metadata value contains an invalid numeric value.
311    pub fn from_v1_metadata<N: TreeNode>(value: &v1::MetadataValue<N>) -> Self {
312        match value {
313            v1::MetadataValue::Boolean(v) => v.value().into(),
314            v1::MetadataValue::Integer(v) => v.value().expect("number should be in range").into(),
315            v1::MetadataValue::Float(v) => v.value().expect("number should be in range").into(),
316            v1::MetadataValue::String(v) => PrimitiveValue::new_string(
317                v.text()
318                    .expect("metadata strings shouldn't have placeholders")
319                    .text(),
320            )
321            .into(),
322            v1::MetadataValue::Null(_) => Self::new_none(Type::None),
323            v1::MetadataValue::Object(o) => Object::from_v1_metadata(o.items()).into(),
324            v1::MetadataValue::Array(a) => Array::new_unchecked(
325                ANALYSIS_STDLIB.array_object_type().clone(),
326                a.elements().map(|v| Value::from_v1_metadata(&v)).collect(),
327            )
328            .into(),
329        }
330    }
331
332    /// Constructs a new none value with the given type.
333    ///
334    /// # Panics
335    ///
336    /// Panics if the provided type is not optional.
337    pub fn new_none(ty: Type) -> Self {
338        assert!(ty.is_optional(), "the provided `None` type is not optional");
339        Self::None(NoneValue::new(ty))
340    }
341
342    /// Gets the type of the value.
343    pub fn ty(&self) -> Type {
344        match self {
345            Self::None(v) => v.ty().clone(),
346            Self::Primitive(v) => v.ty(),
347            Self::Compound(v) => v.ty(),
348            Self::Hidden(v) => v.ty(),
349            Self::Call(v) => Type::Call(v.ty().clone()),
350            Self::TypeNameRef(v) => v.ty().clone(),
351        }
352    }
353
354    /// Determines if the value is none.
355    pub fn is_none(&self) -> bool {
356        matches!(self, Self::None(_))
357    }
358
359    /// Gets the value as a primitive value.
360    ///
361    /// Returns `None` if the value is not a primitive value.
362    pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
363        match self {
364            Self::Primitive(v) => Some(v),
365            _ => None,
366        }
367    }
368
369    /// Gets the value as a compound value.
370    ///
371    /// Returns `None` if the value is not a compound value.
372    pub fn as_compound(&self) -> Option<&CompoundValue> {
373        match self {
374            Self::Compound(v) => Some(v),
375            _ => None,
376        }
377    }
378
379    /// Gets the value as a `Boolean`.
380    ///
381    /// Returns `None` if the value is not a `Boolean`.
382    pub fn as_boolean(&self) -> Option<bool> {
383        match self {
384            Self::Primitive(PrimitiveValue::Boolean(v)) => Some(*v),
385            _ => None,
386        }
387    }
388
389    /// Unwraps the value into a `Boolean`.
390    ///
391    /// # Panics
392    ///
393    /// Panics if the value is not a `Boolean`.
394    pub fn unwrap_boolean(self) -> bool {
395        match self {
396            Self::Primitive(PrimitiveValue::Boolean(v)) => v,
397            _ => panic!("value is not a boolean"),
398        }
399    }
400
401    /// Gets the value as an `Int`.
402    ///
403    /// Returns `None` if the value is not an `Int`.
404    pub fn as_integer(&self) -> Option<i64> {
405        match self {
406            Self::Primitive(PrimitiveValue::Integer(v)) => Some(*v),
407            _ => None,
408        }
409    }
410
411    /// Unwraps the value into an integer.
412    ///
413    /// # Panics
414    ///
415    /// Panics if the value is not an integer.
416    pub fn unwrap_integer(self) -> i64 {
417        match self {
418            Self::Primitive(PrimitiveValue::Integer(v)) => v,
419            _ => panic!("value is not an integer"),
420        }
421    }
422
423    /// Gets the value as a `Float`.
424    ///
425    /// Returns `None` if the value is not a `Float`.
426    pub fn as_float(&self) -> Option<f64> {
427        match self {
428            Self::Primitive(PrimitiveValue::Float(v)) => Some((*v).into()),
429            _ => None,
430        }
431    }
432
433    /// Unwraps the value into a `Float`.
434    ///
435    /// # Panics
436    ///
437    /// Panics if the value is not a `Float`.
438    pub fn unwrap_float(self) -> f64 {
439        match self {
440            Self::Primitive(PrimitiveValue::Float(v)) => v.into(),
441            _ => panic!("value is not a float"),
442        }
443    }
444
445    /// Gets the value as a `String`.
446    ///
447    /// Returns `None` if the value is not a `String`.
448    pub fn as_string(&self) -> Option<&Arc<String>> {
449        match self {
450            Self::Primitive(PrimitiveValue::String(s)) => Some(s),
451            _ => None,
452        }
453    }
454
455    /// Unwraps the value into a `String`.
456    ///
457    /// # Panics
458    ///
459    /// Panics if the value is not a `String`.
460    pub fn unwrap_string(self) -> Arc<String> {
461        match self {
462            Self::Primitive(PrimitiveValue::String(s)) => s,
463            _ => panic!("value is not a string"),
464        }
465    }
466
467    /// Gets the value as a `File`.
468    ///
469    /// Returns `None` if the value is not a `File`.
470    pub fn as_file(&self) -> Option<&HostPath> {
471        match self {
472            Self::Primitive(PrimitiveValue::File(p)) => Some(p),
473            _ => None,
474        }
475    }
476
477    /// Unwraps the value into a `File`.
478    ///
479    /// # Panics
480    ///
481    /// Panics if the value is not a `File`.
482    pub fn unwrap_file(self) -> HostPath {
483        match self {
484            Self::Primitive(PrimitiveValue::File(p)) => p,
485            _ => panic!("value is not a file"),
486        }
487    }
488
489    /// Gets the value as a `Directory`.
490    ///
491    /// Returns `None` if the value is not a `Directory`.
492    pub fn as_directory(&self) -> Option<&HostPath> {
493        match self {
494            Self::Primitive(PrimitiveValue::Directory(p)) => Some(p),
495            _ => None,
496        }
497    }
498
499    /// Unwraps the value into a `Directory`.
500    ///
501    /// # Panics
502    ///
503    /// Panics if the value is not a `Directory`.
504    pub fn unwrap_directory(self) -> HostPath {
505        match self {
506            Self::Primitive(PrimitiveValue::Directory(p)) => p,
507            _ => panic!("value is not a directory"),
508        }
509    }
510
511    /// Gets the value as a `Pair`.
512    ///
513    /// Returns `None` if the value is not a `Pair`.
514    pub fn as_pair(&self) -> Option<&Pair> {
515        match self {
516            Self::Compound(CompoundValue::Pair(v)) => Some(v),
517            _ => None,
518        }
519    }
520
521    /// Unwraps the value into a `Pair`.
522    ///
523    /// # Panics
524    ///
525    /// Panics if the value is not a `Pair`.
526    pub fn unwrap_pair(self) -> Pair {
527        match self {
528            Self::Compound(CompoundValue::Pair(v)) => v,
529            _ => panic!("value is not a pair"),
530        }
531    }
532
533    /// Gets the value as an `Array`.
534    ///
535    /// Returns `None` if the value is not an `Array`.
536    pub fn as_array(&self) -> Option<&Array> {
537        match self {
538            Self::Compound(CompoundValue::Array(v)) => Some(v),
539            _ => None,
540        }
541    }
542
543    /// Unwraps the value into an `Array`.
544    ///
545    /// # Panics
546    ///
547    /// Panics if the value is not an `Array`.
548    pub fn unwrap_array(self) -> Array {
549        match self {
550            Self::Compound(CompoundValue::Array(v)) => v,
551            _ => panic!("value is not an array"),
552        }
553    }
554
555    /// Gets the value as a `Map`.
556    ///
557    /// Returns `None` if the value is not a `Map`.
558    pub fn as_map(&self) -> Option<&Map> {
559        match self {
560            Self::Compound(CompoundValue::Map(v)) => Some(v),
561            _ => None,
562        }
563    }
564
565    /// Unwraps the value into a `Map`.
566    ///
567    /// # Panics
568    ///
569    /// Panics if the value is not a `Map`.
570    pub fn unwrap_map(self) -> Map {
571        match self {
572            Self::Compound(CompoundValue::Map(v)) => v,
573            _ => panic!("value is not a map"),
574        }
575    }
576
577    /// Gets the value as an `Object`.
578    ///
579    /// Returns `None` if the value is not an `Object`.
580    pub fn as_object(&self) -> Option<&Object> {
581        match self {
582            Self::Compound(CompoundValue::Object(v)) => Some(v),
583            _ => None,
584        }
585    }
586
587    /// Unwraps the value into an `Object`.
588    ///
589    /// # Panics
590    ///
591    /// Panics if the value is not an `Object`.
592    pub fn unwrap_object(self) -> Object {
593        match self {
594            Self::Compound(CompoundValue::Object(v)) => v,
595            _ => panic!("value is not an object"),
596        }
597    }
598
599    /// Gets the value as a `Struct`.
600    ///
601    /// Returns `None` if the value is not a `Struct`.
602    pub fn as_struct(&self) -> Option<&Struct> {
603        match self {
604            Self::Compound(CompoundValue::Struct(v)) => Some(v),
605            _ => None,
606        }
607    }
608
609    /// Unwraps the value into a `Struct`.
610    ///
611    /// # Panics
612    ///
613    /// Panics if the value is not a `Map`.
614    pub fn unwrap_struct(self) -> Struct {
615        match self {
616            Self::Compound(CompoundValue::Struct(v)) => v,
617            _ => panic!("value is not a struct"),
618        }
619    }
620
621    /// Gets the value as a pre-evaluation task.
622    ///
623    /// Returns `None` if the value is not a pre-evaluation task.
624    pub fn as_task_pre_evaluation(&self) -> Option<&TaskPreEvaluationValue> {
625        match self {
626            Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => Some(v),
627            _ => None,
628        }
629    }
630
631    /// Unwraps the value into a pre-evaluation task.
632    ///
633    /// # Panics
634    ///
635    /// Panics if the value is not a pre-evaluation task.
636    pub fn unwrap_task_pre_evaluation(self) -> TaskPreEvaluationValue {
637        match self {
638            Self::Hidden(HiddenValue::TaskPreEvaluation(v)) => v,
639            _ => panic!("value is not a pre-evaluation task"),
640        }
641    }
642
643    /// Gets the value as a post-evaluation task.
644    ///
645    /// Returns `None` if the value is not a post-evaluation task.
646    pub fn as_task_post_evaluation(&self) -> Option<&TaskPostEvaluationValue> {
647        match self {
648            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
649            _ => None,
650        }
651    }
652
653    /// Gets a mutable reference to the value as a post-evaluation task.
654    ///
655    /// Returns `None` if the value is not a post-evaluation task.
656    pub(crate) fn as_task_post_evaluation_mut(&mut self) -> Option<&mut TaskPostEvaluationValue> {
657        match self {
658            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => Some(v),
659            _ => None,
660        }
661    }
662
663    /// Unwraps the value into a post-evaluation task.
664    ///
665    /// # Panics
666    ///
667    /// Panics if the value is not a post-evaluation task.
668    pub fn unwrap_task_post_evaluation(self) -> TaskPostEvaluationValue {
669        match self {
670            Self::Hidden(HiddenValue::TaskPostEvaluation(v)) => v,
671            _ => panic!("value is not a post-evaluation task"),
672        }
673    }
674
675    /// Gets the value as a hints value.
676    ///
677    /// Returns `None` if the value is not a hints value.
678    pub fn as_hints(&self) -> Option<&HintsValue> {
679        match self {
680            Self::Hidden(HiddenValue::Hints(v)) => Some(v),
681            _ => None,
682        }
683    }
684
685    /// Unwraps the value into a hints value.
686    ///
687    /// # Panics
688    ///
689    /// Panics if the value is not a hints value.
690    pub fn unwrap_hints(self) -> HintsValue {
691        match self {
692            Self::Hidden(HiddenValue::Hints(v)) => v,
693            _ => panic!("value is not a hints value"),
694        }
695    }
696
697    /// Gets the value as a call value.
698    ///
699    /// Returns `None` if the value is not a call value.
700    pub fn as_call(&self) -> Option<&CallValue> {
701        match self {
702            Self::Call(v) => Some(v),
703            _ => None,
704        }
705    }
706
707    /// Unwraps the value into a call value.
708    ///
709    /// # Panics
710    ///
711    /// Panics if the value is not a call value.
712    pub fn unwrap_call(self) -> CallValue {
713        match self {
714            Self::Call(v) => v,
715            _ => panic!("value is not a call value"),
716        }
717    }
718
719    /// Visits any paths referenced by this value.
720    ///
721    /// The callback is invoked for each `File` and `Directory` value referenced
722    /// by this value.
723    pub(crate) fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
724    where
725        F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
726    {
727        match self {
728            Self::Primitive(PrimitiveValue::File(path)) => cb(true, path),
729            Self::Primitive(PrimitiveValue::Directory(path)) => cb(false, path),
730            Self::Compound(v) => v.visit_paths(cb),
731            _ => Ok(()),
732        }
733    }
734
735    /// Check that any paths referenced by a `File` or `Directory` value within
736    /// this value exist, and return a new value with any relevant host
737    /// paths transformed by the given `translate()` function.
738    ///
739    /// If a `File` or `Directory` value is optional and the path does not
740    /// exist, it is replaced with a WDL none value.
741    ///
742    /// If a `File` or `Directory` value is required and the path does not
743    /// exist, an error is returned.
744    ///
745    /// If a local base directory is provided, it will be joined with any
746    /// relative local paths prior to checking for existence.
747    ///
748    /// The provided transferer is used for checking remote URL existence.
749    ///
750    /// TODO ACF 2025-11-10: this function is an intermediate step on the way to
751    /// more thoroughly refactoring the code between `sprocket` and
752    /// `wdl_engine`. Expect this interface to change soon!
753    pub(crate) async fn resolve_paths<F>(
754        &self,
755        optional: bool,
756        base_dir: Option<&Path>,
757        transferer: Option<&dyn Transferer>,
758        translate: &F,
759    ) -> Result<Self>
760    where
761        F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
762    {
763        fn new_file_or_directory(is_file: bool, path: impl Into<HostPath>) -> PrimitiveValue {
764            if is_file {
765                PrimitiveValue::File(path.into())
766            } else {
767                PrimitiveValue::Directory(path.into())
768            }
769        }
770
771        match self {
772            Self::Primitive(v @ PrimitiveValue::File(path))
773            | Self::Primitive(v @ PrimitiveValue::Directory(path)) => {
774                // We treat file and directory paths almost entirely the same, other than when
775                // reporting errors and choosing which variant to return in the result
776                let is_file = v.as_file().is_some();
777                let path = translate(path)?;
778
779                if path::is_file_url(path.as_str()) {
780                    // File URLs must be absolute paths, so we just check whether it exists without
781                    // performing any joining
782                    let exists = path
783                        .as_str()
784                        .parse::<Url>()
785                        .ok()
786                        .and_then(|url| url.to_file_path().ok())
787                        .map(|p| p.exists())
788                        .unwrap_or(false);
789                    if exists {
790                        let v = new_file_or_directory(is_file, path);
791                        return Ok(Self::Primitive(v));
792                    }
793
794                    if optional && !exists {
795                        return Ok(Value::new_none(self.ty().optional()));
796                    }
797
798                    bail!("path `{path}` does not exist");
799                } else if path::is_supported_url(path.as_str()) {
800                    match transferer {
801                        Some(transferer) => {
802                            let exists = transferer
803                                .exists(
804                                    &path
805                                        .as_str()
806                                        .parse()
807                                        .with_context(|| format!("invalid URL `{path}`"))?,
808                                )
809                                .await?;
810                            if exists {
811                                let v = new_file_or_directory(is_file, path);
812                                return Ok(Self::Primitive(v));
813                            }
814
815                            if optional && !exists {
816                                return Ok(Value::new_none(self.ty().optional()));
817                            }
818
819                            bail!("URL `{path}` does not exist");
820                        }
821                        None => {
822                            // Assume the URL exists
823                            let v = new_file_or_directory(is_file, path);
824                            return Ok(Self::Primitive(v));
825                        }
826                    }
827                }
828
829                // Check for existence
830                let exists_path: Cow<'_, Path> = base_dir
831                    .map(|d| d.join(path.as_str()).into())
832                    .unwrap_or_else(|| Path::new(path.as_str()).into());
833                if is_file && !exists_path.is_file() {
834                    if optional {
835                        return Ok(Value::new_none(self.ty().optional()));
836                    } else {
837                        bail!("file `{}` does not exist", exists_path.display());
838                    }
839                } else if !is_file && !exists_path.is_dir() {
840                    if optional {
841                        return Ok(Value::new_none(self.ty().optional()));
842                    } else {
843                        bail!("directory `{}` does not exist", exists_path.display())
844                    }
845                }
846
847                let v = new_file_or_directory(is_file, path);
848                Ok(Self::Primitive(v))
849            }
850            Self::Compound(v) => Ok(Self::Compound(
851                v.resolve_paths(base_dir, transferer, translate)
852                    .boxed()
853                    .await?,
854            )),
855            v => Ok(v.clone()),
856        }
857    }
858
859    /// Determines if two values have equality according to the WDL
860    /// specification.
861    ///
862    /// Returns `None` if the two values cannot be compared for equality.
863    pub fn equals(left: &Self, right: &Self) -> Option<bool> {
864        match (left, right) {
865            (Value::None(_), Value::None(_)) => Some(true),
866            (Value::None(_), _) | (_, Value::None(_)) => Some(false),
867            (Value::Primitive(left), Value::Primitive(right)) => {
868                Some(PrimitiveValue::compare(left, right)? == Ordering::Equal)
869            }
870            (Value::Compound(left), Value::Compound(right)) => CompoundValue::equals(left, right),
871            _ => None,
872        }
873    }
874}
875
876impl fmt::Display for Value {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        match self {
879            Self::None(_) => write!(f, "None"),
880            Self::Primitive(v) => v.fmt(f),
881            Self::Compound(v) => v.fmt(f),
882            Self::Hidden(v) => v.fmt(f),
883            Self::Call(c) => c.fmt(f),
884            Self::TypeNameRef(v) => v.ty().fmt(f),
885        }
886    }
887}
888
889impl Coercible for Value {
890    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
891        if target.is_union() || target.is_none() || self.ty().eq(target) {
892            return Ok(self.clone());
893        }
894
895        match self {
896            Self::None(_) => {
897                if target.is_optional() {
898                    Ok(Self::new_none(target.clone()))
899                } else {
900                    bail!("cannot coerce `None` to non-optional {target:#}");
901                }
902            }
903            // String -> Enum Variant
904            Self::Primitive(PrimitiveValue::String(s)) if target.as_enum().is_some() => {
905                // SAFETY: we just checked above that this is an enum type.
906                let enum_ty = target.as_enum().unwrap();
907
908                if enum_ty
909                    .variants()
910                    .iter()
911                    .any(|variant_name| variant_name == s.as_str())
912                {
913                    if let Some(context) = context {
914                        if let Ok(value) = context.enum_variant_value(enum_ty.name(), s) {
915                            return Ok(Value::Compound(CompoundValue::EnumVariant(
916                                EnumVariant::new(enum_ty.clone(), s.as_str(), value),
917                            )));
918                        } else {
919                            bail!(
920                                "enum variant value lookup failed for variant `{s}` in enum `{}`",
921                                enum_ty.name()
922                            );
923                        }
924                    } else {
925                        bail!(
926                            "context does not exist when creating enum variant value `{s}` in \
927                             enum `{}`",
928                            enum_ty.name()
929                        );
930                    }
931                }
932
933                let variants = if enum_ty.variants().is_empty() {
934                    None
935                } else {
936                    let mut variant_names = enum_ty.variants().to_vec();
937                    variant_names.sort();
938                    Some(format!(" (variants: `{}`)", variant_names.join("`, `")))
939                }
940                .unwrap_or_default();
941
942                bail!(
943                    "cannot coerce type `String` to {target:#}: variant `{s}` not found in enum \
944                     `{}`{variants}",
945                    enum_ty.name()
946                );
947            }
948            // Enum Variant -> String
949            Self::Compound(CompoundValue::EnumVariant(e))
950                if target
951                    .as_primitive()
952                    .map(|t| matches!(t, PrimitiveType::String))
953                    .unwrap_or(false) =>
954            {
955                Ok(Value::Primitive(PrimitiveValue::new_string(e.name())))
956            }
957            Self::Primitive(v) => v.coerce(context, target).map(Self::Primitive),
958            Self::Compound(v) => v.coerce(context, target).map(Self::Compound),
959            Self::Hidden(v) => v.coerce(context, target).map(Self::Hidden),
960            Self::Call(_) => {
961                bail!("call values cannot be coerced to any other type");
962            }
963            Self::TypeNameRef(_) => {
964                bail!("type name references cannot be coerced to any other type");
965            }
966        }
967    }
968}
969
970impl From<bool> for Value {
971    fn from(value: bool) -> Self {
972        Self::Primitive(value.into())
973    }
974}
975
976impl From<i64> for Value {
977    fn from(value: i64) -> Self {
978        Self::Primitive(value.into())
979    }
980}
981
982impl TryFrom<u64> for Value {
983    type Error = std::num::TryFromIntError;
984
985    fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
986        let value: i64 = value.try_into()?;
987        Ok(value.into())
988    }
989}
990
991impl From<f64> for Value {
992    fn from(value: f64) -> Self {
993        Self::Primitive(value.into())
994    }
995}
996
997impl From<String> for Value {
998    fn from(value: String) -> Self {
999        Self::Primitive(value.into())
1000    }
1001}
1002
1003impl From<PrimitiveValue> for Value {
1004    fn from(value: PrimitiveValue) -> Self {
1005        Self::Primitive(value)
1006    }
1007}
1008
1009impl From<Option<PrimitiveValue>> for Value {
1010    fn from(value: Option<PrimitiveValue>) -> Self {
1011        match value {
1012            Some(v) => v.into(),
1013            None => Self::new_none(Type::None),
1014        }
1015    }
1016}
1017
1018impl From<CompoundValue> for Value {
1019    fn from(value: CompoundValue) -> Self {
1020        Self::Compound(value)
1021    }
1022}
1023
1024impl From<HiddenValue> for Value {
1025    fn from(value: HiddenValue) -> Self {
1026        Self::Hidden(value)
1027    }
1028}
1029
1030impl From<Pair> for Value {
1031    fn from(value: Pair) -> Self {
1032        Self::Compound(value.into())
1033    }
1034}
1035
1036impl From<Array> for Value {
1037    fn from(value: Array) -> Self {
1038        Self::Compound(value.into())
1039    }
1040}
1041
1042impl From<Map> for Value {
1043    fn from(value: Map) -> Self {
1044        Self::Compound(value.into())
1045    }
1046}
1047
1048impl From<Object> for Value {
1049    fn from(value: Object) -> Self {
1050        Self::Compound(value.into())
1051    }
1052}
1053
1054impl From<Struct> for Value {
1055    fn from(value: Struct) -> Self {
1056        Self::Compound(value.into())
1057    }
1058}
1059
1060impl From<CallValue> for Value {
1061    fn from(value: CallValue) -> Self {
1062        Self::Call(value)
1063    }
1064}
1065
1066impl<'de> serde::Deserialize<'de> for Value {
1067    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1068    where
1069        D: serde::Deserializer<'de>,
1070    {
1071        use serde::Deserialize as _;
1072
1073        /// Visitor for deserialization.
1074        struct Visitor;
1075
1076        impl<'de> serde::de::Visitor<'de> for Visitor {
1077            type Value = Value;
1078
1079            fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
1080            where
1081                E: serde::de::Error,
1082            {
1083                Ok(Value::new_none(Type::None))
1084            }
1085
1086            fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
1087            where
1088                E: serde::de::Error,
1089            {
1090                Ok(Value::new_none(Type::None))
1091            }
1092
1093            fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
1094            where
1095                D: serde::Deserializer<'de>,
1096            {
1097                Value::deserialize(deserializer)
1098            }
1099
1100            fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
1101            where
1102                E: serde::de::Error,
1103            {
1104                Ok(Value::Primitive(PrimitiveValue::Boolean(v)))
1105            }
1106
1107            fn visit_i64<E>(self, v: i64) -> std::result::Result<Self::Value, E>
1108            where
1109                E: serde::de::Error,
1110            {
1111                Ok(Value::Primitive(PrimitiveValue::Integer(v)))
1112            }
1113
1114            fn visit_u64<E>(self, v: u64) -> std::result::Result<Self::Value, E>
1115            where
1116                E: serde::de::Error,
1117            {
1118                Ok(Value::Primitive(PrimitiveValue::Integer(
1119                    v.try_into().map_err(|_| {
1120                        E::custom("integer not in range for a 64-bit signed integer")
1121                    })?,
1122                )))
1123            }
1124
1125            fn visit_f64<E>(self, v: f64) -> std::result::Result<Self::Value, E>
1126            where
1127                E: serde::de::Error,
1128            {
1129                Ok(Value::Primitive(PrimitiveValue::Float(v.into())))
1130            }
1131
1132            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
1133            where
1134                E: serde::de::Error,
1135            {
1136                Ok(Value::Primitive(PrimitiveValue::new_string(v)))
1137            }
1138
1139            fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
1140            where
1141                E: serde::de::Error,
1142            {
1143                Ok(Value::Primitive(PrimitiveValue::new_string(v)))
1144            }
1145
1146            fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error>
1147            where
1148                A: serde::de::SeqAccess<'de>,
1149            {
1150                use serde::de::Error as _;
1151
1152                let mut elements = vec![];
1153                while let Some(element) = seq.next_element::<Value>()? {
1154                    elements.push(element);
1155                }
1156
1157                // Try to find a mutually-agreeable common type for the elements of the array.
1158                let mut candidate_ty = None;
1159                for element in elements.iter() {
1160                    let new_candidate_ty = element.ty();
1161                    let old_candidate_ty =
1162                        candidate_ty.get_or_insert_with(|| new_candidate_ty.clone());
1163                    let Some(new_common_ty) = old_candidate_ty.common_type(&new_candidate_ty)
1164                    else {
1165                        return Err(A::Error::custom(format!(
1166                            "a common element type does not exist between {old_candidate_ty:#} \
1167                             and {new_candidate_ty:#}"
1168                        )));
1169                    };
1170                    candidate_ty = Some(new_common_ty);
1171                }
1172                // An empty array's elements have the `Union` type.
1173                let array_ty = ArrayType::new(candidate_ty.unwrap_or(Type::Union));
1174                Ok(Array::new(array_ty.clone(), elements)
1175                    .map_err(|e| {
1176                        A::Error::custom(format!("cannot coerce value to {array_ty:#}: {e:#}"))
1177                    })?
1178                    .into())
1179            }
1180
1181            fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
1182            where
1183                A: serde::de::MapAccess<'de>,
1184            {
1185                let mut members = IndexMap::new();
1186                while let Some(key) = map.next_key::<String>()? {
1187                    members.insert(key, map.next_value()?);
1188                }
1189
1190                Ok(Value::Compound(CompoundValue::Object(Object::new(members))))
1191            }
1192
1193            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1194                write!(f, "a WDL value")
1195            }
1196        }
1197
1198        deserializer.deserialize_any(Visitor)
1199    }
1200}
1201
1202/// Represents a primitive WDL value.
1203///
1204/// Primitive values are cheap to clone.
1205#[derive(Debug, Clone)]
1206pub enum PrimitiveValue {
1207    /// The value is a `Boolean`.
1208    Boolean(bool),
1209    /// The value is an `Int`.
1210    Integer(i64),
1211    /// The value is a `Float`.
1212    Float(OrderedFloat<f64>),
1213    /// The value is a `String`.
1214    String(Arc<String>),
1215    /// The value is a `File`.
1216    File(HostPath),
1217    /// The value is a `Directory`.
1218    Directory(HostPath),
1219}
1220
1221impl PrimitiveValue {
1222    /// Creates a new `String` value.
1223    pub fn new_string(s: impl Into<String>) -> Self {
1224        Self::String(Arc::new(s.into()))
1225    }
1226
1227    /// Creates a new `File` value.
1228    pub fn new_file(path: impl Into<HostPath>) -> Self {
1229        Self::File(path.into())
1230    }
1231
1232    /// Creates a new `Directory` value.
1233    pub fn new_directory(path: impl Into<HostPath>) -> Self {
1234        Self::Directory(path.into())
1235    }
1236
1237    /// Gets the type of the value.
1238    pub fn ty(&self) -> Type {
1239        match self {
1240            Self::Boolean(_) => PrimitiveType::Boolean.into(),
1241            Self::Integer(_) => PrimitiveType::Integer.into(),
1242            Self::Float(_) => PrimitiveType::Float.into(),
1243            Self::String(_) => PrimitiveType::String.into(),
1244            Self::File(_) => PrimitiveType::File.into(),
1245            Self::Directory(_) => PrimitiveType::Directory.into(),
1246        }
1247    }
1248
1249    /// Gets the value as a `Boolean`.
1250    ///
1251    /// Returns `None` if the value is not a `Boolean`.
1252    pub fn as_boolean(&self) -> Option<bool> {
1253        match self {
1254            Self::Boolean(v) => Some(*v),
1255            _ => None,
1256        }
1257    }
1258
1259    /// Unwraps the value into a `Boolean`.
1260    ///
1261    /// # Panics
1262    ///
1263    /// Panics if the value is not a `Boolean`.
1264    pub fn unwrap_boolean(self) -> bool {
1265        match self {
1266            Self::Boolean(v) => v,
1267            _ => panic!("value is not a boolean"),
1268        }
1269    }
1270
1271    /// Gets the value as an `Int`.
1272    ///
1273    /// Returns `None` if the value is not an `Int`.
1274    pub fn as_integer(&self) -> Option<i64> {
1275        match self {
1276            Self::Integer(v) => Some(*v),
1277            _ => None,
1278        }
1279    }
1280
1281    /// Unwraps the value into an integer.
1282    ///
1283    /// # Panics
1284    ///
1285    /// Panics if the value is not an integer.
1286    pub fn unwrap_integer(self) -> i64 {
1287        match self {
1288            Self::Integer(v) => v,
1289            _ => panic!("value is not an integer"),
1290        }
1291    }
1292
1293    /// Gets the value as a `Float`.
1294    ///
1295    /// Returns `None` if the value is not a `Float`.
1296    pub fn as_float(&self) -> Option<f64> {
1297        match self {
1298            Self::Float(v) => Some((*v).into()),
1299            _ => None,
1300        }
1301    }
1302
1303    /// Unwraps the value into a `Float`.
1304    ///
1305    /// # Panics
1306    ///
1307    /// Panics if the value is not a `Float`.
1308    pub fn unwrap_float(self) -> f64 {
1309        match self {
1310            Self::Float(v) => v.into(),
1311            _ => panic!("value is not a float"),
1312        }
1313    }
1314
1315    /// Gets the value as a `String`.
1316    ///
1317    /// Returns `None` if the value is not a `String`.
1318    pub fn as_string(&self) -> Option<&Arc<String>> {
1319        match self {
1320            Self::String(s) => Some(s),
1321            _ => None,
1322        }
1323    }
1324
1325    /// Unwraps the value into a `String`.
1326    ///
1327    /// # Panics
1328    ///
1329    /// Panics if the value is not a `String`.
1330    pub fn unwrap_string(self) -> Arc<String> {
1331        match self {
1332            Self::String(s) => s,
1333            _ => panic!("value is not a string"),
1334        }
1335    }
1336
1337    /// Gets the value as a `File`.
1338    ///
1339    /// Returns `None` if the value is not a `File`.
1340    pub fn as_file(&self) -> Option<&HostPath> {
1341        match self {
1342            Self::File(p) => Some(p),
1343            _ => None,
1344        }
1345    }
1346
1347    /// Unwraps the value into a `File`.
1348    ///
1349    /// # Panics
1350    ///
1351    /// Panics if the value is not a `File`.
1352    pub fn unwrap_file(self) -> HostPath {
1353        match self {
1354            Self::File(p) => p,
1355            _ => panic!("value is not a file"),
1356        }
1357    }
1358
1359    /// Gets the value as a `Directory`.
1360    ///
1361    /// Returns `None` if the value is not a `Directory`.
1362    pub fn as_directory(&self) -> Option<&HostPath> {
1363        match self {
1364            Self::Directory(p) => Some(p),
1365            _ => None,
1366        }
1367    }
1368
1369    /// Unwraps the value into a `Directory`.
1370    ///
1371    /// # Panics
1372    ///
1373    /// Panics if the value is not a `Directory`.
1374    pub fn unwrap_directory(self) -> HostPath {
1375        match self {
1376            Self::Directory(p) => p,
1377            _ => panic!("value is not a directory"),
1378        }
1379    }
1380
1381    /// Compares two values for an ordering according to the WDL specification.
1382    ///
1383    /// Unlike a `PartialOrd` implementation, this takes into account automatic
1384    /// coercions.
1385    ///
1386    /// Returns `None` if the values cannot be compared based on their types.
1387    pub fn compare(left: &Self, right: &Self) -> Option<Ordering> {
1388        match (left, right) {
1389            (Self::Boolean(left), Self::Boolean(right)) => Some(left.cmp(right)),
1390            (Self::Integer(left), Self::Integer(right)) => Some(left.cmp(right)),
1391            (Self::Integer(left), Self::Float(right)) => {
1392                Some(OrderedFloat(*left as f64).cmp(right))
1393            }
1394            (Self::Float(left), Self::Integer(right)) => {
1395                Some(left.cmp(&OrderedFloat(*right as f64)))
1396            }
1397            (Self::Float(left), Self::Float(right)) => Some(left.cmp(right)),
1398            (Self::String(left), Self::String(right))
1399            | (Self::String(left), Self::File(HostPath(right)))
1400            | (Self::String(left), Self::Directory(HostPath(right)))
1401            | (Self::File(HostPath(left)), Self::File(HostPath(right)))
1402            | (Self::File(HostPath(left)), Self::String(right))
1403            | (Self::Directory(HostPath(left)), Self::Directory(HostPath(right)))
1404            | (Self::Directory(HostPath(left)), Self::String(right)) => Some(left.cmp(right)),
1405            _ => None,
1406        }
1407    }
1408
1409    /// Gets a raw display of the value.
1410    ///
1411    /// This differs from the [Display][fmt::Display] implementation in that
1412    /// strings, files, and directories are not quoted and not escaped.
1413    ///
1414    /// The provided coercion context is used to translate host paths to guest
1415    /// paths; if `None`, `File` and `Directory` values are displayed as-is.
1416    pub(crate) fn raw<'a>(
1417        &'a self,
1418        context: Option<&'a dyn EvaluationContext>,
1419    ) -> impl fmt::Display + use<'a> {
1420        /// Helper for displaying a raw value.
1421        struct Display<'a> {
1422            /// The value to display.
1423            value: &'a PrimitiveValue,
1424            /// The coercion context.
1425            context: Option<&'a dyn EvaluationContext>,
1426        }
1427
1428        impl fmt::Display for Display<'_> {
1429            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1430                match self.value {
1431                    PrimitiveValue::Boolean(v) => write!(f, "{v}"),
1432                    PrimitiveValue::Integer(v) => write!(f, "{v}"),
1433                    PrimitiveValue::Float(v) => write!(f, "{v:.6?}"),
1434                    PrimitiveValue::String(v) => write!(f, "{v}"),
1435                    PrimitiveValue::File(v) => {
1436                        write!(
1437                            f,
1438                            "{v}",
1439                            v = self
1440                                .context
1441                                .and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
1442                                .unwrap_or(Cow::Borrowed(&v.0))
1443                        )
1444                    }
1445                    PrimitiveValue::Directory(v) => {
1446                        write!(
1447                            f,
1448                            "{v}",
1449                            v = self
1450                                .context
1451                                .and_then(|c| c.guest_path(v).map(|p| Cow::Owned(p.0)))
1452                                .unwrap_or(Cow::Borrowed(&v.0))
1453                        )
1454                    }
1455                }
1456            }
1457        }
1458
1459        Display {
1460            value: self,
1461            context,
1462        }
1463    }
1464}
1465
1466impl fmt::Display for PrimitiveValue {
1467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1468        match self {
1469            Self::Boolean(v) => write!(f, "{v}"),
1470            Self::Integer(v) => write!(f, "{v}"),
1471            Self::Float(v) => write!(f, "{v:.6?}"),
1472            Self::String(s) | Self::File(HostPath(s)) | Self::Directory(HostPath(s)) => {
1473                f.write_str("\"")?;
1474                write_escaped_wdl_string(f, s.as_str())?;
1475                f.write_str("\"")
1476            }
1477        }
1478    }
1479}
1480
1481impl PartialEq for PrimitiveValue {
1482    fn eq(&self, other: &Self) -> bool {
1483        Self::compare(self, other) == Some(Ordering::Equal)
1484    }
1485}
1486
1487impl Eq for PrimitiveValue {}
1488
1489impl Hash for PrimitiveValue {
1490    fn hash<H: Hasher>(&self, state: &mut H) {
1491        match self {
1492            Self::Boolean(v) => {
1493                0.hash(state);
1494                v.hash(state);
1495            }
1496            Self::Integer(v) => {
1497                1.hash(state);
1498                v.hash(state);
1499            }
1500            Self::Float(v) => {
1501                // Hash this with the same discriminant as integer; this allows coercion from
1502                // int to float.
1503                1.hash(state);
1504                v.hash(state);
1505            }
1506            Self::String(v) | Self::File(HostPath(v)) | Self::Directory(HostPath(v)) => {
1507                // Hash these with the same discriminant; this allows coercion from file and
1508                // directory to string
1509                2.hash(state);
1510                v.hash(state);
1511            }
1512        }
1513    }
1514}
1515
1516impl From<bool> for PrimitiveValue {
1517    fn from(value: bool) -> Self {
1518        Self::Boolean(value)
1519    }
1520}
1521
1522impl From<i64> for PrimitiveValue {
1523    fn from(value: i64) -> Self {
1524        Self::Integer(value)
1525    }
1526}
1527
1528impl From<f64> for PrimitiveValue {
1529    fn from(value: f64) -> Self {
1530        Self::Float(value.into())
1531    }
1532}
1533
1534impl From<String> for PrimitiveValue {
1535    fn from(value: String) -> Self {
1536        Self::String(value.into())
1537    }
1538}
1539
1540impl Coercible for PrimitiveValue {
1541    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
1542        if target.is_union() || target.is_none() || self.ty().eq(target) {
1543            return Ok(self.clone());
1544        }
1545
1546        match self {
1547            Self::Boolean(v) => {
1548                target
1549                    .as_primitive()
1550                    .and_then(|ty| match ty {
1551                        // Boolean -> Boolean
1552                        PrimitiveType::Boolean => Some(Self::Boolean(*v)),
1553                        _ => None,
1554                    })
1555                    .with_context(|| format!("cannot coerce type `Boolean` to {target:#}"))
1556            }
1557            Self::Integer(v) => {
1558                target
1559                    .as_primitive()
1560                    .and_then(|ty| match ty {
1561                        // Int -> Int
1562                        PrimitiveType::Integer => Some(Self::Integer(*v)),
1563                        // Int -> Float
1564                        PrimitiveType::Float => Some(Self::Float((*v as f64).into())),
1565                        _ => None,
1566                    })
1567                    .with_context(|| format!("cannot coerce type `Int` to {target:#}"))
1568            }
1569            Self::Float(v) => {
1570                target
1571                    .as_primitive()
1572                    .and_then(|ty| match ty {
1573                        // Float -> Float
1574                        PrimitiveType::Float => Some(Self::Float(*v)),
1575                        _ => None,
1576                    })
1577                    .with_context(|| format!("cannot coerce type `Float` to {target:#}"))
1578            }
1579            Self::String(s) => {
1580                target
1581                    .as_primitive()
1582                    .and_then(|ty| match ty {
1583                        // String -> String
1584                        PrimitiveType::String => Some(Self::String(s.clone())),
1585                        // String -> File
1586                        PrimitiveType::File => Some(Self::File(
1587                            context
1588                                .and_then(|c| c.host_path(&GuestPath(s.clone())))
1589                                .unwrap_or_else(|| s.clone().into()),
1590                        )),
1591                        // String -> Directory
1592                        PrimitiveType::Directory => Some(Self::Directory(
1593                            context
1594                                .and_then(|c| c.host_path(&GuestPath(s.clone())))
1595                                .unwrap_or_else(|| s.clone().into()),
1596                        )),
1597                        _ => None,
1598                    })
1599                    .with_context(|| format!("cannot coerce type `String` to {target:#}"))
1600            }
1601            Self::File(p) => {
1602                target
1603                    .as_primitive()
1604                    .and_then(|ty| match ty {
1605                        // File -> File
1606                        PrimitiveType::File => Some(Self::File(p.clone())),
1607                        // File -> String
1608                        PrimitiveType::String => Some(Self::String(
1609                            context
1610                                .and_then(|c| c.guest_path(p).map(Into::into))
1611                                .unwrap_or_else(|| p.clone().into()),
1612                        )),
1613                        _ => None,
1614                    })
1615                    .with_context(|| format!("cannot coerce type `File` to {target:#}"))
1616            }
1617            Self::Directory(p) => {
1618                target
1619                    .as_primitive()
1620                    .and_then(|ty| match ty {
1621                        // Directory -> Directory
1622                        PrimitiveType::Directory => Some(Self::Directory(p.clone())),
1623                        // Directory -> String
1624                        PrimitiveType::String => Some(Self::String(
1625                            context
1626                                .and_then(|c| c.guest_path(p).map(Into::into))
1627                                .unwrap_or_else(|| p.clone().into()),
1628                        )),
1629                        _ => None,
1630                    })
1631                    .with_context(|| format!("cannot coerce type `Directory` to {target:#}"))
1632            }
1633        }
1634    }
1635}
1636
1637/// The inner representation of a pair value.
1638#[derive(Debug)]
1639struct PairInner {
1640    /// The type of the pair.
1641    ty: Type,
1642    /// The left value of the pair.
1643    left: Value,
1644    /// The right value of the pair.
1645    right: Value,
1646}
1647
1648/// Represents a `Pair` value.
1649///
1650/// Pairs are cheap to clone.
1651#[derive(Debug, Clone)]
1652pub struct Pair(Arc<PairInner>);
1653
1654impl Pair {
1655    /// Creates a new `Pair` value.
1656    ///
1657    /// Returns an error if either the `left` value or the `right` value did not
1658    /// coerce to the pair's `left` type or `right` type, respectively.
1659    pub fn new(ty: PairType, left: impl Into<Value>, right: impl Into<Value>) -> Result<Self> {
1660        Self::new_with_context(None, ty, left, right)
1661    }
1662
1663    /// Creates a new `Pair` value with the given evaluation context.
1664    ///
1665    /// Returns an error if either the `left` value or the `right` value did not
1666    /// coerce to the pair's `left` type or `right` type, respectively.
1667    pub(crate) fn new_with_context(
1668        context: Option<&dyn EvaluationContext>,
1669        ty: PairType,
1670        left: impl Into<Value>,
1671        right: impl Into<Value>,
1672    ) -> Result<Self> {
1673        let left = left
1674            .into()
1675            .coerce(context, ty.left_type())
1676            .context("failed to coerce pair's left value")?;
1677        let right = right
1678            .into()
1679            .coerce(context, ty.right_type())
1680            .context("failed to coerce pair's right value")?;
1681        Ok(Self::new_unchecked(ty, left, right))
1682    }
1683
1684    /// Constructs a new pair without checking the given left and right conform
1685    /// to the given type.
1686    pub(crate) fn new_unchecked(ty: impl Into<Type>, left: Value, right: Value) -> Self {
1687        let ty = ty.into();
1688        assert!(ty.as_pair().is_some());
1689        Self(Arc::new(PairInner {
1690            ty: ty.require(),
1691            left,
1692            right,
1693        }))
1694    }
1695
1696    /// Gets the type of the `Pair`.
1697    pub fn ty(&self) -> Type {
1698        self.0.ty.clone()
1699    }
1700
1701    /// Gets the left value of the `Pair`.
1702    pub fn left(&self) -> &Value {
1703        &self.0.left
1704    }
1705
1706    /// Gets the right value of the `Pair`.
1707    pub fn right(&self) -> &Value {
1708        &self.0.right
1709    }
1710}
1711
1712impl fmt::Display for Pair {
1713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1714        write!(
1715            f,
1716            "({left}, {right})",
1717            left = self.0.left,
1718            right = self.0.right
1719        )
1720    }
1721}
1722
1723/// The inner representation of an array value.
1724#[derive(Debug)]
1725struct ArrayInner {
1726    /// The type of the array.
1727    ty: Type,
1728    /// The array's elements.
1729    elements: Vec<Value>,
1730}
1731
1732/// Represents an `Array` value.
1733///
1734/// Arrays are cheap to clone.
1735#[derive(Debug, Clone)]
1736pub struct Array(Arc<ArrayInner>);
1737
1738impl Array {
1739    /// Creates a new `Array` value for the given array type.
1740    ///
1741    /// Returns an error if an element did not coerce to the array's element
1742    /// type.
1743    pub fn new<V>(ty: ArrayType, elements: impl IntoIterator<Item = V>) -> Result<Self>
1744    where
1745        V: Into<Value>,
1746    {
1747        Self::new_with_context(None, ty, elements)
1748    }
1749
1750    /// Creates a new `Array` value for the given array type and evaluation
1751    /// context.
1752    ///
1753    /// Returns an error if an element did not coerce to the array's element
1754    /// type.
1755    pub(crate) fn new_with_context<V>(
1756        context: Option<&dyn EvaluationContext>,
1757        ty: ArrayType,
1758        elements: impl IntoIterator<Item = V>,
1759    ) -> Result<Self>
1760    where
1761        V: Into<Value>,
1762    {
1763        let element_type = ty.element_type();
1764        let elements = elements
1765            .into_iter()
1766            .enumerate()
1767            .map(|(i, v)| {
1768                let v = v.into();
1769                v.coerce(context, element_type)
1770                    .with_context(|| format!("failed to coerce array element at index {i}"))
1771            })
1772            .collect::<Result<Vec<_>>>()?;
1773
1774        Ok(Self::new_unchecked(ty, elements))
1775    }
1776
1777    /// Constructs a new array without checking the given elements conform to
1778    /// the given type.
1779    ///
1780    /// # Panics
1781    ///
1782    /// Panics if the given type is not an array type.
1783    pub(crate) fn new_unchecked(ty: impl Into<Type>, elements: Vec<Value>) -> Self {
1784        let ty = if let Type::Compound(CompoundType::Array(ty), _) = ty.into() {
1785            Type::Compound(CompoundType::Array(ty.unqualified()), false)
1786        } else {
1787            panic!("type is not an array type");
1788        };
1789
1790        Self(Arc::new(ArrayInner { ty, elements }))
1791    }
1792
1793    /// Gets the type of the `Array` value.
1794    pub fn ty(&self) -> Type {
1795        self.0.ty.clone()
1796    }
1797
1798    /// Converts the array value to a slice of values.
1799    pub fn as_slice(&self) -> &[Value] {
1800        &self.0.elements
1801    }
1802
1803    /// Returns the number of elements in the array.
1804    pub fn len(&self) -> usize {
1805        self.0.elements.len()
1806    }
1807
1808    /// Returns `true` if the array has no elements.
1809    pub fn is_empty(&self) -> bool {
1810        self.0.elements.is_empty()
1811    }
1812}
1813
1814impl fmt::Display for Array {
1815    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1816        write!(f, "[")?;
1817
1818        for (i, element) in self.0.elements.iter().enumerate() {
1819            if i > 0 {
1820                write!(f, ", ")?;
1821            }
1822
1823            write!(f, "{element}")?;
1824        }
1825
1826        write!(f, "]")
1827    }
1828}
1829
1830/// The inner representation of a map value.
1831#[derive(Debug)]
1832struct MapInner {
1833    /// The type of the map value.
1834    ty: Type,
1835    /// The elements of the map value.
1836    elements: IndexMap<PrimitiveValue, Value>,
1837}
1838
1839/// Represents a `Map` value.
1840///
1841/// Maps are cheap to clone.
1842#[derive(Debug, Clone)]
1843pub struct Map(Arc<MapInner>);
1844
1845impl Map {
1846    /// Creates a new `Map` value.
1847    ///
1848    /// Returns an error if a key or value did not coerce to the map's key or
1849    /// value type, respectively.
1850    pub fn new<K, V>(ty: MapType, elements: impl IntoIterator<Item = (K, V)>) -> Result<Self>
1851    where
1852        K: Into<PrimitiveValue>,
1853        V: Into<Value>,
1854    {
1855        Self::new_with_context(None, ty, elements)
1856    }
1857
1858    /// Creates a new `Map` value with the given evaluation context.
1859    ///
1860    /// Returns an error if a key or value did not coerce to the map's key or
1861    /// value type, respectively.
1862    pub(crate) fn new_with_context<K, V>(
1863        context: Option<&dyn EvaluationContext>,
1864        ty: MapType,
1865        elements: impl IntoIterator<Item = (K, V)>,
1866    ) -> Result<Self>
1867    where
1868        K: Into<PrimitiveValue>,
1869        V: Into<Value>,
1870    {
1871        let key_type = ty.key_type();
1872        let value_type = ty.value_type();
1873
1874        let elements = elements
1875            .into_iter()
1876            .enumerate()
1877            .map(|(i, (k, v))| {
1878                let k = k.into();
1879                let v = v.into();
1880                Ok((
1881                    k.coerce(context, key_type).with_context(|| {
1882                        format!("failed to coerce map key for element at index {i}")
1883                    })?,
1884                    v.coerce(context, value_type).with_context(|| {
1885                        format!("failed to coerce map value for element at index {i}")
1886                    })?,
1887                ))
1888            })
1889            .collect::<Result<_>>()?;
1890
1891        Ok(Self::new_unchecked(ty, elements))
1892    }
1893
1894    /// Constructs a new map without checking the given elements conform to the
1895    /// given type.
1896    ///
1897    /// # Panics
1898    ///
1899    /// Panics if the given type is not a map type.
1900    pub(crate) fn new_unchecked(
1901        ty: impl Into<Type>,
1902        elements: IndexMap<PrimitiveValue, Value>,
1903    ) -> Self {
1904        let ty = ty.into();
1905        assert!(ty.as_map().is_some());
1906        Self(Arc::new(MapInner {
1907            ty: ty.require(),
1908            elements,
1909        }))
1910    }
1911
1912    /// Gets the type of the `Map` value.
1913    pub fn ty(&self) -> Type {
1914        self.0.ty.clone()
1915    }
1916
1917    /// Iterates the elements of the map.
1918    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&PrimitiveValue, &Value)> {
1919        self.0.elements.iter()
1920    }
1921
1922    /// Iterates the keys of the map.
1923    pub fn keys(&self) -> impl ExactSizeIterator<Item = &PrimitiveValue> {
1924        self.0.elements.keys()
1925    }
1926
1927    /// Iterates the values of the map.
1928    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
1929        self.0.elements.values()
1930    }
1931
1932    /// Determines if the map contains the given key.
1933    pub fn contains_key(&self, key: &PrimitiveValue) -> bool {
1934        self.0.elements.contains_key(key)
1935    }
1936
1937    /// Gets a value from the map by key.
1938    pub fn get(&self, key: &PrimitiveValue) -> Option<&Value> {
1939        self.0.elements.get(key)
1940    }
1941
1942    /// Returns the number of elements in the map.
1943    pub fn len(&self) -> usize {
1944        self.0.elements.len()
1945    }
1946
1947    /// Returns `true` if the map has no elements.
1948    pub fn is_empty(&self) -> bool {
1949        self.0.elements.is_empty()
1950    }
1951}
1952
1953impl fmt::Display for Map {
1954    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1955        write!(f, "{{")?;
1956
1957        for (i, (k, v)) in self.iter().enumerate() {
1958            if i > 0 {
1959                write!(f, ", ")?;
1960            }
1961
1962            write!(f, "{k}: {v}")?;
1963        }
1964
1965        write!(f, "}}")
1966    }
1967}
1968
1969/// Represents an `Object` value.
1970///
1971/// Objects are cheap to clone.
1972#[derive(Debug, Clone)]
1973pub struct Object {
1974    /// The members of the object.
1975    pub(crate) members: Arc<IndexMap<String, Value>>,
1976}
1977
1978impl Object {
1979    /// Creates a new `Object` value.
1980    ///
1981    /// Keys **must** be known WDL identifiers checked by the caller.
1982    pub(crate) fn new(members: IndexMap<String, Value>) -> Self {
1983        Self {
1984            members: Arc::new(members),
1985        }
1986    }
1987
1988    /// Returns an empty object.
1989    pub fn empty() -> Self {
1990        Self::new(IndexMap::default())
1991    }
1992
1993    /// Creates an object from an iterator of V1 AST metadata items.
1994    pub fn from_v1_metadata<N: TreeNode>(
1995        items: impl Iterator<Item = v1::MetadataObjectItem<N>>,
1996    ) -> Self {
1997        Object::new(
1998            items
1999                .map(|i| {
2000                    (
2001                        i.name().text().to_string(),
2002                        Value::from_v1_metadata(&i.value()),
2003                    )
2004                })
2005                .collect::<IndexMap<_, _>>(),
2006        )
2007    }
2008
2009    /// Gets the type of the `Object` value.
2010    pub fn ty(&self) -> Type {
2011        Type::Object
2012    }
2013
2014    /// Iterates the members of the object.
2015    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
2016        self.members.iter().map(|(k, v)| (k.as_str(), v))
2017    }
2018
2019    /// Iterates the keys of the object.
2020    pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
2021        self.members.keys().map(|k| k.as_str())
2022    }
2023
2024    /// Iterates the values of the object.
2025    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
2026        self.members.values()
2027    }
2028
2029    /// Determines if the object contains the given key.
2030    pub fn contains_key(&self, key: &str) -> bool {
2031        self.members.contains_key(key)
2032    }
2033
2034    /// Gets a value from the object by key.
2035    pub fn get(&self, key: &str) -> Option<&Value> {
2036        self.members.get(key)
2037    }
2038
2039    /// Returns the number of members in the object.
2040    pub fn len(&self) -> usize {
2041        self.members.len()
2042    }
2043
2044    /// Returns `true` if the object has no members.
2045    pub fn is_empty(&self) -> bool {
2046        self.members.is_empty()
2047    }
2048}
2049
2050impl fmt::Display for Object {
2051    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2052        write!(f, "object {{")?;
2053
2054        for (i, (k, v)) in self.iter().enumerate() {
2055            if i > 0 {
2056                write!(f, ", ")?;
2057            }
2058
2059            write!(f, "{k}: {v}")?;
2060        }
2061
2062        write!(f, "}}")
2063    }
2064}
2065
2066/// The inner representation of a struct value.
2067#[derive(Debug)]
2068struct StructInner {
2069    /// The type of the struct value.
2070    ty: Type,
2071    /// The name of the struct.
2072    ///
2073    /// Stored as `Arc<String>` to share the allocation with the analysis
2074    /// type system rather than cloning the string on every struct
2075    /// construction.
2076    name: Arc<String>,
2077    /// The members of the struct value.
2078    members: IndexMap<String, Value>,
2079}
2080
2081/// Represents a `Struct` value.
2082///
2083/// Struct values are cheap to clone.
2084#[derive(Debug, Clone)]
2085pub struct Struct(Arc<StructInner>);
2086
2087impl Struct {
2088    /// Creates a new struct value.
2089    ///
2090    /// Returns an error if the struct type does not contain a member of a given
2091    /// name or if a value does not coerce to the corresponding member's type.
2092    pub fn new<S, V>(ty: StructType, members: impl IntoIterator<Item = (S, V)>) -> Result<Self>
2093    where
2094        S: Into<String>,
2095        V: Into<Value>,
2096    {
2097        Self::new_with_context(None, ty, members)
2098    }
2099
2100    /// Creates a new struct value with the given evaluation context.
2101    ///
2102    /// Returns an error if the struct type does not contain a member of a given
2103    /// name or if a value does not coerce to the corresponding member's type.
2104    pub(crate) fn new_with_context<S, V>(
2105        context: Option<&dyn EvaluationContext>,
2106        ty: StructType,
2107        members: impl IntoIterator<Item = (S, V)>,
2108    ) -> Result<Self>
2109    where
2110        S: Into<String>,
2111        V: Into<Value>,
2112    {
2113        let mut members = members
2114            .into_iter()
2115            .map(|(n, v)| {
2116                let n = n.into();
2117                let v = v.into();
2118                let v = v
2119                    .coerce(
2120                        context,
2121                        ty.members().get(&n).ok_or_else(|| {
2122                            anyhow!("struct does not contain a member named `{n}`")
2123                        })?,
2124                    )
2125                    .with_context(|| format!("failed to coerce struct member `{n}`"))?;
2126                Ok((n, v))
2127            })
2128            .collect::<Result<IndexMap<_, _>>>()?;
2129
2130        for (name, ty) in ty.members().iter() {
2131            // Check for optional members that should be set to none
2132            if ty.is_optional() {
2133                if !members.contains_key(name) {
2134                    members.insert(name.clone(), Value::new_none(ty.clone()));
2135                }
2136            } else {
2137                // Check for a missing required member
2138                if !members.contains_key(name) {
2139                    bail!("missing a value for struct member `{name}`");
2140                }
2141            }
2142        }
2143
2144        let name = Arc::new(ty.name().to_string());
2145        Ok(Self::new_unchecked(ty, name, members))
2146    }
2147
2148    /// Constructs a new struct without checking the given members conform to
2149    /// the given type.
2150    ///
2151    /// # Panics
2152    ///
2153    /// Panics if the given type is not a struct type.
2154    pub(crate) fn new_unchecked(
2155        ty: impl Into<Type>,
2156        name: Arc<String>,
2157        members: impl Into<IndexMap<String, Value>>,
2158    ) -> Self {
2159        let ty = ty.into();
2160        assert!(ty.as_struct().is_some());
2161        Self(Arc::new(StructInner {
2162            ty: ty.require(),
2163            name,
2164            members: members.into(),
2165        }))
2166    }
2167
2168    /// Gets the type of the `Struct` value.
2169    pub fn ty(&self) -> Type {
2170        self.0.ty.clone()
2171    }
2172
2173    /// Gets the name of the struct.
2174    pub fn name(&self) -> &Arc<String> {
2175        &self.0.name
2176    }
2177
2178    /// Iterates the members of the struct.
2179    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&str, &Value)> {
2180        self.0.members.iter().map(|(k, v)| (k.as_str(), v))
2181    }
2182
2183    /// Iterates the keys of the struct.
2184    pub fn keys(&self) -> impl ExactSizeIterator<Item = &str> {
2185        self.0.members.keys().map(|k| k.as_str())
2186    }
2187
2188    /// Iterates the values of the struct.
2189    pub fn values(&self) -> impl ExactSizeIterator<Item = &Value> {
2190        self.0.members.values()
2191    }
2192
2193    /// Determines if the struct contains the given member name.
2194    pub fn contains_key(&self, key: &str) -> bool {
2195        self.0.members.contains_key(key)
2196    }
2197
2198    /// Gets a value from the struct by member name.
2199    pub fn get(&self, key: &str) -> Option<&Value> {
2200        self.0.members.get(key)
2201    }
2202}
2203
2204impl fmt::Display for Struct {
2205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2206        write!(f, "{name} {{", name = self.0.name)?;
2207
2208        for (i, (k, v)) in self.0.members.iter().enumerate() {
2209            if i > 0 {
2210                write!(f, ", ")?;
2211            }
2212
2213            write!(f, "{k}: {v}")?;
2214        }
2215
2216        write!(f, "}}")
2217    }
2218}
2219
2220/// The inner representation of an enum variant value.
2221#[derive(Debug)]
2222struct EnumVariantInner {
2223    /// The type of the enum containing this variant.
2224    enum_ty: EnumType,
2225    /// The index of the variant in the enum type.
2226    variant_index: usize,
2227    /// The value of the variant.
2228    value: Value,
2229}
2230
2231/// An enum variant value.
2232///
2233/// A variant enum is the name of the enum variant and the type of the enum from
2234/// which that variant can be looked up.
2235///
2236/// This type is cheaply cloneable.
2237#[derive(Debug, Clone)]
2238pub struct EnumVariant(Arc<EnumVariantInner>);
2239
2240impl PartialEq for EnumVariant {
2241    fn eq(&self, other: &Self) -> bool {
2242        self.0.enum_ty == other.0.enum_ty && self.0.variant_index == other.0.variant_index
2243    }
2244}
2245
2246impl EnumVariant {
2247    /// Attempts to create a new enum variant from a enum type and variant name.
2248    ///
2249    /// # Panics
2250    ///
2251    /// Panics if the given variant name is not present in the given enum type.
2252    pub fn new(enum_ty: impl Into<EnumType>, name: &str, value: impl Into<Value>) -> Self {
2253        let enum_ty = enum_ty.into();
2254
2255        let variant_index = enum_ty
2256            .variants()
2257            .iter()
2258            .position(|v| v == name)
2259            .expect("variant name must exist in enum type");
2260
2261        Self(Arc::new(EnumVariantInner {
2262            enum_ty,
2263            variant_index,
2264            value: value.into(),
2265        }))
2266    }
2267
2268    /// Gets the type of the enum.
2269    pub fn enum_ty(&self) -> EnumType {
2270        self.0.enum_ty.clone()
2271    }
2272
2273    /// Gets the name of the variant.
2274    pub fn name(&self) -> &str {
2275        &self.0.enum_ty.variants()[self.0.variant_index]
2276    }
2277
2278    /// Gets the value of the variant.
2279    pub fn value(&self) -> &Value {
2280        &self.0.value
2281    }
2282}
2283
2284/// Displays the variant name when an enum is used in string interpolation.
2285///
2286/// # Design Decision
2287///
2288/// When an enum variant is interpolated in a WDL string (e.g., `"~{Color.Red}"`
2289/// where `Red = "#FF0000"`), this implementation displays the **variant name**
2290/// (`"Red"`) rather than the underlying **value** (`"#FF0000"`).
2291///
2292/// This design choice treats enum variants as named identifiers, providing
2293/// stable, human-readable output that doesn't depend on the underlying value
2294/// representation. To access the underlying value explicitly, use the `value()`
2295/// standard library function.
2296///
2297/// # Example
2298///
2299/// ```wdl
2300/// enum Color {
2301///     Red = "#FF0000",
2302///     Green = "#00FF00"
2303/// }
2304///
2305/// String name = "~{Color.Red}"       # Produces "Red"
2306/// String hex_value = value(Color.Red)  # Produces "#FF0000"
2307/// ```
2308impl fmt::Display for EnumVariant {
2309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2310        write!(f, "{}", self.name())
2311    }
2312}
2313
2314/// Represents a compound value.
2315///
2316/// Compound values are cheap to clone.
2317#[derive(Debug, Clone)]
2318pub enum CompoundValue {
2319    /// The value is a `Pair` of values.
2320    Pair(Pair),
2321    /// The value is an `Array` of values.
2322    Array(Array),
2323    /// The value is a `Map` of values.
2324    Map(Map),
2325    /// The value is an `Object`.
2326    Object(Object),
2327    /// The value is a struct.
2328    Struct(Struct),
2329    /// The value is an enum variant.
2330    EnumVariant(EnumVariant),
2331}
2332
2333impl CompoundValue {
2334    /// Gets the type of the compound value.
2335    pub fn ty(&self) -> Type {
2336        match self {
2337            CompoundValue::Pair(v) => v.ty(),
2338            CompoundValue::Array(v) => v.ty(),
2339            CompoundValue::Map(v) => v.ty(),
2340            CompoundValue::Object(v) => v.ty(),
2341            CompoundValue::Struct(v) => v.ty(),
2342            CompoundValue::EnumVariant(v) => v.enum_ty().into(),
2343        }
2344    }
2345
2346    /// Gets the value as a `Pair`.
2347    ///
2348    /// Returns `None` if the value is not a `Pair`.
2349    pub fn as_pair(&self) -> Option<&Pair> {
2350        match self {
2351            Self::Pair(v) => Some(v),
2352            _ => None,
2353        }
2354    }
2355
2356    /// Unwraps the value into a `Pair`.
2357    ///
2358    /// # Panics
2359    ///
2360    /// Panics if the value is not a `Pair`.
2361    pub fn unwrap_pair(self) -> Pair {
2362        match self {
2363            Self::Pair(v) => v,
2364            _ => panic!("value is not a pair"),
2365        }
2366    }
2367
2368    /// Gets the value as an `Array`.
2369    ///
2370    /// Returns `None` if the value is not an `Array`.
2371    pub fn as_array(&self) -> Option<&Array> {
2372        match self {
2373            Self::Array(v) => Some(v),
2374            _ => None,
2375        }
2376    }
2377
2378    /// Unwraps the value into an `Array`.
2379    ///
2380    /// # Panics
2381    ///
2382    /// Panics if the value is not an `Array`.
2383    pub fn unwrap_array(self) -> Array {
2384        match self {
2385            Self::Array(v) => v,
2386            _ => panic!("value is not an array"),
2387        }
2388    }
2389
2390    /// Gets the value as a `Map`.
2391    ///
2392    /// Returns `None` if the value is not a `Map`.
2393    pub fn as_map(&self) -> Option<&Map> {
2394        match self {
2395            Self::Map(v) => Some(v),
2396            _ => None,
2397        }
2398    }
2399
2400    /// Unwraps the value into a `Map`.
2401    ///
2402    /// # Panics
2403    ///
2404    /// Panics if the value is not a `Map`.
2405    pub fn unwrap_map(self) -> Map {
2406        match self {
2407            Self::Map(v) => v,
2408            _ => panic!("value is not a map"),
2409        }
2410    }
2411
2412    /// Gets the value as an `Object`.
2413    ///
2414    /// Returns `None` if the value is not an `Object`.
2415    pub fn as_object(&self) -> Option<&Object> {
2416        match self {
2417            Self::Object(v) => Some(v),
2418            _ => None,
2419        }
2420    }
2421
2422    /// Unwraps the value into an `Object`.
2423    ///
2424    /// # Panics
2425    ///
2426    /// Panics if the value is not an `Object`.
2427    pub fn unwrap_object(self) -> Object {
2428        match self {
2429            Self::Object(v) => v,
2430            _ => panic!("value is not an object"),
2431        }
2432    }
2433
2434    /// Gets the value as a `Struct`.
2435    ///
2436    /// Returns `None` if the value is not a `Struct`.
2437    pub fn as_struct(&self) -> Option<&Struct> {
2438        match self {
2439            Self::Struct(v) => Some(v),
2440            _ => None,
2441        }
2442    }
2443
2444    /// Unwraps the value into a `Struct`.
2445    ///
2446    /// # Panics
2447    ///
2448    /// Panics if the value is not a `Map`.
2449    pub fn unwrap_struct(self) -> Struct {
2450        match self {
2451            Self::Struct(v) => v,
2452            _ => panic!("value is not a struct"),
2453        }
2454    }
2455
2456    /// Gets the value as an `EnumVariant`.
2457    ///
2458    /// Returns `None` if the value is not an `EnumVariant`.
2459    pub fn as_enum_variant(&self) -> Option<&EnumVariant> {
2460        match self {
2461            Self::EnumVariant(v) => Some(v),
2462            _ => None,
2463        }
2464    }
2465
2466    /// Unwraps the value into an `EnumVariant`.
2467    ///
2468    /// # Panics
2469    ///
2470    /// Panics if the value is not an `EnumVariant`.
2471    pub fn unwrap_enum_variant(self) -> EnumVariant {
2472        match self {
2473            Self::EnumVariant(v) => v,
2474            _ => panic!("value is not an enum"),
2475        }
2476    }
2477
2478    /// Compares two compound values for equality based on the WDL
2479    /// specification.
2480    ///
2481    /// Returns `None` if the two compound values cannot be compared for
2482    /// equality.
2483    pub fn equals(left: &Self, right: &Self) -> Option<bool> {
2484        // The values must have type equivalence to compare for compound values
2485        // Coercion doesn't take place for this check
2486        if left.ty() != right.ty() {
2487            return None;
2488        }
2489
2490        match (left, right) {
2491            (Self::Pair(left), Self::Pair(right)) => Some(
2492                Value::equals(left.left(), right.left())?
2493                    && Value::equals(left.right(), right.right())?,
2494            ),
2495            (CompoundValue::Array(left), CompoundValue::Array(right)) => Some(
2496                left.len() == right.len()
2497                    && left
2498                        .as_slice()
2499                        .iter()
2500                        .zip(right.as_slice())
2501                        .all(|(l, r)| Value::equals(l, r).unwrap_or(false)),
2502            ),
2503            (CompoundValue::Map(left), CompoundValue::Map(right)) => Some(
2504                left.len() == right.len()
2505                    // Maps are ordered, so compare via iteration
2506                    && left.iter().zip(right.iter()).all(|((lk, lv), (rk, rv))| {
2507                        lk == rk && Value::equals(lv, rv).unwrap_or(false)
2508                    }),
2509            ),
2510            (CompoundValue::Object(left), CompoundValue::Object(right)) => Some(
2511                left.len() == right.len()
2512                    && left.iter().all(|(k, left)| match right.get(k) {
2513                        Some(right) => Value::equals(left, right).unwrap_or(false),
2514                        None => false,
2515                    }),
2516            ),
2517            (CompoundValue::Struct(left), CompoundValue::Struct(right)) => Some(
2518                left.0.members.len() == right.0.members.len()
2519                    && left
2520                        .0
2521                        .members
2522                        .iter()
2523                        .all(|(k, lv)| match right.0.members.get(k) {
2524                            Some(rv) => Value::equals(lv, rv).unwrap_or(false),
2525                            None => false,
2526                        }),
2527            ),
2528            (CompoundValue::EnumVariant(left), CompoundValue::EnumVariant(right)) => {
2529                Some(left.enum_ty() == right.enum_ty() && left.name() == right.name())
2530            }
2531            _ => None,
2532        }
2533    }
2534
2535    /// Visits any paths referenced by this value.
2536    ///
2537    /// The callback is invoked for each `File` and `Directory` value referenced
2538    /// by this value.
2539    fn visit_paths<F>(&self, cb: &mut F) -> Result<()>
2540    where
2541        F: FnMut(bool, &HostPath) -> Result<()> + Send + Sync,
2542    {
2543        match self {
2544            Self::Pair(pair) => {
2545                pair.left().visit_paths(cb)?;
2546                pair.right().visit_paths(cb)?;
2547            }
2548            Self::Array(array) => {
2549                for v in array.as_slice() {
2550                    v.visit_paths(cb)?;
2551                }
2552            }
2553            Self::Map(map) => {
2554                for (k, v) in map.iter() {
2555                    match k {
2556                        PrimitiveValue::File(path) => cb(true, path)?,
2557                        PrimitiveValue::Directory(path) => cb(false, path)?,
2558                        _ => {}
2559                    }
2560
2561                    v.visit_paths(cb)?;
2562                }
2563            }
2564            Self::Object(object) => {
2565                for v in object.values() {
2566                    v.visit_paths(cb)?;
2567                }
2568            }
2569            Self::Struct(s) => {
2570                for v in s.values() {
2571                    v.visit_paths(cb)?;
2572                }
2573            }
2574            Self::EnumVariant(e) => {
2575                e.value().visit_paths(cb)?;
2576            }
2577        }
2578
2579        Ok(())
2580    }
2581
2582    /// Like [`Value::resolve_paths()`], but for recurring into
2583    /// [`CompoundValue`]s.
2584    fn resolve_paths<'a, F>(
2585        &'a self,
2586        base_dir: Option<&'a Path>,
2587        transferer: Option<&'a dyn Transferer>,
2588        translate: &'a F,
2589    ) -> BoxFuture<'a, Result<Self>>
2590    where
2591        F: Fn(&HostPath) -> Result<HostPath> + Send + Sync,
2592    {
2593        async move {
2594            match self {
2595                Self::Pair(pair) => {
2596                    let ty = pair.0.ty.as_pair().expect("should be a pair type");
2597                    let (left_optional, right_optional) =
2598                        (ty.left_type().is_optional(), ty.right_type().is_optional());
2599                    let fst = pair
2600                        .0
2601                        .left
2602                        .resolve_paths(left_optional, base_dir, transferer, translate)
2603                        .await?;
2604                    let snd = pair
2605                        .0
2606                        .right
2607                        .resolve_paths(right_optional, base_dir, transferer, translate)
2608                        .await?;
2609                    Ok(Self::Pair(Pair::new_unchecked(ty.clone(), fst, snd)))
2610                }
2611                Self::Array(array) => {
2612                    let ty = array.0.ty.as_array().expect("should be an array type");
2613                    let optional = ty.element_type().is_optional();
2614                    if !array.0.elements.is_empty() {
2615                        let resolved_elements = futures::stream::iter(array.0.elements.iter())
2616                            .then(|v| v.resolve_paths(optional, base_dir, transferer, translate))
2617                            .try_collect::<Vec<Value>>()
2618                            .await?;
2619                        Ok(Self::Array(Array::new_unchecked(
2620                            ty.clone(),
2621                            resolved_elements,
2622                        )))
2623                    } else {
2624                        Ok(self.clone())
2625                    }
2626                }
2627                Self::Map(map) => {
2628                    let ty = map.0.ty.as_map().expect("should be a map type").clone();
2629                    let (key_optional, value_optional) =
2630                        (ty.key_type().is_optional(), ty.value_type().is_optional());
2631                    if !map.0.elements.is_empty() {
2632                        let resolved_elements = futures::stream::iter(map.0.elements.iter())
2633                            .then(async |(k, v)| {
2634                                let resolved_key = Value::from(k.clone())
2635                                    .resolve_paths(key_optional, base_dir, transferer, translate)
2636                                    .await?
2637                                    .as_primitive()
2638                                    .cloned()
2639                                    .expect("key should be primitive");
2640                                let resolved_value = v
2641                                    .resolve_paths(value_optional, base_dir, transferer, translate)
2642                                    .await?;
2643                                Ok::<_, anyhow::Error>((resolved_key, resolved_value))
2644                            })
2645                            .try_collect()
2646                            .await?;
2647                        Ok(Self::Map(Map::new_unchecked(ty, resolved_elements)))
2648                    } else {
2649                        Ok(Self::Map(Map::new_unchecked(ty, IndexMap::new())))
2650                    }
2651                }
2652                Self::Object(object) => {
2653                    if object.is_empty() {
2654                        Ok(self.clone())
2655                    } else {
2656                        let resolved_members = futures::stream::iter(object.iter())
2657                            .then(async |(n, v)| {
2658                                let resolved = v
2659                                    .resolve_paths(false, base_dir, transferer, translate)
2660                                    .await?;
2661                                Ok::<_, anyhow::Error>((n.to_string(), resolved))
2662                            })
2663                            .try_collect()
2664                            .await?;
2665                        Ok(Self::Object(Object::new(resolved_members)))
2666                    }
2667                }
2668                Self::Struct(s) => {
2669                    let ty = s.0.ty.as_struct().expect("should be a struct type");
2670                    let name = s.name().clone();
2671                    let resolved_members: IndexMap<String, Value> = futures::stream::iter(s.iter())
2672                        .then(async |(n, v)| {
2673                            let resolved = v
2674                                .resolve_paths(
2675                                    ty.members()[n].is_optional(),
2676                                    base_dir,
2677                                    transferer,
2678                                    translate,
2679                                )
2680                                .await?;
2681                            Ok::<_, anyhow::Error>((n.to_string(), resolved))
2682                        })
2683                        .try_collect()
2684                        .await?;
2685                    Ok(Self::Struct(Struct::new_unchecked(
2686                        ty.clone(),
2687                        name,
2688                        resolved_members,
2689                    )))
2690                }
2691                Self::EnumVariant(e) => {
2692                    let optional = e.enum_ty().inner_value_type().is_optional();
2693                    let value =
2694                        e.0.value
2695                            .resolve_paths(optional, base_dir, transferer, translate)
2696                            .await?;
2697                    Ok(Self::EnumVariant(EnumVariant::new(
2698                        e.0.enum_ty.clone(),
2699                        e.name(),
2700                        value,
2701                    )))
2702                }
2703            }
2704        }
2705        .boxed()
2706    }
2707}
2708
2709impl fmt::Display for CompoundValue {
2710    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2711        match self {
2712            Self::Pair(v) => v.fmt(f),
2713            Self::Array(v) => v.fmt(f),
2714            Self::Map(v) => v.fmt(f),
2715            Self::Object(v) => v.fmt(f),
2716            Self::Struct(v) => v.fmt(f),
2717            Self::EnumVariant(v) => v.fmt(f),
2718        }
2719    }
2720}
2721
2722impl Coercible for CompoundValue {
2723    fn coerce(&self, context: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
2724        if target.is_union() || target.is_none() || self.ty().eq(target) {
2725            return Ok(self.clone());
2726        }
2727
2728        if let Type::Compound(target_ty, _) = target {
2729            match (self, target_ty) {
2730                // Array[X] -> Array[Y](+) where X -> Y
2731                (Self::Array(v), CompoundType::Array(target_ty)) => {
2732                    // Don't allow coercion when the source is empty but the target has the
2733                    // non-empty qualifier
2734                    if v.is_empty() && target_ty.is_non_empty() {
2735                        bail!("cannot coerce empty array value to non-empty array {target:#}");
2736                    }
2737
2738                    return Ok(Self::Array(Array::new_with_context(
2739                        context,
2740                        target_ty.clone(),
2741                        v.as_slice().iter().cloned(),
2742                    )?));
2743                }
2744                // Map[W, Y] -> Map[X, Z] where W -> X and Y -> Z
2745                (Self::Map(v), CompoundType::Map(target_ty)) => {
2746                    return Ok(Self::Map(Map::new_with_context(
2747                        context,
2748                        target_ty.clone(),
2749                        v.iter().map(|(k, v)| (k.clone(), v.clone())),
2750                    )?));
2751                }
2752                // Pair[W, Y] -> Pair[X, Z] where W -> X and Y -> Z
2753                (Self::Pair(v), CompoundType::Pair(target_ty)) => {
2754                    return Ok(Self::Pair(Pair::new_with_context(
2755                        context,
2756                        target_ty.clone(),
2757                        v.0.left.clone(),
2758                        v.0.right.clone(),
2759                    )?));
2760                }
2761                // Map[X, Y] -> Struct where: X -> String
2762                (Self::Map(v), CompoundType::Custom(CustomType::Struct(target_ty))) => {
2763                    let len = v.len();
2764                    let expected_len = target_ty.members().len();
2765
2766                    if len != expected_len {
2767                        bail!(
2768                            "cannot coerce a map of {len} element{s1} to {target:#} as the struct \
2769                             has {expected_len} member{s2}",
2770                            s1 = if len == 1 { "" } else { "s" },
2771                            s2 = if expected_len == 1 { "" } else { "s" }
2772                        );
2773                    }
2774
2775                    return Ok(Self::Struct(Struct::new_unchecked(
2776                        target.clone(),
2777                        target_ty.name().clone(),
2778                        v.iter()
2779                            .map(|(k, v)| {
2780                                let k = k
2781                                    .coerce(context, &PrimitiveType::String.into())
2782                                    .with_context(|| {
2783                                        format!(
2784                                            "cannot coerce a map of {map_type:#} to {target:#} as \
2785                                             the key type cannot coerce to type `String`",
2786                                            map_type = v.ty()
2787                                        )
2788                                    })?
2789                                    .unwrap_string();
2790                                let ty =
2791                                    target_ty.members().get(k.as_ref()).with_context(|| {
2792                                        format!(
2793                                            "cannot coerce a map with key `{k}` to {target:#} as \
2794                                             the struct does not contain a member with that name"
2795                                        )
2796                                    })?;
2797                                let v = v.coerce(context, ty).with_context(|| {
2798                                    format!("failed to coerce value of map key `{k}")
2799                                })?;
2800                                Ok((k.to_string(), v))
2801                            })
2802                            .collect::<Result<IndexMap<_, _>>>()?,
2803                    )));
2804                }
2805                // Struct -> Map[X, Y] where: String -> X
2806                (Self::Struct(s), CompoundType::Map(map_ty)) => {
2807                    let key_ty = map_ty.key_type();
2808                    if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
2809                        bail!(
2810                            "cannot coerce a struct to {target:#} as key {key_ty:#} cannot be \
2811                             coerced from type `String`"
2812                        );
2813                    }
2814
2815                    let value_ty = map_ty.value_type();
2816                    return Ok(Self::Map(Map::new_unchecked(
2817                        target.clone(),
2818                        s.0.members
2819                            .iter()
2820                            .map(|(n, v)| {
2821                                let v = v
2822                                    .coerce(context, value_ty)
2823                                    .with_context(|| format!("failed to coerce member `{n}`"))?;
2824                                Ok((
2825                                    PrimitiveValue::new_string(n)
2826                                        .coerce(context, key_ty)
2827                                        .expect("should coerce"),
2828                                    v,
2829                                ))
2830                            })
2831                            .collect::<Result<_>>()?,
2832                    )));
2833                }
2834                // Object -> Map[X, Y] where: String -> X
2835                (Self::Object(object), CompoundType::Map(map_ty)) => {
2836                    let key_ty = map_ty.key_type();
2837                    if !Type::from(PrimitiveType::String).is_coercible_to(key_ty) {
2838                        bail!(
2839                            "cannot coerce an object to {target:#} as key {key_ty:#} cannot be \
2840                             coerced from type `String`"
2841                        );
2842                    }
2843
2844                    let value_ty = map_ty.value_type();
2845                    return Ok(Self::Map(Map::new_unchecked(
2846                        target.clone(),
2847                        object
2848                            .iter()
2849                            .map(|(n, v)| {
2850                                let v = v
2851                                    .coerce(context, value_ty)
2852                                    .with_context(|| format!("failed to coerce member `{n}`"))?;
2853                                Ok((
2854                                    PrimitiveValue::new_string(n)
2855                                        .coerce(context, key_ty)
2856                                        .expect("should coerce"),
2857                                    v,
2858                                ))
2859                            })
2860                            .collect::<Result<_>>()?,
2861                    )));
2862                }
2863                // Object -> Struct
2864                (Self::Object(v), CompoundType::Custom(CustomType::Struct(struct_ty))) => {
2865                    return Ok(Self::Struct(Struct::new_with_context(
2866                        context,
2867                        struct_ty.clone(),
2868                        v.iter().map(|(k, v)| (k, v.clone())),
2869                    )?));
2870                }
2871                // Struct -> Struct
2872                (Self::Struct(v), CompoundType::Custom(CustomType::Struct(struct_ty))) => {
2873                    let len = v.0.members.len();
2874                    let expected_len = struct_ty.members().len();
2875
2876                    if len != expected_len {
2877                        bail!(
2878                            "cannot coerce a struct of {len} members{s1} to struct type \
2879                             `{target:#}` as the target struct has {expected_len} member{s2}",
2880                            s1 = if len == 1 { "" } else { "s" },
2881                            s2 = if expected_len == 1 { "" } else { "s" }
2882                        );
2883                    }
2884
2885                    return Ok(Self::Struct(Struct::new_unchecked(
2886                        target.clone(),
2887                        struct_ty.name().clone(),
2888                        v.0.members
2889                            .iter()
2890                            .map(|(k, v)| {
2891                                let ty = struct_ty.members().get(k).ok_or_else(|| {
2892                                    anyhow!(
2893                                        "cannot coerce a struct with member `{k}` to struct type \
2894                                         `{target:#}` as the target struct does not contain a \
2895                                         member with that name",
2896                                    )
2897                                })?;
2898                                let v = v
2899                                    .coerce(context, ty)
2900                                    .with_context(|| format!("failed to coerce member `{k}`"))?;
2901                                Ok((k.clone(), v))
2902                            })
2903                            .collect::<Result<IndexMap<_, _>>>()?,
2904                    )));
2905                }
2906                _ => {}
2907            }
2908        }
2909
2910        if let Type::Object = target {
2911            match self {
2912                // Map[X, Y] -> Object where: X -> String
2913                Self::Map(v) => {
2914                    return Ok(Self::Object(Object::new(
2915                        v.iter()
2916                            .map(|(k, v)| {
2917                                let k = k
2918                                    .coerce(context, &PrimitiveType::String.into())
2919                                    .with_context(|| {
2920                                        format!(
2921                                            "cannot coerce a map of {map_type:#} to type `Object` \
2922                                             as the key type cannot coerce to type `String`",
2923                                            map_type = v.ty()
2924                                        )
2925                                    })?
2926                                    .unwrap_string();
2927                                Ok((k.to_string(), v.clone()))
2928                            })
2929                            .collect::<Result<IndexMap<_, _>>>()?,
2930                    )));
2931                }
2932                // Struct -> Object
2933                Self::Struct(v) => {
2934                    return Ok(Self::Object(Object {
2935                        members: Arc::new(v.0.members.clone()),
2936                    }));
2937                }
2938                _ => {}
2939            };
2940        }
2941
2942        bail!(
2943            "cannot coerce a value of {ty:#} to {target:#}",
2944            ty = self.ty()
2945        );
2946    }
2947}
2948
2949impl From<Pair> for CompoundValue {
2950    fn from(value: Pair) -> Self {
2951        Self::Pair(value)
2952    }
2953}
2954
2955impl From<Array> for CompoundValue {
2956    fn from(value: Array) -> Self {
2957        Self::Array(value)
2958    }
2959}
2960
2961impl From<Map> for CompoundValue {
2962    fn from(value: Map) -> Self {
2963        Self::Map(value)
2964    }
2965}
2966
2967impl From<Object> for CompoundValue {
2968    fn from(value: Object) -> Self {
2969        Self::Object(value)
2970    }
2971}
2972
2973impl From<Struct> for CompoundValue {
2974    fn from(value: Struct) -> Self {
2975        Self::Struct(value)
2976    }
2977}
2978
2979/// Represents a hidden value.
2980///
2981/// Hidden values are cheap to clone.
2982#[derive(Debug, Clone)]
2983pub enum HiddenValue {
2984    /// The value is a hints value.
2985    ///
2986    /// Hints values only appear in a task hints section in WDL 1.2.
2987    Hints(HintsValue),
2988    /// The value is an input value.
2989    ///
2990    /// Input values only appear in a task hints section in WDL 1.2.
2991    Input(InputValue),
2992    /// The value is an output value.
2993    ///
2994    /// Output values only appear in a task hints section in WDL 1.2.
2995    Output(OutputValue),
2996    /// The value is a task variable before evaluation.
2997    ///
2998    /// This value occurs during requirements, hints, and runtime section
2999    /// evaluation in WDL 1.3+ tasks.
3000    TaskPreEvaluation(TaskPreEvaluationValue),
3001    /// The value is a task variable after evaluation.
3002    ///
3003    /// This value occurs during command and output section evaluation in
3004    /// WDL 1.2+ tasks.
3005    TaskPostEvaluation(TaskPostEvaluationValue),
3006    /// The value is a previous requirements value.
3007    ///
3008    /// This value contains the previous attempt's requirements and is available
3009    /// in WDL 1.3+ via `task.previous`.
3010    PreviousTaskData(PreviousTaskDataValue),
3011}
3012
3013impl HiddenValue {
3014    /// Gets the type of the value.
3015    pub fn ty(&self) -> Type {
3016        match self {
3017            Self::Hints(_) => Type::Hidden(HiddenType::Hints),
3018            Self::Input(_) => Type::Hidden(HiddenType::Input),
3019            Self::Output(_) => Type::Hidden(HiddenType::Output),
3020            Self::TaskPreEvaluation(_) => Type::Hidden(HiddenType::TaskPreEvaluation),
3021            Self::TaskPostEvaluation(_) => Type::Hidden(HiddenType::TaskPostEvaluation),
3022            Self::PreviousTaskData(_) => Type::Hidden(HiddenType::PreviousTaskData),
3023        }
3024    }
3025}
3026
3027impl fmt::Display for HiddenValue {
3028    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3029        match self {
3030            Self::Hints(v) => v.fmt(f),
3031            Self::Input(v) => v.fmt(f),
3032            Self::Output(v) => v.fmt(f),
3033            Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => write!(f, "task"),
3034            Self::PreviousTaskData(_) => write!(f, "task.previous"),
3035        }
3036    }
3037}
3038
3039impl Coercible for HiddenValue {
3040    fn coerce(&self, _: Option<&dyn EvaluationContext>, target: &Type) -> Result<Self> {
3041        match self {
3042            Self::Hints(_) => {
3043                if matches!(target, Type::Hidden(HiddenType::Hints)) {
3044                    return Ok(self.clone());
3045                }
3046
3047                bail!("hints values cannot be coerced to any other type");
3048            }
3049            Self::Input(_) => {
3050                if matches!(target, Type::Hidden(HiddenType::Input)) {
3051                    return Ok(self.clone());
3052                }
3053
3054                bail!("input values cannot be coerced to any other type");
3055            }
3056            Self::Output(_) => {
3057                if matches!(target, Type::Hidden(HiddenType::Output)) {
3058                    return Ok(self.clone());
3059                }
3060
3061                bail!("output values cannot be coerced to any other type");
3062            }
3063            Self::TaskPreEvaluation(_) | Self::TaskPostEvaluation(_) => {
3064                if matches!(
3065                    target,
3066                    Type::Hidden(HiddenType::TaskPreEvaluation)
3067                        | Type::Hidden(HiddenType::TaskPostEvaluation)
3068                ) {
3069                    return Ok(self.clone());
3070                }
3071
3072                bail!("task variables cannot be coerced to any other type");
3073            }
3074            Self::PreviousTaskData(_) => {
3075                if matches!(target, Type::Hidden(HiddenType::PreviousTaskData)) {
3076                    return Ok(self.clone());
3077                }
3078
3079                bail!("previous task data values cannot be coerced to any other type");
3080            }
3081        }
3082    }
3083}
3084
3085/// Immutable data for task values after requirements evaluation (WDL 1.2+).
3086///
3087/// Contains all evaluated requirement fields.
3088#[derive(Debug, Clone)]
3089pub(crate) struct TaskPostEvaluationData {
3090    /// The container image that was actually used for execution, if the task
3091    /// runs in a container.
3092    container: Option<Arc<String>>,
3093    /// The allocated number of cpus for the task.
3094    cpu: f64,
3095    /// The allocated memory (in bytes) for the task.
3096    memory: i64,
3097    /// The GPU allocations for the task.
3098    ///
3099    /// An array with one specification per allocated GPU; the specification is
3100    /// execution engine-specific.
3101    gpu: Array,
3102    /// The FPGA allocations for the task.
3103    ///
3104    /// An array with one specification per allocated FPGA; the specification is
3105    /// execution engine-specific.
3106    fpga: Array,
3107    /// The disk allocations for the task.
3108    ///
3109    /// A map with one entry for each disk mount point.
3110    ///
3111    /// The key is the mount point and the value is the initial amount of disk
3112    /// space allocated, in bytes.
3113    disks: Map,
3114    /// The maximum number of retries for the task.
3115    max_retries: i64,
3116}
3117
3118/// Represents a `task.previous` value containing data from a previous attempt.
3119///
3120/// The value is cheaply cloned.
3121#[derive(Debug, Clone)]
3122pub struct PreviousTaskDataValue(Option<Arc<TaskPostEvaluationData>>);
3123
3124impl PreviousTaskDataValue {
3125    /// Creates a new previous task data from task post-evaluation data.
3126    pub(crate) fn new(data: Arc<TaskPostEvaluationData>) -> Self {
3127        Self(Some(data))
3128    }
3129
3130    /// Creates an empty previous task data (for first attempt).
3131    pub(crate) fn empty() -> Self {
3132        Self(None)
3133    }
3134
3135    /// Gets the value of a field in the previous task data.
3136    ///
3137    /// Returns `None` if the field name is not valid for previous task data.
3138    ///
3139    /// Returns `Some(Value::None)` for valid fields when there is no previous
3140    /// data (first attempt).
3141    pub fn field(&self, name: &str) -> Option<Value> {
3142        match name {
3143            TASK_FIELD_MEMORY => Some(
3144                self.0
3145                    .as_ref()
3146                    .map(|data| Value::from(data.memory))
3147                    .unwrap_or_else(|| {
3148                        Value::new_none(Type::from(PrimitiveType::Integer).optional())
3149                    }),
3150            ),
3151            TASK_FIELD_CPU => Some(
3152                self.0
3153                    .as_ref()
3154                    .map(|data| Value::from(data.cpu))
3155                    .unwrap_or_else(|| {
3156                        Value::new_none(Type::from(PrimitiveType::Float).optional())
3157                    }),
3158            ),
3159            TASK_FIELD_CONTAINER => Some(
3160                self.0
3161                    .as_ref()
3162                    .and_then(|data| {
3163                        data.container
3164                            .as_ref()
3165                            .map(|c| PrimitiveValue::String(c.clone()).into())
3166                    })
3167                    .unwrap_or_else(|| {
3168                        Value::new_none(Type::from(PrimitiveType::String).optional())
3169                    }),
3170            ),
3171            TASK_FIELD_GPU => Some(
3172                self.0
3173                    .as_ref()
3174                    .map(|data| Value::from(data.gpu.clone()))
3175                    .unwrap_or_else(|| {
3176                        Value::new_none(Type::Compound(
3177                            CompoundType::Array(ArrayType::new(PrimitiveType::String)),
3178                            true,
3179                        ))
3180                    }),
3181            ),
3182            TASK_FIELD_FPGA => Some(
3183                self.0
3184                    .as_ref()
3185                    .map(|data| Value::from(data.fpga.clone()))
3186                    .unwrap_or_else(|| {
3187                        Value::new_none(Type::Compound(
3188                            CompoundType::Array(ArrayType::new(PrimitiveType::String)),
3189                            true,
3190                        ))
3191                    }),
3192            ),
3193            TASK_FIELD_DISKS => Some(
3194                self.0
3195                    .as_ref()
3196                    .map(|data| Value::from(data.disks.clone()))
3197                    .unwrap_or_else(|| {
3198                        Value::new_none(Type::Compound(
3199                            MapType::new(PrimitiveType::String, PrimitiveType::Integer).into(),
3200                            true,
3201                        ))
3202                    }),
3203            ),
3204            TASK_FIELD_MAX_RETRIES => Some(
3205                self.0
3206                    .as_ref()
3207                    .map(|data| Value::from(data.max_retries))
3208                    .unwrap_or_else(|| {
3209                        Value::new_none(Type::from(PrimitiveType::Integer).optional())
3210                    }),
3211            ),
3212            _ => None,
3213        }
3214    }
3215}
3216
3217/// The inner representation of a pre-evaluation task value.
3218#[derive(Debug, Clone)]
3219struct TaskPreEvaluationInner {
3220    /// The task name.
3221    name: Arc<String>,
3222    /// The task id.
3223    id: Arc<String>,
3224    /// The current task attempt count.
3225    ///
3226    /// The value must be 0 the first time the task is executed and incremented
3227    /// by 1 each time the task is retried (if any).
3228    attempt: i64,
3229    /// The task's `meta` section as an object.
3230    meta: Object,
3231    /// The tasks's `parameter_meta` section as an object.
3232    parameter_meta: Object,
3233    /// The task's extension metadata.
3234    ext: Object,
3235    /// The previous attempt's task data (WDL 1.3+).
3236    ///
3237    /// Contains the evaluated task data from the previous attempt.
3238    ///
3239    /// On the first attempt, this is empty.
3240    previous: PreviousTaskDataValue,
3241}
3242
3243/// Represents a `task` variable value before requirements evaluation (WDL
3244/// 1.3+).
3245///
3246/// Only exposes `name`, `id`, `attempt`, `previous`, and metadata fields.
3247///
3248/// Task values are cheap to clone.
3249#[derive(Debug, Clone)]
3250pub struct TaskPreEvaluationValue(Arc<TaskPreEvaluationInner>);
3251
3252impl TaskPreEvaluationValue {
3253    /// Constructs a new pre-evaluation task value with the given name and
3254    /// identifier.
3255    pub(crate) fn new(
3256        name: impl Into<String>,
3257        id: impl Into<String>,
3258        attempt: i64,
3259        meta: Object,
3260        parameter_meta: Object,
3261        ext: Object,
3262    ) -> Self {
3263        Self(Arc::new(TaskPreEvaluationInner {
3264            name: Arc::new(name.into()),
3265            id: Arc::new(id.into()),
3266            meta,
3267            parameter_meta,
3268            ext,
3269            attempt,
3270            previous: PreviousTaskDataValue::empty(),
3271        }))
3272    }
3273
3274    /// Sets the previous task data for retry attempts.
3275    pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
3276        Arc::get_mut(&mut self.0)
3277            .expect("task value must be uniquely owned to mutate")
3278            .previous = PreviousTaskDataValue::new(data);
3279    }
3280
3281    /// Gets the task name.
3282    pub fn name(&self) -> &Arc<String> {
3283        &self.0.name
3284    }
3285
3286    /// Gets the unique ID of the task.
3287    pub fn id(&self) -> &Arc<String> {
3288        &self.0.id
3289    }
3290
3291    /// Gets current task attempt count.
3292    pub fn attempt(&self) -> i64 {
3293        self.0.attempt
3294    }
3295
3296    /// Accesses a field of the task value by name.
3297    ///
3298    /// Returns `None` if the name is not a known field name.
3299    pub fn field(&self, name: &str) -> Option<Value> {
3300        match name {
3301            TASK_FIELD_NAME => Some(PrimitiveValue::String(self.0.name.clone()).into()),
3302            TASK_FIELD_ID => Some(PrimitiveValue::String(self.0.id.clone()).into()),
3303            TASK_FIELD_ATTEMPT => Some(self.0.attempt.into()),
3304            TASK_FIELD_META => Some(self.0.meta.clone().into()),
3305            TASK_FIELD_PARAMETER_META => Some(self.0.parameter_meta.clone().into()),
3306            TASK_FIELD_EXT => Some(self.0.ext.clone().into()),
3307            TASK_FIELD_PREVIOUS => {
3308                Some(HiddenValue::PreviousTaskData(self.0.previous.clone()).into())
3309            }
3310            _ => None,
3311        }
3312    }
3313}
3314
3315/// The inner representation of a post-evaluation task value.
3316#[derive(Debug, Clone)]
3317struct TaskPostEvaluationInner {
3318    /// The immutable data for task values including evaluated requirements.
3319    data: Arc<TaskPostEvaluationData>,
3320    /// The task name.
3321    name: Arc<String>,
3322    /// The task id.
3323    id: Arc<String>,
3324    /// The current task attempt count.
3325    ///
3326    /// The value must be 0 the first time the task is executed and incremented
3327    /// by 1 each time the task is retried (if any).
3328    attempt: i64,
3329    /// The task's `meta` section as an object.
3330    meta: Object,
3331    /// The tasks's `parameter_meta` section as an object.
3332    parameter_meta: Object,
3333    /// The task's extension metadata.
3334    ext: Object,
3335    /// The task's return code.
3336    ///
3337    /// Initially set to [`None`], but set after task execution completes.
3338    return_code: Option<i64>,
3339    /// The time by which the task must be completed, as a Unix time stamp.
3340    ///
3341    /// A value of `None` indicates there is no deadline.
3342    end_time: Option<i64>,
3343    /// The previous attempt's task data (WDL 1.3+).
3344    ///
3345    /// Contains the evaluated task data from the previous attempt.
3346    ///
3347    /// On the first attempt, this is empty.
3348    previous: PreviousTaskDataValue,
3349}
3350
3351/// Represents a `task` variable value after requirements evaluation (WDL 1.2+).
3352///
3353/// Exposes all task fields including evaluated constraints.
3354///
3355/// Task values are cheap to clone.
3356#[derive(Debug, Clone)]
3357pub struct TaskPostEvaluationValue(Arc<TaskPostEvaluationInner>);
3358
3359impl TaskPostEvaluationValue {
3360    /// Constructs a new post-evaluation task value with the given name,
3361    /// identifier, and constraints.
3362    #[allow(clippy::too_many_arguments)]
3363    pub(crate) fn new(
3364        name: impl Into<String>,
3365        id: impl Into<String>,
3366        constraints: &TaskExecutionConstraints,
3367        max_retries: i64,
3368        attempt: i64,
3369        meta: Object,
3370        parameter_meta: Object,
3371        ext: Object,
3372    ) -> Self {
3373        Self(Arc::new(TaskPostEvaluationInner {
3374            name: Arc::new(name.into()),
3375            id: Arc::new(id.into()),
3376            data: Arc::new(TaskPostEvaluationData {
3377                // NOTE: initialized as `None` because the actual container
3378                // used is not known until after execution completes. It is
3379                // set via `set_container()` once the backend resolves which
3380                // candidate image was pulled.
3381                container: None,
3382                cpu: constraints.cpu,
3383                memory: constraints
3384                    .memory
3385                    .try_into()
3386                    .expect("memory exceeds a valid WDL value"),
3387                gpu: Array::new_unchecked(
3388                    ANALYSIS_STDLIB.array_string_type().clone(),
3389                    constraints
3390                        .gpu
3391                        .iter()
3392                        .map(|v| PrimitiveValue::new_string(v).into())
3393                        .collect(),
3394                ),
3395                fpga: Array::new_unchecked(
3396                    ANALYSIS_STDLIB.array_string_type().clone(),
3397                    constraints
3398                        .fpga
3399                        .iter()
3400                        .map(|v| PrimitiveValue::new_string(v).into())
3401                        .collect(),
3402                ),
3403                disks: Map::new_unchecked(
3404                    ANALYSIS_STDLIB.map_string_int_type().clone(),
3405                    constraints
3406                        .disks
3407                        .iter()
3408                        .map(|(k, v)| (PrimitiveValue::new_string(k), (*v).into()))
3409                        .collect(),
3410                ),
3411                max_retries,
3412            }),
3413            attempt,
3414            meta,
3415            parameter_meta,
3416            ext,
3417            return_code: None,
3418            end_time: None,
3419            previous: PreviousTaskDataValue::empty(),
3420        }))
3421    }
3422
3423    /// Gets the task name.
3424    pub fn name(&self) -> &Arc<String> {
3425        &self.0.name
3426    }
3427
3428    /// Gets the unique ID of the task.
3429    pub fn id(&self) -> &Arc<String> {
3430        &self.0.id
3431    }
3432
3433    /// Gets the container in which the task is executing.
3434    pub fn container(&self) -> Option<&Arc<String>> {
3435        self.0.data.container.as_ref()
3436    }
3437
3438    /// Gets the allocated number of cpus for the task.
3439    pub fn cpu(&self) -> f64 {
3440        self.0.data.cpu
3441    }
3442
3443    /// Gets the allocated memory (in bytes) for the task.
3444    pub fn memory(&self) -> i64 {
3445        self.0.data.memory
3446    }
3447
3448    /// Gets the GPU allocations for the task.
3449    ///
3450    /// An array with one specification per allocated GPU; the specification is
3451    /// execution engine-specific.
3452    pub fn gpu(&self) -> &Array {
3453        &self.0.data.gpu
3454    }
3455
3456    /// Gets the FPGA allocations for the task.
3457    ///
3458    /// An array with one specification per allocated FPGA; the specification is
3459    /// execution engine-specific.
3460    pub fn fpga(&self) -> &Array {
3461        &self.0.data.fpga
3462    }
3463
3464    /// Gets the disk allocations for the task.
3465    ///
3466    /// A map with one entry for each disk mount point.
3467    ///
3468    /// The key is the mount point and the value is the initial amount of disk
3469    /// space allocated, in bytes.
3470    pub fn disks(&self) -> &Map {
3471        &self.0.data.disks
3472    }
3473
3474    /// Gets current task attempt count.
3475    ///
3476    /// The value must be 0 the first time the task is executed and incremented
3477    /// by 1 each time the task is retried (if any).
3478    pub fn attempt(&self) -> i64 {
3479        self.0.attempt
3480    }
3481
3482    /// Gets the time by which the task must be completed, as a Unix time stamp.
3483    ///
3484    /// A value of `None` indicates there is no deadline.
3485    pub fn end_time(&self) -> Option<i64> {
3486        self.0.end_time
3487    }
3488
3489    /// Gets the task's return code.
3490    ///
3491    /// Initially set to `None`, but set after task execution completes.
3492    pub fn return_code(&self) -> Option<i64> {
3493        self.0.return_code
3494    }
3495
3496    /// Gets the task's `meta` section as an object.
3497    pub fn meta(&self) -> &Object {
3498        &self.0.meta
3499    }
3500
3501    /// Gets the tasks's `parameter_meta` section as an object.
3502    pub fn parameter_meta(&self) -> &Object {
3503        &self.0.parameter_meta
3504    }
3505
3506    /// Gets the task's extension metadata.
3507    pub fn ext(&self) -> &Object {
3508        &self.0.ext
3509    }
3510
3511    /// Sets the container image after task execution has completed.
3512    pub(crate) fn set_container(&mut self, container: String) {
3513        let inner = Arc::get_mut(&mut self.0).expect("task value must be uniquely owned to mutate");
3514        Arc::make_mut(&mut inner.data).container = Some(Arc::new(container));
3515    }
3516
3517    /// Sets the return code after the task execution has completed.
3518    pub(crate) fn set_return_code(&mut self, code: i32) {
3519        Arc::get_mut(&mut self.0)
3520            .expect("task value must be uniquely owned to mutate")
3521            .return_code = Some(code as i64);
3522    }
3523
3524    /// Sets the attempt number for the task.
3525    pub(crate) fn set_attempt(&mut self, attempt: i64) {
3526        Arc::get_mut(&mut self.0)
3527            .expect("task value must be uniquely owned to mutate")
3528            .attempt = attempt;
3529    }
3530
3531    /// Sets the previous task data for retry attempts.
3532    pub(crate) fn set_previous(&mut self, data: Arc<TaskPostEvaluationData>) {
3533        Arc::get_mut(&mut self.0)
3534            .expect("task value must be uniquely owned to mutate")
3535            .previous = PreviousTaskDataValue::new(data);
3536    }
3537
3538    /// Gets the task post-evaluation data.
3539    pub(crate) fn data(&self) -> &Arc<TaskPostEvaluationData> {
3540        &self.0.data
3541    }
3542
3543    /// Accesses a field of the task value by name.
3544    ///
3545    /// Returns `None` if the name is not a known field name.
3546    pub fn field(&self, version: SupportedVersion, name: &str) -> Option<Value> {
3547        match name {
3548            TASK_FIELD_NAME => Some(PrimitiveValue::String(self.0.name.clone()).into()),
3549            TASK_FIELD_ID => Some(PrimitiveValue::String(self.0.id.clone()).into()),
3550            TASK_FIELD_ATTEMPT => Some(self.0.attempt.into()),
3551            TASK_FIELD_CONTAINER => Some(
3552                self.0
3553                    .data
3554                    .container
3555                    .clone()
3556                    .map(|c| PrimitiveValue::String(c).into())
3557                    .unwrap_or_else(|| {
3558                        Value::new_none(
3559                            task_member_type_post_evaluation(version, TASK_FIELD_CONTAINER)
3560                                .expect("failed to get task field type"),
3561                        )
3562                    }),
3563            ),
3564            TASK_FIELD_CPU => Some(self.0.data.cpu.into()),
3565            TASK_FIELD_MEMORY => Some(self.0.data.memory.into()),
3566            TASK_FIELD_GPU => Some(self.0.data.gpu.clone().into()),
3567            TASK_FIELD_FPGA => Some(self.0.data.fpga.clone().into()),
3568            TASK_FIELD_DISKS => Some(self.0.data.disks.clone().into()),
3569            TASK_FIELD_END_TIME => Some(self.0.end_time.map(Into::into).unwrap_or_else(|| {
3570                Value::new_none(
3571                    task_member_type_post_evaluation(version, TASK_FIELD_END_TIME)
3572                        .expect("failed to get task field type"),
3573                )
3574            })),
3575            TASK_FIELD_RETURN_CODE => {
3576                Some(self.0.return_code.map(Into::into).unwrap_or_else(|| {
3577                    Value::new_none(
3578                        task_member_type_post_evaluation(version, TASK_FIELD_RETURN_CODE)
3579                            .expect("failed to get task field type"),
3580                    )
3581                }))
3582            }
3583            TASK_FIELD_META => Some(self.0.meta.clone().into()),
3584            TASK_FIELD_PARAMETER_META => Some(self.0.parameter_meta.clone().into()),
3585            TASK_FIELD_EXT => Some(self.0.ext.clone().into()),
3586            TASK_FIELD_MAX_RETRIES if version >= SupportedVersion::V1(V1::Three) => {
3587                Some(self.0.data.max_retries.into())
3588            }
3589            TASK_FIELD_PREVIOUS if version >= SupportedVersion::V1(V1::Three) => {
3590                Some(HiddenValue::PreviousTaskData(self.0.previous.clone()).into())
3591            }
3592            _ => None,
3593        }
3594    }
3595}
3596
3597/// Represents a hints value from a WDL 1.2 hints section.
3598///
3599/// Hints values are cheap to clone.
3600#[derive(Debug, Clone)]
3601pub struct HintsValue(Object);
3602
3603impl HintsValue {
3604    /// Creates a new hints value.
3605    pub fn new(members: IndexMap<String, Value>) -> Self {
3606        Self(Object::new(members))
3607    }
3608
3609    /// Converts the hints value to an object.
3610    pub fn as_object(&self) -> &Object {
3611        &self.0
3612    }
3613}
3614
3615impl From<HintsValue> for Value {
3616    fn from(value: HintsValue) -> Self {
3617        Self::Hidden(HiddenValue::Hints(value))
3618    }
3619}
3620
3621impl fmt::Display for HintsValue {
3622    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3623        write!(f, "hints {{")?;
3624
3625        for (i, (k, v)) in self.0.iter().enumerate() {
3626            if i > 0 {
3627                write!(f, ", ")?;
3628            }
3629
3630            write!(f, "{k}: {v}")?;
3631        }
3632
3633        write!(f, "}}")
3634    }
3635}
3636
3637impl From<Object> for HintsValue {
3638    fn from(value: Object) -> Self {
3639        Self(value)
3640    }
3641}
3642
3643/// Represents an input value from a WDL 1.2 hints section.
3644///
3645/// Input values are cheap to clone.
3646#[derive(Debug, Clone)]
3647pub struct InputValue(Object);
3648
3649impl InputValue {
3650    /// Creates a new input value.
3651    pub fn new(members: IndexMap<String, Value>) -> Self {
3652        Self(Object::new(members))
3653    }
3654
3655    /// Converts the input value to an object.
3656    pub fn as_object(&self) -> &Object {
3657        &self.0
3658    }
3659}
3660
3661impl From<InputValue> for Value {
3662    fn from(value: InputValue) -> Self {
3663        Self::Hidden(HiddenValue::Input(value))
3664    }
3665}
3666
3667impl fmt::Display for InputValue {
3668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3669        write!(f, "input {{")?;
3670
3671        for (i, (k, v)) in self.0.iter().enumerate() {
3672            if i > 0 {
3673                write!(f, ", ")?;
3674            }
3675
3676            write!(f, "{k}: {v}")?;
3677        }
3678
3679        write!(f, "}}")
3680    }
3681}
3682
3683impl From<Object> for InputValue {
3684    fn from(value: Object) -> Self {
3685        Self(value)
3686    }
3687}
3688
3689/// Represents an output value from a WDL 1.2 hints section.
3690///
3691/// Output values are cheap to clone.
3692#[derive(Debug, Clone)]
3693pub struct OutputValue(Object);
3694
3695impl OutputValue {
3696    /// Creates a new output value.
3697    pub fn new(members: IndexMap<String, Value>) -> Self {
3698        Self(Object::new(members))
3699    }
3700
3701    /// Converts the output value to an object.
3702    pub fn as_object(&self) -> &Object {
3703        &self.0
3704    }
3705}
3706
3707impl From<OutputValue> for Value {
3708    fn from(value: OutputValue) -> Self {
3709        Self::Hidden(HiddenValue::Output(value))
3710    }
3711}
3712
3713impl fmt::Display for OutputValue {
3714    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3715        write!(f, "output {{")?;
3716
3717        for (i, (k, v)) in self.0.iter().enumerate() {
3718            if i > 0 {
3719                write!(f, ", ")?;
3720            }
3721
3722            write!(f, "{k}: {v}")?;
3723        }
3724
3725        write!(f, "}}")
3726    }
3727}
3728
3729impl From<Object> for OutputValue {
3730    fn from(value: Object) -> Self {
3731        Self(value)
3732    }
3733}
3734
3735/// Represents the outputs of a call.
3736///
3737/// Call values are cheap to clone.
3738#[derive(Debug, Clone)]
3739pub struct CallValue {
3740    /// The type of the call.
3741    ty: Arc<CallType>,
3742    /// The outputs of the call.
3743    outputs: Arc<Outputs>,
3744}
3745
3746impl CallValue {
3747    /// Constructs a new call value without checking the outputs conform to the
3748    /// call type.
3749    pub(crate) fn new_unchecked(ty: CallType, outputs: Arc<Outputs>) -> Self {
3750        Self {
3751            ty: Arc::new(ty),
3752            outputs,
3753        }
3754    }
3755
3756    /// Gets the type of the call.
3757    pub fn ty(&self) -> &CallType {
3758        &self.ty
3759    }
3760
3761    /// Gets the outputs of the call.
3762    pub fn outputs(&self) -> &Outputs {
3763        self.outputs.as_ref()
3764    }
3765}
3766
3767impl fmt::Display for CallValue {
3768    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3769        write!(f, "call output {{")?;
3770
3771        for (i, (k, v)) in self.outputs.iter().enumerate() {
3772            if i > 0 {
3773                write!(f, ", ")?;
3774            }
3775
3776            write!(f, "{k}: {v}")?;
3777        }
3778
3779        write!(f, "}}")
3780    }
3781}
3782
3783/// Serializes a value with optional serialization of pairs.
3784pub(crate) struct ValueSerializer<'a> {
3785    /// The evaluation context to use for host-to-guest path translations.
3786    context: Option<&'a dyn EvaluationContext>,
3787    /// The value to serialize.
3788    value: &'a Value,
3789    /// Whether pairs should be serialized as a map with `left` and `right`
3790    /// keys.
3791    allow_pairs: bool,
3792}
3793
3794impl<'a> ValueSerializer<'a> {
3795    /// Constructs a new `ValueSerializer`.
3796    ///
3797    /// If the provided evaluation context is `None`, host to guest translation
3798    /// is not performed; `File` and `Directory` values will serialize directly
3799    /// as a string.
3800    pub fn new(
3801        context: Option<&'a dyn EvaluationContext>,
3802        value: &'a Value,
3803        allow_pairs: bool,
3804    ) -> Self {
3805        Self {
3806            context,
3807            value,
3808            allow_pairs,
3809        }
3810    }
3811}
3812
3813impl serde::Serialize for ValueSerializer<'_> {
3814    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3815    where
3816        S: serde::Serializer,
3817    {
3818        use serde::ser::Error;
3819
3820        match &self.value {
3821            Value::None(_) => serializer.serialize_none(),
3822            Value::Primitive(v) => {
3823                PrimitiveValueSerializer::new(self.context, v).serialize(serializer)
3824            }
3825            Value::Compound(v) => CompoundValueSerializer::new(self.context, v, self.allow_pairs)
3826                .serialize(serializer),
3827            Value::Call(_) | Value::Hidden(_) | Value::TypeNameRef(_) => {
3828                Err(S::Error::custom("value cannot be serialized"))
3829            }
3830        }
3831    }
3832}
3833
3834/// Responsible for serializing primitive values.
3835pub(crate) struct PrimitiveValueSerializer<'a> {
3836    /// The evaluation context to use for host-to-guest path translations.
3837    context: Option<&'a dyn EvaluationContext>,
3838    /// The primitive value to serialize.
3839    value: &'a PrimitiveValue,
3840}
3841
3842impl<'a> PrimitiveValueSerializer<'a> {
3843    /// Constructs a new `PrimitiveValueSerializer`.
3844    ///
3845    /// If the provided evaluation context is `None`, host to guest translation
3846    /// is not performed; `File` and `Directory` values will serialize directly
3847    /// as a string.
3848    pub fn new(context: Option<&'a dyn EvaluationContext>, value: &'a PrimitiveValue) -> Self {
3849        Self { context, value }
3850    }
3851}
3852
3853impl serde::Serialize for PrimitiveValueSerializer<'_> {
3854    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3855    where
3856        S: serde::Serializer,
3857    {
3858        match self.value {
3859            PrimitiveValue::Boolean(v) => v.serialize(serializer),
3860            PrimitiveValue::Integer(v) => v.serialize(serializer),
3861            PrimitiveValue::Float(v) => v.serialize(serializer),
3862            PrimitiveValue::String(s) => s.serialize(serializer),
3863            PrimitiveValue::File(p) | PrimitiveValue::Directory(p) => {
3864                let path = self
3865                    .context
3866                    .and_then(|c| c.guest_path(p).map(|p| Cow::Owned(p.0)))
3867                    .unwrap_or(Cow::Borrowed(&p.0));
3868
3869                path.serialize(serializer)
3870            }
3871        }
3872    }
3873}
3874
3875/// Serializes a `CompoundValue` with optional serialization of pairs.
3876pub(crate) struct CompoundValueSerializer<'a> {
3877    /// The evaluation context to use for host-to-guest path translations.
3878    context: Option<&'a dyn EvaluationContext>,
3879    /// The compound value to serialize.
3880    value: &'a CompoundValue,
3881    /// Whether pairs should be serialized as a map with `left` and `right`
3882    /// keys.
3883    allow_pairs: bool,
3884}
3885
3886impl<'a> CompoundValueSerializer<'a> {
3887    /// Constructs a new `CompoundValueSerializer`.
3888    ///
3889    /// If the provided evaluation context is `None`, host to guest translation
3890    /// is not performed; `File` and `Directory` values will serialize directly
3891    /// as a string.
3892    pub fn new(
3893        context: Option<&'a dyn EvaluationContext>,
3894        value: &'a CompoundValue,
3895        allow_pairs: bool,
3896    ) -> Self {
3897        Self {
3898            context,
3899            value,
3900            allow_pairs,
3901        }
3902    }
3903}
3904
3905impl serde::Serialize for CompoundValueSerializer<'_> {
3906    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
3907    where
3908        S: serde::Serializer,
3909    {
3910        use serde::ser::Error;
3911
3912        match &self.value {
3913            CompoundValue::Pair(pair) if self.allow_pairs => {
3914                let mut map = serializer.serialize_map(Some(2))?;
3915                let left = ValueSerializer::new(self.context, pair.left(), self.allow_pairs);
3916                let right = ValueSerializer::new(self.context, pair.right(), self.allow_pairs);
3917                map.serialize_entry("left", &left)?;
3918                map.serialize_entry("right", &right)?;
3919                map.end()
3920            }
3921            CompoundValue::Pair(_) => Err(S::Error::custom("a pair cannot be serialized")),
3922            CompoundValue::Array(v) => {
3923                let mut seq = serializer.serialize_seq(Some(v.len()))?;
3924                for v in v.as_slice() {
3925                    seq.serialize_element(&ValueSerializer::new(
3926                        self.context,
3927                        v,
3928                        self.allow_pairs,
3929                    ))?;
3930                }
3931
3932                seq.end()
3933            }
3934            CompoundValue::Map(v) => {
3935                let mut map = serializer.serialize_map(Some(v.len()))?;
3936                for (k, v) in v.iter() {
3937                    match k {
3938                        PrimitiveValue::String(s) => {
3939                            map.serialize_entry(
3940                                s.as_str(),
3941                                &ValueSerializer::new(self.context, v, self.allow_pairs),
3942                            )?;
3943                        }
3944                        PrimitiveValue::File(p) | PrimitiveValue::Directory(p) => {
3945                            map.serialize_entry(
3946                                p.as_str(),
3947                                &ValueSerializer::new(self.context, v, self.allow_pairs),
3948                            )?;
3949                        }
3950                        _ => {
3951                            // Serialize the key as a string
3952                            map.serialize_entry(
3953                                &k.raw(None).to_string(),
3954                                &ValueSerializer::new(self.context, v, self.allow_pairs),
3955                            )?;
3956                        }
3957                    }
3958                }
3959
3960                map.end()
3961            }
3962            CompoundValue::Object(object) => {
3963                let mut map = serializer.serialize_map(Some(object.len()))?;
3964                for (k, v) in object.iter() {
3965                    map.serialize_entry(
3966                        k,
3967                        &ValueSerializer::new(self.context, v, self.allow_pairs),
3968                    )?;
3969                }
3970
3971                map.end()
3972            }
3973            CompoundValue::Struct(s) => {
3974                let mut map = serializer.serialize_map(Some(s.0.members.len()))?;
3975                for (k, v) in s.0.members.iter() {
3976                    map.serialize_entry(
3977                        k,
3978                        &ValueSerializer::new(self.context, v, self.allow_pairs),
3979                    )?;
3980                }
3981
3982                map.end()
3983            }
3984            CompoundValue::EnumVariant(e) => serializer.serialize_str(e.name()),
3985        }
3986    }
3987}
3988
3989#[cfg(test)]
3990mod test {
3991    use std::iter::empty;
3992
3993    use approx::assert_relative_eq;
3994    use pretty_assertions::assert_eq;
3995    use wdl_analysis::types::ArrayType;
3996    use wdl_analysis::types::MapType;
3997    use wdl_analysis::types::PairType;
3998    use wdl_analysis::types::StructType;
3999    use wdl_ast::Diagnostic;
4000    use wdl_ast::Span;
4001    use wdl_ast::SupportedVersion;
4002
4003    use super::*;
4004    use crate::EvaluationPath;
4005    use crate::http::Transferer;
4006
4007    #[test]
4008    fn boolean_coercion() {
4009        // Boolean -> Boolean
4010        assert_eq!(
4011            Value::from(false)
4012                .coerce(None, &PrimitiveType::Boolean.into())
4013                .expect("should coerce")
4014                .unwrap_boolean(),
4015            Value::from(false).unwrap_boolean()
4016        );
4017        // Boolean -> String (invalid)
4018        assert_eq!(
4019            format!(
4020                "{e:#}",
4021                e = Value::from(true)
4022                    .coerce(None, &PrimitiveType::String.into())
4023                    .unwrap_err()
4024            ),
4025            "cannot coerce type `Boolean` to type `String`"
4026        );
4027    }
4028
4029    #[test]
4030    fn boolean_display() {
4031        assert_eq!(Value::from(false).to_string(), "false");
4032        assert_eq!(Value::from(true).to_string(), "true");
4033    }
4034
4035    #[test]
4036    fn integer_coercion() {
4037        // Int -> Int
4038        assert_eq!(
4039            Value::from(12345)
4040                .coerce(None, &PrimitiveType::Integer.into())
4041                .expect("should coerce")
4042                .unwrap_integer(),
4043            Value::from(12345).unwrap_integer()
4044        );
4045        // Int -> Float
4046        assert_relative_eq!(
4047            Value::from(12345)
4048                .coerce(None, &PrimitiveType::Float.into())
4049                .expect("should coerce")
4050                .unwrap_float(),
4051            Value::from(12345.0).unwrap_float()
4052        );
4053        // Int -> Boolean (invalid)
4054        assert_eq!(
4055            format!(
4056                "{e:#}",
4057                e = Value::from(12345)
4058                    .coerce(None, &PrimitiveType::Boolean.into())
4059                    .unwrap_err()
4060            ),
4061            "cannot coerce type `Int` to type `Boolean`"
4062        );
4063    }
4064
4065    #[test]
4066    fn integer_display() {
4067        assert_eq!(Value::from(12345).to_string(), "12345");
4068        assert_eq!(Value::from(-12345).to_string(), "-12345");
4069    }
4070
4071    #[test]
4072    fn float_coercion() {
4073        // Float -> Float
4074        assert_relative_eq!(
4075            Value::from(12345.0)
4076                .coerce(None, &PrimitiveType::Float.into())
4077                .expect("should coerce")
4078                .unwrap_float(),
4079            Value::from(12345.0).unwrap_float()
4080        );
4081        // Float -> Int (invalid)
4082        assert_eq!(
4083            format!(
4084                "{e:#}",
4085                e = Value::from(12345.0)
4086                    .coerce(None, &PrimitiveType::Integer.into())
4087                    .unwrap_err()
4088            ),
4089            "cannot coerce type `Float` to type `Int`"
4090        );
4091    }
4092
4093    #[test]
4094    fn float_display() {
4095        assert_eq!(Value::from(12345.12345).to_string(), "12345.123450");
4096        assert_eq!(Value::from(-12345.12345).to_string(), "-12345.123450");
4097    }
4098
4099    #[test]
4100    fn string_coercion() {
4101        let value = PrimitiveValue::new_string("foo");
4102        // String -> String
4103        assert_eq!(
4104            value
4105                .coerce(None, &PrimitiveType::String.into())
4106                .expect("should coerce"),
4107            value
4108        );
4109        // String -> File
4110        assert_eq!(
4111            value
4112                .coerce(None, &PrimitiveType::File.into())
4113                .expect("should coerce"),
4114            PrimitiveValue::File(value.as_string().expect("should be string").clone().into())
4115        );
4116        // String -> Directory
4117        assert_eq!(
4118            value
4119                .coerce(None, &PrimitiveType::Directory.into())
4120                .expect("should coerce"),
4121            PrimitiveValue::Directory(value.as_string().expect("should be string").clone().into())
4122        );
4123        // String -> Boolean (invalid)
4124        assert_eq!(
4125            format!(
4126                "{e:#}",
4127                e = value
4128                    .coerce(None, &PrimitiveType::Boolean.into())
4129                    .unwrap_err()
4130            ),
4131            "cannot coerce type `String` to type `Boolean`"
4132        );
4133
4134        struct Context;
4135
4136        impl EvaluationContext for Context {
4137            fn version(&self) -> SupportedVersion {
4138                unimplemented!()
4139            }
4140
4141            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
4142                unimplemented!()
4143            }
4144
4145            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
4146                unimplemented!()
4147            }
4148
4149            fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
4150                unimplemented!()
4151            }
4152
4153            fn base_dir(&self) -> &EvaluationPath {
4154                unimplemented!()
4155            }
4156
4157            fn temp_dir(&self) -> &Path {
4158                unimplemented!()
4159            }
4160
4161            fn transferer(&self) -> &dyn Transferer {
4162                unimplemented!()
4163            }
4164
4165            fn host_path(&self, path: &GuestPath) -> Option<HostPath> {
4166                if path.as_str() == "/mnt/task/input/0/path" {
4167                    Some(HostPath::new("/some/host/path"))
4168                } else {
4169                    None
4170                }
4171            }
4172        }
4173
4174        // String (guest path) -> File
4175        assert_eq!(
4176            PrimitiveValue::new_string("/mnt/task/input/0/path")
4177                .coerce(Some(&Context), &PrimitiveType::File.into())
4178                .expect("should coerce")
4179                .unwrap_file()
4180                .as_str(),
4181            "/some/host/path"
4182        );
4183
4184        // String (not a guest path) -> File
4185        assert_eq!(
4186            value
4187                .coerce(Some(&Context), &PrimitiveType::File.into())
4188                .expect("should coerce")
4189                .unwrap_file()
4190                .as_str(),
4191            "foo"
4192        );
4193
4194        // String (guest path) -> Directory
4195        assert_eq!(
4196            PrimitiveValue::new_string("/mnt/task/input/0/path")
4197                .coerce(Some(&Context), &PrimitiveType::Directory.into())
4198                .expect("should coerce")
4199                .unwrap_directory()
4200                .as_str(),
4201            "/some/host/path"
4202        );
4203
4204        // String (not a guest path) -> Directory
4205        assert_eq!(
4206            value
4207                .coerce(Some(&Context), &PrimitiveType::Directory.into())
4208                .expect("should coerce")
4209                .unwrap_directory()
4210                .as_str(),
4211            "foo"
4212        );
4213    }
4214
4215    #[test]
4216    fn string_display() {
4217        let value = PrimitiveValue::new_string("hello world!");
4218        assert_eq!(value.to_string(), "\"hello world!\"");
4219    }
4220
4221    #[test]
4222    fn string_display_escapes_special_characters() {
4223        let value = PrimitiveValue::new_string(
4224            "\u{1b}[31m${name} ~{color} \"quoted\" \\\\ tab\tline\ncarriage\r$HOME ~user",
4225        );
4226        assert_eq!(
4227            value.to_string(),
4228            r#""\x1B[31m\${name} \~{color} \"quoted\" \\\\ tab\tline\ncarriage\r$HOME ~user""#
4229        );
4230    }
4231
4232    #[test]
4233    fn file_coercion() {
4234        let value = PrimitiveValue::new_file("foo");
4235
4236        // File -> File
4237        assert_eq!(
4238            value
4239                .coerce(None, &PrimitiveType::File.into())
4240                .expect("should coerce"),
4241            value
4242        );
4243        // File -> String
4244        assert_eq!(
4245            value
4246                .coerce(None, &PrimitiveType::String.into())
4247                .expect("should coerce"),
4248            PrimitiveValue::String(value.as_file().expect("should be file").0.clone())
4249        );
4250        // File -> Directory (invalid)
4251        assert_eq!(
4252            format!(
4253                "{e:#}",
4254                e = value
4255                    .coerce(None, &PrimitiveType::Directory.into())
4256                    .unwrap_err()
4257            ),
4258            "cannot coerce type `File` to type `Directory`"
4259        );
4260
4261        struct Context;
4262
4263        impl EvaluationContext for Context {
4264            fn version(&self) -> SupportedVersion {
4265                unimplemented!()
4266            }
4267
4268            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
4269                unimplemented!()
4270            }
4271
4272            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
4273                unimplemented!()
4274            }
4275
4276            fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
4277                unimplemented!()
4278            }
4279
4280            fn base_dir(&self) -> &EvaluationPath {
4281                unimplemented!()
4282            }
4283
4284            fn temp_dir(&self) -> &Path {
4285                unimplemented!()
4286            }
4287
4288            fn transferer(&self) -> &dyn Transferer {
4289                unimplemented!()
4290            }
4291
4292            fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
4293                if path.as_str() == "/some/host/path" {
4294                    Some(GuestPath::new("/mnt/task/input/0/path"))
4295                } else {
4296                    None
4297                }
4298            }
4299        }
4300
4301        // File (mapped) -> String
4302        assert_eq!(
4303            PrimitiveValue::new_file("/some/host/path")
4304                .coerce(Some(&Context), &PrimitiveType::String.into())
4305                .expect("should coerce")
4306                .unwrap_string()
4307                .as_str(),
4308            "/mnt/task/input/0/path"
4309        );
4310
4311        // File (not mapped) -> String
4312        assert_eq!(
4313            value
4314                .coerce(Some(&Context), &PrimitiveType::String.into())
4315                .expect("should coerce")
4316                .unwrap_string()
4317                .as_str(),
4318            "foo"
4319        );
4320    }
4321
4322    #[test]
4323    fn file_display() {
4324        let value = PrimitiveValue::new_file("/foo/bar/baz.txt");
4325        assert_eq!(value.to_string(), "\"/foo/bar/baz.txt\"");
4326    }
4327
4328    #[test]
4329    fn directory_coercion() {
4330        let value = PrimitiveValue::new_directory("foo");
4331
4332        // Directory -> Directory
4333        assert_eq!(
4334            value
4335                .coerce(None, &PrimitiveType::Directory.into())
4336                .expect("should coerce"),
4337            value
4338        );
4339        // Directory -> String
4340        assert_eq!(
4341            value
4342                .coerce(None, &PrimitiveType::String.into())
4343                .expect("should coerce"),
4344            PrimitiveValue::String(value.as_directory().expect("should be directory").0.clone())
4345        );
4346        // Directory -> File (invalid)
4347        assert_eq!(
4348            format!(
4349                "{e:#}",
4350                e = value.coerce(None, &PrimitiveType::File.into()).unwrap_err()
4351            ),
4352            "cannot coerce type `Directory` to type `File`"
4353        );
4354
4355        struct Context;
4356
4357        impl EvaluationContext for Context {
4358            fn version(&self) -> SupportedVersion {
4359                unimplemented!()
4360            }
4361
4362            fn resolve_name(&self, _: &str, _: Span) -> Result<Value, Diagnostic> {
4363                unimplemented!()
4364            }
4365
4366            fn resolve_type_name(&self, _: &str, _: Span) -> Result<Type, Diagnostic> {
4367                unimplemented!()
4368            }
4369
4370            fn enum_variant_value(&self, _: &str, _: &str) -> Result<Value, Diagnostic> {
4371                unimplemented!()
4372            }
4373
4374            fn base_dir(&self) -> &EvaluationPath {
4375                unimplemented!()
4376            }
4377
4378            fn temp_dir(&self) -> &Path {
4379                unimplemented!()
4380            }
4381
4382            fn transferer(&self) -> &dyn Transferer {
4383                unimplemented!()
4384            }
4385
4386            fn guest_path(&self, path: &HostPath) -> Option<GuestPath> {
4387                if path.as_str() == "/some/host/path" {
4388                    Some(GuestPath::new("/mnt/task/input/0/path"))
4389                } else {
4390                    None
4391                }
4392            }
4393        }
4394
4395        // Directory (mapped) -> String
4396        assert_eq!(
4397            PrimitiveValue::new_directory("/some/host/path")
4398                .coerce(Some(&Context), &PrimitiveType::String.into())
4399                .expect("should coerce")
4400                .unwrap_string()
4401                .as_str(),
4402            "/mnt/task/input/0/path"
4403        );
4404
4405        // Directory (not mapped) -> String
4406        assert_eq!(
4407            value
4408                .coerce(Some(&Context), &PrimitiveType::String.into())
4409                .expect("should coerce")
4410                .unwrap_string()
4411                .as_str(),
4412            "foo"
4413        );
4414    }
4415
4416    #[test]
4417    fn directory_display() {
4418        let value = PrimitiveValue::new_directory("/foo/bar/baz");
4419        assert_eq!(value.to_string(), "\"/foo/bar/baz\"");
4420    }
4421
4422    #[test]
4423    fn none_coercion() {
4424        // None -> String?
4425        assert!(
4426            Value::new_none(Type::None)
4427                .coerce(None, &Type::from(PrimitiveType::String).optional())
4428                .expect("should coerce")
4429                .is_none(),
4430        );
4431
4432        // None -> String (invalid)
4433        assert_eq!(
4434            format!(
4435                "{e:#}",
4436                e = Value::new_none(Type::None)
4437                    .coerce(None, &PrimitiveType::String.into())
4438                    .unwrap_err()
4439            ),
4440            "cannot coerce `None` to non-optional type `String`"
4441        );
4442    }
4443
4444    #[test]
4445    fn none_display() {
4446        assert_eq!(Value::new_none(Type::None).to_string(), "None");
4447    }
4448
4449    #[test]
4450    fn array_coercion() {
4451        let src_ty = ArrayType::new(PrimitiveType::Integer);
4452        let target_ty: Type = ArrayType::new(PrimitiveType::Float).into();
4453
4454        // Array[Int] -> Array[Float]
4455        let src: CompoundValue = Array::new(src_ty, [1, 2, 3])
4456            .expect("should create array value")
4457            .into();
4458        let target = src.coerce(None, &target_ty).expect("should coerce");
4459        assert_eq!(
4460            target.unwrap_array().to_string(),
4461            "[1.000000, 2.000000, 3.000000]"
4462        );
4463
4464        // Array[Int] -> Array[String] (invalid)
4465        let target_ty: Type = ArrayType::new(PrimitiveType::String).into();
4466        assert_eq!(
4467            format!("{e:#}", e = src.coerce(None, &target_ty).unwrap_err()),
4468            "failed to coerce array element at index 0: cannot coerce type `Int` to type `String`"
4469        );
4470    }
4471
4472    #[test]
4473    fn non_empty_array_coercion() {
4474        let ty = ArrayType::new(PrimitiveType::String);
4475        let target_ty: Type = ArrayType::non_empty(PrimitiveType::String).into();
4476
4477        // Array[String] (non-empty) -> Array[String]+
4478        let string = PrimitiveValue::new_string("foo");
4479        let value: Value = Array::new(ty.clone(), [string])
4480            .expect("should create array")
4481            .into();
4482        assert!(value.coerce(None, &target_ty).is_ok(), "should coerce");
4483
4484        // Array[String] (empty) -> Array[String]+ (invalid)
4485        let value: Value = Array::new::<Value>(ty, [])
4486            .expect("should create array")
4487            .into();
4488        assert_eq!(
4489            format!("{e:#}", e = value.coerce(None, &target_ty).unwrap_err()),
4490            "cannot coerce empty array value to non-empty array type `Array[String]+`"
4491        );
4492    }
4493
4494    #[test]
4495    fn array_display() {
4496        let ty = ArrayType::new(PrimitiveType::Integer);
4497        let value: Value = Array::new(ty, [1, 2, 3])
4498            .expect("should create array")
4499            .into();
4500
4501        assert_eq!(value.to_string(), "[1, 2, 3]");
4502    }
4503
4504    #[test]
4505    fn map_coerce() {
4506        let key1 = PrimitiveValue::new_file("foo");
4507        let value1 = PrimitiveValue::new_string("bar");
4508        let key2 = PrimitiveValue::new_file("baz");
4509        let value2 = PrimitiveValue::new_string("qux");
4510
4511        let ty = MapType::new(PrimitiveType::File, PrimitiveType::String);
4512        let file_to_string: Value = Map::new(ty, [(key1, value1), (key2, value2)])
4513            .expect("should create map value")
4514            .into();
4515
4516        // Map[File, String] -> Map[String, File]
4517        let ty = MapType::new(PrimitiveType::String, PrimitiveType::File).into();
4518        let string_to_file = file_to_string
4519            .coerce(None, &ty)
4520            .expect("value should coerce");
4521        assert_eq!(
4522            string_to_file.to_string(),
4523            r#"{"foo": "bar", "baz": "qux"}"#
4524        );
4525
4526        // Map[String, File] -> Map[Int, File] (invalid)
4527        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::File).into();
4528        assert_eq!(
4529            format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4530            "failed to coerce map key for element at index 0: cannot coerce type `String` to type \
4531             `Int`"
4532        );
4533
4534        // Map[String, File] -> Map[String, Int] (invalid)
4535        let ty = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
4536        assert_eq!(
4537            format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4538            "failed to coerce map value for element at index 0: cannot coerce type `File` to type \
4539             `Int`"
4540        );
4541
4542        // Map[String, File] -> Struct
4543        let ty = StructType::new(
4544            "Foo",
4545            [("foo", PrimitiveType::File), ("baz", PrimitiveType::File)],
4546        )
4547        .into();
4548        let struct_value = string_to_file
4549            .coerce(None, &ty)
4550            .expect("value should coerce");
4551        assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
4552
4553        // Map[File, String] -> Struct
4554        let ty = StructType::new(
4555            "Foo",
4556            [
4557                ("foo", PrimitiveType::String),
4558                ("baz", PrimitiveType::String),
4559            ],
4560        )
4561        .into();
4562        let struct_value = file_to_string
4563            .coerce(None, &ty)
4564            .expect("value should coerce");
4565        assert_eq!(struct_value.to_string(), r#"Foo {foo: "bar", baz: "qux"}"#);
4566
4567        // Map[String, File] -> Struct (invalid)
4568        let ty = StructType::new(
4569            "Foo",
4570            [
4571                ("foo", PrimitiveType::File),
4572                ("baz", PrimitiveType::File),
4573                ("qux", PrimitiveType::File),
4574            ],
4575        )
4576        .into();
4577        assert_eq!(
4578            format!("{e:#}", e = string_to_file.coerce(None, &ty).unwrap_err()),
4579            "cannot coerce a map of 2 elements to an instance of struct `Foo` as the struct has 3 \
4580             members"
4581        );
4582
4583        // Map[String, File] -> Object
4584        let object_value = string_to_file
4585            .coerce(None, &Type::Object)
4586            .expect("value should coerce");
4587        assert_eq!(
4588            object_value.to_string(),
4589            r#"object {foo: "bar", baz: "qux"}"#
4590        );
4591
4592        // Map[File, String] -> Object
4593        let object_value = file_to_string
4594            .coerce(None, &Type::Object)
4595            .expect("value should coerce");
4596        assert_eq!(
4597            object_value.to_string(),
4598            r#"object {foo: "bar", baz: "qux"}"#
4599        );
4600    }
4601
4602    #[test]
4603    fn map_display() {
4604        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
4605        let value: Value = Map::new(ty, [(1, true), (2, false)])
4606            .expect("should create map value")
4607            .into();
4608        assert_eq!(value.to_string(), "{1: true, 2: false}");
4609    }
4610
4611    #[test]
4612    fn pair_coercion() {
4613        let left = PrimitiveValue::new_file("foo");
4614        let right = PrimitiveValue::new_string("bar");
4615
4616        let ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
4617        let value: Value = Pair::new(ty, left, right)
4618            .expect("should create pair value")
4619            .into();
4620
4621        // Pair[File, String] -> Pair[String, File]
4622        let ty = PairType::new(PrimitiveType::String, PrimitiveType::File).into();
4623        let value = value.coerce(None, &ty).expect("value should coerce");
4624        assert_eq!(value.to_string(), r#"("foo", "bar")"#);
4625
4626        // Pair[String, File] -> Pair[Int, Int]
4627        let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
4628        assert_eq!(
4629            format!("{e:#}", e = value.coerce(None, &ty).unwrap_err()),
4630            "failed to coerce pair's left value: cannot coerce type `String` to type `Int`"
4631        );
4632    }
4633
4634    #[test]
4635    fn pair_display() {
4636        let ty = PairType::new(PrimitiveType::Integer, PrimitiveType::Boolean);
4637        let value: Value = Pair::new(ty, 12345, false)
4638            .expect("should create pair value")
4639            .into();
4640        assert_eq!(value.to_string(), "(12345, false)");
4641    }
4642
4643    #[test]
4644    fn struct_coercion() {
4645        let ty = StructType::new(
4646            "Foo",
4647            [
4648                ("foo", PrimitiveType::Float),
4649                ("bar", PrimitiveType::Float),
4650                ("baz", PrimitiveType::Float),
4651            ],
4652        );
4653        let value: Value = Struct::new(ty, [("foo", 1.0), ("bar", 2.0), ("baz", 3.0)])
4654            .expect("should create map value")
4655            .into();
4656
4657        // Struct -> Map[String, Float]
4658        let ty = MapType::new(PrimitiveType::String, PrimitiveType::Float).into();
4659        let map_value = value.coerce(None, &ty).expect("value should coerce");
4660        assert_eq!(
4661            map_value.to_string(),
4662            r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
4663        );
4664
4665        // Struct -> Map[File, Float]
4666        let ty = MapType::new(PrimitiveType::File, PrimitiveType::Float).into();
4667        let map_value = value.coerce(None, &ty).expect("value should coerce");
4668        assert_eq!(
4669            map_value.to_string(),
4670            r#"{"foo": 1.000000, "bar": 2.000000, "baz": 3.000000}"#
4671        );
4672
4673        // Struct -> Struct
4674        let ty = StructType::new(
4675            "Bar",
4676            [
4677                ("foo", PrimitiveType::Float),
4678                ("bar", PrimitiveType::Float),
4679                ("baz", PrimitiveType::Float),
4680            ],
4681        )
4682        .into();
4683        let struct_value = value.coerce(None, &ty).expect("value should coerce");
4684        assert_eq!(
4685            struct_value.to_string(),
4686            r#"Bar {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
4687        );
4688
4689        // Struct -> Object
4690        let object_value = value
4691            .coerce(None, &Type::Object)
4692            .expect("value should coerce");
4693        assert_eq!(
4694            object_value.to_string(),
4695            r#"object {foo: 1.000000, bar: 2.000000, baz: 3.000000}"#
4696        );
4697    }
4698
4699    #[test]
4700    fn struct_display() {
4701        let ty = StructType::new(
4702            "Foo",
4703            [
4704                ("foo", PrimitiveType::Float),
4705                ("bar", PrimitiveType::String),
4706                ("baz", PrimitiveType::Integer),
4707            ],
4708        );
4709        let value: Value = Struct::new(
4710            ty,
4711            [
4712                ("foo", Value::from(1.101)),
4713                ("bar", PrimitiveValue::new_string("foo").into()),
4714                ("baz", 1234.into()),
4715            ],
4716        )
4717        .expect("should create map value")
4718        .into();
4719        assert_eq!(
4720            value.to_string(),
4721            r#"Foo {foo: 1.101000, bar: "foo", baz: 1234}"#
4722        );
4723    }
4724
4725    #[test]
4726    fn pair_serialization() {
4727        let pair_ty = PairType::new(PrimitiveType::File, PrimitiveType::String);
4728        let pair: Value = Pair::new(
4729            pair_ty,
4730            PrimitiveValue::new_file("foo"),
4731            PrimitiveValue::new_string("bar"),
4732        )
4733        .expect("should create pair value")
4734        .into();
4735        // Serialize pair with `left` and `right` keys
4736        let value_serializer = ValueSerializer::new(None, &pair, true);
4737        let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
4738        assert_eq!(serialized, r#"{"left":"foo","right":"bar"}"#);
4739
4740        // Serialize pair without `left` and `right` keys (should fail)
4741        let value_serializer = ValueSerializer::new(None, &pair, false);
4742        assert!(serde_json::to_string(&value_serializer).is_err());
4743
4744        let array_ty = ArrayType::new(PairType::new(PrimitiveType::File, PrimitiveType::String));
4745        let array: Value = Array::new(array_ty, [pair])
4746            .expect("should create array value")
4747            .into();
4748
4749        // Serialize array of pairs with `left` and `right` keys
4750        let value_serializer = ValueSerializer::new(None, &array, true);
4751        let serialized = serde_json::to_string(&value_serializer).expect("should serialize");
4752        assert_eq!(serialized, r#"[{"left":"foo","right":"bar"}]"#);
4753    }
4754
4755    #[test]
4756    fn type_name_ref_equality() {
4757        use wdl_analysis::types::EnumType;
4758
4759        let enum_type = Type::Compound(
4760            CompoundType::Custom(CustomType::Enum(
4761                EnumType::new(
4762                    "MyEnum",
4763                    Span::new(0, 0),
4764                    Type::Primitive(PrimitiveType::Integer, false),
4765                    Vec::<(String, Type)>::new(),
4766                    &[],
4767                )
4768                .expect("should create enum type"),
4769            )),
4770            false,
4771        );
4772
4773        let value1 = Value::TypeNameRef(TypeNameRefValue::new(enum_type.clone()));
4774        let value2 = Value::TypeNameRef(TypeNameRefValue::new(enum_type.clone()));
4775
4776        assert_eq!(value1.ty(), value2.ty());
4777    }
4778
4779    #[test]
4780    fn type_name_ref_ty() {
4781        let struct_type = Type::Compound(
4782            CompoundType::Custom(CustomType::Struct(StructType::new(
4783                "MyStruct",
4784                empty::<(&str, Type)>(),
4785            ))),
4786            false,
4787        );
4788
4789        let value = Value::TypeNameRef(TypeNameRefValue::new(struct_type.clone()));
4790        assert_eq!(value.ty(), struct_type);
4791    }
4792
4793    #[test]
4794    fn type_name_ref_display() {
4795        use wdl_analysis::types::EnumType;
4796
4797        let enum_type = Type::Compound(
4798            CompoundType::Custom(CustomType::Enum(
4799                EnumType::new(
4800                    "Color",
4801                    Span::new(0, 0),
4802                    Type::Primitive(PrimitiveType::Integer, false),
4803                    Vec::<(String, Type)>::new(),
4804                    &[],
4805                )
4806                .expect("should create enum type"),
4807            )),
4808            false,
4809        );
4810
4811        let value = Value::TypeNameRef(TypeNameRefValue::new(enum_type));
4812        assert_eq!(value.to_string(), "Color");
4813    }
4814}