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