Skip to main content

wdl_analysis/
stdlib.rs

1//! Representation of WDL standard library functions.
2
3use std::cell::Cell;
4use std::fmt;
5use std::fmt::Write;
6use std::sync::LazyLock;
7
8use indexmap::IndexMap;
9use indexmap::IndexSet;
10use wdl_ast::SupportedVersion;
11use wdl_ast::version::V1;
12
13use crate::types::ArrayType;
14use crate::types::Coercible;
15use crate::types::CompoundType;
16use crate::types::MapType;
17use crate::types::Optional;
18use crate::types::PairType;
19use crate::types::PrimitiveType;
20use crate::types::Type;
21
22mod constraints;
23
24pub use constraints::*;
25
26/// The maximum number of allowable type parameters in a function signature.
27///
28/// This is intentionally set low to limit the amount of space needed to store
29/// associated data.
30///
31/// Accessing `STDLIB` will panic if a signature is defined that exceeds this
32/// number.
33pub const MAX_TYPE_PARAMETERS: usize = 4;
34
35#[allow(clippy::missing_docs_in_private_items)]
36const _: () = assert!(
37    MAX_TYPE_PARAMETERS < usize::BITS as usize,
38    "the maximum number of type parameters cannot exceed the number of bits in usize"
39);
40
41/// The maximum (inclusive) number of parameters to any standard library
42/// function.
43///
44/// A function cannot be defined with more than this number of parameters and
45/// accessing `STDLIB` will panic if a signature is defined that exceeds this
46/// number.
47///
48/// As new standard library functions are implemented, the maximum will be
49/// increased.
50pub const MAX_PARAMETERS: usize = 4;
51
52/// A helper function for writing uninferred type parameter constraints to a
53/// given writer.
54fn write_uninferred_constraints(
55    s: &mut impl fmt::Write,
56    params: &TypeParameters<'_>,
57) -> Result<(), fmt::Error> {
58    for (i, (name, constraint)) in params
59        .referenced()
60        .filter_map(|(p, ty)| {
61            // Only consider uninferred type parameters that are constrained
62            if ty.is_some() {
63                return None;
64            }
65
66            Some((p.name, p.constraint()?))
67        })
68        .enumerate()
69    {
70        if i == 0 {
71            s.write_str(" where ")?;
72        } else if i > 1 {
73            s.write_str(", ")?;
74        }
75
76        write!(s, "`{name}`: {desc}", desc = constraint.description())?;
77    }
78
79    Ok(())
80}
81
82/// An error that may occur when binding arguments to a standard library
83/// function.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum FunctionBindError {
86    /// The function isn't supported for the specified version of WDL.
87    RequiresVersion(SupportedVersion),
88    /// There are too few arguments to bind the call.
89    ///
90    /// The value is the minimum number of arguments required.
91    TooFewArguments(usize),
92    /// There are too many arguments to bind the call.
93    ///
94    /// The value is the maximum number of arguments allowed.
95    TooManyArguments(usize),
96    /// An argument type was mismatched.
97    ArgumentTypeMismatch {
98        /// The index of the mismatched argument.
99        index: usize,
100        /// The expected type for the argument.
101        expected: String,
102    },
103    /// The function call arguments were ambiguous.
104    Ambiguous {
105        /// The first conflicting function signature.
106        first: String,
107        /// The second conflicting function signature.
108        second: String,
109    },
110}
111
112/// Represents a generic type to a standard library function.
113#[derive(Debug, Clone)]
114pub enum GenericType {
115    /// The type is a type parameter (e.g. `X`).
116    Parameter(&'static str),
117    /// The type is a type parameter, but unqualified; for example, if the type
118    /// parameter was bound to type `X?`, then the unqualified type would be
119    /// `X`.
120    UnqualifiedParameter(&'static str),
121    /// The type is a generic `Array`.
122    Array(GenericArrayType),
123    /// The type is a generic `Pair`.
124    Pair(GenericPairType),
125    /// The type is a generic `Map`.
126    Map(GenericMapType),
127    /// The type is a generic value contained within an enum.
128    EnumInnerValue(GenericEnumInnerValueType),
129}
130
131impl GenericType {
132    /// Returns an object that implements `Display` for formatting the type.
133    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
134        #[allow(clippy::missing_docs_in_private_items)]
135        struct Display<'a> {
136            params: &'a TypeParameters<'a>,
137            ty: &'a GenericType,
138        }
139
140        impl fmt::Display for Display<'_> {
141            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142                match self.ty {
143                    GenericType::Parameter(name) | GenericType::UnqualifiedParameter(name) => {
144                        let (_, ty) = self.params.get(name).expect("the name should be present");
145                        match ty {
146                            Some(ty) => {
147                                if let GenericType::UnqualifiedParameter(_) = self.ty {
148                                    ty.require().fmt(f)
149                                } else {
150                                    ty.fmt(f)
151                                }
152                            }
153                            None => {
154                                write!(
155                                    f,
156                                    "{prefix}{name}{suffix}",
157                                    prefix = if f.alternate() { "generic type `" } else { "" },
158                                    suffix = if f.alternate() { "`" } else { "" },
159                                )
160                            }
161                        }
162                    }
163                    GenericType::Array(ty) => ty.display(self.params).fmt(f),
164                    GenericType::Pair(ty) => ty.display(self.params).fmt(f),
165                    GenericType::Map(ty) => ty.display(self.params).fmt(f),
166                    GenericType::EnumInnerValue(ty) => ty.display(self.params).fmt(f),
167                }
168            }
169        }
170
171        Display { params, ty: self }
172    }
173
174    /// Infers any type parameters from the generic type.
175    fn infer_type_parameters(
176        &self,
177        ty: &Type,
178        params: &mut TypeParameters<'_>,
179        ignore_constraints: bool,
180    ) {
181        match self {
182            Self::Parameter(name) | Self::UnqualifiedParameter(name) => {
183                // Verify the type satisfies any constraint
184                let (param, _) = params.get(name).expect("should have parameter");
185
186                if !ignore_constraints
187                    && let Some(constraint) = param.constraint()
188                    && !constraint.satisfied(ty)
189                {
190                    return;
191                }
192
193                params.set_inferred_type(name, ty.clone());
194            }
195            Self::Array(array) => array.infer_type_parameters(ty, params, ignore_constraints),
196            Self::Pair(pair) => pair.infer_type_parameters(ty, params, ignore_constraints),
197            Self::Map(map) => map.infer_type_parameters(ty, params, ignore_constraints),
198            Self::EnumInnerValue(_) => {
199                // NOTE: this is an intentional no-op—the value type is derived
200                // from the choice parameter, not inferred from arguments.
201            }
202        }
203    }
204
205    /// Realizes the generic type.
206    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
207        match self {
208            Self::Parameter(name) => {
209                params
210                    .get(name)
211                    .expect("type parameter should be present")
212                    .1
213            }
214            Self::UnqualifiedParameter(name) => params
215                .get(name)
216                .expect("type parameter should be present")
217                .1
218                .map(|ty| ty.require()),
219            Self::Array(ty) => ty.realize(params),
220            Self::Pair(ty) => ty.realize(params),
221            Self::Map(ty) => ty.realize(params),
222            Self::EnumInnerValue(ty) => ty.realize(params),
223        }
224    }
225
226    /// Asserts that the type parameters referenced by the type are valid.
227    ///
228    /// # Panics
229    ///
230    /// Panics if referenced type parameter is invalid.
231    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
232        match self {
233            Self::Parameter(n) | Self::UnqualifiedParameter(n) => assert!(
234                parameters.iter().any(|p| p.name == *n),
235                "generic type references unknown type parameter `{n}`"
236            ),
237            Self::Array(a) => a.assert_type_parameters(parameters),
238            Self::Pair(p) => p.assert_type_parameters(parameters),
239            Self::Map(m) => m.assert_type_parameters(parameters),
240            Self::EnumInnerValue(e) => e.assert_type_parameters(parameters),
241        }
242    }
243}
244
245impl From<GenericArrayType> for GenericType {
246    fn from(value: GenericArrayType) -> Self {
247        Self::Array(value)
248    }
249}
250
251impl From<GenericPairType> for GenericType {
252    fn from(value: GenericPairType) -> Self {
253        Self::Pair(value)
254    }
255}
256
257impl From<GenericMapType> for GenericType {
258    fn from(value: GenericMapType) -> Self {
259        Self::Map(value)
260    }
261}
262
263impl From<GenericEnumInnerValueType> for GenericType {
264    fn from(value: GenericEnumInnerValueType) -> Self {
265        Self::EnumInnerValue(value)
266    }
267}
268
269/// Represents a generic `Array` type.
270#[derive(Debug, Clone)]
271pub struct GenericArrayType {
272    /// The array's element type.
273    element_type: Box<FunctionalType>,
274    /// Whether or not the array is non-empty.
275    non_empty: bool,
276}
277
278impl GenericArrayType {
279    /// Constructs a new generic array type.
280    pub fn new(element_type: impl Into<FunctionalType>) -> Self {
281        Self {
282            element_type: Box::new(element_type.into()),
283            non_empty: false,
284        }
285    }
286
287    /// Constructs a new non-empty generic array type.
288    pub fn non_empty(element_type: impl Into<FunctionalType>) -> Self {
289        Self {
290            element_type: Box::new(element_type.into()),
291            non_empty: true,
292        }
293    }
294
295    /// Gets the array's element type.
296    pub fn element_type(&self) -> &FunctionalType {
297        &self.element_type
298    }
299
300    /// Determines if the array type is non-empty.
301    pub fn is_non_empty(&self) -> bool {
302        self.non_empty
303    }
304
305    /// Returns an object that implements `Display` for formatting the type.
306    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
307        #[allow(clippy::missing_docs_in_private_items)]
308        struct Display<'a> {
309            params: &'a TypeParameters<'a>,
310            ty: &'a GenericArrayType,
311        }
312
313        impl fmt::Display for Display<'_> {
314            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315                write!(
316                    f,
317                    "{prefix}Array[{ty}]{plus}{suffix}",
318                    prefix = if f.alternate() { "generic type `" } else { "" },
319                    ty = self.ty.element_type.display(self.params),
320                    plus = if self.ty.is_non_empty() { "+" } else { "" },
321                    suffix = if f.alternate() { "`" } else { "" },
322                )
323            }
324        }
325
326        Display { params, ty: self }
327    }
328
329    /// Infers any type parameters from the generic type.
330    fn infer_type_parameters(
331        &self,
332        ty: &Type,
333        params: &mut TypeParameters<'_>,
334        ignore_constraints: bool,
335    ) {
336        match ty {
337            Type::Union => {
338                self.element_type
339                    .infer_type_parameters(&Type::Union, params, ignore_constraints);
340            }
341            Type::Compound(CompoundType::Array(ty), false) => {
342                self.element_type.infer_type_parameters(
343                    ty.element_type(),
344                    params,
345                    ignore_constraints,
346                );
347            }
348            _ => {}
349        }
350    }
351
352    /// Realizes the generic type to an `Array`.
353    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
354        let ty = self.element_type.realize(params)?;
355        if self.non_empty {
356            Some(ArrayType::non_empty(ty).into())
357        } else {
358            Some(ArrayType::new(ty).into())
359        }
360    }
361
362    /// Asserts that the type parameters referenced by the type are valid.
363    ///
364    /// # Panics
365    ///
366    /// Panics if referenced type parameter is invalid.
367    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
368        self.element_type.assert_type_parameters(parameters);
369    }
370}
371
372/// Represents a generic `Pair` type.
373#[derive(Debug, Clone)]
374pub struct GenericPairType {
375    /// The type of the left element of the pair.
376    left_type: Box<FunctionalType>,
377    /// The type of the right element of the pair.
378    right_type: Box<FunctionalType>,
379}
380
381impl GenericPairType {
382    /// Constructs a new generic pair type.
383    pub fn new(
384        left_type: impl Into<FunctionalType>,
385        right_type: impl Into<FunctionalType>,
386    ) -> Self {
387        Self {
388            left_type: Box::new(left_type.into()),
389            right_type: Box::new(right_type.into()),
390        }
391    }
392
393    /// Gets the pairs's left type.
394    pub fn left_type(&self) -> &FunctionalType {
395        &self.left_type
396    }
397
398    /// Gets the pairs's right type.
399    pub fn right_type(&self) -> &FunctionalType {
400        &self.right_type
401    }
402
403    /// Returns an object that implements `Display` for formatting the type.
404    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
405        #[allow(clippy::missing_docs_in_private_items)]
406        struct Display<'a> {
407            params: &'a TypeParameters<'a>,
408            ty: &'a GenericPairType,
409        }
410
411        impl fmt::Display for Display<'_> {
412            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413                write!(
414                    f,
415                    "{prefix}Pair[{left}, {right}]{suffix}",
416                    prefix = if f.alternate() { "generic type `" } else { "" },
417                    left = self.ty.left_type.display(self.params),
418                    right = self.ty.right_type.display(self.params),
419                    suffix = if f.alternate() { "`" } else { "" },
420                )
421            }
422        }
423
424        Display { params, ty: self }
425    }
426
427    /// Infers any type parameters from the generic type.
428    fn infer_type_parameters(
429        &self,
430        ty: &Type,
431        params: &mut TypeParameters<'_>,
432        ignore_constraints: bool,
433    ) {
434        match ty {
435            Type::Union => {
436                self.left_type
437                    .infer_type_parameters(&Type::Union, params, ignore_constraints);
438                self.right_type
439                    .infer_type_parameters(&Type::Union, params, ignore_constraints);
440            }
441            Type::Compound(CompoundType::Pair(ty), false) => {
442                self.left_type
443                    .infer_type_parameters(ty.left_type(), params, ignore_constraints);
444                self.right_type
445                    .infer_type_parameters(ty.right_type(), params, ignore_constraints);
446            }
447            _ => {}
448        }
449    }
450
451    /// Realizes the generic type to a `Pair`.
452    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
453        let left_type = self.left_type.realize(params)?;
454        let right_type = self.right_type.realize(params)?;
455        Some(PairType::new(left_type, right_type).into())
456    }
457
458    /// Asserts that the type parameters referenced by the type are valid.
459    ///
460    /// # Panics
461    ///
462    /// Panics if referenced type parameter is invalid.
463    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
464        self.left_type.assert_type_parameters(parameters);
465        self.right_type.assert_type_parameters(parameters);
466    }
467}
468
469/// Represents a generic `Map` type.
470#[derive(Debug, Clone)]
471pub struct GenericMapType {
472    /// The key type of the map.
473    key_type: Box<FunctionalType>,
474    /// The value type of the map.
475    value_type: Box<FunctionalType>,
476}
477
478impl GenericMapType {
479    /// Constructs a new generic map type.
480    pub fn new(key_type: impl Into<FunctionalType>, value_type: impl Into<FunctionalType>) -> Self {
481        Self {
482            key_type: Box::new(key_type.into()),
483            value_type: Box::new(value_type.into()),
484        }
485    }
486
487    /// Gets the maps's key type.
488    pub fn key_type(&self) -> &FunctionalType {
489        &self.key_type
490    }
491
492    /// Gets the maps's value type.
493    pub fn value_type(&self) -> &FunctionalType {
494        &self.value_type
495    }
496
497    /// Returns an object that implements `Display` for formatting the type.
498    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
499        #[allow(clippy::missing_docs_in_private_items)]
500        struct Display<'a> {
501            params: &'a TypeParameters<'a>,
502            ty: &'a GenericMapType,
503        }
504
505        impl fmt::Display for Display<'_> {
506            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507                write!(
508                    f,
509                    "{prefix}Map[{key}, {value}]{suffix}",
510                    prefix = if f.alternate() { "generic type `" } else { "" },
511                    key = self.ty.key_type.display(self.params),
512                    value = self.ty.value_type.display(self.params),
513                    suffix = if f.alternate() { "`" } else { "" },
514                )
515            }
516        }
517
518        Display { params, ty: self }
519    }
520
521    /// Infers any type parameters from the generic type.
522    fn infer_type_parameters(
523        &self,
524        ty: &Type,
525        params: &mut TypeParameters<'_>,
526        ignore_constraints: bool,
527    ) {
528        match ty {
529            Type::Union => {
530                self.key_type
531                    .infer_type_parameters(&Type::Union, params, ignore_constraints);
532                self.value_type
533                    .infer_type_parameters(&Type::Union, params, ignore_constraints);
534            }
535            Type::Compound(CompoundType::Map(ty), false) => {
536                self.key_type
537                    .infer_type_parameters(ty.key_type(), params, ignore_constraints);
538                self.value_type
539                    .infer_type_parameters(ty.value_type(), params, ignore_constraints);
540            }
541            _ => {}
542        }
543    }
544
545    /// Realizes the generic type to a `Map`.
546    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
547        let key_type = self.key_type.realize(params)?;
548        let value_type = self.value_type.realize(params)?;
549        Some(MapType::new(key_type, value_type).into())
550    }
551
552    /// Asserts that the type parameters referenced by the type are valid.
553    ///
554    /// # Panics
555    ///
556    /// Panics if referenced type parameter is invalid.
557    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
558        self.key_type.assert_type_parameters(parameters);
559        self.value_type.assert_type_parameters(parameters);
560    }
561}
562
563/// Represents a generic inner value of an enum.
564#[derive(Debug, Clone)]
565pub struct GenericEnumInnerValueType {
566    /// The inner value parameter.
567    param: &'static str,
568}
569
570impl GenericEnumInnerValueType {
571    /// Constructs a new generic enum inner value type.
572    pub fn new(param: &'static str) -> Self {
573        Self { param }
574    }
575
576    /// Gets the inner value parameter name.
577    pub fn param(&self) -> &'static str {
578        self.param
579    }
580
581    /// Returns an object that implements `Display` for formatting the type.
582    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
583        #[allow(clippy::missing_docs_in_private_items)]
584        struct Display<'a> {
585            params: &'a TypeParameters<'a>,
586            ty: &'a GenericEnumInnerValueType,
587        }
588
589        impl fmt::Display for Display<'_> {
590            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591                let (_, choice_ty) = self
592                    .params
593                    .get(self.ty.param)
594                    .expect("choice parameter should be present");
595
596                match choice_ty.as_ref().and_then(|t| t.as_enum()) {
597                    Some(enum_ty) => write!(f, "{}", enum_ty.inner_value_type()),
598                    // NOTE: non-enums should gracefully fail.
599                    _ => write!(f, "{}", self.ty.param),
600                }
601            }
602        }
603
604        Display { params, ty: self }
605    }
606
607    /// Realizes the generic type to the enum's inner value type.
608    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
609        let (_, choice_ty) = params
610            .get(self.param)
611            .expect("choice parameter should be present");
612
613        // NOTE: non-enums should gracefully fail.
614        choice_ty
615            .as_ref()
616            .and_then(|t| t.as_enum())
617            .map(|enum_ty| enum_ty.inner_value_type().clone())
618    }
619
620    /// Asserts that the type parameters referenced by the type are valid.
621    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
622        assert!(
623            parameters.iter().any(|p| p.name == self.param),
624            "generic enum choice type references unknown type parameter `{}`",
625            self.param
626        );
627    }
628}
629
630/// Represents a collection of type parameters.
631#[derive(Debug, Clone)]
632pub struct TypeParameters<'a> {
633    /// The collection of type parameters.
634    parameters: &'a [TypeParameter],
635    /// The inferred types for the type parameters.
636    inferred_types: [Option<Type>; MAX_TYPE_PARAMETERS],
637    /// A bitset of type parameters that have been referenced since the last
638    /// call to `reset`.
639    referenced: Cell<usize>,
640}
641
642impl<'a> TypeParameters<'a> {
643    /// Constructs a new type parameters collection using `None` as the
644    /// calculated parameter types.
645    ///
646    /// # Panics
647    ///
648    /// Panics if the count of the given type parameters exceeds the maximum
649    /// allowed.
650    pub fn new(parameters: &'a [TypeParameter]) -> Self {
651        assert!(
652            parameters.len() <= MAX_TYPE_PARAMETERS,
653            "no more than {MAX_TYPE_PARAMETERS} type parameters is supported"
654        );
655
656        Self {
657            parameters,
658            inferred_types: [const { None }; MAX_TYPE_PARAMETERS],
659            referenced: Cell::new(0),
660        }
661    }
662
663    /// Gets a type parameter and its inferred type from the collection.
664    ///
665    /// Returns `None` if the name is not a type parameter.
666    ///
667    /// This method also marks the type parameter as referenced.
668    pub fn get(&self, name: &str) -> Option<(&TypeParameter, Option<Type>)> {
669        let index = self.parameters.iter().position(|p| p.name == name)?;
670
671        // Mark the parameter as referenced
672        self.referenced.set(self.referenced.get() | (1 << index));
673
674        Some((&self.parameters[index], self.inferred_types[index].clone()))
675    }
676
677    /// Reset any referenced type parameters.
678    pub fn reset(&self) {
679        self.referenced.set(0);
680    }
681
682    /// Gets an iterator of the type parameters that have been referenced since
683    /// the last reset.
684    pub fn referenced(&self) -> impl Iterator<Item = (&TypeParameter, Option<Type>)> + use<'_> {
685        let mut bits = self.referenced.get();
686        std::iter::from_fn(move || {
687            if bits == 0 {
688                return None;
689            }
690
691            let index = bits.trailing_zeros() as usize;
692            let parameter = &self.parameters[index];
693            let ty = self.inferred_types[index].clone();
694            bits ^= bits & bits.overflowing_neg().0;
695            Some((parameter, ty))
696        })
697    }
698
699    /// Sets the inferred type of a type parameter.
700    ///
701    /// Note that a type parameter can only be inferred once; subsequent
702    /// attempts to set the inferred type will be ignored.
703    ///
704    /// # Panics
705    ///
706    /// Panics if the given name is not a type parameter.
707    fn set_inferred_type(&mut self, name: &str, ty: Type) {
708        let index = self
709            .parameters
710            .iter()
711            .position(|p| p.name == name)
712            .unwrap_or_else(|| panic!("unknown type parameter `{name}`"));
713
714        self.inferred_types[index].get_or_insert(ty);
715    }
716}
717
718/// Represents a type of a function parameter or return.
719#[derive(Debug, Clone)]
720pub enum FunctionalType {
721    /// The parameter type is a concrete WDL type.
722    Concrete(Type),
723    /// The parameter type is a generic type.
724    Generic(GenericType),
725}
726
727impl FunctionalType {
728    /// Determines if the type is generic.
729    pub fn is_generic(&self) -> bool {
730        matches!(self, Self::Generic(_))
731    }
732
733    /// Returns the concrete type.
734    ///
735    /// Returns `None` if the type is not concrete.
736    pub fn concrete_type(&self) -> Option<&Type> {
737        match self {
738            Self::Concrete(ty) => Some(ty),
739            Self::Generic(_) => None,
740        }
741    }
742
743    /// Returns an object that implements `Display` for formatting the type.
744    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
745        #[allow(clippy::missing_docs_in_private_items)]
746        struct Display<'a> {
747            params: &'a TypeParameters<'a>,
748            ty: &'a FunctionalType,
749        }
750
751        impl fmt::Display for Display<'_> {
752            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753                match self.ty {
754                    FunctionalType::Concrete(ty) => ty.fmt(f),
755                    FunctionalType::Generic(ty) => ty.display(self.params).fmt(f),
756                }
757            }
758        }
759
760        Display { params, ty: self }
761    }
762
763    /// Infers any type parameters if the type is generic.
764    fn infer_type_parameters(
765        &self,
766        ty: &Type,
767        params: &mut TypeParameters<'_>,
768        ignore_constraints: bool,
769    ) {
770        if let Self::Generic(generic) = self {
771            generic.infer_type_parameters(ty, params, ignore_constraints);
772        }
773    }
774
775    /// Realizes the type if the type is generic.
776    fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
777        match self {
778            FunctionalType::Concrete(ty) => Some(ty.clone()),
779            FunctionalType::Generic(ty) => ty.realize(params),
780        }
781    }
782
783    /// Asserts that the type parameters referenced by the type are valid.
784    ///
785    /// # Panics
786    ///
787    /// Panics if referenced type parameter is invalid.
788    fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
789        if let FunctionalType::Generic(ty) = self {
790            ty.assert_type_parameters(parameters)
791        }
792    }
793}
794
795impl From<Type> for FunctionalType {
796    fn from(value: Type) -> Self {
797        Self::Concrete(value)
798    }
799}
800
801impl From<PrimitiveType> for FunctionalType {
802    fn from(value: PrimitiveType) -> Self {
803        Self::Concrete(value.into())
804    }
805}
806
807impl From<ArrayType> for FunctionalType {
808    fn from(value: ArrayType) -> Self {
809        Self::Concrete(value.into())
810    }
811}
812
813impl From<MapType> for FunctionalType {
814    fn from(value: MapType) -> Self {
815        Self::Concrete(value.into())
816    }
817}
818
819impl From<GenericType> for FunctionalType {
820    fn from(value: GenericType) -> Self {
821        Self::Generic(value)
822    }
823}
824
825impl From<GenericArrayType> for FunctionalType {
826    fn from(value: GenericArrayType) -> Self {
827        Self::Generic(GenericType::Array(value))
828    }
829}
830
831impl From<GenericPairType> for FunctionalType {
832    fn from(value: GenericPairType) -> Self {
833        Self::Generic(GenericType::Pair(value))
834    }
835}
836
837impl From<GenericMapType> for FunctionalType {
838    fn from(value: GenericMapType) -> Self {
839        Self::Generic(GenericType::Map(value))
840    }
841}
842
843impl From<GenericEnumInnerValueType> for FunctionalType {
844    fn from(value: GenericEnumInnerValueType) -> Self {
845        Self::Generic(GenericType::EnumInnerValue(value))
846    }
847}
848
849/// Represents a type parameter to a function.
850#[derive(Debug)]
851pub struct TypeParameter {
852    /// The name of the type parameter.
853    name: &'static str,
854    /// The type parameter constraint.
855    constraint: Option<Box<dyn Constraint>>,
856}
857
858impl TypeParameter {
859    /// Creates a new type parameter without a constraint.
860    pub fn any(name: &'static str) -> Self {
861        Self {
862            name,
863            constraint: None,
864        }
865    }
866
867    /// Creates a new type parameter with the given constraint.
868    pub fn new(name: &'static str, constraint: impl Constraint + 'static) -> Self {
869        Self {
870            name,
871            constraint: Some(Box::new(constraint)),
872        }
873    }
874
875    /// Gets the name of the type parameter.
876    pub fn name(&self) -> &str {
877        self.name
878    }
879
880    /// Gets the constraint of the type parameter.
881    pub fn constraint(&self) -> Option<&dyn Constraint> {
882        self.constraint.as_deref()
883    }
884}
885
886/// Represents the kind of binding for arguments to a function.
887#[derive(Debug, Clone)]
888enum BindingKind {
889    /// The binding was an equivalence binding, meaning all of the provided
890    /// arguments had type equivalence with corresponding concrete parameters.
891    ///
892    /// The value is the bound return type of the function.
893    Equivalence(Type),
894    /// The binding was a coercion binding, meaning at least one of the provided
895    /// arguments needed to be coerced.
896    ///
897    /// The value it the bound return type of the function.
898    Coercion(Type),
899}
900
901impl BindingKind {
902    /// Gets the binding's return type.
903    pub fn ret(&self) -> &Type {
904        match self {
905            Self::Equivalence(ty) | Self::Coercion(ty) => ty,
906        }
907    }
908}
909
910/// Represents a parameter to a standard library function.
911#[derive(Debug)]
912pub struct FunctionParameter {
913    /// The name of the parameter.
914    name: &'static str,
915    /// The type of the parameter.
916    ty: FunctionalType,
917    /// The description of the parameter.
918    description: &'static str,
919}
920
921impl FunctionParameter {
922    /// Gets the name of the parameter.
923    pub fn name(&self) -> &'static str {
924        self.name
925    }
926
927    /// Gets the type of the parameter.
928    pub fn ty(&self) -> &FunctionalType {
929        &self.ty
930    }
931
932    /// Gets the description of the parameter.
933    #[allow(dead_code)]
934    pub fn description(&self) -> &'static str {
935        self.description
936    }
937}
938
939/// Represents a WDL function signature.
940#[derive(Debug)]
941pub struct FunctionSignature {
942    /// The minimum required version for the function signature.
943    minimum_version: Option<SupportedVersion>,
944    /// The generic type parameters of the function.
945    type_parameters: Vec<TypeParameter>,
946    /// The number of required parameters of the function.
947    required: Option<usize>,
948    /// The parameters of the function.
949    parameters: Vec<FunctionParameter>,
950    /// The return type of the function.
951    ret: FunctionalType,
952    /// The function definition
953    definition: Option<&'static str>,
954}
955
956impl FunctionSignature {
957    /// Builds a function signature builder.
958    pub fn builder() -> FunctionSignatureBuilder {
959        FunctionSignatureBuilder::new()
960    }
961
962    /// Gets the minimum version required to call this function signature.
963    pub fn minimum_version(&self) -> SupportedVersion {
964        self.minimum_version
965            .unwrap_or(SupportedVersion::V1(V1::Zero))
966    }
967
968    /// Gets the function's type parameters.
969    pub fn type_parameters(&self) -> &[TypeParameter] {
970        &self.type_parameters
971    }
972
973    /// Gets the function's parameters.
974    pub fn parameters(&self) -> &[FunctionParameter] {
975        &self.parameters
976    }
977
978    /// Gets the minimum number of required parameters.
979    ///
980    /// For a function without optional parameters, this will be the same as the
981    /// number of parameters for the function.
982    pub fn required(&self) -> usize {
983        self.required.unwrap_or(self.parameters.len())
984    }
985
986    /// Gets the function's return type.
987    pub fn ret(&self) -> &FunctionalType {
988        &self.ret
989    }
990
991    /// Gets the function's definition.
992    pub fn definition(&self) -> Option<&'static str> {
993        self.definition
994    }
995
996    /// Determines if the function signature is generic.
997    pub fn is_generic(&self) -> bool {
998        self.generic_parameter_count() > 0 || self.ret.is_generic()
999    }
1000
1001    /// Gets the count of generic parameters for the function.
1002    pub fn generic_parameter_count(&self) -> usize {
1003        self.parameters.iter().filter(|p| p.ty.is_generic()).count()
1004    }
1005
1006    /// Returns an object that implements `Display` for formatting the signature
1007    /// with the given function name.
1008    pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
1009        #[allow(clippy::missing_docs_in_private_items)]
1010        struct Display<'a> {
1011            params: &'a TypeParameters<'a>,
1012            sig: &'a FunctionSignature,
1013        }
1014
1015        impl fmt::Display for Display<'_> {
1016            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017                f.write_char('(')?;
1018
1019                self.params.reset();
1020                let required = self.sig.required();
1021                for (i, parameter) in self.sig.parameters.iter().enumerate() {
1022                    if i > 0 {
1023                        f.write_str(", ")?;
1024                    }
1025
1026                    if i >= required {
1027                        f.write_char('<')?;
1028                    }
1029
1030                    write!(
1031                        f,
1032                        "{name}: {ty}",
1033                        name = parameter.name(),
1034                        ty = parameter.ty().display(self.params)
1035                    )?;
1036
1037                    if i >= required {
1038                        f.write_char('>')?;
1039                    }
1040                }
1041
1042                write!(f, ") -> {ret}", ret = self.sig.ret.display(self.params))?;
1043                write_uninferred_constraints(f, self.params)?;
1044
1045                Ok(())
1046            }
1047        }
1048
1049        Display { params, sig: self }
1050    }
1051
1052    /// Infers the concrete types of any type parameters for the function
1053    /// signature.
1054    ///
1055    /// Returns the collection of type parameters.
1056    fn infer_type_parameters(
1057        &self,
1058        arguments: &[Type],
1059        ignore_constraints: bool,
1060    ) -> TypeParameters<'_> {
1061        let mut parameters = TypeParameters::new(&self.type_parameters);
1062        for (parameter, argument) in self.parameters.iter().zip(arguments.iter()) {
1063            parameter
1064                .ty
1065                .infer_type_parameters(argument, &mut parameters, ignore_constraints);
1066        }
1067
1068        parameters
1069    }
1070
1071    /// Determines if the there is an insufficient number of arguments to bind
1072    /// to this signature.
1073    fn insufficient_arguments(&self, arguments: &[Type]) -> bool {
1074        arguments.len() < self.required() || arguments.len() > self.parameters.len()
1075    }
1076
1077    /// Binds the function signature to the given arguments.
1078    ///
1079    /// This function will infer the type parameters for the arguments and
1080    /// ensure that the argument types are equivalent to the parameter types.
1081    ///
1082    /// If an argument is not type equivalent, an attempt is made to coerce the
1083    /// type.
1084    ///
1085    /// Returns the realized type of the function's return type.
1086    fn bind(
1087        &self,
1088        version: SupportedVersion,
1089        arguments: &[Type],
1090    ) -> Result<BindingKind, FunctionBindError> {
1091        if version < self.minimum_version() {
1092            return Err(FunctionBindError::RequiresVersion(self.minimum_version()));
1093        }
1094
1095        let required = self.required();
1096        if arguments.len() < required {
1097            return Err(FunctionBindError::TooFewArguments(required));
1098        }
1099
1100        if arguments.len() > self.parameters.len() {
1101            return Err(FunctionBindError::TooManyArguments(self.parameters.len()));
1102        }
1103
1104        // Ensure the argument types are correct for the function
1105        let mut coerced = false;
1106        let type_parameters = self.infer_type_parameters(arguments, false);
1107        for (i, (parameter, argument)) in self.parameters.iter().zip(arguments.iter()).enumerate() {
1108            match parameter.ty.realize(&type_parameters) {
1109                Some(ty) => {
1110                    // If a coercion hasn't occurred yet, check for type
1111                    // equivalence For the purpose of this
1112                    // check, also accept equivalence of `T` if the
1113                    // parameter type is `T?`; otherwise, fall back to coercion
1114                    if !coerced && argument != &ty && argument != &ty.require() {
1115                        coerced = true;
1116                    }
1117
1118                    if coerced && !argument.is_coercible_to(&ty) {
1119                        return Err(FunctionBindError::ArgumentTypeMismatch {
1120                            index: i,
1121                            expected: format!("{ty:#}"),
1122                        });
1123                    }
1124                }
1125                None if argument.is_union() => {
1126                    // If the type is `Union`, accept it as indeterminate
1127                    continue;
1128                }
1129                None => {
1130                    // Otherwise, this is a type mismatch
1131                    type_parameters.reset();
1132
1133                    let mut expected = String::new();
1134
1135                    write!(
1136                        &mut expected,
1137                        "{param:#}",
1138                        param = parameter.ty.display(&type_parameters)
1139                    )
1140                    .unwrap();
1141
1142                    write_uninferred_constraints(&mut expected, &type_parameters).unwrap();
1143                    return Err(FunctionBindError::ArgumentTypeMismatch { index: i, expected });
1144                }
1145            }
1146        }
1147
1148        // Finally, realize the return type; if it fails to realize, it means
1149        // there was at least one uninferred type parameter; we return
1150        // `Union` instead to indicate that the return value is
1151        // indeterminate.
1152        let ret = self.ret().realize(&type_parameters).unwrap_or(Type::Union);
1153
1154        if coerced {
1155            Ok(BindingKind::Coercion(ret))
1156        } else {
1157            Ok(BindingKind::Equivalence(ret))
1158        }
1159    }
1160}
1161
1162impl Default for FunctionSignature {
1163    fn default() -> Self {
1164        Self {
1165            minimum_version: None,
1166            type_parameters: Default::default(),
1167            required: Default::default(),
1168            parameters: Default::default(),
1169            ret: FunctionalType::Concrete(Type::Union),
1170            definition: None,
1171        }
1172    }
1173}
1174
1175/// Represents a function signature builder.
1176#[derive(Debug, Default)]
1177pub struct FunctionSignatureBuilder(FunctionSignature);
1178
1179impl FunctionSignatureBuilder {
1180    /// Constructs a new function signature builder.
1181    pub fn new() -> Self {
1182        Self(Default::default())
1183    }
1184
1185    /// Sets the minimum required version for the function signature.
1186    pub fn min_version(mut self, version: SupportedVersion) -> Self {
1187        self.0.minimum_version = Some(version);
1188        self
1189    }
1190
1191    /// Adds a constrained type parameter to the function signature.
1192    pub fn type_parameter(
1193        mut self,
1194        name: &'static str,
1195        constraint: impl Constraint + 'static,
1196    ) -> Self {
1197        self.0
1198            .type_parameters
1199            .push(TypeParameter::new(name, constraint));
1200        self
1201    }
1202
1203    /// Adds an unconstrained type parameter to the function signature.
1204    pub fn any_type_parameter(mut self, name: &'static str) -> Self {
1205        self.0.type_parameters.push(TypeParameter::any(name));
1206        self
1207    }
1208
1209    /// Adds a parameter to the function signature.
1210    pub fn parameter(
1211        mut self,
1212        name: &'static str,
1213        ty: impl Into<FunctionalType>,
1214        description: &'static str,
1215    ) -> Self {
1216        self.0.parameters.push(FunctionParameter {
1217            name,
1218            ty: ty.into(),
1219            description,
1220        });
1221        self
1222    }
1223
1224    /// Sets the return value in the function signature.
1225    ///
1226    /// If this is not called, the function signature will return a `Union`
1227    /// type.
1228    pub fn ret(mut self, ret: impl Into<FunctionalType>) -> Self {
1229        self.0.ret = ret.into();
1230        self
1231    }
1232
1233    /// Sets the number of required parameters in the function signature.
1234    pub fn required(mut self, required: usize) -> Self {
1235        self.0.required = Some(required);
1236        self
1237    }
1238
1239    /// Sets the definition of the function.
1240    pub fn definition(mut self, definition: &'static str) -> Self {
1241        self.0.definition = Some(definition);
1242        self
1243    }
1244
1245    /// Consumes the builder and produces the function signature.
1246    ///
1247    /// # Panics
1248    ///
1249    /// This method panics if the function signature is invalid.
1250    pub fn build(self) -> FunctionSignature {
1251        let sig = self.0;
1252
1253        // Ensure the number of required parameters doesn't exceed the number of
1254        // parameters
1255        if let Some(required) = sig.required
1256            && required > sig.parameters.len()
1257        {
1258            panic!("number of required parameters exceeds the number of parameters");
1259        }
1260
1261        assert!(
1262            sig.type_parameters.len() <= MAX_TYPE_PARAMETERS,
1263            "too many type parameters"
1264        );
1265
1266        assert!(
1267            sig.parameters.len() <= MAX_PARAMETERS,
1268            "too many parameters"
1269        );
1270
1271        // Ensure any generic type parameters indexes are in range for the
1272        // parameters
1273        for parameter in sig.parameters.iter() {
1274            parameter.ty.assert_type_parameters(&sig.type_parameters)
1275        }
1276
1277        sig.ret().assert_type_parameters(&sig.type_parameters);
1278
1279        assert!(sig.definition.is_some(), "functions should have definition");
1280
1281        sig
1282    }
1283}
1284
1285/// Represents information relating to how a function binds to its arguments.
1286#[derive(Debug, Clone)]
1287pub struct Binding<'a> {
1288    /// The calculated return type from the function given the argument types.
1289    return_type: Type,
1290    /// The function overload index.
1291    ///
1292    /// For monomorphic functions, this will always be zero.
1293    index: usize,
1294    /// The signature that was bound.
1295    signature: &'a FunctionSignature,
1296}
1297
1298impl Binding<'_> {
1299    /// Gets the calculated return type of the bound function.
1300    pub fn return_type(&self) -> &Type {
1301        &self.return_type
1302    }
1303
1304    /// Gets the overload index.
1305    ///
1306    /// For monomorphic functions, this will always be zero.
1307    pub fn index(&self) -> usize {
1308        self.index
1309    }
1310
1311    /// Gets the signature that was bound.
1312    pub fn signature(&self) -> &FunctionSignature {
1313        self.signature
1314    }
1315}
1316
1317/// Represents a WDL function.
1318#[derive(Debug)]
1319pub enum Function {
1320    /// The function is monomorphic.
1321    Monomorphic(MonomorphicFunction),
1322    /// The function is polymorphic.
1323    Polymorphic(PolymorphicFunction),
1324}
1325
1326impl Function {
1327    /// Gets the minimum WDL version required to call this function.
1328    pub fn minimum_version(&self) -> SupportedVersion {
1329        match self {
1330            Self::Monomorphic(f) => f.minimum_version(),
1331            Self::Polymorphic(f) => f.minimum_version(),
1332        }
1333    }
1334
1335    /// Gets the minimum and maximum number of parameters the function has for
1336    /// the given WDL version.
1337    ///
1338    /// Returns `None` if the function is not supported for the given version.
1339    pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1340        match self {
1341            Self::Monomorphic(f) => f.param_min_max(version),
1342            Self::Polymorphic(f) => f.param_min_max(version),
1343        }
1344    }
1345
1346    /// Binds the function to the given arguments.
1347    pub fn bind<'a>(
1348        &'a self,
1349        version: SupportedVersion,
1350        arguments: &[Type],
1351    ) -> Result<Binding<'a>, FunctionBindError> {
1352        match self {
1353            Self::Monomorphic(f) => f.bind(version, arguments),
1354            Self::Polymorphic(f) => f.bind(version, arguments),
1355        }
1356    }
1357
1358    /// Realizes the return type of the function without constraints.
1359    ///
1360    /// This is typically called after a failure to bind a function so that the
1361    /// return type can be calculated despite the failure.
1362    ///
1363    /// As such, it attempts to realize any type parameters without constraints,
1364    /// as an unsatisfied constraint likely caused the bind failure.
1365    pub fn realize_unconstrained_return_type(&self, arguments: &[Type]) -> Type {
1366        match self {
1367            Self::Monomorphic(f) => {
1368                let type_parameters = f.signature.infer_type_parameters(arguments, true);
1369                f.signature
1370                    .ret()
1371                    .realize(&type_parameters)
1372                    .unwrap_or(Type::Union)
1373            }
1374            Self::Polymorphic(f) => {
1375                let mut ty = None;
1376
1377                // For polymorphic functions, the calculated return type must be
1378                // the same for each overload
1379                for signature in &f.signatures {
1380                    let type_parameters = signature.infer_type_parameters(arguments, true);
1381                    let ret_ty = signature
1382                        .ret()
1383                        .realize(&type_parameters)
1384                        .unwrap_or(Type::Union);
1385
1386                    if ty.get_or_insert(ret_ty.clone()) != &ret_ty {
1387                        return Type::Union;
1388                    }
1389                }
1390
1391                ty.unwrap_or(Type::Union)
1392            }
1393        }
1394    }
1395}
1396
1397/// Represents a monomorphic function.
1398///
1399/// In this context, a monomorphic function has only a single type (i.e.
1400/// signature).
1401#[derive(Debug)]
1402pub struct MonomorphicFunction {
1403    /// The signature of the function.
1404    signature: FunctionSignature,
1405}
1406
1407impl MonomorphicFunction {
1408    /// Constructs a new monomorphic function.
1409    pub fn new(signature: FunctionSignature) -> Self {
1410        Self { signature }
1411    }
1412
1413    /// Gets the minimum WDL version required to call this function.
1414    pub fn minimum_version(&self) -> SupportedVersion {
1415        self.signature.minimum_version()
1416    }
1417
1418    /// Gets the minimum and maximum number of parameters the function has for
1419    /// the given WDL version.
1420    ///
1421    /// Returns `None` if the function is not supported for the given version.
1422    pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1423        if version < self.signature.minimum_version() {
1424            return None;
1425        }
1426
1427        Some((self.signature.required(), self.signature.parameters.len()))
1428    }
1429
1430    /// Gets the signature of the function.
1431    pub fn signature(&self) -> &FunctionSignature {
1432        &self.signature
1433    }
1434
1435    /// Binds the function to the given arguments.
1436    pub fn bind<'a>(
1437        &'a self,
1438        version: SupportedVersion,
1439        arguments: &[Type],
1440    ) -> Result<Binding<'a>, FunctionBindError> {
1441        let return_type = self.signature.bind(version, arguments)?.ret().clone();
1442        Ok(Binding {
1443            return_type,
1444            index: 0,
1445            signature: &self.signature,
1446        })
1447    }
1448}
1449
1450impl From<MonomorphicFunction> for Function {
1451    fn from(value: MonomorphicFunction) -> Self {
1452        Self::Monomorphic(value)
1453    }
1454}
1455
1456/// Represents a polymorphic function.
1457///
1458/// In this context, a polymorphic function has more than one type (i.e.
1459/// signature); overload resolution is used to determine which signature binds
1460/// to the function call.
1461#[derive(Debug)]
1462pub struct PolymorphicFunction {
1463    /// The signatures of the function.
1464    signatures: Vec<FunctionSignature>,
1465}
1466
1467impl PolymorphicFunction {
1468    /// Constructs a new polymorphic function.
1469    ///
1470    /// # Panics
1471    ///
1472    /// Panics if the number of signatures is less than two.
1473    pub fn new(signatures: Vec<FunctionSignature>) -> Self {
1474        assert!(
1475            signatures.len() > 1,
1476            "a polymorphic function must have at least two signatures"
1477        );
1478
1479        Self { signatures }
1480    }
1481
1482    /// Gets the minimum WDL version required to call this function.
1483    pub fn minimum_version(&self) -> SupportedVersion {
1484        self.signatures
1485            .iter()
1486            .fold(None, |v: Option<SupportedVersion>, s| {
1487                Some(
1488                    v.map(|v| v.min(s.minimum_version()))
1489                        .unwrap_or_else(|| s.minimum_version()),
1490                )
1491            })
1492            .expect("there should be at least one signature")
1493    }
1494
1495    /// Gets the minimum and maximum number of parameters the function has for
1496    /// the given WDL version.
1497    ///
1498    /// Returns `None` if the function is not supported for the given version.
1499    pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1500        let mut min = usize::MAX;
1501        let mut max = 0;
1502        for sig in self
1503            .signatures
1504            .iter()
1505            .filter(|s| s.minimum_version() <= version)
1506        {
1507            min = std::cmp::min(min, sig.required());
1508            max = std::cmp::max(max, sig.parameters().len());
1509        }
1510
1511        if min == usize::MAX {
1512            return None;
1513        }
1514
1515        Some((min, max))
1516    }
1517
1518    /// Gets the signatures of the function.
1519    pub fn signatures(&self) -> &[FunctionSignature] {
1520        &self.signatures
1521    }
1522
1523    /// Binds the function to the given arguments.
1524    ///
1525    /// This performs overload resolution for the polymorphic function.
1526    pub fn bind<'a>(
1527        &'a self,
1528        version: SupportedVersion,
1529        arguments: &[Type],
1530    ) -> Result<Binding<'a>, FunctionBindError> {
1531        // Ensure that there is at least one signature with a matching minimum
1532        // version.
1533        let min_version = self.minimum_version();
1534        if version < min_version {
1535            return Err(FunctionBindError::RequiresVersion(min_version));
1536        }
1537
1538        // Next check the min/max parameter counts
1539        let (min, max) = self
1540            .param_min_max(version)
1541            .expect("should have at least one signature for the version");
1542        if arguments.len() < min {
1543            return Err(FunctionBindError::TooFewArguments(min));
1544        }
1545
1546        if arguments.len() > max {
1547            return Err(FunctionBindError::TooManyArguments(max));
1548        }
1549
1550        // Overload resolution precedence is from most specific to least
1551        // specific:
1552        // * Non-generic exact match
1553        // * Non-generic with coercion
1554        // * Generic exact match
1555        // * Generic with coercion
1556
1557        let mut max_mismatch_index = 0;
1558        let mut expected_types = IndexSet::new();
1559
1560        for generic in [false, true] {
1561            let mut exact: Option<(usize, Type)> = None;
1562            let mut coercion1: Option<(usize, Type)> = None;
1563            let mut coercion2 = None;
1564            for (index, signature) in self.signatures.iter().enumerate().filter(|(_, s)| {
1565                s.is_generic() == generic
1566                    && s.minimum_version() <= version
1567                    && !s.insufficient_arguments(arguments)
1568            }) {
1569                match signature.bind(version, arguments) {
1570                    Ok(BindingKind::Equivalence(ty)) => {
1571                        // We cannot have more than one exact match
1572                        if let Some((previous, _)) = exact {
1573                            return Err(FunctionBindError::Ambiguous {
1574                                first: self.signatures[previous]
1575                                    .display(&TypeParameters::new(
1576                                        &self.signatures[previous].type_parameters,
1577                                    ))
1578                                    .to_string(),
1579                                second: self.signatures[index]
1580                                    .display(&TypeParameters::new(
1581                                        &self.signatures[index].type_parameters,
1582                                    ))
1583                                    .to_string(),
1584                            });
1585                        }
1586
1587                        exact = Some((index, ty));
1588                    }
1589                    Ok(BindingKind::Coercion(ty)) => {
1590                        // If this is the first coercion, store it; otherwise,
1591                        // store the second
1592                        // coercion index; if there's more than one coercion,
1593                        // we'll report an error
1594                        // below after ensuring there's no exact match
1595                        if coercion1.is_none() {
1596                            coercion1 = Some((index, ty));
1597                        } else {
1598                            coercion2.get_or_insert(index);
1599                        }
1600                    }
1601                    Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1602                        // We'll report an argument mismatch for the greatest
1603                        // argument index
1604                        if index > max_mismatch_index {
1605                            max_mismatch_index = index;
1606                            expected_types.clear();
1607                        }
1608
1609                        if index == max_mismatch_index {
1610                            expected_types.insert(expected);
1611                        }
1612                    }
1613                    Err(
1614                        FunctionBindError::RequiresVersion(_)
1615                        | FunctionBindError::Ambiguous { .. }
1616                        | FunctionBindError::TooFewArguments(_)
1617                        | FunctionBindError::TooManyArguments(_),
1618                    ) => unreachable!("should not encounter these errors due to above filter"),
1619                }
1620            }
1621
1622            if let Some((index, ty)) = exact {
1623                return Ok(Binding {
1624                    return_type: ty,
1625                    index,
1626                    signature: &self.signatures[index],
1627                });
1628            }
1629
1630            // Ensure there wasn't more than one coercion
1631            if let Some(previous) = coercion2 {
1632                let index = coercion1.unwrap().0;
1633                return Err(FunctionBindError::Ambiguous {
1634                    first: self.signatures[previous]
1635                        .display(&TypeParameters::new(
1636                            &self.signatures[previous].type_parameters,
1637                        ))
1638                        .to_string(),
1639                    second: self.signatures[index]
1640                        .display(&TypeParameters::new(
1641                            &self.signatures[index].type_parameters,
1642                        ))
1643                        .to_string(),
1644                });
1645            }
1646
1647            if let Some((index, ty)) = coercion1 {
1648                return Ok(Binding {
1649                    return_type: ty,
1650                    index,
1651                    signature: &self.signatures[index],
1652                });
1653            }
1654        }
1655
1656        assert!(!expected_types.is_empty());
1657
1658        let mut expected = String::new();
1659        for (i, ty) in expected_types.iter().enumerate() {
1660            if i > 0 {
1661                if expected_types.len() == 2 {
1662                    expected.push_str(" or ");
1663                } else if i == expected_types.len() - 1 {
1664                    expected.push_str(", or ");
1665                } else {
1666                    expected.push_str(", ");
1667                }
1668            }
1669
1670            expected.push_str(ty);
1671        }
1672
1673        Err(FunctionBindError::ArgumentTypeMismatch {
1674            index: max_mismatch_index,
1675            expected,
1676        })
1677    }
1678}
1679
1680impl From<PolymorphicFunction> for Function {
1681    fn from(value: PolymorphicFunction) -> Self {
1682        Self::Polymorphic(value)
1683    }
1684}
1685
1686/// A representation of the standard library.
1687#[derive(Debug)]
1688pub struct StandardLibrary {
1689    /// A map of function name to function definition.
1690    functions: IndexMap<&'static str, Function>,
1691    /// The type for `Array[Int]`.
1692    array_int: ArrayType,
1693    /// The type for `Array[String]`.
1694    array_string: ArrayType,
1695    /// The type for `Array[File]`.
1696    array_file: ArrayType,
1697    /// The type for `Array[Object]`.
1698    array_object: ArrayType,
1699    /// The type for `Array[String]+`.
1700    array_string_non_empty: ArrayType,
1701    /// The type for `Array[Array[String]]`.
1702    array_array_string: ArrayType,
1703    /// The type for `Map[String, String]`.
1704    map_string_string: MapType,
1705    /// The type for `Map[String, Int]`.
1706    map_string_int: MapType,
1707}
1708
1709impl StandardLibrary {
1710    /// Gets a standard library function by name.
1711    pub fn function(&self, name: &str) -> Option<&Function> {
1712        self.functions.get(name)
1713    }
1714
1715    /// Gets an iterator over all the functions in the standard library.
1716    pub fn functions(&self) -> impl ExactSizeIterator<Item = (&'static str, &Function)> {
1717        self.functions.iter().map(|(n, f)| (*n, f))
1718    }
1719
1720    /// Gets the type for `Array[Int]`.
1721    pub fn array_int_type(&self) -> &ArrayType {
1722        &self.array_int
1723    }
1724
1725    /// Gets the type for `Array[String]`.
1726    pub fn array_string_type(&self) -> &ArrayType {
1727        &self.array_string
1728    }
1729
1730    /// Gets the type for `Array[File]`.
1731    pub fn array_file_type(&self) -> &ArrayType {
1732        &self.array_file
1733    }
1734
1735    /// Gets the type for `Array[Object]`.
1736    pub fn array_object_type(&self) -> &ArrayType {
1737        &self.array_object
1738    }
1739
1740    /// Gets the type for `Array[String]+`.
1741    pub fn array_string_non_empty_type(&self) -> &ArrayType {
1742        &self.array_string_non_empty
1743    }
1744
1745    /// Gets the type for `Array[Array[String]]`.
1746    pub fn array_array_string_type(&self) -> &ArrayType {
1747        &self.array_array_string
1748    }
1749
1750    /// Gets the type for `Map[String, String]`.
1751    pub fn map_string_string_type(&self) -> &MapType {
1752        &self.map_string_string
1753    }
1754
1755    /// Gets the type for `Map[String, Int]`.
1756    pub fn map_string_int_type(&self) -> &MapType {
1757        &self.map_string_int
1758    }
1759}
1760
1761/// Represents the WDL standard library.
1762pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
1763    let array_int = ArrayType::new(PrimitiveType::Integer);
1764    let array_string = ArrayType::new(PrimitiveType::String);
1765    let array_file = ArrayType::new(PrimitiveType::File);
1766    let array_object = ArrayType::new(Type::Object);
1767    let array_string_non_empty = ArrayType::non_empty(PrimitiveType::String);
1768    let array_array_string = ArrayType::new(array_string.clone());
1769    let map_string_string = MapType::new(PrimitiveType::String, PrimitiveType::String);
1770    let map_string_int = MapType::new(PrimitiveType::String, PrimitiveType::Integer);
1771    let mut functions = IndexMap::new();
1772
1773    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#floor
1774    assert!(
1775        functions
1776            .insert(
1777                "floor",
1778                MonomorphicFunction::new(
1779                    FunctionSignature::builder()
1780                        .parameter("value", PrimitiveType::Float, "The number to round.")
1781                        .ret(PrimitiveType::Integer)
1782                        .definition(
1783                            r#"
1784Rounds a floating point number **down** to the next lower integer.
1785
1786**Parameters**:
1787
17881. `Float`: the number to round.
1789
1790**Returns**: An integer.
1791
1792Example: test_floor.wdl
1793
1794```wdl
1795version 1.2
1796
1797workflow test_floor {
1798  input {
1799    Int i1
1800  }
1801
1802  Int i2 = i1 - 1
1803  Float f1 = i1
1804  Float f2 = i1 - 0.1
1805
1806  output {
1807    Array[Boolean] all_true = [floor(f1) == i1, floor(f2) == i2]
1808  }
1809}
1810```"#
1811                        )
1812                        .build(),
1813                )
1814                .into(),
1815            )
1816            .is_none()
1817    );
1818
1819    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#ceil
1820    assert!(
1821        functions
1822            .insert(
1823                "ceil",
1824                MonomorphicFunction::new(
1825                    FunctionSignature::builder()
1826                        .parameter("value", PrimitiveType::Float, "The number to round.")
1827                        .ret(PrimitiveType::Integer)
1828                        .definition(
1829                            r#"
1830Rounds a floating point number **up** to the next higher integer.
1831
1832**Parameters**:
1833
18341. `Float`: the number to round.
1835
1836**Returns**: An integer.
1837
1838Example: test_ceil.wdl
1839
1840```wdl
1841version 1.2
1842
1843workflow test_ceil {
1844  input {
1845    Int i1
1846  }
1847
1848  Int i2 = i1 + 1
1849  Float f1 = i1
1850  Float f2 = i1 + 0.1
1851
1852  output {
1853    Array[Boolean] all_true = [ceil(f1) == i1, ceil(f2) == i2]
1854  }
1855}
1856```
1857"#
1858                        )
1859                        .build(),
1860                )
1861                .into(),
1862            )
1863            .is_none()
1864    );
1865
1866    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#round
1867    assert!(
1868        functions
1869            .insert(
1870                "round",
1871                MonomorphicFunction::new(
1872                    FunctionSignature::builder()
1873                        .parameter("value", PrimitiveType::Float, "The number to round.")
1874                        .ret(PrimitiveType::Integer)
1875                        .definition(r#"
1876Rounds a floating point number to the nearest integer based on standard rounding rules ("round half up").
1877
1878**Parameters**:
1879
18801. `Float`: the number to round.
1881
1882**Returns**: An integer.
1883
1884Example: test_round.wdl
1885
1886```wdl
1887version 1.2
1888
1889workflow test_round {
1890  input {
1891    Int i1
1892  }
1893
1894  Int i2 = i1 + 1
1895  Float f1 = i1 + 0.49
1896  Float f2 = i1 + 0.50
1897
1898  output {
1899    Array[Boolean] all_true = [round(f1) == i1, round(f2) == i2]
1900  }
1901}
1902```
1903"#
1904                    )
1905                        .build(),
1906                )
1907                .into(),
1908            )
1909            .is_none()
1910    );
1911
1912    const MIN_DEFINITION: &str = r#"
1913Returns the smaller of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1914
1915**Parameters**:
1916
19171. `Int|Float`: the first number to compare.
19182. `Int|Float`: the second number to compare.
1919
1920**Returns**: The smaller of the two arguments.
1921
1922Example: test_min.wdl
1923
1924```wdl
1925version 1.2
1926
1927workflow test_min {
1928  input {
1929    Int value1
1930    Float value2
1931  }
1932
1933  output {
1934    # these two expressions are equivalent
1935    Float min1 = if value1 < value2 then value1 else value2
1936    Float min2 = min(value1, value2)
1937  }
1938}
1939```
1940"#;
1941
1942    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#min
1943    assert!(
1944        functions
1945            .insert(
1946                "min",
1947                PolymorphicFunction::new(vec![
1948                    FunctionSignature::builder()
1949                        .min_version(SupportedVersion::V1(V1::One))
1950                        .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1951                        .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1952                        .ret(PrimitiveType::Integer)
1953                        .definition(MIN_DEFINITION)
1954                        .build(),
1955                    FunctionSignature::builder()
1956                        .min_version(SupportedVersion::V1(V1::One))
1957                        .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1958                        .parameter("b", PrimitiveType::Float, "The second number to compare.")
1959                        .ret(PrimitiveType::Float)
1960                        .definition(MIN_DEFINITION)
1961                        .build(),
1962                    FunctionSignature::builder()
1963                        .min_version(SupportedVersion::V1(V1::One))
1964                        .parameter("a", PrimitiveType::Float, "The first number to compare.")
1965                        .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1966                        .ret(PrimitiveType::Float)
1967                        .definition(MIN_DEFINITION)
1968                        .build(),
1969                    FunctionSignature::builder()
1970                        .min_version(SupportedVersion::V1(V1::One))
1971                        .parameter("a", PrimitiveType::Float, "The first number to compare.")
1972                        .parameter("b", PrimitiveType::Float, "The second number to compare.")
1973                        .ret(PrimitiveType::Float)
1974                        .definition(MIN_DEFINITION)
1975                        .build(),
1976                ])
1977                .into(),
1978            )
1979            .is_none()
1980    );
1981
1982    const MAX_DEFINITION: &str = r#"
1983Returns the larger of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1984
1985**Parameters**:
1986
19871. `Int|Float`: the first number to compare.
19882. `Int|Float`: the second number to compare.
1989
1990**Returns**: The larger of the two arguments.
1991
1992Example: test_max.wdl
1993
1994```wdl
1995version 1.2
1996
1997workflow test_max {
1998  input {
1999    Int value1
2000    Float value2
2001  }
2002
2003  output {
2004    # these two expressions are equivalent
2005    Float min1 = if value1 > value2 then value1 else value2
2006    Float min2 = max(value1, value2)
2007  }
2008}
2009```
2010"#;
2011
2012    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#max
2013    assert!(
2014        functions
2015            .insert(
2016                "max",
2017                PolymorphicFunction::new(vec![
2018                    FunctionSignature::builder()
2019                        .min_version(SupportedVersion::V1(V1::One))
2020                        .parameter("a", PrimitiveType::Integer, "The first number to compare.")
2021                        .parameter("b", PrimitiveType::Integer, "The second number to compare.")
2022                        .ret(PrimitiveType::Integer)
2023                        .definition(MAX_DEFINITION)
2024                        .build(),
2025                    FunctionSignature::builder()
2026                        .min_version(SupportedVersion::V1(V1::One))
2027                        .parameter("a", PrimitiveType::Integer, "The first number to compare.")
2028                        .parameter("b", PrimitiveType::Float, "The second number to compare.")
2029                        .ret(PrimitiveType::Float)
2030                        .definition(MAX_DEFINITION)
2031                        .build(),
2032                    FunctionSignature::builder()
2033                        .min_version(SupportedVersion::V1(V1::One))
2034                        .parameter("a", PrimitiveType::Float, "The first number to compare.")
2035                        .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
2036                        .ret(PrimitiveType::Float)
2037                        .definition(MAX_DEFINITION)
2038                        .build(),
2039                    FunctionSignature::builder()
2040                        .min_version(SupportedVersion::V1(V1::One))
2041                        .parameter("a", PrimitiveType::Float, "The first number to compare.")
2042                        .parameter("b", PrimitiveType::Float, "The second number to compare.")
2043                        .ret(PrimitiveType::Float)
2044                        .definition(MAX_DEFINITION)
2045                        .build(),
2046                ])
2047                .into(),
2048            )
2049            .is_none()
2050    );
2051
2052    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-find
2053    assert!(
2054        functions
2055            .insert(
2056                "find",
2057                MonomorphicFunction::new(
2058                    FunctionSignature::builder()
2059                        .min_version(SupportedVersion::V1(V1::Two))
2060                        .parameter("input", PrimitiveType::String, "The input string to search.")
2061                        .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2062                        .ret(Type::from(PrimitiveType::String).optional())
2063                        .definition(
2064                            r#"
2065Given two `String` parameters `input` and `pattern`, searches for the occurrence of `pattern` within `input` and returns the first match or `None` if there are no matches. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2066
2067Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
2068
2069```wdl
2070String? first_match = find("hello\tBob", "\t")
2071```
2072
2073**Parameters**
2074
20751. `String`: the input string to search.
20762. `String`: the pattern to search for.
2077
2078**Returns**: The contents of the first match, or `None` if `pattern` does not match `input`.
2079
2080Example: test_find_task.wdl
2081
2082```wdl
2083version 1.2
2084workflow find_string {
2085  input {
2086    String in = "hello world"
2087    String pattern1 = "e..o"
2088    String pattern2 = "goodbye"
2089  }
2090  output {
2091    String? match1 = find(in, pattern1)  # "ello"
2092    String? match2 = find(in, pattern2)  # None
2093  }
2094}
2095```
2096"#
2097                        )
2098                        .build(),
2099                )
2100                .into(),
2101            )
2102            .is_none()
2103    );
2104
2105    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-matches
2106    assert!(
2107        functions
2108            .insert(
2109                "matches",
2110                MonomorphicFunction::new(
2111                    FunctionSignature::builder()
2112                        .min_version(SupportedVersion::V1(V1::Two))
2113                        .parameter("input", PrimitiveType::String, "The input string to search.")
2114                        .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2115                        .ret(PrimitiveType::Boolean)
2116                        .definition(
2117                            r#"
2118Given two `String` parameters `input` and `pattern`, tests whether `pattern` matches `input` at least once. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2119
2120To test whether `pattern` matches the entire `input`, make sure to begin and end the pattern with anchors. For example:
2121
2122```wdl
2123Boolean full_match = matches("abc123", "^a.+3$")
2124```
2125
2126Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
2127
2128```wdl
2129Boolean has_tab = matches("hello\tBob", "\t")
2130```
2131
2132**Parameters**
2133
21341. `String`: the input string to search.
21352. `String`: the pattern to search for.
2136
2137**Returns**: `true` if `pattern` matches `input` at least once, otherwise `false`.
2138
2139Example: test_matches_task.wdl
2140
2141```wdl
2142version 1.2
2143workflow contains_string {
2144  input {
2145    File fastq
2146  }
2147  output {
2148    Boolean is_compressed = matches(basename(fastq), "\\.(gz|zip|zstd)")
2149    Boolean is_read1 = matches(basename(fastq), "_R1")
2150  }
2151}
2152```
2153"#
2154                        )
2155                        .build(),
2156                )
2157                .into(),
2158            )
2159            .is_none()
2160    );
2161
2162    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#sub
2163    assert!(
2164        functions
2165            .insert(
2166                "sub",
2167                MonomorphicFunction::new(
2168                    FunctionSignature::builder()
2169                        .parameter("input", PrimitiveType::String, "The input string.")
2170                        .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2171                        .parameter("replace", PrimitiveType::String, "The replacement string.")
2172                        .ret(PrimitiveType::String)
2173                        .definition(
2174                            r#"
2175Given three `String` parameters `input`, `pattern`, `replace`, this function replaces all non-overlapping occurrences of `pattern` in `input` by `replace`. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2176Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., "\t").
2177
2178đź—‘ The option for execution engines to allow other regular expression grammars besides POSIX ERE is deprecated.
2179
2180**Parameters**:
2181
21821. `String`: the input string.
21832. `String`: the pattern to search for.
21843. `String`: the replacement string.
2185
2186**Returns**: the input string, with all occurrences of the pattern replaced by the replacement string.
2187
2188Example: test_sub.wdl
2189
2190```wdl
2191version 1.2
2192
2193workflow test_sub {
2194  String chocolike = "I like chocolate when\nit's late"
2195
2196  output {
2197    String chocolove = sub(chocolike, "like", "love") # I love chocolate when\nit's late
2198    String chocoearly = sub(chocolike, "late", "early") # I like chocoearly when\nit's early
2199    String chocolate = sub(chocolike, "late$", "early") # I like chocolate when\nit's early
2200    String chocoearlylate = sub(chocolike, "[^ ]late", "early") # I like chocearly when\nit's late
2201    String choco4 = sub(chocolike, " [:alpha:]{4} ", " 4444 ") # I 4444 chocolate 4444\nit's late
2202    String no_newline = sub(chocolike, "\n", " ") # "I like chocolate when it's late"
2203  }
2204}
2205```
2206"#
2207                        )
2208                        .build(),
2209                )
2210                .into(),
2211            )
2212            .is_none()
2213    );
2214
2215    // https://github.com/openwdl/wdl/blob/wdl-1.3/SPEC.md#-split
2216    assert!(
2217        functions
2218            .insert(
2219                "split",
2220                MonomorphicFunction::new(
2221                    FunctionSignature::builder()
2222                        .min_version(SupportedVersion::V1(V1::Three))
2223                        .parameter("input", PrimitiveType::String, "The input string.")
2224                        .parameter("delimiter", PrimitiveType::String, "The delimiter to split on as a regular expression.")
2225                        .ret(array_string.clone())
2226                        .definition(
2227                            r#"
2228Given the two `String` parameters `input` and `delimiter`, this function splits the input string on the provided delimiter and stores the results in a `Array[String]`. `delimiter` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2229Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., `"\\t"`).
2230
2231**Parameters**:
2232
22331. `String`: the input string.
22342. `String`: the delimiter to split on as a regular expression.
2235
2236**Returns**: the parts of the input string split by the delimiter. If the input delimiter does not match anything in the input string, an array containing a single entry of the input string is returned.
2237
2238<details>
2239<summary>
2240Example: test_split.wdl
2241
2242```wdl
2243version 1.3
2244
2245workflow test_split {
2246  String in = "Here's an example\nthat takes up multiple lines"
2247
2248  output {
2249    Array[String] split_by_word = split(in, " ")
2250    Array[String] split_by_newline = split(in, "\\n")
2251    Array[String] split_by_both = split(in, "\s")
2252  }
2253}
2254```
2255"#
2256                        )
2257                        .build(),
2258                )
2259                .into(),
2260            )
2261            .is_none()
2262    );
2263
2264    const BASENAME_DEFINITION: &str = r#"
2265Returns the "basename" of a file or directory - the name after the last directory separator in the path.
2266
2267The optional second parameter specifies a literal suffix to remove from the file name. If the file name does not end with the specified suffix then it is ignored.
2268
2269**Parameters**
2270
22711. `File|Directory`: Path of the file or directory to read. If the argument is a `String`, it is assumed to be a local file path relative to the current working directory of the task.
22722. `String`: (Optional) Suffix to remove from the file name.
2273
2274**Returns**: The file's basename as a `String`.
2275
2276Example: test_basename.wdl
2277
2278```wdl
2279version 1.2
2280
2281workflow test_basename {
2282  output {
2283    Boolean is_true1 = basename("/path/to/file.txt") == "file.txt"
2284    Boolean is_true2 = basename("/path/to/file.txt", ".txt") == "file"
2285    Boolean is_true3 = basename("/path/to/dir") == "dir"
2286  }
2287}
2288```
2289"#;
2290
2291    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#basename
2292    assert!(
2293        functions
2294            .insert(
2295                "basename",
2296                PolymorphicFunction::new(vec![
2297                    FunctionSignature::builder()
2298                        .required(1)
2299                        .parameter(
2300                            "path",
2301                            PrimitiveType::File,
2302                            "Path of the file or directory to read. If the argument is a \
2303                             `String`, it is assumed to be a local file path relative to the \
2304                             current working directory of the task.",
2305                        )
2306                        .parameter(
2307                            "suffix",
2308                            PrimitiveType::String,
2309                            "(Optional) Suffix to remove from the file name.",
2310                        )
2311                        .ret(PrimitiveType::String)
2312                        .definition(BASENAME_DEFINITION)
2313                        .build(),
2314                    // This overload isn't explicitly specified in the spec, but the spec
2315                    // allows for `String` where file/directory are accepted; an explicit
2316                    // `String` overload is required as `String` may coerce to either `File` or
2317                    // `Directory`, which is ambiguous.
2318                    FunctionSignature::builder()
2319                        .min_version(SupportedVersion::V1(V1::Two))
2320                        .required(1)
2321                        .parameter(
2322                            "path",
2323                            PrimitiveType::String,
2324                            "Path of the file or directory to read. If the argument is a \
2325                             `String`, it is assumed to be a local file path relative to the \
2326                             current working directory of the task."
2327                        )
2328                        .parameter(
2329                            "suffix",
2330                            PrimitiveType::String,
2331                            "(Optional) Suffix to remove from the file name."
2332                        )
2333                        .ret(PrimitiveType::String)
2334                        .definition(BASENAME_DEFINITION)
2335                        .build(),
2336                    FunctionSignature::builder()
2337                        .min_version(SupportedVersion::V1(V1::Two))
2338                        .required(1)
2339                        .parameter(
2340                            "path",
2341                            PrimitiveType::Directory,
2342                            "Path of the file or directory to read. If the argument is a \
2343                             `String`, it is assumed to be a local file path relative to the \
2344                             current working directory of the task.",
2345                        )
2346                        .parameter(
2347                            "suffix",
2348                            PrimitiveType::String,
2349                            "(Optional) Suffix to remove from the file name.",
2350                        )
2351                        .ret(PrimitiveType::String)
2352                        .definition(BASENAME_DEFINITION)
2353                        .build(),
2354                ])
2355                .into(),
2356            )
2357            .is_none()
2358    );
2359
2360    const JOIN_PATHS_DEFINITION: &str = r#"
2361Joins together two or more paths into an absolute path in the execution environment's filesystem.
2362
2363There are three variants of this function:
2364
23651. `String join_paths(Directory, String)`: Joins together exactly two paths. The second path is relative to the first directory and may specify a file or directory.
23662. `String join_paths(Directory, Array[String]+)`: Joins together any number of relative paths with a base directory. The paths in the array argument must all be relative. The *last* element may specify a file or directory; all other elements must specify a directory.
23673. `String join_paths(Array[String]+)`: Joins together any number of paths. The array must not be empty. The *first* element of the array may be either absolute or relative; subsequent path(s) must be relative. The *last* element may specify a file or directory; all other elements must specify a directory.
2368
2369An absolute path starts with `/` and indicates that the path is relative to the root of the environment in which the task is executed. Only the first path may be absolute. If any subsequent paths are absolute, it is an error.
2370
2371A relative path does not start with `/` and indicates the path is relative to its parent directory. It is up to the execution engine to determine which directory to use as the parent when resolving relative paths; by default it is the working directory in which the task is executed.
2372
2373**Parameters**
2374
23751. `Directory|Array[String]+`: Either a directory path or an array of paths.
23762. `String|Array[String]+`: A relative path or paths; only allowed if the first argument is a `Directory`.
2377
2378**Returns**: A `String` representing an absolute path that results from joining all the paths in order (left-to-right), and resolving the resulting path against the default parent directory if it is relative.
2379
2380Example: join_paths_task.wdl
2381
2382```wdl
2383version 1.2
2384
2385task join_paths {
2386  input {
2387    Directory abs_dir = "/usr"
2388    String abs_str = "/usr"
2389    String rel_dir_str = "bin"
2390    String rel_file = "echo"
2391  }
2392
2393  # these are all equivalent to '/usr/bin/echo'
2394  String bin1 = join_paths(abs_dir, [rel_dir_str, rel_file])
2395  String bin2 = join_paths(abs_str, [rel_dir_str, rel_file])
2396  String bin3 = join_paths([abs_str, rel_dir_str, rel_file])
2397
2398  command <<<
2399    ~{bin1} -n "hello" > output.txt
2400  >>>
2401
2402  output {
2403    Boolean bins_equal = (bin1 == bin2) && (bin1 == bin3)
2404    String result = read_string("output.txt")
2405  }
2406
2407  runtime {
2408    container: "ubuntu:latest"
2409  }
2410}
2411```
2412"#;
2413
2414    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-join_paths
2415    assert!(
2416        functions
2417            .insert(
2418                "join_paths",
2419                PolymorphicFunction::new(vec![
2420                    FunctionSignature::builder()
2421                        .min_version(SupportedVersion::V1(V1::Two))
2422                        .parameter(
2423                            "base",
2424                            PrimitiveType::Directory,
2425                            "Either a path or an array of paths.",
2426                        )
2427                        .parameter(
2428                            "relative",
2429                            PrimitiveType::String,
2430                            "A relative path or paths; only allowed if the first argument is a \
2431                             `Directory`.",
2432                        )
2433                        .ret(PrimitiveType::String)
2434                        .definition(JOIN_PATHS_DEFINITION)
2435                        .build(),
2436                    FunctionSignature::builder()
2437                        .min_version(SupportedVersion::V1(V1::Two))
2438                        .parameter(
2439                            "base",
2440                            PrimitiveType::Directory,
2441                            "Either a path or an array of paths."
2442                        )
2443                        .parameter(
2444                            "relative",
2445                            array_string_non_empty.clone(),
2446                            "A relative path or paths; only allowed if the first argument is a \
2447                             `Directory`."
2448                        )
2449                        .ret(PrimitiveType::String)
2450                        .definition(JOIN_PATHS_DEFINITION)
2451                        .build(),
2452                    FunctionSignature::builder()
2453                        .min_version(SupportedVersion::V1(V1::Two))
2454                        .parameter(
2455                            "paths",
2456                            array_string_non_empty.clone(),
2457                            "Either a path or an array of paths."
2458                        )
2459                        .ret(PrimitiveType::String)
2460                        .definition(JOIN_PATHS_DEFINITION)
2461                        .build(),
2462                ])
2463                .into(),
2464            )
2465            .is_none()
2466    );
2467
2468    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#glob
2469    assert!(
2470        functions
2471            .insert(
2472                "glob",
2473                MonomorphicFunction::new(
2474                    FunctionSignature::builder()
2475                        .parameter("pattern", PrimitiveType::String, "The glob string.")
2476                        .ret(array_file.clone())
2477                        .definition(
2478                            r#"
2479Returns the Bash expansion of the [glob string](https://en.wikipedia.org/wiki/Glob_(programming)) relative to the task's execution directory, and in the same order.
2480
2481`glob` finds all of the files (but not the directories) in the same order as would be matched by running `echo <glob>` in Bash from the task's execution directory.
2482
2483At least in standard Bash, glob expressions are not evaluated recursively, i.e., files in nested directories are not included.
2484
2485**Parameters**:
2486
24871. `String`: The glob string.
2488
2489**Returns**: A array of all files matched by the glob.
2490
2491Example: gen_files_task.wdl
2492
2493```wdl
2494version 1.2
2495
2496task gen_files {
2497  input {
2498    Int num_files
2499  }
2500
2501  command <<<
2502    for i in 1..~{num_files}; do
2503      printf ${i} > a_file_${i}.txt
2504    done
2505    mkdir a_dir
2506    touch a_dir/a_inner.txt
2507  >>>
2508
2509  output {
2510    Array[File] files = glob("a_*")
2511    Int glob_len = length(files)
2512  }
2513}
2514```
2515"#
2516                        )
2517                        .build(),
2518                )
2519                .into(),
2520            )
2521            .is_none()
2522    );
2523
2524    const SIZE_DEFINITION: &str = r#"
2525Determines the size of a file, directory, or the sum total sizes of the files/directories contained within a compound value. The files may be optional values; `None` values have a size of `0.0`. By default, the size is returned in bytes unless the optional second argument is specified with a [unit](#units-of-storage)
2526
2527In the second variant of the `size` function, the parameter type `X` represents any compound type that contains `File` or `File?` nested at any depth.
2528
2529If the size cannot be represented in the specified unit because the resulting value is too large to fit in a `Float`, an error is raised. It is recommended to use a unit that will always be large enough to handle any expected inputs without numerical overflow.
2530
2531**Parameters**
2532
25331. `File|File?|Directory|Directory?|X|X?`: A file, directory, or a compound value containing files/directories, for which to determine the size.
25342. `String`: (Optional) The unit of storage; defaults to 'B'.
2535
2536**Returns**: The size of the files/directories as a `Float`.
2537
2538Example: file_sizes_task.wdl
2539
2540```wdl
2541version 1.2
2542
2543task file_sizes {
2544  command <<<
2545    printf "this file is 22 bytes\n" > created_file
2546  >>>
2547
2548  File? missing_file = None
2549
2550  output {
2551    File created_file = "created_file"
2552    Float missing_file_bytes = size(missing_file)
2553    Float created_file_bytes = size(created_file, "B")
2554    Float multi_file_kb = size([created_file, missing_file], "K")
2555
2556    Map[String, Pair[Int, File]] nested = {
2557      "a": (10, created_file),
2558      "b": (50, missing_file)
2559    }
2560    Float nested_bytes = size(nested)
2561  }
2562
2563  requirements {
2564    container: "ubuntu:latest"
2565  }
2566}
2567```
2568"#;
2569
2570    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#size
2571    assert!(
2572        functions
2573            .insert(
2574                "size",
2575                PolymorphicFunction::new(vec![
2576                    // This overload isn't explicitly in the spec, but it fixes an ambiguity in 1.2
2577                    // when passed a literal `None` value.
2578                    FunctionSignature::builder()
2579                        .min_version(SupportedVersion::V1(V1::Two))
2580                        .required(1)
2581                        .parameter(
2582                            "value",
2583                            Type::None,
2584                            "A file, directory, or a compound value containing files/directories, \
2585                             for which to determine the size."
2586                        )
2587                        .parameter(
2588                            "unit",
2589                            PrimitiveType::String,
2590                            "(Optional) The unit of storage; defaults to 'B'."
2591                        )
2592                        .ret(PrimitiveType::Float)
2593                        .definition(SIZE_DEFINITION)
2594                        .build(),
2595                    FunctionSignature::builder()
2596                        .required(1)
2597                        .parameter(
2598                            "value",
2599                            Type::from(PrimitiveType::File).optional(),
2600                            "A file, directory, or a compound value containing files/directories, \
2601                             for which to determine the size."
2602                        )
2603                        .parameter(
2604                            "unit",
2605                            PrimitiveType::String,
2606                            "(Optional) The unit of storage; defaults to 'B'."
2607                        )
2608                        .ret(PrimitiveType::Float)
2609                        .definition(SIZE_DEFINITION)
2610                        .build(),
2611                    // This overload isn't explicitly specified in the spec, but the spec
2612                    // allows for `String` where file/directory are accepted; an explicit
2613                    // `String` overload is required as `String` may coerce to either `File` or
2614                    // `Directory`, which is ambiguous.
2615                    FunctionSignature::builder()
2616                        .min_version(SupportedVersion::V1(V1::Two))
2617                        .required(1)
2618                        .parameter(
2619                            "value",
2620                            Type::from(PrimitiveType::String).optional(),
2621                            "A file, directory, or a compound value containing files/directories, \
2622                             for which to determine the size.",
2623                        )
2624                        .parameter(
2625                            "unit",
2626                            PrimitiveType::String,
2627                            "(Optional) The unit of storage; defaults to 'B'.",
2628                        )
2629                        .ret(PrimitiveType::Float)
2630                        .definition(SIZE_DEFINITION)
2631                        .build(),
2632                    FunctionSignature::builder()
2633                        .min_version(SupportedVersion::V1(V1::Two))
2634                        .required(1)
2635                        .parameter(
2636                            "value",
2637                            Type::from(PrimitiveType::Directory).optional(),
2638                            "A file, directory, or a compound value containing files/directories, \
2639                             for which to determine the size."
2640                        )
2641                        .parameter(
2642                            "unit",
2643                            PrimitiveType::String,
2644                            "(Optional) The unit of storage; defaults to 'B'."
2645                        )
2646                        .ret(PrimitiveType::Float)
2647                        .definition(SIZE_DEFINITION)
2648                        .build(),
2649                    FunctionSignature::builder()
2650                        .required(1)
2651                        .type_parameter("X", SizeableConstraint)
2652                        .parameter(
2653                            "value",
2654                            GenericType::Parameter("X"),
2655                            "A file, directory, or a compound value containing files/directories, \
2656                             for which to determine the size."
2657                        )
2658                        .parameter(
2659                            "unit",
2660                            PrimitiveType::String,
2661                            "(Optional) The unit of storage; defaults to 'B'."
2662                        )
2663                        .ret(PrimitiveType::Float)
2664                        .definition(SIZE_DEFINITION)
2665                        .build(),
2666                ])
2667                .into(),
2668            )
2669            .is_none()
2670    );
2671
2672    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#stdout
2673    assert!(
2674        functions
2675            .insert(
2676                "stdout",
2677                MonomorphicFunction::new(
2678                    FunctionSignature::builder()
2679                        .ret(PrimitiveType::File)
2680                        .definition(
2681                            r#"
2682Returns the value of the executed command's standard output (stdout) as a `File`. The engine should give the file a random name and write it in a temporary directory, so as not to conflict with any other task output files.
2683
2684**Parameters**: None
2685
2686**Returns**: A `File` whose contents are the stdout generated by the command of the task where the function is called.
2687
2688Example: echo_stdout.wdl
2689
2690```wdl
2691version 1.2
2692
2693task echo_stdout {
2694  command <<<
2695    printf "hello world"
2696  >>>
2697
2698  output {
2699    File message = read_string(stdout())
2700  }
2701}
2702```
2703"#
2704                        )
2705                        .build(),
2706                )
2707                .into(),
2708            )
2709            .is_none()
2710    );
2711
2712    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#stderr
2713    assert!(
2714        functions
2715            .insert(
2716                "stderr",
2717                MonomorphicFunction::new(
2718                    FunctionSignature::builder()
2719                        .ret(PrimitiveType::File)
2720                        .definition(
2721                            r#"
2722Returns the value of the executed command's standard error (stderr) as a `File`. The file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
2723
2724**Parameters**: None
2725
2726**Returns**: A `File` whose contents are the stderr generated by the command of the task where the function is called.
2727
2728Example: echo_stderr.wdl
2729
2730```wdl
2731version 1.2
2732
2733task echo_stderr {
2734  command <<<
2735    >&2 printf "hello world"
2736  >>>
2737
2738  output {
2739    File message = read_string(stderr())
2740  }
2741}
2742```
2743"#
2744                        )
2745                        .build(),
2746                )
2747                .into(),
2748            )
2749            .is_none()
2750    );
2751
2752    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_string
2753    assert!(
2754        functions
2755            .insert(
2756                "read_string",
2757                MonomorphicFunction::new(
2758                    FunctionSignature::builder()
2759                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2760                        .ret(PrimitiveType::String)
2761                        .definition(
2762                            r#"
2763Reads an entire file as a `String`, with any trailing end-of-line characters (`` and `\n`) stripped off. If the file is empty, an empty string is returned.
2764
2765If the file contains any internal newline characters, they are left in tact.
2766
2767**Parameters**
2768
27691. `File`: Path of the file to read.
2770
2771**Returns**: A `String`.
2772
2773Example: read_string_task.wdl
2774
2775```wdl
2776version 1.2
2777
2778task read_string {
2779  # this file will contain "this\nfile\nhas\nfive\nlines\n"
2780  File f = write_lines(["this", "file", "has", "five", "lines"])
2781
2782  command <<<
2783  cat ~{f}
2784  >>>
2785
2786  output {
2787    # s will contain "this\nfile\nhas\nfive\nlines"
2788    String s = read_string(stdout())
2789  }
2790}
2791```
2792"#
2793                        )
2794                        .build(),
2795                )
2796                .into(),
2797            )
2798            .is_none()
2799    );
2800
2801    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_int
2802    assert!(
2803        functions
2804            .insert(
2805                "read_int",
2806                MonomorphicFunction::new(
2807                    FunctionSignature::builder()
2808                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2809                        .ret(PrimitiveType::Integer)
2810                        .definition(
2811                            r#"
2812Reads a file that contains a single line containing only an integer and (optional) whitespace. If the line contains a valid integer, that value is returned as an `Int`. If the file is empty or does not contain a single integer, an error is raised.
2813
2814**Parameters**
2815
28161. `File`: Path of the file to read.
2817
2818**Returns**: An `Int`.
2819
2820Example: read_int_task.wdl
2821
2822```wdl
2823version 1.2
2824
2825task read_int {
2826  command <<<
2827  printf "  1  \n" > int_file
2828  >>>
2829
2830  output {
2831    Int i = read_int("int_file")
2832  }
2833}
2834```
2835"#
2836                        )
2837                        .build(),
2838                )
2839                .into(),
2840            )
2841            .is_none()
2842    );
2843
2844    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_float
2845    assert!(
2846        functions
2847            .insert(
2848                "read_float",
2849                MonomorphicFunction::new(
2850                    FunctionSignature::builder()
2851                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2852                        .ret(PrimitiveType::Float)
2853                        .definition(
2854                            r#"
2855Reads a file that contains only a numeric value and (optional) whitespace. If the line contains a valid floating point number, that value is returned as a `Float`. If the file is empty or does not contain a single float, an error is raised.
2856
2857**Parameters**
2858
28591. `File`: Path of the file to read.
2860
2861**Returns**: A `Float`.
2862
2863Example: read_float_task.wdl
2864
2865```wdl
2866version 1.2
2867
2868task read_float {
2869  command <<<
2870  printf "  1  \n" > int_file
2871  printf "  2.0  \n" > float_file
2872  >>>
2873
2874  output {
2875    Float f1 = read_float("int_file")
2876    Float f2 = read_float("float_file")
2877  }
2878}
2879```
2880"#
2881                        )
2882                        .build(),
2883                )
2884                .into(),
2885            )
2886            .is_none()
2887    );
2888
2889    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_boolean
2890    assert!(
2891        functions
2892            .insert(
2893                "read_boolean",
2894                MonomorphicFunction::new(
2895                    FunctionSignature::builder()
2896                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2897                        .ret(PrimitiveType::Boolean)
2898                        .definition(
2899                            r#"
2900Reads a file that contains a single line containing only a boolean value and (optional) whitespace. If the non-whitespace content of the line is "true" or "false", that value is returned as a `Boolean`. If the file is empty or does not contain a single boolean, an error is raised. The comparison is case- and whitespace-insensitive.
2901
2902**Parameters**
2903
29041. `File`: Path of the file to read.
2905
2906**Returns**: A `Boolean`.
2907
2908Example: read_bool_task.wdl
2909
2910```wdl
2911version 1.2
2912
2913task read_bool {
2914  command <<<
2915  printf "  true  \n" > true_file
2916  printf "  FALSE  \n" > false_file
2917  >>>
2918
2919  output {
2920    Boolean b1 = read_boolean("true_file")
2921    Boolean b2 = read_boolean("false_file")
2922  }
2923}
2924```
2925"#
2926                        )
2927                        .build(),
2928                )
2929                .into(),
2930            )
2931            .is_none()
2932    );
2933
2934    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_lines
2935    assert!(
2936        functions
2937            .insert(
2938                "read_lines",
2939                MonomorphicFunction::new(
2940                    FunctionSignature::builder()
2941                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2942                        .ret(array_string.clone())
2943                        .definition(
2944                            r#"
2945Reads each line of a file as a `String`, and returns all lines in the file as an `Array[String]`. Trailing end-of-line characters (`` and `\n`) are removed from each line.
2946
2947The order of the lines in the returned `Array[String]` is the order in which the lines appear in the file.
2948
2949If the file is empty, an empty array is returned.
2950
2951**Parameters**
2952
29531. `File`: Path of the file to read.
2954
2955**Returns**: An `Array[String]` representation of the lines in the file.
2956
2957Example: grep_task.wdl
2958
2959```wdl
2960version 1.2
2961
2962task grep {
2963  input {
2964    String pattern
2965    File file
2966  }
2967
2968  command <<<
2969    grep '~{pattern}' ~{file}
2970  >>>
2971
2972  output {
2973    Array[String] matches = read_lines(stdout())
2974  }
2975
2976  requirements {
2977    container: "ubuntu:latest"
2978  }
2979}
2980```
2981"#
2982                        )
2983                        .build(),
2984                )
2985                .into(),
2986            )
2987            .is_none()
2988    );
2989
2990    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_lines
2991    assert!(
2992        functions
2993            .insert(
2994                "write_lines",
2995                MonomorphicFunction::new(
2996                    FunctionSignature::builder()
2997                        .parameter("array", array_string.clone(), "`Array` of strings to write.")
2998                        .ret(PrimitiveType::File)
2999                        .definition(
3000                            r#"
3001Writes a file with one line for each element in a `Array[String]`. All lines are terminated by the newline (`\n`) character (following the [POSIX standard](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206)). If the `Array` is empty, an empty file is written.
3002
3003**Parameters**
3004
30051. `Array[String]`: Array of strings to write.
3006
3007**Returns**: A `File`.
3008
3009Example: write_lines_task.wdl
3010
3011```wdl
3012version 1.2
3013
3014task write_lines {
3015  input {
3016    Array[String] array = ["first", "second", "third"]
3017  }
3018
3019  command <<<
3020    paste -s -d'\t' ~{write_lines(array)}
3021  >>>
3022
3023  output {
3024    String s = read_string(stdout())
3025  }
3026
3027  requirements {
3028    container: "ubuntu:latest"
3029  }
3030}
3031```
3032"#
3033                        )
3034                        .build(),
3035                )
3036                .into(),
3037            )
3038            .is_none()
3039    );
3040
3041    const READ_TSV_DEFINITION: &str = r#"
3042Reads a tab-separated value (TSV) file as an `Array[Array[String]]` representing a table of values. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3043
3044This function has three variants:
3045
30461. `Array[Array[String]] read_tsv(File, [false])`: Returns each row of the table as an `Array[String]`. There is no requirement that the rows of the table are all the same length.
30472. `Array[Object] read_tsv(File, true)`: The second parameter must be `true` and specifies that the TSV file contains a header line. Each row is returned as an `Object` with its keys determined by the header (the first line in the file) and its values as `String`s. All rows in the file must be the same length and the field names in the header row must be valid `Object` field names, or an error is raised.
30483. `Array[Object] read_tsv(File, Boolean, Array[String])`: The second parameter specifies whether the TSV file contains a header line, and the third parameter is an array of field names that is used to specify the field names to use for the returned `Object`s. If the second parameter is `true`, the specified field names override those in the file's header (i.e., the header line is ignored).
3049
3050If the file is empty, an empty array is returned.
3051
3052If the entire contents of the file can not be read for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, not having access to the file, resource limitations (e.g. memory) when reading the file, and implementation-imposed file size limits.
3053
3054**Parameters**
3055
30561. `File`: The TSV file to read.
30572. `Boolean`: (Optional) Whether to treat the file's first line as a header.
30583. `Array[String]`: (Optional) An array of field names. If specified, then the second parameter is also required.
3059
3060**Returns**: An `Array` of rows in the TSV file, where each row is an `Array[String]` of fields or an `Object` with keys determined by the second and third parameters and `String` values.
3061
3062Example: read_tsv_task.wdl
3063
3064```wdl
3065version 1.2
3066
3067task read_tsv {
3068  command <<<
3069    {
3070      printf "row1\tvalue1\n"
3071      printf "row2\tvalue2\n"
3072      printf "row3\tvalue3\n"
3073    } >> data.no_headers.tsv
3074
3075    {
3076      printf "header1\theader2\n"
3077      printf "row1\tvalue1\n"
3078      printf "row2\tvalue2\n"
3079      printf "row3\tvalue3\n"
3080    } >> data.headers.tsv
3081  >>>
3082
3083  output {
3084    Array[Array[String]] output_table = read_tsv("data.no_headers.tsv")
3085    Array[Object] output_objs1 = read_tsv("data.no_headers.tsv", false, ["name", "value"])
3086    Array[Object] output_objs2 = read_tsv("data.headers.tsv", true)
3087    Array[Object] output_objs3 = read_tsv("data.headers.tsv", true, ["name", "value"])
3088  }
3089}
3090```
3091"#;
3092
3093    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_tsv
3094    assert!(
3095        functions
3096            .insert(
3097                "read_tsv",
3098                PolymorphicFunction::new(vec![
3099                    FunctionSignature::builder()
3100                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
3101                        .ret(array_array_string.clone())
3102                        .definition(READ_TSV_DEFINITION)
3103                        .build(),
3104                    FunctionSignature::builder()
3105                        .min_version(SupportedVersion::V1(V1::Two))
3106                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
3107                        .parameter(
3108                            "header",
3109                            PrimitiveType::Boolean,
3110                            "(Optional) Whether to treat the file's first line as a header.",
3111                        )
3112                        .ret(array_object.clone())
3113                        .definition(READ_TSV_DEFINITION)
3114                        .build(),
3115                    FunctionSignature::builder()
3116                        .min_version(SupportedVersion::V1(V1::Two))
3117                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
3118                        .parameter(
3119                            "header",
3120                            PrimitiveType::Boolean,
3121                            "(Optional) Whether to treat the file's first line as a header.",
3122                        )
3123                        .parameter(
3124                            "columns",
3125                            array_string.clone(),
3126                            "(Optional) An array of field names. If specified, then the second \
3127                             parameter is also required.",
3128                        )
3129                        .ret(array_object.clone())
3130                        .definition(READ_TSV_DEFINITION)
3131                        .build(),
3132                ])
3133                .into(),
3134            )
3135            .is_none()
3136    );
3137
3138    const WRITE_TSV_DEFINITION: &str = r#"
3139Given an `Array` of elements, writes a tab-separated value (TSV) file with one line for each element.
3140
3141There are three variants of this function:
3142
31431. `File write_tsv(Array[Array[String]])`: Each element is concatenated using a tab ('\t') delimiter and written as a row in the file. There is no header row.
3144
31452. `File write_tsv(Array[Array[String]], true, Array[String])`: The second argument must be `true` and the third argument provides an `Array` of column names. The column names are concatenated to create a header that is written as the first row of the file. All elements must be the same length as the header array.
3146
31473. `File write_tsv(Array[Struct], [Boolean, [Array[String]]])`: Each element is a struct whose field values are concatenated in the order the fields are defined. The optional second argument specifies whether to write a header row. If it is `true`, then the header is created from the struct field names. If the second argument is `true`, then the optional third argument may be used to specify column names to use instead of the struct field names.
3148
3149Each line is terminated by the newline (`\n`) character.
3150
3151The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3152
3153If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3154
3155
3156**Parameters**
3157
31581. `Array[Array[String]] | Array[Struct]`: An array of rows, where each row is either an `Array` of column values or a struct whose values are the column values.
31592. `Boolean`: (Optional) Whether to write a header row.
31603. `Array[String]`: An array of column names. If the first argument is `Array[Array[String]]` and the second argument is `true` then it is required, otherwise it is optional. Ignored if the second argument is `false`.
3161
3162
3163**Returns**: A `File`.
3164
3165Example: write_tsv_task.wdl
3166
3167```wdl
3168version 1.2
3169
3170task write_tsv {
3171  input {
3172    Array[Array[String]] array = [["one", "two", "three"], ["un", "deux", "trois"]]
3173    Array[Numbers] structs = [
3174      Numbers {
3175        first: "one",
3176        second: "two",
3177        third: "three"
3178      },
3179      Numbers {
3180        first: "un",
3181        second: "deux",
3182        third: "trois"
3183      }
3184    ]
3185  }
3186
3187  command <<<
3188    cut -f 1 ~{write_tsv(array)} >> array_no_header.txt
3189    cut -f 1 ~{write_tsv(array, true, ["first", "second", "third"])} > array_header.txt
3190    cut -f 1 ~{write_tsv(structs)} >> structs_default.txt
3191    cut -f 2 ~{write_tsv(structs, false)} >> structs_no_header.txt
3192    cut -f 2 ~{write_tsv(structs, true)} >> structs_header.txt
3193    cut -f 3 ~{write_tsv(structs, true, ["no1", "no2", "no3"])} >> structs_user_header.txt
3194  >>>
3195
3196  output {
3197    Array[String] array_no_header = read_lines("array_no_header.txt")
3198    Array[String] array_header = read_lines("array_header.txt")
3199    Array[String] structs_default = read_lines("structs_default.txt")
3200    Array[String] structs_no_header = read_lines("structs_no_header.txt")
3201    Array[String] structs_header = read_lines("structs_header.txt")
3202    Array[String] structs_user_header = read_lines("structs_user_header.txt")
3203
3204  }
3205
3206  requirements {
3207    container: "ubuntu:latest"
3208  }
3209}
3210```
3211"#;
3212
3213    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_tsv
3214    assert!(
3215        functions
3216            .insert(
3217                "write_tsv",
3218                PolymorphicFunction::new(vec![
3219                    FunctionSignature::builder()
3220                        .parameter(
3221                            "data",
3222                            array_array_string.clone(),
3223                            "An array of rows, where each row is either an `Array` of column \
3224                             values or a struct whose values are the column values.",
3225                        )
3226                        .ret(PrimitiveType::File)
3227                        .definition(WRITE_TSV_DEFINITION)
3228                        .build(),
3229                    FunctionSignature::builder()
3230                        .min_version(SupportedVersion::V1(V1::Two))
3231                        .parameter(
3232                            "data",
3233                            array_array_string.clone(),
3234                            "An array of rows, where each row is either an `Array` of column \
3235                             values or a struct whose values are the column values.",
3236                        )
3237                        .parameter(
3238                            "header",
3239                            PrimitiveType::Boolean,
3240                            "(Optional) Whether to write a header row.",
3241                        )
3242                        .parameter(
3243                            "columns",
3244                            array_string.clone(),
3245                            "An array of column names. If the first argument is \
3246                             `Array[Array[String]]` and the second argument is true then it is \
3247                             required, otherwise it is optional. Ignored if the second argument \
3248                             is false."
3249                        )
3250                        .ret(PrimitiveType::File)
3251                        .definition(WRITE_TSV_DEFINITION)
3252                        .build(),
3253                    FunctionSignature::builder()
3254                        .min_version(SupportedVersion::V1(V1::Two))
3255                        .type_parameter("S", PrimitiveStructConstraint)
3256                        .required(1)
3257                        .parameter(
3258                            "data",
3259                            GenericArrayType::new(GenericType::Parameter("S")),
3260                            "An array of rows, where each row is either an `Array` of column \
3261                             values or a struct whose values are the column values.",
3262                        )
3263                        .parameter(
3264                            "header",
3265                            PrimitiveType::Boolean,
3266                            "(Optional) Whether to write a header row.",
3267                        )
3268                        .parameter(
3269                            "columns",
3270                            array_string.clone(),
3271                            "An array of column names. If the first argument is \
3272                             `Array[Array[String]]` and the second argument is true then it is \
3273                             required, otherwise it is optional. Ignored if the second argument \
3274                             is false."
3275                        )
3276                        .ret(PrimitiveType::File)
3277                        .definition(WRITE_TSV_DEFINITION)
3278                        .build(),
3279                ])
3280                .into(),
3281            )
3282            .is_none()
3283    );
3284
3285    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_map
3286    assert!(
3287        functions
3288            .insert(
3289                "read_map",
3290                MonomorphicFunction::new(
3291                    FunctionSignature::builder()
3292                        .parameter(
3293                            "file",
3294                            PrimitiveType::File,
3295                            "Path of the two-column TSV file to read.",
3296                        )
3297                        .ret(map_string_string.clone())
3298                        .definition(
3299                            r#"
3300Reads a tab-separated value (TSV) file representing a set of pairs. Each row must have exactly two columns, e.g., `col1\tcol2`. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3301
3302Each pair is added to a `Map[String, String]` in order. The values in the first column must be unique; if there are any duplicate keys, an error is raised.
3303
3304If the file is empty, an empty map is returned.
3305
3306**Parameters**
3307
33081. `File`: Path of the two-column TSV file to read.
3309
3310**Returns**: A `Map[String, String]`, with one element for each row in the TSV file.
3311
3312Example: read_map_task.wdl
3313
3314```wdl
3315version 1.2
3316
3317task read_map {
3318  command <<<
3319    printf "key1\tvalue1\n" >> map_file
3320    printf "key2\tvalue2\n" >> map_file
3321  >>>
3322
3323  output {
3324    Map[String, String] mapping = read_map(stdout())
3325  }
3326}
3327```
3328"#
3329                        )
3330                        .build(),
3331                )
3332                .into(),
3333            )
3334            .is_none()
3335    );
3336
3337    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_map
3338    assert!(
3339        functions
3340            .insert(
3341                "write_map",
3342                MonomorphicFunction::new(
3343                    FunctionSignature::builder()
3344                        .parameter(
3345                            "map",
3346                            map_string_string.clone(),
3347                            "A `Map`, where each element will be a row in the generated file.",
3348                        )
3349                        .ret(PrimitiveType::File)
3350                        .definition(
3351                            r#"
3352Writes a tab-separated value (TSV) file with one line for each element in a `Map[String, String]`. Each element is concatenated into a single tab-delimited string of the format `~{key}\t~{value}`. Each line is terminated by the newline (`\n`) character. If the `Map` is empty, an empty file is written.
3353
3354Since `Map`s are ordered, the order of the lines in the file is guaranteed to be the same order that the elements were added to the `Map`.
3355
3356**Parameters**
3357
33581. `Map[String, String]`: A `Map`, where each element will be a row in the generated file.
3359
3360**Returns**: A `File`.
3361
3362Example: write_map_task.wdl
3363
3364```wdl
3365version 1.2
3366
3367task write_map {
3368  input {
3369    Map[String, String] map = {"key1": "value1", "key2": "value2"}
3370  }
3371
3372  command <<<
3373    cut -f 1 ~{write_map(map)}
3374  >>>
3375
3376  output {
3377    Array[String] keys = read_lines(stdout())
3378  }
3379
3380  requirements {
3381    container: "ubuntu:latest"
3382  }
3383}
3384```
3385"#
3386                        )
3387                        .build(),
3388                )
3389                .into(),
3390            )
3391            .is_none()
3392    );
3393
3394    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_json
3395    assert!(
3396        functions
3397            .insert(
3398                "read_json",
3399                MonomorphicFunction::new(
3400                    FunctionSignature::builder()
3401                        .parameter("file", PrimitiveType::File, "Path of the JSON file to read.")
3402                        .ret(Type::Union)
3403                        .definition(
3404                            r#"
3405Reads a JSON file into a WDL value whose type depends on the file's contents. The mapping of JSON type to WDL type is:
3406
3407| JSON Type | WDL Type         |
3408| --------- | ---------------- |
3409| object    | `Object`         |
3410| array     | `Array[X]`       |
3411| number    | `Int` or `Float` |
3412| string    | `String`         |
3413| boolean   | `Boolean`        |
3414| null      | `None`           |
3415
3416The return value is of type [`Union`](#union-hidden-type) and must be used in a context where it can be coerced to the expected type, or an error is raised. For example, if the JSON file contains `null`, then the return value will be `None`, meaning the value can only be used in a context where an optional type is expected.
3417
3418If the JSON file contains an array, then all the elements of the array must be coercible to the same type, or an error is raised.
3419
3420The `read_json` function does not have access to any WDL type information, so it cannot return an instance of a specific `Struct` type. Instead, it returns a generic `Object` value that must be coerced to the desired `Struct` type.
3421
3422Note that an empty file is not valid according to the JSON specification, and so calling `read_json` on an empty file raises an error.
3423
3424**Parameters**
3425
34261. `File`: Path of the JSON file to read.
3427
3428**Returns**: A value whose type is dependent on the contents of the JSON file.
3429
3430Example: read_person.wdl
3431
3432```wdl
3433version 1.2
3434
3435struct Person {
3436  String name
3437  Int age
3438}
3439
3440workflow read_person {
3441  input {
3442    File json_file
3443  }
3444
3445  output {
3446    Person p = read_json(json_file)
3447  }
3448}
3449```
3450"#
3451                        )
3452                        .build(),
3453                )
3454                .into(),
3455            )
3456            .is_none()
3457    );
3458
3459    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_json
3460    assert!(
3461        functions
3462            .insert(
3463                "write_json",
3464                MonomorphicFunction::new(
3465                    FunctionSignature::builder()
3466                        .type_parameter("X", JsonSerializableConstraint)
3467                        .parameter(
3468                            "value",
3469                            GenericType::Parameter("X"),
3470                            "A WDL value of a supported type.",
3471                        )
3472                        .ret(PrimitiveType::File)
3473                        .definition(
3474                            r#"
3475Writes a JSON file with the serialized form of a WDL value. The following WDL types can be serialized:
3476
3477| WDL Type         | JSON Type |
3478| ---------------- | --------- |
3479| `Struct`         | object    |
3480| `Object`         | object    |
3481| `Map[String, X]` | object    |
3482| `Array[X]`       | array     |
3483| `Int`            | number    |
3484| `Float`          | number    |
3485| `String`         | string    |
3486| `File`           | string    |
3487| `Boolean`        | boolean   |
3488| `None`           | null      |
3489
3490When serializing compound types, all nested types must be serializable or an error is raised.
3491
3492**Parameters**
3493
34941. `X`: A WDL value of a supported type.
3495
3496**Returns**: A `File`.
3497
3498Example: write_json_fail.wdl
3499
3500```wdl
3501version 1.2
3502
3503workflow write_json_fail {
3504  Pair[Int, Map[Int, String]] x = (1, {2: "hello"})
3505  # this fails with an error - Map with Int keys is not serializable
3506  File f = write_json(x)
3507}
3508```
3509"#
3510                        )
3511                        .build(),
3512                )
3513                .into(),
3514            )
3515            .is_none()
3516    );
3517
3518    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_object
3519    assert!(
3520        functions
3521            .insert(
3522                "read_object",
3523                MonomorphicFunction::new(
3524                    FunctionSignature::builder()
3525                        .parameter(
3526                            "file",
3527                            PrimitiveType::File,
3528                            "Path of the two-row TSV file to read.",
3529                        )
3530                        .ret(Type::Object)
3531                        .definition(
3532                            r#"
3533Reads a tab-separated value (TSV) file representing the names and values of the members of an `Object`. There must be exactly two rows, and each row must have the same number of elements, otherwise an error is raised. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3534
3535The first row specifies the object member names. The names in the first row must be unique; if there are any duplicate names, an error is raised.
3536
3537The second row specifies the object member values corresponding to the names in the first row. All of the `Object`'s values are of type `String`.
3538
3539**Parameters**
3540
35411. `File`: Path of the two-row TSV file to read.
3542
3543**Returns**: An `Object`, with as many members as there are unique names in the TSV.
3544
3545Example: read_object_task.wdl
3546
3547```wdl
3548version 1.2
3549
3550task read_object {
3551  command <<<
3552    python <<CODE
3553    print('\t'.join(["key_{}".format(i) for i in range(3)]))
3554    print('\t'.join(["value_{}".format(i) for i in range(3)]))
3555    CODE
3556  >>>
3557
3558  output {
3559    Object my_obj = read_object(stdout())
3560  }
3561
3562  requirements {
3563    container: "python:latest"
3564  }
3565}
3566```
3567"#
3568                        )
3569                        .build(),
3570                )
3571                .into(),
3572            )
3573            .is_none()
3574    );
3575
3576    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_objects
3577    assert!(
3578        functions
3579            .insert(
3580                "read_objects",
3581                MonomorphicFunction::new(
3582                    FunctionSignature::builder()
3583                        .parameter("file", PrimitiveType::File, "The file to read.")
3584                        .ret(array_object.clone())
3585                        .definition(
3586                            r#"
3587Reads a tab-separated value (TSV) file representing the names and values of the members of any number of `Object`s. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3588
3589The first line of the file must be a header row with the names of the object members. The names in the first row must be unique; if there are any duplicate names, an error is raised.
3590
3591There are any number of additional rows, where each additional row contains the values of an object corresponding to the member names. Each row in the file must have the same number of fields as the header row. All of the `Object`'s values are of type `String`.
3592
3593If the file is empty or contains only a header line, an empty array is returned.
3594
3595**Parameters**
3596
35971. `File`: Path of the TSV file to read.
3598
3599**Returns**: An `Array[Object]`, with `N-1` elements, where `N` is the number of rows in the file.
3600
3601Example: read_objects_task.wdl
3602
3603```wdl
3604version 1.2
3605
3606task read_objects {
3607  command <<<
3608    python <<CODE
3609    print('\t'.join(["key_{}".format(i) for i in range(3)]))
3610    print('\t'.join(["value_A{}".format(i) for i in range(3)]))
3611    print('\t'.join(["value_B{}".format(i) for i in range(3)]))
3612    print('\t'.join(["value_C{}".format(i) for i in range(3)]))
3613    CODE
3614  >>>
3615
3616  output {
3617    Array[Object] my_obj = read_objects(stdout())
3618  }
3619"#
3620                        )
3621                        .build(),
3622                )
3623                .into(),
3624            )
3625            .is_none()
3626    );
3627
3628    const WRITE_OBJECT_DEFINITION: &str = r#"
3629Writes a tab-separated value (TSV) file representing the names and values of the members of an `Object`. The file will contain exactly two rows. The first row specifies the object member names. The second row specifies the object member values corresponding to the names in the first row.
3630
3631Each line is terminated by the newline (`\n`) character.
3632
3633The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3634
3635If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3636
3637**Parameters**
3638
36391. `Object`: An `Object` whose members will be written to the file.
3640
3641**Returns**: A `File`.
3642
3643Example: write_object_task.wdl
3644
3645```wdl
3646version 1.2
3647
3648task write_object {
3649  input {
3650    Object my_obj = {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"}
3651  }
3652
3653  command <<<
3654    cat ~{write_object(my_obj)}
3655  >>>
3656
3657  output {
3658    Object new_obj = read_object(stdout())
3659  }
3660}
3661```
3662"#;
3663
3664    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_object
3665    assert!(
3666        functions
3667            .insert(
3668                "write_object",
3669                PolymorphicFunction::new(vec![
3670                    FunctionSignature::builder()
3671                        .parameter("object", Type::Object, "An object to write.")
3672                        .ret(PrimitiveType::File)
3673                        .definition(WRITE_OBJECT_DEFINITION)
3674                        .build(),
3675                    FunctionSignature::builder()
3676                        .min_version(SupportedVersion::V1(V1::One))
3677                        .type_parameter("S", PrimitiveStructConstraint)
3678                        .parameter("object", GenericType::Parameter("S"), "An object to write.")
3679                        .ret(PrimitiveType::File)
3680                        .definition(WRITE_OBJECT_DEFINITION)
3681                        .build(),
3682                ])
3683                .into(),
3684            )
3685            .is_none()
3686    );
3687
3688    const WRITE_OBJECTS_DEFINITION: &str = r#"
3689Writes a tab-separated value (TSV) file representing the names and values of the members of any number of `Object`s. The first line of the file will be a header row with the names of the object members. There will be one additional row for each element in the input array, where each additional row contains the values of an object corresponding to the member names.
3690
3691Each line is terminated by the newline (`\n`) character.
3692
3693The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3694
3695If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3696
3697**Parameters**
3698
36991. `Array[Object]`: An `Array[Object]` whose elements will be written to the file.
3700
3701**Returns**: A `File`.
3702
3703Example: write_objects_task.wdl
3704
3705```wdl
3706version 1.2
3707
3708task write_objects {
3709  input {
3710    Array[Object] my_objs = [
3711      {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"},
3712      {"key_0": "value_B0", "key_1": "value_B1", "key_2": "value_B2"},
3713      {"key_0": "value_C0", "key_1": "value_C1", "key_2": "value_C2"}
3714    ]
3715  }
3716
3717  command <<<
3718    cat ~{write_objects(my_objs)}
3719  >>>
3720
3721  output {
3722    Array[Object] new_objs = read_objects(stdout())
3723  }
3724}
3725```
3726"#;
3727
3728    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_objects
3729    assert!(
3730        functions
3731            .insert(
3732                "write_objects",
3733                PolymorphicFunction::new(vec![
3734                    FunctionSignature::builder()
3735                        .parameter("objects", array_object.clone(), "The objects to write.")
3736                        .ret(PrimitiveType::File)
3737                        .definition(WRITE_OBJECTS_DEFINITION)
3738                        .build(),
3739                    FunctionSignature::builder()
3740                        .min_version(SupportedVersion::V1(V1::One))
3741                        .type_parameter("S", PrimitiveStructConstraint)
3742                        .parameter(
3743                            "objects",
3744                            GenericArrayType::new(GenericType::Parameter("S")),
3745                            "The objects to write."
3746                        )
3747                        .ret(PrimitiveType::File)
3748                        .definition(WRITE_OBJECTS_DEFINITION)
3749                        .build(),
3750                ])
3751                .into(),
3752            )
3753            .is_none()
3754    );
3755
3756    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#prefix
3757    assert!(
3758        functions
3759            .insert(
3760                "prefix",
3761                MonomorphicFunction::new(
3762                    FunctionSignature::builder()
3763                        .type_parameter("P", PrimitiveTypeConstraint)
3764                        .parameter(
3765                            "prefix",
3766                            PrimitiveType::String,
3767                            "The prefix to prepend to each element in the array.",
3768                        )
3769                        .parameter(
3770                            "array",
3771                            GenericArrayType::new(GenericType::Parameter("P")),
3772                            "Array with a primitive element type.",
3773                        )
3774                        .ret(array_string.clone())
3775                        .definition(
3776                            r#"
3777Given a `String` `prefix` and an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is prepended with `prefix`. The elements of `a` are converted to `String`s before being prepended. If `a` is empty, an empty array is returned.
3778
3779**Parameters**
3780
37811. `String`: The string to prepend.
37822. `Array[X]`: The array whose elements will be prepended.
3783
3784**Returns**: A new `Array[String]` with the prepended elements.
3785
3786Example: prefix_task.wdl
3787
3788```wdl
3789version 1.2
3790
3791task prefix {
3792  input {
3793    Array[Int] ints = [1, 2, 3]
3794  }
3795
3796  output {
3797    Array[String] prefixed_ints = prefix("file_", ints) # ["file_1", "file_2", "file_3"]
3798  }
3799}
3800```
3801"#
3802                        )
3803                        .build(),
3804                )
3805                .into(),
3806            )
3807            .is_none()
3808    );
3809
3810    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#suffix
3811    assert!(
3812        functions
3813            .insert(
3814                "suffix",
3815                MonomorphicFunction::new(
3816                    FunctionSignature::builder()
3817                        .min_version(SupportedVersion::V1(V1::One))
3818                        .type_parameter("P", PrimitiveTypeConstraint)
3819                        .parameter(
3820                            "suffix",
3821                            PrimitiveType::String,
3822                            "The suffix to append to each element in the array.",
3823                        )
3824                        .parameter(
3825                            "array",
3826                            GenericArrayType::new(GenericType::Parameter("P")),
3827                            "Array with a primitive element type.",
3828                        )
3829                        .ret(array_string.clone())
3830                        .definition(
3831                            r#"
3832Given a `String` `suffix` and an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is appended with `suffix`. The elements of `a` are converted to `String`s before being appended. If `a` is empty, an empty array is returned.
3833
3834**Parameters**
3835
38361. `String`: The string to append.
38372. `Array[X]`: The array whose elements will be appended.
3838
3839**Returns**: A new `Array[String]` with the appended elements.
3840
3841Example: suffix_task.wdl
3842
3843```wdl
3844version 1.2
3845
3846task suffix {
3847  input {
3848    Array[Int] ints = [1, 2, 3]
3849  }
3850
3851  output {
3852    Array[String] suffixed_ints = suffix(".txt", ints) # ["1.txt", "2.txt", "3.txt"]
3853  }
3854}
3855```
3856"#
3857                        )
3858                        .build(),
3859                )
3860                .into(),
3861            )
3862            .is_none()
3863    );
3864
3865    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#quote
3866    assert!(
3867        functions
3868            .insert(
3869                "quote",
3870                MonomorphicFunction::new(
3871                    FunctionSignature::builder()
3872                        .min_version(SupportedVersion::V1(V1::One))
3873                        .type_parameter("P", PrimitiveTypeConstraint)
3874                        .parameter(
3875                            "array",
3876                            GenericArrayType::new(GenericType::Parameter("P")),
3877                            "Array with a primitive element type.",
3878                        )
3879                        .ret(array_string.clone())
3880                        .definition(
3881                            r#"
3882Given an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is converted to a `String` and then surrounded by double quotes (`"`). If `a` is empty, an empty array is returned.
3883
3884**Parameters**
3885
38861. `Array[X]`: The array whose elements will be quoted.
3887
3888**Returns**: A new `Array[String]` with the quoted elements.
3889
3890Example: quote_task.wdl
3891
3892```wdl
3893version 1.2
3894
3895task quote {
3896  input {
3897    Array[String] strings = ["hello", "world"]
3898  }
3899
3900  output {
3901    Array[String] quoted_strings = quote(strings) # ["\"hello\"", "\"world\""]
3902  }
3903}
3904```
3905"#
3906                        )
3907                        .build(),
3908                )
3909                .into(),
3910            )
3911            .is_none()
3912    );
3913
3914    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#squote
3915    assert!(
3916        functions
3917            .insert(
3918                "squote",
3919                MonomorphicFunction::new(
3920                    FunctionSignature::builder()
3921                        .min_version(SupportedVersion::V1(V1::One))
3922                        .type_parameter("P", PrimitiveTypeConstraint)
3923                        .parameter("array", GenericArrayType::new(GenericType::Parameter("P")), "The array of values.")                        .ret(array_string.clone())
3924                        .definition(
3925                            r#"
3926Given an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is converted to a `String` and then surrounded by single quotes (`'`). If `a` is empty, an empty array is returned.
3927
3928**Parameters**
3929
39301. `Array[X]`: The array whose elements will be single-quoted.
3931
3932**Returns**: A new `Array[String]` with the single-quoted elements.
3933
3934Example: squote_task.wdl
3935
3936```wdl
3937version 1.2
3938
3939task squote {
3940  input {
3941    Array[String] strings = ["hello", "world"]
3942  }
3943
3944  output {
3945    Array[String] squoted_strings = squote(strings) # ["'hello'", "'world'"]
3946  }
3947}
3948```
3949"#
3950                        )
3951                        .build(),
3952                )
3953                .into(),
3954            )
3955            .is_none()
3956    );
3957
3958    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#sep
3959    assert!(
3960        functions
3961            .insert(
3962                "sep",
3963                MonomorphicFunction::new(
3964                    FunctionSignature::builder()
3965                        .min_version(SupportedVersion::V1(V1::One))
3966                        .type_parameter("P", PrimitiveTypeConstraint)
3967                        .parameter("separator", PrimitiveType::String, "Separator string.")
3968                        .parameter(
3969                            "array",
3970                            GenericArrayType::new(GenericType::Parameter("P")),
3971                            "`Array` of strings to concatenate.",
3972                        )
3973                        .ret(PrimitiveType::String)
3974                        .definition(
3975                            r#"
3976Given a `String` `separator` and an `Array[X]` `a`, returns a new `String` where each element `x` of `a` is converted to a `String` and then joined by `separator`. If `a` is empty, an empty string is returned.
3977
3978**Parameters**
3979
39801. `String`: The string to use as a separator.
39812. `Array[X]`: The array whose elements will be joined.
3982
3983**Returns**: A new `String` with the joined elements.
3984
3985Example: sep_task.wdl
3986
3987```wdl
3988version 1.2
3989
3990task sep {
3991  input {
3992    Array[Int] ints = [1, 2, 3]
3993  }
3994
3995  output {
3996    String joined_ints = sep(",", ints) # "1,2,3"
3997  }
3998}
3999```
4000"#
4001                        )
4002                        .build(),
4003                )
4004                .into(),
4005            )
4006            .is_none()
4007    );
4008
4009    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#range
4010    assert!(
4011        functions
4012            .insert(
4013                "range",
4014                MonomorphicFunction::new(
4015                    FunctionSignature::builder()
4016                        .parameter("n", PrimitiveType::Integer, "The length of array to create.")
4017                        .ret(array_int.clone())
4018                        .definition(
4019                            r#"
4020Returns an `Array[Int]` of integers from `0` up to (but not including) the given `Int` `n`. If `n` is less than or equal to `0`, an empty array is returned.
4021
4022**Parameters**
4023
40241. `Int`: The upper bound (exclusive) of the range.
4025
4026**Returns**: An `Array[Int]` of integers.
4027
4028Example: range_task.wdl
4029
4030```wdl
4031version 1.2
4032
4033task range {
4034  input {
4035    Int n = 5
4036  }
4037
4038  output {
4039    Array[Int] r = range(n) # [0, 1, 2, 3, 4]
4040  }
4041}
4042```
4043"#
4044                        )
4045                        .build(),
4046                )
4047                .into(),
4048            )
4049            .is_none()
4050    );
4051
4052    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#transpose
4053    assert!(
4054        functions
4055            .insert(
4056                "transpose",
4057                MonomorphicFunction::new(
4058                    FunctionSignature::builder()
4059                        .any_type_parameter("X")
4060                        .parameter(
4061                            "array",
4062                            GenericArrayType::new(GenericArrayType::new(
4063                                GenericType::Parameter("X"),
4064                            )),
4065                            "A M*N two-dimensional array.",
4066                        )
4067                        .ret(GenericArrayType::new(GenericArrayType::new(
4068                            GenericType::Parameter("X"),
4069                        )))
4070                        .definition(
4071                            r#"
4072Given an `Array[Array[X]]` `a`, returns a new `Array[Array[X]]` where the rows and columns of `a` are swapped. If `a` is empty, an empty array is returned.
4073
4074If the inner arrays are not all the same length, an error is raised.
4075
4076**Parameters**
4077
40781. `Array[Array[X]]`: The array to transpose.
4079
4080**Returns**: A new `Array[Array[X]]` with the rows and columns swapped.
4081
4082Example: transpose_task.wdl
4083
4084```wdl
4085version 1.2
4086
4087task transpose {
4088  input {
4089    Array[Array[Int]] matrix = [[1, 2, 3], [4, 5, 6]]
4090  }
4091
4092  output {
4093    Array[Array[Int]] transposed_matrix = transpose(matrix) # [[1, 4], [2, 5], [3, 6]]
4094  }
4095}
4096```
4097"#
4098                        )
4099                        .build(),
4100                )
4101                .into(),
4102            )
4103            .is_none()
4104    );
4105
4106    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#cross
4107    assert!(
4108        functions
4109            .insert(
4110                "cross",
4111                MonomorphicFunction::new(
4112                    FunctionSignature::builder()
4113                        .any_type_parameter("X")
4114                        .any_type_parameter("Y")
4115                        .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4116                        .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4117                        .ret(GenericArrayType::new(GenericPairType::new(
4118                            GenericType::Parameter("X"),
4119                            GenericType::Parameter("Y"),
4120                        )))
4121                        .definition(
4122                            r#"
4123Given two `Array`s `a` and `b`, returns a new `Array[Pair[X, Y]]` where each element is a `Pair` of an element from `a` and an element from `b`. The order of the elements in the returned array is such that all elements from `b` are paired with the first element of `a`, then all elements from `b` are paired with the second element of `a`, and so on.
4124
4125If either `a` or `b` is empty, an empty array is returned.
4126
4127**Parameters**
4128
41291. `Array[X]`: The first array.
41302. `Array[Y]`: The second array.
4131
4132**Returns**: A new `Array[Pair[X, Y]]` with the cross product of the two arrays.
4133
4134Example: cross_task.wdl
4135
4136```wdl
4137version 1.2
4138
4139task cross {
4140  input {
4141    Array[Int] ints = [1, 2]
4142    Array[String] strings = ["a", "b"]
4143  }
4144
4145  output {
4146    Array[Pair[Int, String]] crossed = cross(ints, strings) # [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
4147  }
4148}
4149```
4150"#
4151                        )
4152                        .build(),
4153                )
4154                .into(),
4155            )
4156            .is_none()
4157    );
4158
4159    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#zip
4160    assert!(
4161        functions
4162            .insert(
4163                "zip",
4164                MonomorphicFunction::new(
4165                    FunctionSignature::builder()
4166                        .any_type_parameter("X")
4167                        .any_type_parameter("Y")
4168                        .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4169                        .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4170                        .ret(GenericArrayType::new(GenericPairType::new(
4171                            GenericType::Parameter("X"),
4172                            GenericType::Parameter("Y"),
4173                        )))
4174                        .definition(
4175                            r#"
4176Given two `Array`s `a` and `b`, returns a new `Array[Pair[X, Y]]` where each element is a `Pair` of an element from `a` and an element from `b` at the same index. The length of the returned array is the minimum of the lengths of `a` and `b`.
4177
4178If either `a` or `b` is empty, an empty array is returned.
4179
4180**Parameters**
4181
41821. `Array[X]`: The first array.
41832. `Array[Y]`: The second array.
4184
4185**Returns**: A new `Array[Pair[X, Y]]` with the zipped elements.
4186
4187Example: zip_task.wdl
4188
4189```wdl
4190version 1.2
4191
4192task zip {
4193  input {
4194    Array[Int] ints = [1, 2, 3]
4195    Array[String] strings = ["a", "b"]
4196  }
4197
4198  output {
4199    Array[Pair[Int, String]] zipped = zip(ints, strings) # [(1, "a"), (2, "b")]
4200  }
4201}
4202```
4203"#
4204                        )
4205                        .build(),
4206                )
4207                .into(),
4208            )
4209            .is_none()
4210    );
4211
4212    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#unzip
4213    assert!(
4214        functions
4215            .insert(
4216                "unzip",
4217                MonomorphicFunction::new(
4218                    FunctionSignature::builder()
4219                        .min_version(SupportedVersion::V1(V1::One))
4220                        .any_type_parameter("X")
4221                        .any_type_parameter("Y")
4222                        .parameter(
4223                            "array",
4224                            GenericArrayType::new(GenericPairType::new(
4225                                GenericType::Parameter("X"),
4226                                GenericType::Parameter("Y"),
4227                            )),
4228                            "The `Array` of `Pairs` of length N to unzip.",
4229                        )
4230                        .ret(GenericPairType::new(
4231                            GenericArrayType::new(GenericType::Parameter("X")),
4232                            GenericArrayType::new(GenericType::Parameter("Y")),
4233                        ))
4234                        .definition(
4235                            r#"
4236Given an `Array[Pair[X, Y]]` `a`, returns a new `Pair[Array[X], Array[Y]]` where the first element of the `Pair` is an `Array` of all the first elements of the `Pair`s in `a`, and the second element of the `Pair` is an `Array` of all the second elements of the `Pair`s in `a`.
4237
4238If `a` is empty, a `Pair` of two empty arrays is returned.
4239
4240**Parameters**
4241
42421. `Array[Pair[X, Y]]`: The array of pairs to unzip.
4243
4244**Returns**: A new `Pair[Array[X], Array[Y]]` with the unzipped elements.
4245
4246Example: unzip_task.wdl
4247
4248```wdl
4249version 1.2
4250
4251task unzip {
4252  input {
4253    Array[Pair[Int, String]] zipped = [(1, "a"), (2, "b")]
4254  }
4255
4256  output {
4257    Pair[Array[Int], Array[String]] unzipped = unzip(zipped) # ([1, 2], ["a", "b"])
4258  }
4259}
4260```
4261"#
4262                        )
4263                        .build(),
4264                )
4265                .into(),
4266            )
4267            .is_none()
4268    );
4269
4270    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-contains
4271    assert!(
4272        functions
4273            .insert(
4274                "contains",
4275                MonomorphicFunction::new(
4276                    FunctionSignature::builder()
4277                        .min_version(SupportedVersion::V1(V1::Two))
4278                        .type_parameter("P", PrimitiveTypeConstraint)
4279                        .parameter(
4280                            "array",
4281                            GenericArrayType::new(GenericType::Parameter("P")),
4282                            "An array of any primitive type.",
4283                        )
4284                        .parameter(
4285                            "value",
4286                            GenericType::Parameter("P"),
4287                            "A primitive value of the same type as the array. If the array's \
4288                             type is optional, then the value may also be optional.",
4289                        )
4290                        .ret(PrimitiveType::Boolean)
4291                        .definition(
4292                            r#"
4293Given an `Array[X]` `a` and a value `v` of type `X`, returns `true` if `v` is present in `a`, otherwise `false`.
4294
4295**Parameters**
4296
42971. `Array[X]`: The array to search.
42982. `X`: The value to search for.
4299
4300**Returns**: `true` if `v` is present in `a`, otherwise `false`.
4301
4302Example: contains_task.wdl
4303
4304```wdl
4305version 1.2
4306
4307task contains {
4308  input {
4309    Array[Int] ints = [1, 2, 3]
4310  }
4311
4312  output {
4313    Boolean contains_2 = contains(ints, 2) # true
4314    Boolean contains_4 = contains(ints, 4) # false
4315  }
4316}
4317```
4318"#
4319                        )
4320                        .build(),
4321                )
4322                .into(),
4323            )
4324            .is_none()
4325    );
4326
4327    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-chunk
4328    assert!(
4329        functions
4330            .insert(
4331                "chunk",
4332                MonomorphicFunction::new(
4333                    FunctionSignature::builder()
4334                        .min_version(SupportedVersion::V1(V1::Two))
4335                        .any_type_parameter("X")
4336                        .parameter(
4337                            "array",
4338                            GenericArrayType::new(GenericType::Parameter("X")),
4339                            "The array to split. May be empty.",
4340                        )
4341                        .parameter("size", PrimitiveType::Integer, "The desired length of the sub-arrays. Must be > 0.")
4342                        .ret(GenericArrayType::new(GenericArrayType::new(
4343                            GenericType::Parameter("X"),
4344                        )))
4345                        .definition(
4346                            r#"
4347Given an `Array[X]` `a` and an `Int` `size`, returns a new `Array[Array[X]]` where each inner array has at most `size` elements. The last inner array may have fewer than `size` elements. If `a` is empty, an empty array is returned.
4348
4349If `size` is less than or equal to `0`, an error is raised.
4350
4351**Parameters**
4352
43531. `Array[X]`: The array to chunk.
43542. `Int`: The maximum size of each chunk.
4355
4356**Returns**: A new `Array[Array[X]]` with the chunked elements.
4357
4358Example: chunk_task.wdl
4359
4360```wdl
4361version 1.2
4362
4363task chunk {
4364  input {
4365    Array[Int] ints = [1, 2, 3, 4, 5]
4366  }
4367
4368  output {
4369    Array[Array[Int]] chunked = chunk(ints, 2) # [[1, 2], [3, 4], [5]]
4370  }
4371}
4372```
4373"#
4374                        )
4375                        .build(),
4376                )
4377                .into(),
4378            )
4379            .is_none()
4380    );
4381
4382    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#flatten
4383    assert!(
4384        functions
4385            .insert(
4386                "flatten",
4387                MonomorphicFunction::new(
4388                    FunctionSignature::builder()
4389                        .any_type_parameter("X")
4390                        .parameter(
4391                            "array",
4392                            GenericArrayType::new(GenericArrayType::new(
4393                                GenericType::Parameter("X"),
4394                            )),
4395                            "A nested array to flatten.",
4396                        )
4397                        .ret(GenericArrayType::new(GenericType::Parameter("X")))
4398                        .definition(
4399                            r#"
4400Given an `Array[Array[X]]` `a`, returns a new `Array[X]` where all the elements of the inner arrays are concatenated into a single array. If `a` is empty, an empty array is returned.
4401
4402**Parameters**
4403
44041. `Array[Array[X]]`: The array to flatten.
4405
4406**Returns**: A new `Array[X]` with the flattened elements.
4407
4408Example: flatten_task.wdl
4409
4410```wdl
4411version 1.2
4412
4413task flatten {
4414  input {
4415    Array[Array[Int]] nested_ints = [[1, 2], [3, 4], [5]]
4416  }
4417
4418  output {
4419    Array[Int] flattened_ints = flatten(nested_ints) # [1, 2, 3, 4, 5]
4420  }
4421}
4422```
4423"#
4424                        )
4425                        .build(),
4426                )
4427                .into(),
4428            )
4429            .is_none()
4430    );
4431
4432    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#select_first
4433    assert!(
4434        functions
4435            .insert(
4436                "select_first",
4437                // This differs from the definition of `select_first` in that we can have a single
4438                // signature of `X select_first(Array[X?], [X])`.
4439                MonomorphicFunction::new(
4440                    FunctionSignature::builder()
4441                        .any_type_parameter("X")
4442                        .required(1)
4443                        .parameter(
4444                            "array",
4445                            GenericArrayType::new(GenericType::Parameter("X")),
4446                            "Non-empty `Array` of optional values.",
4447                        )
4448                        .parameter("default", GenericType::UnqualifiedParameter("X"), "(Optional) The default value.")
4449                        .ret(GenericType::UnqualifiedParameter("X"))
4450                        .definition(
4451                            r#"
4452Given an `Array[X?]` `a`, returns the first non-`None` element in `a`. If all elements are `None`, an error is raised.
4453
4454**Parameters**
4455
44561. `Array[X?]`: The array to search.
4457
4458**Returns**: The first non-`None` element.
4459
4460Example: select_first_task.wdl
4461
4462```wdl
4463version 1.2
4464
4465task select_first {
4466  input {
4467    Array[Int?] ints = [None, 1, None, 2]
4468  }
4469
4470  output {
4471    Int first_int = select_first(ints) # 1
4472  }
4473}
4474```
4475"#
4476                        )
4477                        .build(),
4478                )
4479                .into(),
4480            )
4481            .is_none()
4482    );
4483
4484    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#select_all
4485    assert!(
4486        functions
4487            .insert(
4488                "select_all",
4489                MonomorphicFunction::new(
4490                    FunctionSignature::builder()
4491                        .any_type_parameter("X")
4492                        .parameter(
4493                            "array",
4494                            GenericArrayType::new(GenericType::Parameter("X")),
4495                            "`Array` of optional values.",
4496                        )
4497                        .ret(GenericArrayType::new(GenericType::UnqualifiedParameter(
4498                            "X"
4499                        )))
4500                        .definition(
4501                            r#"
4502Given an `Array[X?]` `a`, returns a new `Array[X]` containing all the non-`None` elements in `a`. If all elements are `None`, an empty array is returned.
4503
4504**Parameters**
4505
45061. `Array[X?]`: The array to filter.
4507
4508**Returns**: A new `Array[X]` with all the non-`None` elements.
4509
4510Example: select_all_task.wdl
4511
4512```wdl
4513version 1.2
4514
4515task select_all {
4516  input {
4517    Array[Int?] ints = [None, 1, None, 2]
4518  }
4519
4520  output {
4521    Array[Int] all_ints = select_all(ints) # [1, 2]
4522  }
4523}
4524```
4525"#
4526                        )
4527                        .build(),
4528                )
4529                .into(),
4530            )
4531            .is_none()
4532    );
4533
4534    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#as_pairs
4535    assert!(
4536        functions
4537            .insert(
4538                "as_pairs",
4539                MonomorphicFunction::new(
4540                    FunctionSignature::builder()
4541                        .min_version(SupportedVersion::V1(V1::One))
4542                        .type_parameter("K", MapKeyConstraint)
4543                        .any_type_parameter("V")
4544                        .parameter(
4545                            "map",
4546                            GenericMapType::new(
4547                                GenericType::Parameter("K"),
4548                                GenericType::Parameter("V"),
4549                            ),
4550                            "`Map` to convert to `Pairs`.",
4551                        )
4552                        .ret(GenericArrayType::new(GenericPairType::new(
4553                            GenericType::Parameter("K"),
4554                            GenericType::Parameter("V")
4555                        )))
4556                        .definition(
4557                            r#"
4558Given a `Map[K, V]` `m`, returns a new `Array[Pair[K, V]]` where each element is a `Pair` of a key and its corresponding value from `m`. The order of the elements in the returned array is the same as the order in which the elements were added to the `Map`.
4559
4560If `m` is empty, an empty array is returned.
4561
4562**Parameters**
4563
45641. `Map[K, V]`: The map to convert.
4565
4566**Returns**: A new `Array[Pair[K, V]]` with the key-value pairs.
4567
4568Example: as_pairs_task.wdl
4569
4570```wdl
4571version 1.2
4572
4573task as_pairs {
4574  input {
4575    Map[String, Int] map = {"a": 1, "b": 2}
4576  }
4577
4578  output {
4579    Array[Pair[String, Int]] pairs = as_pairs(map) # [("a", 1), ("b", 2)]
4580  }
4581}
4582```
4583"#
4584                        )
4585                        .build(),
4586                )
4587                .into(),
4588            )
4589            .is_none()
4590    );
4591
4592    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#as_map
4593    assert!(
4594        functions
4595            .insert(
4596                "as_map",
4597                MonomorphicFunction::new(
4598                    FunctionSignature::builder()
4599                        .min_version(SupportedVersion::V1(V1::One))
4600                        .type_parameter("K", MapKeyConstraint)
4601                        .any_type_parameter("V")
4602                        .parameter(
4603                            "pairs",
4604                            GenericArrayType::new(GenericPairType::new(
4605                                GenericType::Parameter("K"),
4606                                GenericType::Parameter("V"),
4607                            )),
4608                            "`Array` of `Pairs` to convert to a `Map`.",
4609                        )
4610                        .ret(GenericMapType::new(
4611                            GenericType::Parameter("K"),
4612                            GenericType::Parameter("V")
4613                        ))
4614                        .definition(
4615                            r#"
4616Given an `Array[Pair[K, V]]` `a`, returns a new `Map[K, V]` where each `Pair` is converted to a key-value pair in the `Map`. If `a` is empty, an empty map is returned.
4617
4618If there are any duplicate keys in `a`, an error is raised.
4619
4620**Parameters**
4621
46221. `Array[Pair[K, V]]`: The array of pairs to convert.
4623
4624**Returns**: A new `Map[K, V]` with the key-value pairs.
4625
4626Example: as_map_task.wdl
4627
4628```wdl
4629version 1.2
4630
4631task as_map {
4632  input {
4633    Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2)]
4634  }
4635
4636  output {
4637    Map[String, Int] map = as_map(pairs) # {"a": 1, "b": 2}
4638  }
4639}
4640```
4641"#
4642                        )
4643                        .build(),
4644                )
4645                .into(),
4646            )
4647            .is_none()
4648    );
4649
4650    const KEYS_DEFINITION: &str = r#"
4651Given a `Map[K, V]` `m`, returns a new `Array[K]` containing all the keys in `m`. The order of the keys in the returned array is the same as the order in which the elements were added to the `Map`.
4652
4653If `m` is empty, an empty array is returned.
4654
4655**Parameters**
4656
46571. `Map[K, V]`: The map to get the keys from.
4658
4659**Returns**: A new `Array[K]` with the keys.
4660
4661Example: keys_map_task.wdl
4662
4663```wdl
4664version 1.2
4665
4666task keys_map {
4667  input {
4668    Map[String, Int] map = {"a": 1, "b": 2}
4669  }
4670
4671  output {
4672    Array[String] keys = keys(map) # ["a", "b"]
4673  }
4674}
4675```
4676"#;
4677
4678    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#keys
4679    assert!(
4680        functions
4681            .insert(
4682                "keys",
4683                PolymorphicFunction::new(vec![
4684                    FunctionSignature::builder()
4685                        .min_version(SupportedVersion::V1(V1::One))
4686                        .type_parameter("K", MapKeyConstraint)
4687                        .any_type_parameter("V")
4688                        .parameter(
4689                            "map",
4690                            GenericMapType::new(
4691                                GenericType::Parameter("K"),
4692                                GenericType::Parameter("V"),
4693                            ),
4694                            "Collection from which to extract keys.",
4695                        )
4696                        .ret(GenericArrayType::new(GenericType::Parameter("K")))
4697                        .definition(KEYS_DEFINITION)
4698                        .build(),
4699                    FunctionSignature::builder()
4700                        .min_version(SupportedVersion::V1(V1::Two))
4701                        .type_parameter("S", StructConstraint)
4702                        .parameter(
4703                            "struct",
4704                            GenericType::Parameter("S"),
4705                            "Collection from which to extract keys.",
4706                        )
4707                        .ret(array_string.clone())
4708                        .definition(KEYS_DEFINITION)
4709                        .build(),
4710                    FunctionSignature::builder()
4711                        .min_version(SupportedVersion::V1(V1::Two))
4712                        .parameter(
4713                            "object",
4714                            Type::Object,
4715                            "Collection from which to extract keys.",
4716                        )
4717                        .ret(array_string.clone())
4718                        .definition(KEYS_DEFINITION)
4719                        .build(),
4720                ])
4721                .into(),
4722            )
4723            .is_none()
4724    );
4725
4726    const CONTAINS_KEY_DEFINITION: &str = r#"
4727Given a `Map[K, V]` `m` and a key `k` of type `K`, returns `true` if `k` is present in `m`, otherwise `false`.
4728
4729**Parameters**
4730
47311. `Map[K, V]`: The map to search.
47322. `K`: The key to search for.
4733
4734**Returns**: `true` if `k` is present in `m`, otherwise `false`.
4735
4736Example: contains_key_map_task.wdl
4737
4738```wdl
4739version 1.2
4740
4741task contains_key_map {
4742  input {
4743    Map[String, Int] map = {"a": 1, "b": 2}
4744  }
4745
4746  output {
4747    Boolean contains_a = contains_key(map, "a") # true
4748    Boolean contains_c = contains_key(map, "c") # false
4749  }
4750}
4751```
4752"#;
4753
4754    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#contains_key
4755    assert!(
4756        functions
4757            .insert(
4758                "contains_key",
4759                PolymorphicFunction::new(vec![
4760                        FunctionSignature::builder()
4761                            .min_version(SupportedVersion::V1(V1::Two))
4762                            .type_parameter("K", MapKeyConstraint)
4763                            .any_type_parameter("V")
4764                            .parameter(
4765                                "map",
4766                                GenericMapType::new(
4767                                    GenericType::Parameter("K"),
4768                                    GenericType::Parameter("V"),
4769                                ),
4770                                "Collection to search for the key.",
4771                            )
4772                            .parameter(
4773                                "key",
4774                                GenericType::Parameter("K"),
4775                                "The key to search for. If the first argument is a `Map`, then \
4776                                 the key must be of the same type as the `Map`'s key type. If the \
4777                                 `Map`'s key type is optional then the key may also be optional. \
4778                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4779                                 `Object`, then the key may be either a `String` or \
4780                                 `Array[String]`."
4781                            )
4782                            .ret(PrimitiveType::Boolean)
4783                            .definition(CONTAINS_KEY_DEFINITION)
4784                            .build(),
4785                        FunctionSignature::builder()
4786                            .min_version(SupportedVersion::V1(V1::Two))
4787                            .parameter("object", Type::Object, "Collection to search for the key.")
4788                            .parameter(
4789                                "key",
4790                                PrimitiveType::String,
4791                                "The key to search for. If the first argument is a `Map`, then \
4792                                 the key must be of the same type as the `Map`'s key type. If the \
4793                                 `Map`'s key type is optional then the key may also be optional. \
4794                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4795                                 `Object`, then the key may be either a `String` or \
4796                                 `Array[String]`."
4797                            )
4798                            .ret(PrimitiveType::Boolean)
4799                            .definition(CONTAINS_KEY_DEFINITION)
4800                            .build(),
4801                        FunctionSignature::builder()
4802                            .min_version(SupportedVersion::V1(V1::Two))
4803                            .any_type_parameter("V")
4804                            .parameter(
4805                                "map",
4806                                GenericMapType::new(
4807                                    PrimitiveType::String,
4808                                    GenericType::Parameter("V"),
4809                                ),
4810                                "Collection to search for the key.",
4811                            )
4812                            .parameter(
4813                                "keys",
4814                                array_string.clone(),
4815                                "The key to search for. If the first argument is a `Map`, then \
4816                                 the key must be of the same type as the `Map`'s key type. If the \
4817                                 `Map`'s key type is optional then the key may also be optional. \
4818                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4819                                 `Object`, then the key may be either a `String` or \
4820                                 `Array[String]`."
4821                            )
4822                            .ret(PrimitiveType::Boolean)
4823                            .definition(CONTAINS_KEY_DEFINITION)
4824                            .build(),
4825                        FunctionSignature::builder()
4826                            .min_version(SupportedVersion::V1(V1::Two))
4827                            .type_parameter("S", StructConstraint)
4828                            .parameter(
4829                                "struct",
4830                                GenericType::Parameter("S"),
4831                                "Collection to search for the key.",
4832                            )
4833                            .parameter(
4834                                "keys",
4835                                array_string.clone(),
4836                                "The key to search for. If the first argument is a `Map`, then \
4837                                 the key must be of the same type as the `Map`'s key type. If the \
4838                                 `Map`'s key type is optional then the key may also be optional. \
4839                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4840                                 `Object`, then the key may be either a `String` or \
4841                                 `Array[String]`."
4842                            )
4843                            .ret(PrimitiveType::Boolean)
4844                            .definition(CONTAINS_KEY_DEFINITION)
4845                            .build(),
4846                        FunctionSignature::builder()
4847                            .min_version(SupportedVersion::V1(V1::Two))
4848                            .parameter("object", Type::Object, "Collection to search for the key.")
4849                            .parameter(
4850                                "keys",
4851                                array_string.clone(),
4852                                "The key to search for. If the first argument is a `Map`, then \
4853                                 the key must be of the same type as the `Map`'s key type. If the \
4854                                 `Map`'s key type is optional then the key may also be optional. \
4855                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4856                                 `Object`, then the key may be either a `String` or \
4857                                 `Array[String]`."
4858                            )
4859                            .ret(PrimitiveType::Boolean)
4860                            .definition(CONTAINS_KEY_DEFINITION)
4861                            .build(),
4862                    ])
4863                .into(),
4864            )
4865            .is_none()
4866    );
4867
4868    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-values
4869    assert!(
4870        functions
4871            .insert(
4872                "values",
4873                MonomorphicFunction::new(
4874                    FunctionSignature::builder()
4875                        .min_version(SupportedVersion::V1(V1::Two))
4876                        .type_parameter("K", MapKeyConstraint)
4877                        .any_type_parameter("V")
4878                        .parameter(
4879                            "map",
4880                            GenericMapType::new(
4881                                GenericType::Parameter("K"),
4882                                GenericType::Parameter("V"),
4883                            ),
4884                            "`Map` from which to extract values.",
4885                        )
4886                        .ret(GenericArrayType::new(GenericType::Parameter("V")))
4887                        .definition(
4888                            r#"
4889Given a `Map[K, V]` `m`, returns a new `Array[V]` containing all the values in `m`. The order of the values in the returned array is the same as the order in which the elements were added to the `Map`.
4890
4891If `m` is empty, an empty array is returned.
4892
4893**Parameters**
4894
48951. `Map[K, V]`: The map to get the values from.
4896
4897**Returns**: A new `Array[V]` with the values.
4898
4899Example: values_map_task.wdl
4900
4901```wdl
4902version 1.2
4903
4904task values_map {
4905  input {
4906    Map[String, Int] map = {"a": 1, "b": 2}
4907  }
4908
4909  output {
4910    Array[Int] values = values(map) # [1, 2]
4911  }
4912}
4913```
4914"#
4915                        )
4916                        .build(),
4917                )
4918                .into(),
4919            )
4920            .is_none()
4921    );
4922
4923    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#collect_by_key
4924    assert!(
4925        functions
4926            .insert(
4927                "collect_by_key",
4928                MonomorphicFunction::new(
4929                    FunctionSignature::builder()
4930                        .min_version(SupportedVersion::V1(V1::One))
4931                        .type_parameter("K", MapKeyConstraint)
4932                        .any_type_parameter("V")
4933                        .parameter(
4934                            "pairs",
4935                            GenericArrayType::new(GenericPairType::new(
4936                                GenericType::Parameter("K"),
4937                                GenericType::Parameter("V"),
4938                            )),
4939                            "`Array` of `Pairs` to group.",
4940                        )
4941                        .ret(GenericMapType::new(
4942                            GenericType::Parameter("K"),
4943                            GenericArrayType::new(GenericType::Parameter("V"))
4944                        ))
4945                        .definition(
4946                            r#"
4947Given an `Array[Pair[K, V]]` `a`, returns a new `Map[K, Array[V]]` where each key `K` maps to an `Array` of all the values `V` that were paired with `K` in `a`. The order of the values in the inner arrays is the same as the order in which they appeared in `a`.
4948
4949If `a` is empty, an empty map is returned.
4950
4951**Parameters**
4952
49531. `Array[Pair[K, V]]`: The array of pairs to collect.
4954
4955**Returns**: A new `Map[K, Array[V]]` with the collected values.
4956
4957Example: collect_by_key_task.wdl
4958
4959```wdl
4960version 1.2
4961
4962task collect_by_key {
4963  input {
4964    Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2), ("a", 3)]
4965  }
4966
4967  output {
4968    Map[String, Array[Int]] collected = collect_by_key(pairs) # {"a": [1, 3], "b": 2}
4969  }
4970}
4971```
4972"#
4973                        )
4974                        .build(),
4975                )
4976                .into(),
4977            )
4978            .is_none()
4979    );
4980
4981    // Enum functions (WDL 1.3)
4982    assert!(
4983        functions
4984            .insert(
4985                "value",
4986                MonomorphicFunction::new(
4987                    FunctionSignature::builder()
4988                        .min_version(SupportedVersion::V1(V1::Three))
4989                        .any_type_parameter("T")
4990                        .type_parameter("V", EnumChoiceConstraint)
4991                        .parameter(
4992                            "choice",
4993                            GenericType::Parameter("V"),
4994                            "An enum choice of any enum type.",
4995                        )
4996                        .ret(GenericEnumInnerValueType::new("T"))
4997                        .definition(
4998                            r##"
4999Returns the underlying value associated with an enum choice.
5000
5001**Parameters**
5002
50031. `Enum`: an enum choice of any enum type.
5004
5005**Returns**: The choice's associated value.
5006
5007Example: test_enum_value.wdl
5008
5009```wdl
5010version 1.3
5011
5012enum Color {
5013  Red = "#FF0000",
5014  Green = "#00FF00",
5015  Blue = "#0000FF"
5016}
5017
5018enum Priority {
5019  Low = 1,
5020  Medium = 5,
5021  High = 10
5022}
5023
5024workflow test_enum_value {
5025  input {
5026    Color color = Color.Red
5027    Priority priority = Priority.High
5028  }
5029
5030  output {
5031    String choice_name = "~{color}"   # "Red"
5032    String hex_value = value(color)    # "#FF0000"
5033    Int priority_num = value(priority) # 10
5034    Boolean values_equal = value(Color.Red) == value(Color.Red) # true
5035    Boolean choices_equal = Color.Red == Color.Red              # true
5036  }
5037}
5038```
5039"##
5040                        )
5041                        .build(),
5042                )
5043                .into(),
5044            )
5045            .is_none()
5046    );
5047
5048    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#defined
5049    assert!(
5050        functions
5051            .insert(
5052                "defined",
5053                MonomorphicFunction::new(
5054                    FunctionSignature::builder()
5055                        .any_type_parameter("X")
5056                        .parameter(
5057                            "value",
5058                            GenericType::Parameter("X"),
5059                            "Optional value of any type."
5060                        )
5061                        .ret(PrimitiveType::Boolean)
5062                        .definition(
5063                            r#"
5064Given an optional value `x`, returns `true` if `x` is defined (i.e., not `None`), otherwise `false`.
5065
5066**Parameters**
5067
50681. `X?`: The optional value to check.
5069
5070**Returns**: `true` if `x` is defined, otherwise `false`.
5071
5072Example: defined_task.wdl
5073
5074```wdl
5075version 1.2
5076
5077task defined {
5078  input {
5079    Int? x = 1
5080    Int? y = None
5081  }
5082
5083  output {
5084    Boolean x_defined = defined(x) # true
5085    Boolean y_defined = defined(y) # false
5086  }
5087}
5088```
5089"#
5090                        )
5091                        .build(),
5092                )
5093                .into(),
5094            )
5095            .is_none()
5096    );
5097
5098    const LENGTH_DEFINITION: &str = r#"
5099Given an `Array[X]` `a`, returns the number of elements in `a`. If `a` is empty, `0` is returned.
5100
5101**Parameters**
5102
51031. `Array[X]`: The array to get the length from.
5104
5105**Returns**: The number of elements in the array as an `Int`.
5106
5107Example: length_array_task.wdl
5108
5109```wdl
5110version 1.2
5111
5112task length_array {
5113  input {
5114    Array[Int] ints = [1, 2, 3]
5115  }
5116
5117  output {
5118    Int len = length(ints) # 3
5119  }
5120}
5121```
5122"#;
5123
5124    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#length
5125    assert!(
5126        functions
5127            .insert(
5128                "length",
5129                PolymorphicFunction::new(vec![
5130                    FunctionSignature::builder()
5131                        .any_type_parameter("X")
5132                        .parameter(
5133                            "array",
5134                            GenericArrayType::new(GenericType::Parameter("X")),
5135                            "A collection or string whose elements are to be counted.",
5136                        )
5137                        .ret(PrimitiveType::Integer)
5138                        .definition(LENGTH_DEFINITION)
5139                        .build(),
5140                    FunctionSignature::builder()
5141                        .any_type_parameter("K")
5142                        .any_type_parameter("V")
5143                        .parameter(
5144                            "map",
5145                            GenericMapType::new(
5146                                GenericType::Parameter("K"),
5147                                GenericType::Parameter("V"),
5148                            ),
5149                            "A collection or string whose elements are to be counted.",
5150                        )
5151                        .ret(PrimitiveType::Integer)
5152                        .definition(LENGTH_DEFINITION)
5153                        .build(),
5154                    FunctionSignature::builder()
5155                        .parameter(
5156                            "object",
5157                            Type::Object,
5158                            "A collection or string whose elements are to be counted.",
5159                        )
5160                        .ret(PrimitiveType::Integer)
5161                        .definition(LENGTH_DEFINITION)
5162                        .build(),
5163                    FunctionSignature::builder()
5164                        .parameter(
5165                            "string",
5166                            PrimitiveType::String,
5167                            "A collection or string whose elements are to be counted.",
5168                        )
5169                        .ret(PrimitiveType::Integer)
5170                        .definition(LENGTH_DEFINITION)
5171                        .build(),
5172                ])
5173                .into(),
5174            )
5175            .is_none()
5176    );
5177
5178    StandardLibrary {
5179        functions,
5180        array_int,
5181        array_string,
5182        array_file,
5183        array_object,
5184        array_string_non_empty,
5185        array_array_string,
5186        map_string_string,
5187        map_string_int,
5188    }
5189});
5190
5191#[cfg(test)]
5192mod tests {
5193    use pretty_assertions::assert_eq;
5194
5195    use super::*;
5196
5197    #[test_log::test]
5198    fn verify_stdlib_signatures() {
5199        let mut signatures = Vec::new();
5200        for (name, f) in STDLIB.functions() {
5201            match f {
5202                Function::Monomorphic(f) => {
5203                    let params = TypeParameters::new(&f.signature.type_parameters);
5204                    signatures.push(format!("{name}{sig}", sig = f.signature.display(&params)));
5205                }
5206                Function::Polymorphic(f) => {
5207                    for signature in &f.signatures {
5208                        let params = TypeParameters::new(&signature.type_parameters);
5209                        signatures.push(format!("{name}{sig}", sig = signature.display(&params)));
5210                    }
5211                }
5212            }
5213        }
5214
5215        assert_eq!(
5216            signatures,
5217            [
5218                "floor(value: Float) -> Int",
5219                "ceil(value: Float) -> Int",
5220                "round(value: Float) -> Int",
5221                "min(a: Int, b: Int) -> Int",
5222                "min(a: Int, b: Float) -> Float",
5223                "min(a: Float, b: Int) -> Float",
5224                "min(a: Float, b: Float) -> Float",
5225                "max(a: Int, b: Int) -> Int",
5226                "max(a: Int, b: Float) -> Float",
5227                "max(a: Float, b: Int) -> Float",
5228                "max(a: Float, b: Float) -> Float",
5229                "find(input: String, pattern: String) -> String?",
5230                "matches(input: String, pattern: String) -> Boolean",
5231                "sub(input: String, pattern: String, replace: String) -> String",
5232                "split(input: String, delimiter: String) -> Array[String]",
5233                "basename(path: File, <suffix: String>) -> String",
5234                "basename(path: String, <suffix: String>) -> String",
5235                "basename(path: Directory, <suffix: String>) -> String",
5236                "join_paths(base: Directory, relative: String) -> String",
5237                "join_paths(base: Directory, relative: Array[String]+) -> String",
5238                "join_paths(paths: Array[String]+) -> String",
5239                "glob(pattern: String) -> Array[File]",
5240                "size(value: None, <unit: String>) -> Float",
5241                "size(value: File?, <unit: String>) -> Float",
5242                "size(value: String?, <unit: String>) -> Float",
5243                "size(value: Directory?, <unit: String>) -> Float",
5244                "size(value: X, <unit: String>) -> Float where `X`: any compound type that \
5245                 recursively contains a `File` or `Directory`",
5246                "stdout() -> File",
5247                "stderr() -> File",
5248                "read_string(file: File) -> String",
5249                "read_int(file: File) -> Int",
5250                "read_float(file: File) -> Float",
5251                "read_boolean(file: File) -> Boolean",
5252                "read_lines(file: File) -> Array[String]",
5253                "write_lines(array: Array[String]) -> File",
5254                "read_tsv(file: File) -> Array[Array[String]]",
5255                "read_tsv(file: File, header: Boolean) -> Array[Object]",
5256                "read_tsv(file: File, header: Boolean, columns: Array[String]) -> Array[Object]",
5257                "write_tsv(data: Array[Array[String]]) -> File",
5258                "write_tsv(data: Array[Array[String]], header: Boolean, columns: Array[String]) \
5259                 -> File",
5260                "write_tsv(data: Array[S], <header: Boolean>, <columns: Array[String]>) -> File \
5261                 where `S`: any structure containing only primitive types",
5262                "read_map(file: File) -> Map[String, String]",
5263                "write_map(map: Map[String, String]) -> File",
5264                "read_json(file: File) -> Union",
5265                "write_json(value: X) -> File where `X`: any JSON-serializable type",
5266                "read_object(file: File) -> Object",
5267                "read_objects(file: File) -> Array[Object]",
5268                "write_object(object: Object) -> File",
5269                "write_object(object: S) -> File where `S`: any structure containing only \
5270                 primitive types",
5271                "write_objects(objects: Array[Object]) -> File",
5272                "write_objects(objects: Array[S]) -> File where `S`: any structure containing \
5273                 only primitive types",
5274                "prefix(prefix: String, array: Array[P]) -> Array[String] where `P`: any \
5275                 primitive type",
5276                "suffix(suffix: String, array: Array[P]) -> Array[String] where `P`: any \
5277                 primitive type",
5278                "quote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5279                "squote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5280                "sep(separator: String, array: Array[P]) -> String where `P`: any primitive type",
5281                "range(n: Int) -> Array[Int]",
5282                "transpose(array: Array[Array[X]]) -> Array[Array[X]]",
5283                "cross(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5284                "zip(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5285                "unzip(array: Array[Pair[X, Y]]) -> Pair[Array[X], Array[Y]]",
5286                "contains(array: Array[P], value: P) -> Boolean where `P`: any primitive type",
5287                "chunk(array: Array[X], size: Int) -> Array[Array[X]]",
5288                "flatten(array: Array[Array[X]]) -> Array[X]",
5289                "select_first(array: Array[X], <default: X>) -> X",
5290                "select_all(array: Array[X]) -> Array[X]",
5291                "as_pairs(map: Map[K, V]) -> Array[Pair[K, V]] where `K`: any non-optional \
5292                 primitive type",
5293                "as_map(pairs: Array[Pair[K, V]]) -> Map[K, V] where `K`: any non-optional \
5294                 primitive type",
5295                "keys(map: Map[K, V]) -> Array[K] where `K`: any non-optional primitive type",
5296                "keys(struct: S) -> Array[String] where `S`: any structure",
5297                "keys(object: Object) -> Array[String]",
5298                "contains_key(map: Map[K, V], key: K) -> Boolean where `K`: any non-optional \
5299                 primitive type",
5300                "contains_key(object: Object, key: String) -> Boolean",
5301                "contains_key(map: Map[String, V], keys: Array[String]) -> Boolean",
5302                "contains_key(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
5303                "contains_key(object: Object, keys: Array[String]) -> Boolean",
5304                "values(map: Map[K, V]) -> Array[V] where `K`: any non-optional primitive type",
5305                "collect_by_key(pairs: Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any \
5306                 non-optional primitive type",
5307                "value(choice: V) -> T where `V`: any enum choice",
5308                "defined(value: X) -> Boolean",
5309                "length(array: Array[X]) -> Int",
5310                "length(map: Map[K, V]) -> Int",
5311                "length(object: Object) -> Int",
5312                "length(string: String) -> Int",
5313            ]
5314        );
5315    }
5316
5317    #[test_log::test]
5318    fn it_binds_a_simple_function() {
5319        let f = STDLIB.function("floor").expect("should have function");
5320        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5321
5322        let e = f
5323            .bind(SupportedVersion::V1(V1::Zero), &[])
5324            .expect_err("bind should fail");
5325        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5326
5327        let e = f
5328            .bind(
5329                SupportedVersion::V1(V1::One),
5330                &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5331            )
5332            .expect_err("bind should fail");
5333        assert_eq!(e, FunctionBindError::TooManyArguments(1));
5334
5335        // Check for a string argument (should be a type mismatch)
5336        let e = f
5337            .bind(
5338                SupportedVersion::V1(V1::Two),
5339                &[PrimitiveType::String.into()],
5340            )
5341            .expect_err("bind should fail");
5342        assert_eq!(
5343            e,
5344            FunctionBindError::ArgumentTypeMismatch {
5345                index: 0,
5346                expected: "type `Float`".into()
5347            }
5348        );
5349
5350        // Check for Union (i.e. indeterminate)
5351        let binding = f
5352            .bind(SupportedVersion::V1(V1::Zero), &[Type::Union])
5353            .expect("bind should succeed");
5354        assert_eq!(binding.index(), 0);
5355        assert_eq!(binding.return_type().to_string(), "Int");
5356
5357        // Check for a float argument
5358        let binding = f
5359            .bind(
5360                SupportedVersion::V1(V1::One),
5361                &[PrimitiveType::Float.into()],
5362            )
5363            .expect("bind should succeed");
5364        assert_eq!(binding.index(), 0);
5365        assert_eq!(binding.return_type().to_string(), "Int");
5366
5367        // Check for an integer argument (should coerce)
5368        let binding = f
5369            .bind(
5370                SupportedVersion::V1(V1::Two),
5371                &[PrimitiveType::Integer.into()],
5372            )
5373            .expect("bind should succeed");
5374        assert_eq!(binding.index(), 0);
5375        assert_eq!(binding.return_type().to_string(), "Int");
5376    }
5377
5378    #[test_log::test]
5379    fn it_binds_a_generic_function() {
5380        let f = STDLIB.function("values").expect("should have function");
5381        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Two));
5382
5383        let e = f
5384            .bind(SupportedVersion::V1(V1::Zero), &[])
5385            .expect_err("bind should fail");
5386        assert_eq!(
5387            e,
5388            FunctionBindError::RequiresVersion(SupportedVersion::V1(V1::Two))
5389        );
5390
5391        let e = f
5392            .bind(SupportedVersion::V1(V1::Two), &[])
5393            .expect_err("bind should fail");
5394        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5395
5396        let e = f
5397            .bind(
5398                SupportedVersion::V1(V1::Two),
5399                &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5400            )
5401            .expect_err("bind should fail");
5402        assert_eq!(e, FunctionBindError::TooManyArguments(1));
5403
5404        // Check for a string argument (should be a type mismatch)
5405        let e = f
5406            .bind(
5407                SupportedVersion::V1(V1::Two),
5408                &[PrimitiveType::String.into()],
5409            )
5410            .expect_err("bind should fail");
5411        assert_eq!(
5412            e,
5413            FunctionBindError::ArgumentTypeMismatch {
5414                index: 0,
5415                expected: "generic type `Map[K, V]` where `K`: any non-optional primitive type"
5416                    .into()
5417            }
5418        );
5419
5420        // Check for Union (i.e. indeterminate)
5421        let binding = f
5422            .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5423            .expect("bind should succeed");
5424        assert_eq!(binding.index(), 0);
5425        assert_eq!(binding.return_type().to_string(), "Array[Union]");
5426
5427        // Check for a Map[String, String]
5428        let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
5429        let binding = f
5430            .bind(SupportedVersion::V1(V1::Two), &[ty])
5431            .expect("bind should succeed");
5432        assert_eq!(binding.index(), 0);
5433        assert_eq!(binding.return_type().to_string(), "Array[String]");
5434
5435        // Check for a Map[String, Object]
5436        let ty: Type = MapType::new(PrimitiveType::String, Type::Object).into();
5437        let binding = f
5438            .bind(SupportedVersion::V1(V1::Two), &[ty])
5439            .expect("bind should succeed");
5440        assert_eq!(binding.index(), 0);
5441        assert_eq!(binding.return_type().to_string(), "Array[Object]");
5442    }
5443
5444    #[test_log::test]
5445    fn it_removes_qualifiers() {
5446        let f = STDLIB.function("select_all").expect("should have function");
5447        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5448
5449        // Check for a Array[String]
5450        let array_string: Type = ArrayType::new(PrimitiveType::String).into();
5451        let binding = f
5452            .bind(SupportedVersion::V1(V1::One), &[array_string])
5453            .expect("bind should succeed");
5454        assert_eq!(binding.index(), 0);
5455        assert_eq!(binding.return_type().to_string(), "Array[String]");
5456
5457        // Check for a Array[String?] -> Array[String]
5458        let array_optional_string: Type =
5459            ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5460        let binding = f
5461            .bind(SupportedVersion::V1(V1::One), &[array_optional_string])
5462            .expect("bind should succeed");
5463        assert_eq!(binding.index(), 0);
5464        assert_eq!(binding.return_type().to_string(), "Array[String]");
5465
5466        // Check for Union (i.e. indeterminate)
5467        let binding = f
5468            .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5469            .expect("bind should succeed");
5470        assert_eq!(binding.index(), 0);
5471        assert_eq!(binding.return_type().to_string(), "Array[Union]");
5472
5473        // Check for a Array[Array[String]?] -> Array[Array[String]]
5474        let array_string = Type::from(ArrayType::new(PrimitiveType::String)).optional();
5475        let array_array_string = ArrayType::new(array_string).into();
5476        let binding = f
5477            .bind(SupportedVersion::V1(V1::Zero), &[array_array_string])
5478            .expect("bind should succeed");
5479        assert_eq!(binding.index(), 0);
5480        assert_eq!(binding.return_type().to_string(), "Array[Array[String]]");
5481    }
5482
5483    #[test_log::test]
5484    fn it_binds_concrete_overloads() {
5485        let f = STDLIB.function("max").expect("should have function");
5486        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::One));
5487
5488        let e = f
5489            .bind(SupportedVersion::V1(V1::One), &[])
5490            .expect_err("bind should fail");
5491        assert_eq!(e, FunctionBindError::TooFewArguments(2));
5492
5493        let e = f
5494            .bind(
5495                SupportedVersion::V1(V1::Two),
5496                &[
5497                    PrimitiveType::String.into(),
5498                    PrimitiveType::Boolean.into(),
5499                    PrimitiveType::File.into(),
5500                ],
5501            )
5502            .expect_err("bind should fail");
5503        assert_eq!(e, FunctionBindError::TooManyArguments(2));
5504
5505        // Check for `(Int, Int)`
5506        let binding = f
5507            .bind(
5508                SupportedVersion::V1(V1::One),
5509                &[PrimitiveType::Integer.into(), PrimitiveType::Integer.into()],
5510            )
5511            .expect("binding should succeed");
5512        assert_eq!(binding.index(), 0);
5513        assert_eq!(binding.return_type().to_string(), "Int");
5514
5515        // Check for `(Int, Float)`
5516        let binding = f
5517            .bind(
5518                SupportedVersion::V1(V1::Two),
5519                &[PrimitiveType::Integer.into(), PrimitiveType::Float.into()],
5520            )
5521            .expect("binding should succeed");
5522        assert_eq!(binding.index(), 1);
5523        assert_eq!(binding.return_type().to_string(), "Float");
5524
5525        // Check for `(Float, Int)`
5526        let binding = f
5527            .bind(
5528                SupportedVersion::V1(V1::One),
5529                &[PrimitiveType::Float.into(), PrimitiveType::Integer.into()],
5530            )
5531            .expect("binding should succeed");
5532        assert_eq!(binding.index(), 2);
5533        assert_eq!(binding.return_type().to_string(), "Float");
5534
5535        // Check for `(Float, Float)`
5536        let binding = f
5537            .bind(
5538                SupportedVersion::V1(V1::Two),
5539                &[PrimitiveType::Float.into(), PrimitiveType::Float.into()],
5540            )
5541            .expect("binding should succeed");
5542        assert_eq!(binding.index(), 3);
5543        assert_eq!(binding.return_type().to_string(), "Float");
5544
5545        // Check for `(String, Int)`
5546        let e = f
5547            .bind(
5548                SupportedVersion::V1(V1::One),
5549                &[PrimitiveType::String.into(), PrimitiveType::Integer.into()],
5550            )
5551            .expect_err("binding should fail");
5552        assert_eq!(
5553            e,
5554            FunctionBindError::ArgumentTypeMismatch {
5555                index: 0,
5556                expected: "type `Int` or type `Float`".into()
5557            }
5558        );
5559
5560        // Check for `(Int, String)`
5561        let e = f
5562            .bind(
5563                SupportedVersion::V1(V1::Two),
5564                &[PrimitiveType::Integer.into(), PrimitiveType::String.into()],
5565            )
5566            .expect_err("binding should fail");
5567        assert_eq!(
5568            e,
5569            FunctionBindError::ArgumentTypeMismatch {
5570                index: 1,
5571                expected: "type `Int` or type `Float`".into()
5572            }
5573        );
5574
5575        // Check for `(String, Float)`
5576        let e = f
5577            .bind(
5578                SupportedVersion::V1(V1::One),
5579                &[PrimitiveType::String.into(), PrimitiveType::Float.into()],
5580            )
5581            .expect_err("binding should fail");
5582        assert_eq!(
5583            e,
5584            FunctionBindError::ArgumentTypeMismatch {
5585                index: 0,
5586                expected: "type `Int` or type `Float`".into()
5587            }
5588        );
5589
5590        // Check for `(Float, String)`
5591        let e = f
5592            .bind(
5593                SupportedVersion::V1(V1::Two),
5594                &[PrimitiveType::Float.into(), PrimitiveType::String.into()],
5595            )
5596            .expect_err("binding should fail");
5597        assert_eq!(
5598            e,
5599            FunctionBindError::ArgumentTypeMismatch {
5600                index: 1,
5601                expected: "type `Int` or type `Float`".into()
5602            }
5603        );
5604    }
5605
5606    #[test_log::test]
5607    fn it_binds_generic_overloads() {
5608        let f = STDLIB
5609            .function("select_first")
5610            .expect("should have function");
5611        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5612
5613        let e = f
5614            .bind(SupportedVersion::V1(V1::Zero), &[])
5615            .expect_err("bind should fail");
5616        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5617
5618        let e = f
5619            .bind(
5620                SupportedVersion::V1(V1::One),
5621                &[
5622                    PrimitiveType::String.into(),
5623                    PrimitiveType::Boolean.into(),
5624                    PrimitiveType::File.into(),
5625                ],
5626            )
5627            .expect_err("bind should fail");
5628        assert_eq!(e, FunctionBindError::TooManyArguments(2));
5629
5630        // Check `Int`
5631        let e = f
5632            .bind(
5633                SupportedVersion::V1(V1::Two),
5634                &[PrimitiveType::Integer.into()],
5635            )
5636            .expect_err("binding should fail");
5637        assert_eq!(
5638            e,
5639            FunctionBindError::ArgumentTypeMismatch {
5640                index: 0,
5641                expected: "generic type `Array[X]`".into()
5642            }
5643        );
5644
5645        // Check `Array[String?]+`
5646        let array: Type = ArrayType::non_empty(Type::from(PrimitiveType::String).optional()).into();
5647        let binding = f
5648            .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5649            .expect("binding should succeed");
5650        assert_eq!(binding.index(), 0);
5651        assert_eq!(binding.return_type().to_string(), "String");
5652
5653        // Check (`Array[String?]+`, `String`)
5654        let binding = f
5655            .bind(
5656                SupportedVersion::V1(V1::One),
5657                &[array.clone(), PrimitiveType::String.into()],
5658            )
5659            .expect("binding should succeed");
5660        assert_eq!(binding.index(), 0);
5661        assert_eq!(binding.return_type().to_string(), "String");
5662
5663        // Check (`Array[String?]+`, `Int`)
5664        let e = f
5665            .bind(
5666                SupportedVersion::V1(V1::Two),
5667                &[array.clone(), PrimitiveType::Integer.into()],
5668            )
5669            .expect_err("binding should fail");
5670        assert_eq!(
5671            e,
5672            FunctionBindError::ArgumentTypeMismatch {
5673                index: 1,
5674                expected: "type `String`".into()
5675            }
5676        );
5677
5678        // Check `Array[String?]`
5679        let array: Type = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5680        let binding = f
5681            .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5682            .expect("binding should succeed");
5683        assert_eq!(binding.index(), 0);
5684        assert_eq!(binding.return_type().to_string(), "String");
5685
5686        // Check (`Array[String?]`, `String`)
5687        let binding = f
5688            .bind(
5689                SupportedVersion::V1(V1::One),
5690                &[array.clone(), PrimitiveType::String.into()],
5691            )
5692            .expect("binding should succeed");
5693        assert_eq!(binding.index(), 0);
5694        assert_eq!(binding.return_type().to_string(), "String");
5695
5696        // Check (`Array[String?]`, `Int`)
5697        let e = f
5698            .bind(
5699                SupportedVersion::V1(V1::Two),
5700                &[array, PrimitiveType::Integer.into()],
5701            )
5702            .expect_err("binding should fail");
5703        assert_eq!(
5704            e,
5705            FunctionBindError::ArgumentTypeMismatch {
5706                index: 1,
5707                expected: "type `String`".into()
5708            }
5709        );
5710    }
5711}