Skip to main content

wdl_engine/
value.rs

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