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