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    // https://github.com/openwdl/wdl/blob/wdl-1.3/SPEC.md#-split
2098    assert!(
2099        functions
2100            .insert(
2101                "split",
2102                MonomorphicFunction::new(
2103                    FunctionSignature::builder()
2104                        .min_version(SupportedVersion::V1(V1::Three))
2105                        .parameter("input", PrimitiveType::String, "The input string.")
2106                        .parameter("delimiter", PrimitiveType::String, "The delimiter to split on as a regular expression.")
2107                        .ret(array_string.clone())
2108                        .definition(
2109                            r#"
2110Given the two `String` parameters `input` and `delimiter`, this function splits the input string on the provided delimiter and stores the results in a `Array[String]`. `delimiter` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2111Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., `"\\t"`).
2112
2113**Parameters**:
2114
21151. `String`: the input string.
21162. `String`: the delimiter to split on as a regular expression.
2117
2118**Returns**: the parts of the input string split by the delimiter. If the input delimiter does not match anything in the input string, an array containing a single entry of the input string is returned.
2119
2120<details>
2121<summary>
2122Example: test_split.wdl
2123
2124```wdl
2125version 1.3
2126
2127workflow test_split {
2128  String in = "Here's an example\nthat takes up multiple lines"
2129
2130  output {
2131    Array[String] split_by_word = split(in, " ")
2132    Array[String] split_by_newline = split(in, "\\n")
2133    Array[String] split_by_both = split(in, "\s")
2134  }
2135}
2136```
2137"#
2138                        )
2139                        .build(),
2140                )
2141                .into(),
2142            )
2143            .is_none()
2144    );
2145
2146    const BASENAME_DEFINITION: &str = r#"
2147Returns the "basename" of a file or directory - the name after the last directory separator in the path. 
2148
2149The 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.
2150
2151**Parameters**
2152
21531. `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.
21542. `String`: (Optional) Suffix to remove from the file name.
2155
2156**Returns**: The file's basename as a `String`.
2157
2158Example: test_basename.wdl
2159
2160```wdl
2161version 1.2
2162
2163workflow test_basename {
2164  output {
2165    Boolean is_true1 = basename("/path/to/file.txt") == "file.txt"
2166    Boolean is_true2 = basename("/path/to/file.txt", ".txt") == "file"
2167    Boolean is_true3 = basename("/path/to/dir") == "dir" 
2168  }
2169}
2170```
2171"#;
2172
2173    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#basename
2174    assert!(
2175        functions
2176            .insert(
2177                "basename",
2178                PolymorphicFunction::new(vec![
2179                    FunctionSignature::builder()
2180                        .required(1)
2181                        .parameter(
2182                            "path",
2183                            PrimitiveType::File,
2184                            "Path of the file or directory to read. If the argument is a \
2185                             `String`, it is assumed to be a local file path relative to the \
2186                             current working directory of the task.",
2187                        )
2188                        .parameter(
2189                            "suffix",
2190                            PrimitiveType::String,
2191                            "(Optional) Suffix to remove from the file name.",
2192                        )
2193                        .ret(PrimitiveType::String)
2194                        .definition(BASENAME_DEFINITION)
2195                        .build(),
2196                    // This overload isn't explicitly specified in the spec, but the spec
2197                    // allows for `String` where file/directory are accepted; an explicit
2198                    // `String` overload is required as `String` may coerce to either `File` or
2199                    // `Directory`, which is ambiguous.
2200                    FunctionSignature::builder()
2201                        .min_version(SupportedVersion::V1(V1::Two))
2202                        .required(1)
2203                        .parameter(
2204                            "path",
2205                            PrimitiveType::String,
2206                            "Path of the file or directory to read. If the argument is a \
2207                             `String`, it is assumed to be a local file path relative to the \
2208                             current working directory of the task."
2209                        )
2210                        .parameter(
2211                            "suffix",
2212                            PrimitiveType::String,
2213                            "(Optional) Suffix to remove from the file name."
2214                        )
2215                        .ret(PrimitiveType::String)
2216                        .definition(BASENAME_DEFINITION)
2217                        .build(),
2218                    FunctionSignature::builder()
2219                        .min_version(SupportedVersion::V1(V1::Two))
2220                        .required(1)
2221                        .parameter(
2222                            "path",
2223                            PrimitiveType::Directory,
2224                            "Path of the file or directory to read. If the argument is a \
2225                             `String`, it is assumed to be a local file path relative to the \
2226                             current working directory of the task.",
2227                        )
2228                        .parameter(
2229                            "suffix",
2230                            PrimitiveType::String,
2231                            "(Optional) Suffix to remove from the file name.",
2232                        )
2233                        .ret(PrimitiveType::String)
2234                        .definition(BASENAME_DEFINITION)
2235                        .build(),
2236                ])
2237                .into(),
2238            )
2239            .is_none()
2240    );
2241
2242    const JOIN_PATHS_DEFINITION: &str = r#"
2243Joins together two or more paths into an absolute path in the host filesystem.
2244
2245There are three variants of this function:
2246
22471. `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.
22482. `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.
22493. `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.
2250
2251An 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.
2252
2253A 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.
2254
2255**Parameters**
2256
22571. `File|Array[String]+`: Either a path or an array of paths.
22582. `String|Array[String]+`: A relative path or paths; only allowed if the first argument is a `File`.
2259
2260**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.
2261
2262Example: join_paths_task.wdl
2263
2264```wdl
2265version 1.2
2266
2267task resolve_paths_task {
2268  input {
2269    File abs_file = "/usr"
2270    String abs_str = "/usr"
2271    String rel_dir_str = "bin"
2272    File rel_file = "echo"
2273    File rel_dir_file = "mydir"
2274    String rel_str = "mydata.txt"
2275  }
2276
2277  # these are all equivalent to '/usr/bin/echo'
2278  File bin1 = join_paths(abs_file, [rel_dir_str, rel_file])
2279  File bin2 = join_paths(abs_str, [rel_dir_str, rel_file])
2280  File bin3 = join_paths([abs_str, rel_dir_str, rel_file])
2281  
2282  # the default behavior is that this resolves to 
2283  # '<working dir>/mydir/mydata.txt'
2284  File data = join_paths(rel_dir_file, rel_str)
2285  
2286  # this resolves to '<working dir>/bin/echo', which is non-existent
2287  File doesnt_exist = join_paths([rel_dir_str, rel_file])
2288  command <<<
2289    mkdir ~{rel_dir_file}
2290    ~{bin1} -n "hello" > ~{data}
2291  >>>
2292
2293  output {
2294    Boolean bins_equal = (bin1 == bin2) && (bin1 == bin3)
2295    String result = read_string(data)
2296    File? missing_file = doesnt_exist
2297  }
2298  
2299  runtime {
2300    container: "ubuntu:latest"
2301  }
2302}
2303```
2304"#;
2305
2306    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-join_paths
2307    assert!(
2308        functions
2309            .insert(
2310                "join_paths",
2311                PolymorphicFunction::new(vec![
2312                    FunctionSignature::builder()
2313                        .min_version(SupportedVersion::V1(V1::Two))
2314                        .parameter(
2315                            "base",
2316                            PrimitiveType::File,
2317                            "Either a path or an array of paths.",
2318                        )
2319                        .parameter(
2320                            "relative",
2321                            PrimitiveType::String,
2322                            "A relative path or paths; only allowed if the first argument is a \
2323                             `File`.",
2324                        )
2325                        .ret(PrimitiveType::File)
2326                        .definition(JOIN_PATHS_DEFINITION)
2327                        .build(),
2328                    FunctionSignature::builder()
2329                        .min_version(SupportedVersion::V1(V1::Two))
2330                        .parameter(
2331                            "base",
2332                            PrimitiveType::File,
2333                            "Either a path or an array of paths."
2334                        )
2335                        .parameter(
2336                            "relative",
2337                            array_string_non_empty.clone(),
2338                            "A relative path or paths; only allowed if the first argument is a \
2339                             `File`."
2340                        )
2341                        .ret(PrimitiveType::File)
2342                        .definition(JOIN_PATHS_DEFINITION)
2343                        .build(),
2344                    FunctionSignature::builder()
2345                        .min_version(SupportedVersion::V1(V1::Two))
2346                        .parameter(
2347                            "paths",
2348                            array_string_non_empty.clone(),
2349                            "Either a path or an array of paths."
2350                        )
2351                        .ret(PrimitiveType::File)
2352                        .definition(JOIN_PATHS_DEFINITION)
2353                        .build(),
2354                ])
2355                .into(),
2356            )
2357            .is_none()
2358    );
2359
2360    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#glob
2361    assert!(
2362        functions
2363            .insert(
2364                "glob",
2365                MonomorphicFunction::new(
2366                    FunctionSignature::builder()
2367                        .parameter("pattern", PrimitiveType::String, "The glob string.")
2368                        .ret(array_file.clone())
2369                        .definition(
2370                            r#"
2371Returns 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.
2372
2373`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.
2374
2375At least in standard Bash, glob expressions are not evaluated recursively, i.e., files in nested directories are not included. 
2376
2377**Parameters**:
2378
23791. `String`: The glob string.
2380
2381**Returns**: A array of all files matched by the glob.
2382
2383Example: gen_files_task.wdl
2384
2385```wdl
2386version 1.2
2387
2388task gen_files {
2389  input {
2390    Int num_files
2391  }
2392
2393  command <<<
2394    for i in 1..~{num_files}; do
2395      printf ${i} > a_file_${i}.txt
2396    done
2397    mkdir a_dir
2398    touch a_dir/a_inner.txt
2399  >>>
2400
2401  output {
2402    Array[File] files = glob("a_*")
2403    Int glob_len = length(files)
2404  }
2405}
2406```
2407"#
2408                        )
2409                        .build(),
2410                )
2411                .into(),
2412            )
2413            .is_none()
2414    );
2415
2416    const SIZE_DEFINITION: &str = r#"
2417Determines 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)
2418
2419In the second variant of the `size` function, the parameter type `X` represents any compound type that contains `File` or `File?` nested at any depth.
2420
2421If 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.
2422
2423**Parameters**
2424
24251. `File|File?|Directory|Directory?|X|X?`: A file, directory, or a compound value containing files/directories, for which to determine the size.
24262. `String`: (Optional) The unit of storage; defaults to 'B'.
2427
2428**Returns**: The size of the files/directories as a `Float`.
2429
2430Example: file_sizes_task.wdl
2431
2432```wdl
2433version 1.2
2434
2435task file_sizes {
2436  command <<<
2437    printf "this file is 22 bytes\n" > created_file
2438  >>>
2439
2440  File? missing_file = None
2441
2442  output {
2443    File created_file = "created_file"
2444    Float missing_file_bytes = size(missing_file)
2445    Float created_file_bytes = size(created_file, "B")
2446    Float multi_file_kb = size([created_file, missing_file], "K")
2447
2448    Map[String, Pair[Int, File]] nested = {
2449      "a": (10, created_file),
2450      "b": (50, missing_file)
2451    }
2452    Float nested_bytes = size(nested)
2453  }
2454  
2455  requirements {
2456    container: "ubuntu:latest"
2457  }
2458}
2459```
2460"#;
2461
2462    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#size
2463    assert!(
2464        functions
2465            .insert(
2466                "size",
2467                PolymorphicFunction::new(vec![
2468                    // This overload isn't explicitly in the spec, but it fixes an ambiguity in 1.2
2469                    // when passed a literal `None` value.
2470                    FunctionSignature::builder()
2471                        .min_version(SupportedVersion::V1(V1::Two))
2472                        .required(1)
2473                        .parameter(
2474                            "value",
2475                            Type::None,
2476                            "A file, directory, or a compound value containing files/directories, \
2477                             for which to determine the size."
2478                        )
2479                        .parameter(
2480                            "unit",
2481                            PrimitiveType::String,
2482                            "(Optional) The unit of storage; defaults to 'B'."
2483                        )
2484                        .ret(PrimitiveType::Float)
2485                        .definition(SIZE_DEFINITION)
2486                        .build(),
2487                    FunctionSignature::builder()
2488                        .required(1)
2489                        .parameter(
2490                            "value",
2491                            Type::from(PrimitiveType::File).optional(),
2492                            "A file, directory, or a compound value containing files/directories, \
2493                             for which to determine the size."
2494                        )
2495                        .parameter(
2496                            "unit",
2497                            PrimitiveType::String,
2498                            "(Optional) The unit of storage; defaults to 'B'."
2499                        )
2500                        .ret(PrimitiveType::Float)
2501                        .definition(SIZE_DEFINITION)
2502                        .build(),
2503                    // This overload isn't explicitly specified in the spec, but the spec
2504                    // allows for `String` where file/directory are accepted; an explicit
2505                    // `String` overload is required as `String` may coerce to either `File` or
2506                    // `Directory`, which is ambiguous.
2507                    FunctionSignature::builder()
2508                        .min_version(SupportedVersion::V1(V1::Two))
2509                        .required(1)
2510                        .parameter(
2511                            "value",
2512                            Type::from(PrimitiveType::String).optional(),
2513                            "A file, directory, or a compound value containing files/directories, \
2514                             for which to determine the size.",
2515                        )
2516                        .parameter(
2517                            "unit",
2518                            PrimitiveType::String,
2519                            "(Optional) The unit of storage; defaults to 'B'.",
2520                        )
2521                        .ret(PrimitiveType::Float)
2522                        .definition(SIZE_DEFINITION)
2523                        .build(),
2524                    FunctionSignature::builder()
2525                        .min_version(SupportedVersion::V1(V1::Two))
2526                        .required(1)
2527                        .parameter(
2528                            "value",
2529                            Type::from(PrimitiveType::Directory).optional(),
2530                            "A file, directory, or a compound value containing files/directories, \
2531                             for which to determine the size."
2532                        )
2533                        .parameter(
2534                            "unit",
2535                            PrimitiveType::String,
2536                            "(Optional) The unit of storage; defaults to 'B'."
2537                        )
2538                        .ret(PrimitiveType::Float)
2539                        .definition(SIZE_DEFINITION)
2540                        .build(),
2541                    FunctionSignature::builder()
2542                        .required(1)
2543                        .type_parameter("X", SizeableConstraint)
2544                        .parameter(
2545                            "value",
2546                            GenericType::Parameter("X"),
2547                            "A file, directory, or a compound value containing files/directories, \
2548                             for which to determine the size."
2549                        )
2550                        .parameter(
2551                            "unit",
2552                            PrimitiveType::String,
2553                            "(Optional) The unit of storage; defaults to 'B'."
2554                        )
2555                        .ret(PrimitiveType::Float)
2556                        .definition(SIZE_DEFINITION)
2557                        .build(),
2558                ])
2559                .into(),
2560            )
2561            .is_none()
2562    );
2563
2564    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#stdout
2565    assert!(
2566        functions
2567            .insert(
2568                "stdout",
2569                MonomorphicFunction::new(
2570                    FunctionSignature::builder()
2571                        .ret(PrimitiveType::File)
2572                        .definition(
2573                            r#"
2574Returns 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.
2575
2576**Parameters**: None
2577
2578**Returns**: A `File` whose contents are the stdout generated by the command of the task where the function is called.
2579
2580Example: echo_stdout.wdl
2581
2582```wdl
2583version 1.2
2584
2585task echo_stdout {
2586  command <<<
2587    printf "hello world"
2588  >>>
2589
2590  output {
2591    File message = read_string(stdout())
2592  }
2593}
2594```
2595"#
2596                        )
2597                        .build(),
2598                )
2599                .into(),
2600            )
2601            .is_none()
2602    );
2603
2604    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#stderr
2605    assert!(
2606        functions
2607            .insert(
2608                "stderr",
2609                MonomorphicFunction::new(
2610                    FunctionSignature::builder()
2611                        .ret(PrimitiveType::File)
2612                        .definition(
2613                            r#"
2614Returns 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.
2615
2616**Parameters**: None
2617
2618**Returns**: A `File` whose contents are the stderr generated by the command of the task where the function is called.
2619
2620Example: echo_stderr.wdl
2621
2622```wdl
2623version 1.2
2624
2625task echo_stderr {
2626  command <<<
2627    >&2 printf "hello world"
2628  >>>
2629
2630  output {
2631    File message = read_string(stderr())
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_string
2645    assert!(
2646        functions
2647            .insert(
2648                "read_string",
2649                MonomorphicFunction::new(
2650                    FunctionSignature::builder()
2651                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2652                        .ret(PrimitiveType::String)
2653                        .definition(
2654                            r#"
2655Reads 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.
2656
2657If the file contains any internal newline characters, they are left in tact.
2658
2659**Parameters**
2660
26611. `File`: Path of the file to read.
2662
2663**Returns**: A `String`.
2664
2665Example: read_string_task.wdl
2666
2667```wdl
2668version 1.2
2669
2670task read_string {
2671  # this file will contain "this\nfile\nhas\nfive\nlines\n"
2672  File f = write_lines(["this", "file", "has", "five", "lines"])
2673  
2674  command <<<
2675  cat ~{f}
2676  >>>
2677  
2678  output {
2679    # s will contain "this\nfile\nhas\nfive\nlines"
2680    String s = read_string(stdout())
2681  }
2682}
2683```
2684"#
2685                        )
2686                        .build(),
2687                )
2688                .into(),
2689            )
2690            .is_none()
2691    );
2692
2693    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_int
2694    assert!(
2695        functions
2696            .insert(
2697                "read_int",
2698                MonomorphicFunction::new(
2699                    FunctionSignature::builder()
2700                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2701                        .ret(PrimitiveType::Integer)
2702                        .definition(
2703                            r#"
2704Reads 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.
2705
2706**Parameters**
2707
27081. `File`: Path of the file to read.
2709
2710**Returns**: An `Int`.
2711
2712Example: read_int_task.wdl
2713
2714```wdl
2715version 1.2
2716
2717task read_int {
2718  command <<<
2719  printf "  1  \n" > int_file
2720  >>>
2721
2722  output {
2723    Int i = read_int("int_file")
2724  }
2725}
2726```
2727"#
2728                        )
2729                        .build(),
2730                )
2731                .into(),
2732            )
2733            .is_none()
2734    );
2735
2736    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_float
2737    assert!(
2738        functions
2739            .insert(
2740                "read_float",
2741                MonomorphicFunction::new(
2742                    FunctionSignature::builder()
2743                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2744                        .ret(PrimitiveType::Float)
2745                        .definition(
2746                            r#"
2747Reads 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.
2748
2749**Parameters**
2750
27511. `File`: Path of the file to read.
2752
2753**Returns**: A `Float`.
2754
2755Example: read_float_task.wdl
2756
2757```wdl
2758version 1.2
2759
2760task read_float {
2761  command <<<
2762  printf "  1  \n" > int_file
2763  printf "  2.0  \n" > float_file
2764  >>>
2765
2766  output {
2767    Float f1 = read_float("int_file")
2768    Float f2 = read_float("float_file")
2769  }
2770}
2771```
2772"#
2773                        )
2774                        .build(),
2775                )
2776                .into(),
2777            )
2778            .is_none()
2779    );
2780
2781    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_boolean
2782    assert!(
2783        functions
2784            .insert(
2785                "read_boolean",
2786                MonomorphicFunction::new(
2787                    FunctionSignature::builder()
2788                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2789                        .ret(PrimitiveType::Boolean)
2790                        .definition(
2791                            r#"
2792Reads 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.
2793
2794**Parameters**
2795
27961. `File`: Path of the file to read.
2797
2798**Returns**: A `Boolean`.
2799
2800Example: read_bool_task.wdl
2801
2802```wdl
2803version 1.2
2804
2805task read_bool {
2806  command <<<
2807  printf "  true  \n" > true_file
2808  printf "  FALSE  \n" > false_file
2809  >>>
2810
2811  output {
2812    Boolean b1 = read_boolean("true_file")
2813    Boolean b2 = read_boolean("false_file")
2814  }
2815}
2816```
2817"#
2818                        )
2819                        .build(),
2820                )
2821                .into(),
2822            )
2823            .is_none()
2824    );
2825
2826    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_lines
2827    assert!(
2828        functions
2829            .insert(
2830                "read_lines",
2831                MonomorphicFunction::new(
2832                    FunctionSignature::builder()
2833                        .parameter("file", PrimitiveType::File, "Path of the file to read.")
2834                        .ret(array_string.clone())
2835                        .definition(
2836                            r#"
2837Reads 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.
2838
2839The order of the lines in the returned `Array[String]` is the order in which the lines appear in the file.
2840
2841If the file is empty, an empty array is returned.
2842
2843**Parameters**
2844
28451. `File`: Path of the file to read.
2846
2847**Returns**: An `Array[String]` representation of the lines in the file.
2848
2849Example: grep_task.wdl
2850
2851```wdl
2852version 1.2
2853
2854task grep {
2855  input {
2856    String pattern
2857    File file
2858  }
2859
2860  command <<<
2861    grep '~{pattern}' ~{file}
2862  >>>
2863
2864  output {
2865    Array[String] matches = read_lines(stdout())
2866  }
2867  
2868  requirements {
2869    container: "ubuntu:latest"
2870  }
2871}
2872```
2873"#
2874                        )
2875                        .build(),
2876                )
2877                .into(),
2878            )
2879            .is_none()
2880    );
2881
2882    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_lines
2883    assert!(
2884        functions
2885            .insert(
2886                "write_lines",
2887                MonomorphicFunction::new(
2888                    FunctionSignature::builder()
2889                        .parameter("array", array_string.clone(), "`Array` of strings to write.")
2890                        .ret(PrimitiveType::File)
2891                        .definition(
2892                            r#"
2893Writes 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.
2894
2895**Parameters**
2896
28971. `Array[String]`: Array of strings to write.
2898
2899**Returns**: A `File`.
2900
2901Example: write_lines_task.wdl
2902
2903```wdl
2904version 1.2
2905
2906task write_lines {
2907  input {
2908    Array[String] array = ["first", "second", "third"]
2909  }
2910
2911  command <<<
2912    paste -s -d'\t' ~{write_lines(array)}
2913  >>>
2914
2915  output {
2916    String s = read_string(stdout())
2917  }
2918  
2919  requirements {
2920    container: "ubuntu:latest"
2921  }
2922}
2923```
2924"#
2925                        )
2926                        .build(),
2927                )
2928                .into(),
2929            )
2930            .is_none()
2931    );
2932
2933    const READ_TSV_DEFINITION: &str = r#"
2934Reads 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.
2935
2936This function has three variants:
2937
29381. `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.
29392. `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.
29403. `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).
2941
2942If the file is empty, an empty array is returned.
2943
2944If 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.
2945
2946**Parameters**
2947
29481. `File`: The TSV file to read.
29492. `Boolean`: (Optional) Whether to treat the file's first line as a header.
29503. `Array[String]`: (Optional) An array of field names. If specified, then the second parameter is also required.
2951
2952**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.
2953
2954Example: read_tsv_task.wdl
2955
2956```wdl
2957version 1.2
2958
2959task read_tsv {
2960  command <<<
2961    {
2962      printf "row1\tvalue1\n"
2963      printf "row2\tvalue2\n"
2964      printf "row3\tvalue3\n"
2965    } >> data.no_headers.tsv
2966
2967    {
2968      printf "header1\theader2\n"
2969      printf "row1\tvalue1\n"
2970      printf "row2\tvalue2\n"
2971      printf "row3\tvalue3\n"
2972    } >> data.headers.tsv
2973  >>>
2974
2975  output {
2976    Array[Array[String]] output_table = read_tsv("data.no_headers.tsv")
2977    Array[Object] output_objs1 = read_tsv("data.no_headers.tsv", false, ["name", "value"])
2978    Array[Object] output_objs2 = read_tsv("data.headers.tsv", true)
2979    Array[Object] output_objs3 = read_tsv("data.headers.tsv", true, ["name", "value"])
2980  }
2981}
2982```
2983"#;
2984
2985    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_tsv
2986    assert!(
2987        functions
2988            .insert(
2989                "read_tsv",
2990                PolymorphicFunction::new(vec![
2991                    FunctionSignature::builder()
2992                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
2993                        .ret(array_array_string.clone())
2994                        .definition(READ_TSV_DEFINITION)
2995                        .build(),
2996                    FunctionSignature::builder()
2997                        .min_version(SupportedVersion::V1(V1::Two))
2998                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
2999                        .parameter(
3000                            "header",
3001                            PrimitiveType::Boolean,
3002                            "(Optional) Whether to treat the file's first line as a header.",
3003                        )
3004                        .ret(array_object.clone())
3005                        .definition(READ_TSV_DEFINITION)
3006                        .build(),
3007                    FunctionSignature::builder()
3008                        .min_version(SupportedVersion::V1(V1::Two))
3009                        .parameter("file", PrimitiveType::File, "The TSV file to read.")
3010                        .parameter(
3011                            "header",
3012                            PrimitiveType::Boolean,
3013                            "(Optional) Whether to treat the file's first line as a header.",
3014                        )
3015                        .parameter(
3016                            "columns",
3017                            array_string.clone(),
3018                            "(Optional) An array of field names. If specified, then the second \
3019                             parameter is also required.",
3020                        )
3021                        .ret(array_object.clone())
3022                        .definition(READ_TSV_DEFINITION)
3023                        .build(),
3024                ])
3025                .into(),
3026            )
3027            .is_none()
3028    );
3029
3030    const WRITE_TSV_DEFINITION: &str = r#"
3031Given an `Array` of elements, writes a tab-separated value (TSV) file with one line for each element.
3032
3033There are three variants of this function:
3034
30351. `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.
3036
30372. `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.
3038
30393. `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.
3040
3041Each line is terminated by the newline (`\n`) character. 
3042
3043The 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.
3044
3045If 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.
3046
3047
3048**Parameters**
3049
30501. `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.
30512. `Boolean`: (Optional) Whether to write a header row.
30523. `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`.
3053
3054
3055**Returns**: A `File`.
3056
3057Example: write_tsv_task.wdl
3058
3059```wdl
3060version 1.2
3061
3062task write_tsv {
3063  input {
3064    Array[Array[String]] array = [["one", "two", "three"], ["un", "deux", "trois"]]
3065    Array[Numbers] structs = [
3066      Numbers {
3067        first: "one",
3068        second: "two",
3069        third: "three"
3070      },
3071      Numbers {
3072        first: "un",
3073        second: "deux",
3074        third: "trois"
3075      }
3076    ]
3077  }
3078
3079  command <<<
3080    cut -f 1 ~{write_tsv(array)} >> array_no_header.txt
3081    cut -f 1 ~{write_tsv(array, true, ["first", "second", "third"])} > array_header.txt
3082    cut -f 1 ~{write_tsv(structs)} >> structs_default.txt
3083    cut -f 2 ~{write_tsv(structs, false)} >> structs_no_header.txt
3084    cut -f 2 ~{write_tsv(structs, true)} >> structs_header.txt
3085    cut -f 3 ~{write_tsv(structs, true, ["no1", "no2", "no3"])} >> structs_user_header.txt
3086  >>>
3087
3088  output {
3089    Array[String] array_no_header = read_lines("array_no_header.txt")
3090    Array[String] array_header = read_lines("array_header.txt")
3091    Array[String] structs_default = read_lines("structs_default.txt")
3092    Array[String] structs_no_header = read_lines("structs_no_header.txt")
3093    Array[String] structs_header = read_lines("structs_header.txt")
3094    Array[String] structs_user_header = read_lines("structs_user_header.txt")
3095
3096  }
3097  
3098  requirements {
3099    container: "ubuntu:latest"
3100  }
3101}
3102```
3103"#;
3104
3105    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_tsv
3106    assert!(
3107        functions
3108            .insert(
3109                "write_tsv",
3110                PolymorphicFunction::new(vec![
3111                    FunctionSignature::builder()
3112                        .parameter(
3113                            "data",
3114                            array_array_string.clone(),
3115                            "An array of rows, where each row is either an `Array` of column \
3116                             values or a struct whose values are the column values.",
3117                        )
3118                        .ret(PrimitiveType::File)
3119                        .definition(WRITE_TSV_DEFINITION)
3120                        .build(),
3121                    FunctionSignature::builder()
3122                        .min_version(SupportedVersion::V1(V1::Two))
3123                        .parameter(
3124                            "data",
3125                            array_array_string.clone(),
3126                            "An array of rows, where each row is either an `Array` of column \
3127                             values or a struct whose values are the column values.",
3128                        )
3129                        .parameter(
3130                            "header",
3131                            PrimitiveType::Boolean,
3132                            "(Optional) Whether to write a header row.",
3133                        )
3134                        .parameter(
3135                            "columns",
3136                            array_string.clone(),
3137                            "An array of column names. If the first argument is \
3138                             `Array[Array[String]]` and the second argument is true then it is \
3139                             required, otherwise it is optional. Ignored if the second argument \
3140                             is false."
3141                        )
3142                        .ret(PrimitiveType::File)
3143                        .definition(WRITE_TSV_DEFINITION)
3144                        .build(),
3145                    FunctionSignature::builder()
3146                        .min_version(SupportedVersion::V1(V1::Two))
3147                        .type_parameter("S", PrimitiveStructConstraint)
3148                        .required(1)
3149                        .parameter(
3150                            "data",
3151                            GenericArrayType::new(GenericType::Parameter("S")),
3152                            "An array of rows, where each row is either an `Array` of column \
3153                             values or a struct whose values are the column values.",
3154                        )
3155                        .parameter(
3156                            "header",
3157                            PrimitiveType::Boolean,
3158                            "(Optional) Whether to write a header row.",
3159                        )
3160                        .parameter(
3161                            "columns",
3162                            array_string.clone(),
3163                            "An array of column names. If the first argument is \
3164                             `Array[Array[String]]` and the second argument is true then it is \
3165                             required, otherwise it is optional. Ignored if the second argument \
3166                             is false."
3167                        )
3168                        .ret(PrimitiveType::File)
3169                        .definition(WRITE_TSV_DEFINITION)
3170                        .build(),
3171                ])
3172                .into(),
3173            )
3174            .is_none()
3175    );
3176
3177    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_map
3178    assert!(
3179        functions
3180            .insert(
3181                "read_map",
3182                MonomorphicFunction::new(
3183                    FunctionSignature::builder()
3184                        .parameter(
3185                            "file",
3186                            PrimitiveType::File,
3187                            "Path of the two-column TSV file to read.",
3188                        )
3189                        .ret(map_string_string.clone())
3190                        .definition(
3191                            r#"
3192Reads 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.
3193
3194Each 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.
3195
3196If the file is empty, an empty map is returned.
3197
3198**Parameters**
3199
32001. `File`: Path of the two-column TSV file to read.
3201
3202**Returns**: A `Map[String, String]`, with one element for each row in the TSV file.
3203
3204Example: read_map_task.wdl
3205
3206```wdl
3207version 1.2
3208
3209task read_map {
3210  command <<<
3211    printf "key1\tvalue1\n" >> map_file
3212    printf "key2\tvalue2\n" >> map_file
3213  >>>
3214  
3215  output {
3216    Map[String, String] mapping = read_map(stdout())
3217  }
3218}
3219```
3220"#
3221                        )
3222                        .build(),
3223                )
3224                .into(),
3225            )
3226            .is_none()
3227    );
3228
3229    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_map
3230    assert!(
3231        functions
3232            .insert(
3233                "write_map",
3234                MonomorphicFunction::new(
3235                    FunctionSignature::builder()
3236                        .parameter(
3237                            "map",
3238                            map_string_string.clone(),
3239                            "A `Map`, where each element will be a row in the generated file.",
3240                        )
3241                        .ret(PrimitiveType::File)
3242                        .definition(
3243                            r#"
3244Writes 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.
3245
3246Since `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`.
3247
3248**Parameters**
3249
32501. `Map[String, String]`: A `Map`, where each element will be a row in the generated file.
3251
3252**Returns**: A `File`.
3253
3254Example: write_map_task.wdl
3255
3256```wdl
3257version 1.2
3258
3259task write_map {
3260  input {
3261    Map[String, String] map = {"key1": "value1", "key2": "value2"}
3262  }
3263
3264  command <<<
3265    cut -f 1 ~{write_map(map)}
3266  >>>
3267  
3268  output {
3269    Array[String] keys = read_lines(stdout())
3270  }
3271
3272  requirements {
3273    container: "ubuntu:latest"
3274  }
3275}
3276```
3277"#
3278                        )
3279                        .build(),
3280                )
3281                .into(),
3282            )
3283            .is_none()
3284    );
3285
3286    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_json
3287    assert!(
3288        functions
3289            .insert(
3290                "read_json",
3291                MonomorphicFunction::new(
3292                    FunctionSignature::builder()
3293                        .parameter("file", PrimitiveType::File, "Path of the JSON file to read.")
3294                        .ret(Type::Union)
3295                        .definition(
3296                            r#"
3297Reads a JSON file into a WDL value whose type depends on the file's contents. The mapping of JSON type to WDL type is:
3298
3299| JSON Type | WDL Type         |
3300| --------- | ---------------- |
3301| object    | `Object`         |
3302| array     | `Array[X]`       |
3303| number    | `Int` or `Float` |
3304| string    | `String`         |
3305| boolean   | `Boolean`        |
3306| null      | `None`           |
3307
3308The 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.
3309
3310If 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.
3311
3312The `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.
3313
3314Note that an empty file is not valid according to the JSON specification, and so calling `read_json` on an empty file raises an error.
3315
3316**Parameters**
3317
33181. `File`: Path of the JSON file to read.
3319
3320**Returns**: A value whose type is dependent on the contents of the JSON file.
3321
3322Example: read_person.wdl
3323
3324```wdl
3325version 1.2
3326
3327struct Person {
3328  String name
3329  Int age
3330}
3331
3332workflow read_person {
3333  input {
3334    File json_file
3335  }
3336
3337  output {
3338    Person p = read_json(json_file)
3339  }
3340}
3341```
3342"#
3343                        )
3344                        .build(),
3345                )
3346                .into(),
3347            )
3348            .is_none()
3349    );
3350
3351    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_json
3352    assert!(
3353        functions
3354            .insert(
3355                "write_json",
3356                MonomorphicFunction::new(
3357                    FunctionSignature::builder()
3358                        .type_parameter("X", JsonSerializableConstraint)
3359                        .parameter(
3360                            "value",
3361                            GenericType::Parameter("X"),
3362                            "A WDL value of a supported type.",
3363                        )
3364                        .ret(PrimitiveType::File)
3365                        .definition(
3366                            r#"
3367Writes a JSON file with the serialized form of a WDL value. The following WDL types can be serialized:
3368
3369| WDL Type         | JSON Type |
3370| ---------------- | --------- |
3371| `Struct`         | object    |
3372| `Object`         | object    |
3373| `Map[String, X]` | object    |
3374| `Array[X]`       | array     |
3375| `Int`            | number    |
3376| `Float`          | number    |
3377| `String`         | string    |
3378| `File`           | string    |
3379| `Boolean`        | boolean   |
3380| `None`           | null      |
3381
3382When serializing compound types, all nested types must be serializable or an error is raised.
3383
3384**Parameters**
3385
33861. `X`: A WDL value of a supported type.
3387
3388**Returns**: A `File`.
3389
3390Example: write_json_fail.wdl
3391
3392```wdl
3393version 1.2
3394
3395workflow write_json_fail {
3396  Pair[Int, Map[Int, String]] x = (1, {2: "hello"})
3397  # this fails with an error - Map with Int keys is not serializable
3398  File f = write_json(x)
3399}
3400```
3401"#
3402                        )
3403                        .build(),
3404                )
3405                .into(),
3406            )
3407            .is_none()
3408    );
3409
3410    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_object
3411    assert!(
3412        functions
3413            .insert(
3414                "read_object",
3415                MonomorphicFunction::new(
3416                    FunctionSignature::builder()
3417                        .parameter(
3418                            "file",
3419                            PrimitiveType::File,
3420                            "Path of the two-row TSV file to read.",
3421                        )
3422                        .ret(Type::Object)
3423                        .definition(
3424                            r#"
3425Reads 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.
3426
3427The 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.
3428
3429The 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`.
3430
3431**Parameters**
3432
34331. `File`: Path of the two-row TSV file to read.
3434
3435**Returns**: An `Object`, with as many members as there are unique names in the TSV.
3436
3437Example: read_object_task.wdl
3438
3439```wdl
3440version 1.2
3441
3442task read_object {
3443  command <<<
3444    python <<CODE
3445    print('\t'.join(["key_{}".format(i) for i in range(3)]))
3446    print('\t'.join(["value_{}".format(i) for i in range(3)]))
3447    CODE
3448  >>>
3449
3450  output {
3451    Object my_obj = read_object(stdout())
3452  }
3453
3454  requirements {
3455    container: "python:latest"
3456  }
3457}
3458```
3459"#
3460                        )
3461                        .build(),
3462                )
3463                .into(),
3464            )
3465            .is_none()
3466    );
3467
3468    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#read_objects
3469    assert!(
3470        functions
3471            .insert(
3472                "read_objects",
3473                MonomorphicFunction::new(
3474                    FunctionSignature::builder()
3475                        .parameter("file", PrimitiveType::File, "The file to read.")
3476                        .ret(array_object.clone())
3477                        .definition(
3478                            r#"
3479Reads 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.
3480
3481The 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.
3482
3483There 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`.
3484
3485If the file is empty or contains only a header line, an empty array is returned.
3486
3487**Parameters**
3488
34891. `File`: Path of the TSV file to read.
3490
3491**Returns**: An `Array[Object]`, with `N-1` elements, where `N` is the number of rows in the file.
3492
3493Example: read_objects_task.wdl
3494
3495```wdl
3496version 1.2
3497
3498task read_objects {
3499  command <<<
3500    python <<CODE
3501    print('\t'.join(["key_{}".format(i) for i in range(3)]))
3502    print('\t'.join(["value_A{}".format(i) for i in range(3)]))
3503    print('\t'.join(["value_B{}".format(i) for i in range(3)]))
3504    print('\t'.join(["value_C{}".format(i) for i in range(3)]))
3505    CODE
3506  >>>
3507
3508  output {
3509    Array[Object] my_obj = read_objects(stdout())
3510  }
3511"#
3512                        )
3513                        .build(),
3514                )
3515                .into(),
3516            )
3517            .is_none()
3518    );
3519
3520    const WRITE_OBJECT_DEFINITION: &str = r#"
3521Writes 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.
3522
3523Each line is terminated by the newline (`\n`) character. 
3524
3525The 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.
3526
3527If 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.
3528
3529**Parameters**
3530
35311. `Object`: An `Object` whose members will be written to the file.
3532
3533**Returns**: A `File`.
3534
3535Example: write_object_task.wdl
3536
3537```wdl
3538version 1.2
3539
3540task write_object {
3541  input {
3542    Object my_obj = {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"}
3543  }
3544
3545  command <<<
3546    cat ~{write_object(my_obj)}
3547  >>>
3548
3549  output {
3550    Object new_obj = read_object(stdout())
3551  }
3552}
3553```
3554"#;
3555
3556    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_object
3557    assert!(
3558        functions
3559            .insert(
3560                "write_object",
3561                PolymorphicFunction::new(vec![
3562                    FunctionSignature::builder()
3563                        .parameter("object", Type::Object, "An object to write.")
3564                        .ret(PrimitiveType::File)
3565                        .definition(WRITE_OBJECT_DEFINITION)
3566                        .build(),
3567                    FunctionSignature::builder()
3568                        .min_version(SupportedVersion::V1(V1::One))
3569                        .type_parameter("S", PrimitiveStructConstraint)
3570                        .parameter("object", GenericType::Parameter("S"), "An object to write.")
3571                        .ret(PrimitiveType::File)
3572                        .definition(WRITE_OBJECT_DEFINITION)
3573                        .build(),
3574                ])
3575                .into(),
3576            )
3577            .is_none()
3578    );
3579
3580    const WRITE_OBJECTS_DEFINITION: &str = r#"
3581Writes 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.
3582
3583Each line is terminated by the newline (`\n`) character. 
3584
3585The 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.
3586
3587If 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.
3588
3589**Parameters**
3590
35911. `Array[Object]`: An `Array[Object]` whose elements will be written to the file.
3592
3593**Returns**: A `File`.
3594
3595Example: write_objects_task.wdl
3596
3597```wdl
3598version 1.2
3599
3600task write_objects {
3601  input {
3602    Array[Object] my_objs = [
3603      {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"},
3604      {"key_0": "value_B0", "key_1": "value_B1", "key_2": "value_B2"},
3605      {"key_0": "value_C0", "key_1": "value_C1", "key_2": "value_C2"}
3606    ]
3607  }
3608
3609  command <<<
3610    cat ~{write_objects(my_objs)}
3611  >>>
3612
3613  output {
3614    Array[Object] new_objs = read_objects(stdout())
3615  }
3616}
3617```
3618"#;
3619
3620    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#write_objects
3621    assert!(
3622        functions
3623            .insert(
3624                "write_objects",
3625                PolymorphicFunction::new(vec![
3626                    FunctionSignature::builder()
3627                        .parameter("objects", array_object.clone(), "The objects to write.")
3628                        .ret(PrimitiveType::File)
3629                        .definition(WRITE_OBJECTS_DEFINITION)
3630                        .build(),
3631                    FunctionSignature::builder()
3632                        .min_version(SupportedVersion::V1(V1::One))
3633                        .type_parameter("S", PrimitiveStructConstraint)
3634                        .parameter(
3635                            "objects",
3636                            GenericArrayType::new(GenericType::Parameter("S")),
3637                            "The objects to write."
3638                        )
3639                        .ret(PrimitiveType::File)
3640                        .definition(WRITE_OBJECTS_DEFINITION)
3641                        .build(),
3642                ])
3643                .into(),
3644            )
3645            .is_none()
3646    );
3647
3648    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#prefix
3649    assert!(
3650        functions
3651            .insert(
3652                "prefix",
3653                MonomorphicFunction::new(
3654                    FunctionSignature::builder()
3655                        .type_parameter("P", PrimitiveTypeConstraint)
3656                        .parameter(
3657                            "prefix",
3658                            PrimitiveType::String,
3659                            "The prefix to prepend to each element in the array.",
3660                        )
3661                        .parameter(
3662                            "array",
3663                            GenericArrayType::new(GenericType::Parameter("P")),
3664                            "Array with a primitive element type.",
3665                        )
3666                        .ret(array_string.clone())
3667                        .definition(
3668                            r#"
3669Given 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.
3670
3671**Parameters**
3672
36731. `String`: The string to prepend.
36742. `Array[X]`: The array whose elements will be prepended.
3675
3676**Returns**: A new `Array[String]` with the prepended elements.
3677
3678Example: prefix_task.wdl
3679
3680```wdl
3681version 1.2
3682
3683task prefix {
3684  input {
3685    Array[Int] ints = [1, 2, 3]
3686  }
3687
3688  output {
3689    Array[String] prefixed_ints = prefix("file_", ints) # ["file_1", "file_2", "file_3"]
3690  }
3691}
3692```
3693"#
3694                        )
3695                        .build(),
3696                )
3697                .into(),
3698            )
3699            .is_none()
3700    );
3701
3702    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#suffix
3703    assert!(
3704        functions
3705            .insert(
3706                "suffix",
3707                MonomorphicFunction::new(
3708                    FunctionSignature::builder()
3709                        .min_version(SupportedVersion::V1(V1::One))
3710                        .type_parameter("P", PrimitiveTypeConstraint)
3711                        .parameter(
3712                            "suffix",
3713                            PrimitiveType::String,
3714                            "The suffix to append to each element in the array.",
3715                        )
3716                        .parameter(
3717                            "array",
3718                            GenericArrayType::new(GenericType::Parameter("P")),
3719                            "Array with a primitive element type.",
3720                        )
3721                        .ret(array_string.clone())
3722                        .definition(
3723                            r#"
3724Given 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.
3725
3726**Parameters**
3727
37281. `String`: The string to append.
37292. `Array[X]`: The array whose elements will be appended.
3730
3731**Returns**: A new `Array[String]` with the appended elements.
3732
3733Example: suffix_task.wdl
3734
3735```wdl
3736version 1.2
3737
3738task suffix {
3739  input {
3740    Array[Int] ints = [1, 2, 3]
3741  }
3742
3743  output {
3744    Array[String] suffixed_ints = suffix(".txt", ints) # ["1.txt", "2.txt", "3.txt"]
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#quote
3758    assert!(
3759        functions
3760            .insert(
3761                "quote",
3762                MonomorphicFunction::new(
3763                    FunctionSignature::builder()
3764                        .min_version(SupportedVersion::V1(V1::One))
3765                        .type_parameter("P", PrimitiveTypeConstraint)
3766                        .parameter(
3767                            "array",
3768                            GenericArrayType::new(GenericType::Parameter("P")),
3769                            "Array with a primitive element type.",
3770                        )
3771                        .ret(array_string.clone())
3772                        .definition(
3773                            r#"
3774Given 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.
3775
3776**Parameters**
3777
37781. `Array[X]`: The array whose elements will be quoted.
3779
3780**Returns**: A new `Array[String]` with the quoted elements.
3781
3782Example: quote_task.wdl
3783
3784```wdl
3785version 1.2
3786
3787task quote {
3788  input {
3789    Array[String] strings = ["hello", "world"]
3790  }
3791
3792  output {
3793    Array[String] quoted_strings = quote(strings) # ["\"hello\"", "\"world\""]
3794  }
3795}
3796```
3797"#
3798                        )
3799                        .build(),
3800                )
3801                .into(),
3802            )
3803            .is_none()
3804    );
3805
3806    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#squote
3807    assert!(
3808        functions
3809            .insert(
3810                "squote",
3811                MonomorphicFunction::new(
3812                    FunctionSignature::builder()
3813                        .min_version(SupportedVersion::V1(V1::One))
3814                        .type_parameter("P", PrimitiveTypeConstraint)
3815                        .parameter("array", GenericArrayType::new(GenericType::Parameter("P")), "The array of values.")                        .ret(array_string.clone())
3816                        .definition(
3817                            r#"
3818Given 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.
3819
3820**Parameters**
3821
38221. `Array[X]`: The array whose elements will be single-quoted.
3823
3824**Returns**: A new `Array[String]` with the single-quoted elements.
3825
3826Example: squote_task.wdl
3827
3828```wdl
3829version 1.2
3830
3831task squote {
3832  input {
3833    Array[String] strings = ["hello", "world"]
3834  }
3835
3836  output {
3837    Array[String] squoted_strings = squote(strings) # ["'hello'", "'world'"]
3838  }
3839}
3840```
3841"#
3842                        )
3843                        .build(),
3844                )
3845                .into(),
3846            )
3847            .is_none()
3848    );
3849
3850    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#sep
3851    assert!(
3852        functions
3853            .insert(
3854                "sep",
3855                MonomorphicFunction::new(
3856                    FunctionSignature::builder()
3857                        .min_version(SupportedVersion::V1(V1::One))
3858                        .type_parameter("P", PrimitiveTypeConstraint)
3859                        .parameter("separator", PrimitiveType::String, "Separator string.")
3860                        .parameter(
3861                            "array",
3862                            GenericArrayType::new(GenericType::Parameter("P")),
3863                            "`Array` of strings to concatenate.",
3864                        )
3865                        .ret(PrimitiveType::String)
3866                        .definition(
3867                            r#"
3868Given 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.
3869
3870**Parameters**
3871
38721. `String`: The string to use as a separator.
38732. `Array[X]`: The array whose elements will be joined.
3874
3875**Returns**: A new `String` with the joined elements.
3876
3877Example: sep_task.wdl
3878
3879```wdl
3880version 1.2
3881
3882task sep {
3883  input {
3884    Array[Int] ints = [1, 2, 3]
3885  }
3886
3887  output {
3888    String joined_ints = sep(",", ints) # "1,2,3"
3889  }
3890}
3891```
3892"#
3893                        )
3894                        .build(),
3895                )
3896                .into(),
3897            )
3898            .is_none()
3899    );
3900
3901    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#range
3902    assert!(
3903        functions
3904            .insert(
3905                "range",
3906                MonomorphicFunction::new(
3907                    FunctionSignature::builder()
3908                        .parameter("n", PrimitiveType::Integer, "The length of array to create.")
3909                        .ret(array_int.clone())
3910                        .definition(
3911                            r#"
3912Returns 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.
3913
3914**Parameters**
3915
39161. `Int`: The upper bound (exclusive) of the range.
3917
3918**Returns**: An `Array[Int]` of integers.
3919
3920Example: range_task.wdl
3921
3922```wdl
3923version 1.2
3924
3925task range {
3926  input {
3927    Int n = 5
3928  }
3929
3930  output {
3931    Array[Int] r = range(n) # [0, 1, 2, 3, 4]
3932  }
3933}
3934```
3935"#
3936                        )
3937                        .build(),
3938                )
3939                .into(),
3940            )
3941            .is_none()
3942    );
3943
3944    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#transpose
3945    assert!(
3946        functions
3947            .insert(
3948                "transpose",
3949                MonomorphicFunction::new(
3950                    FunctionSignature::builder()
3951                        .any_type_parameter("X")
3952                        .parameter(
3953                            "array",
3954                            GenericArrayType::new(GenericArrayType::new(
3955                                GenericType::Parameter("X"),
3956                            )),
3957                            "A M*N two-dimensional array.",
3958                        )
3959                        .ret(GenericArrayType::new(GenericArrayType::new(
3960                            GenericType::Parameter("X"),
3961                        )))
3962                        .definition(
3963                            r#"
3964Given 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.
3965
3966If the inner arrays are not all the same length, an error is raised.
3967
3968**Parameters**
3969
39701. `Array[Array[X]]`: The array to transpose.
3971
3972**Returns**: A new `Array[Array[X]]` with the rows and columns swapped.
3973
3974Example: transpose_task.wdl
3975
3976```wdl
3977version 1.2
3978
3979task transpose {
3980  input {
3981    Array[Array[Int]] matrix = [[1, 2, 3], [4, 5, 6]]
3982  }
3983
3984  output {
3985    Array[Array[Int]] transposed_matrix = transpose(matrix) # [[1, 4], [2, 5], [3, 6]]
3986  }
3987}
3988```
3989"#
3990                        )
3991                        .build(),
3992                )
3993                .into(),
3994            )
3995            .is_none()
3996    );
3997
3998    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#cross
3999    assert!(
4000        functions
4001            .insert(
4002                "cross",
4003                MonomorphicFunction::new(
4004                    FunctionSignature::builder()
4005                        .any_type_parameter("X")
4006                        .any_type_parameter("Y")
4007                        .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4008                        .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4009                        .ret(GenericArrayType::new(GenericPairType::new(
4010                            GenericType::Parameter("X"),
4011                            GenericType::Parameter("Y"),
4012                        )))
4013                        .definition(
4014                            r#"
4015Given 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.
4016
4017If either `a` or `b` is empty, an empty array is returned.
4018
4019**Parameters**
4020
40211. `Array[X]`: The first array.
40222. `Array[Y]`: The second array.
4023
4024**Returns**: A new `Array[Pair[X, Y]]` with the cross product of the two arrays.
4025
4026Example: cross_task.wdl
4027
4028```wdl
4029version 1.2
4030
4031task cross {
4032  input {
4033    Array[Int] ints = [1, 2]
4034    Array[String] strings = ["a", "b"]
4035  }
4036
4037  output {
4038    Array[Pair[Int, String]] crossed = cross(ints, strings) # [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
4039  }
4040}
4041```
4042"#
4043                        )
4044                        .build(),
4045                )
4046                .into(),
4047            )
4048            .is_none()
4049    );
4050
4051    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#zip
4052    assert!(
4053        functions
4054            .insert(
4055                "zip",
4056                MonomorphicFunction::new(
4057                    FunctionSignature::builder()
4058                        .any_type_parameter("X")
4059                        .any_type_parameter("Y")
4060                        .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4061                        .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4062                        .ret(GenericArrayType::new(GenericPairType::new(
4063                            GenericType::Parameter("X"),
4064                            GenericType::Parameter("Y"),
4065                        )))
4066                        .definition(
4067                            r#"
4068Given 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`.
4069
4070If either `a` or `b` is empty, an empty array is returned.
4071
4072**Parameters**
4073
40741. `Array[X]`: The first array.
40752. `Array[Y]`: The second array.
4076
4077**Returns**: A new `Array[Pair[X, Y]]` with the zipped elements.
4078
4079Example: zip_task.wdl
4080
4081```wdl
4082version 1.2
4083
4084task zip {
4085  input {
4086    Array[Int] ints = [1, 2, 3]
4087    Array[String] strings = ["a", "b"]
4088  }
4089
4090  output {
4091    Array[Pair[Int, String]] zipped = zip(ints, strings) # [(1, "a"), (2, "b")]
4092  }
4093}
4094```
4095"#
4096                        )
4097                        .build(),
4098                )
4099                .into(),
4100            )
4101            .is_none()
4102    );
4103
4104    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#unzip
4105    assert!(
4106        functions
4107            .insert(
4108                "unzip",
4109                MonomorphicFunction::new(
4110                    FunctionSignature::builder()
4111                        .min_version(SupportedVersion::V1(V1::One))
4112                        .any_type_parameter("X")
4113                        .any_type_parameter("Y")
4114                        .parameter(
4115                            "array",
4116                            GenericArrayType::new(GenericPairType::new(
4117                                GenericType::Parameter("X"),
4118                                GenericType::Parameter("Y"),
4119                            )),
4120                            "The `Array` of `Pairs` of length N to unzip.",
4121                        )
4122                        .ret(GenericPairType::new(
4123                            GenericArrayType::new(GenericType::Parameter("X")),
4124                            GenericArrayType::new(GenericType::Parameter("Y")),
4125                        ))
4126                        .definition(
4127                            r#"
4128Given 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`.
4129
4130If `a` is empty, a `Pair` of two empty arrays is returned.
4131
4132**Parameters**
4133
41341. `Array[Pair[X, Y]]`: The array of pairs to unzip.
4135
4136**Returns**: A new `Pair[Array[X], Array[Y]]` with the unzipped elements.
4137
4138Example: unzip_task.wdl
4139
4140```wdl
4141version 1.2
4142
4143task unzip {
4144  input {
4145    Array[Pair[Int, String]] zipped = [(1, "a"), (2, "b")]
4146  }
4147
4148  output {
4149    Pair[Array[Int], Array[String]] unzipped = unzip(zipped) # ([1, 2], ["a", "b"])
4150  }
4151}
4152```
4153"#
4154                        )
4155                        .build(),
4156                )
4157                .into(),
4158            )
4159            .is_none()
4160    );
4161
4162    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-contains
4163    assert!(
4164        functions
4165            .insert(
4166                "contains",
4167                MonomorphicFunction::new(
4168                    FunctionSignature::builder()
4169                        .min_version(SupportedVersion::V1(V1::Two))
4170                        .type_parameter("P", PrimitiveTypeConstraint)
4171                        .parameter(
4172                            "array",
4173                            GenericArrayType::new(GenericType::Parameter("P")),
4174                            "An array of any primitive type.",
4175                        )
4176                        .parameter(
4177                            "value",
4178                            GenericType::Parameter("P"),
4179                            "A primitive value of the same type as the array. If the array's \
4180                             type is optional, then the value may also be optional.",
4181                        )
4182                        .ret(PrimitiveType::Boolean)
4183                        .definition(
4184                            r#"
4185Given an `Array[X]` `a` and a value `v` of type `X`, returns `true` if `v` is present in `a`, otherwise `false`.
4186
4187**Parameters**
4188
41891. `Array[X]`: The array to search.
41902. `X`: The value to search for.
4191
4192**Returns**: `true` if `v` is present in `a`, otherwise `false`.
4193
4194Example: contains_task.wdl
4195
4196```wdl
4197version 1.2
4198
4199task contains {
4200  input {
4201    Array[Int] ints = [1, 2, 3]
4202  }
4203
4204  output {
4205    Boolean contains_2 = contains(ints, 2) # true
4206    Boolean contains_4 = contains(ints, 4) # false
4207  }
4208}
4209```
4210"#
4211                        )
4212                        .build(),
4213                )
4214                .into(),
4215            )
4216            .is_none()
4217    );
4218
4219    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-chunk
4220    assert!(
4221        functions
4222            .insert(
4223                "chunk",
4224                MonomorphicFunction::new(
4225                    FunctionSignature::builder()
4226                        .min_version(SupportedVersion::V1(V1::Two))
4227                        .any_type_parameter("X")
4228                        .parameter(
4229                            "array",
4230                            GenericArrayType::new(GenericType::Parameter("X")),
4231                            "The array to split. May be empty.",
4232                        )
4233                        .parameter("size", PrimitiveType::Integer, "The desired length of the sub-arrays. Must be > 0.")
4234                        .ret(GenericArrayType::new(GenericArrayType::new(
4235                            GenericType::Parameter("X"),
4236                        )))
4237                        .definition(
4238                            r#"
4239Given 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.
4240
4241If `size` is less than or equal to `0`, an error is raised.
4242
4243**Parameters**
4244
42451. `Array[X]`: The array to chunk.
42462. `Int`: The maximum size of each chunk.
4247
4248**Returns**: A new `Array[Array[X]]` with the chunked elements.
4249
4250Example: chunk_task.wdl
4251
4252```wdl
4253version 1.2
4254
4255task chunk {
4256  input {
4257    Array[Int] ints = [1, 2, 3, 4, 5]
4258  }
4259
4260  output {
4261    Array[Array[Int]] chunked = chunk(ints, 2) # [[1, 2], [3, 4], [5]]
4262  }
4263}
4264```
4265"#
4266                        )
4267                        .build(),
4268                )
4269                .into(),
4270            )
4271            .is_none()
4272    );
4273
4274    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#flatten
4275    assert!(
4276        functions
4277            .insert(
4278                "flatten",
4279                MonomorphicFunction::new(
4280                    FunctionSignature::builder()
4281                        .any_type_parameter("X")
4282                        .parameter(
4283                            "array",
4284                            GenericArrayType::new(GenericArrayType::new(
4285                                GenericType::Parameter("X"),
4286                            )),
4287                            "A nested array to flatten.",
4288                        )
4289                        .ret(GenericArrayType::new(GenericType::Parameter("X")))
4290                        .definition(
4291                            r#"
4292Given 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.
4293
4294**Parameters**
4295
42961. `Array[Array[X]]`: The array to flatten.
4297
4298**Returns**: A new `Array[X]` with the flattened elements.
4299
4300Example: flatten_task.wdl
4301
4302```wdl
4303version 1.2
4304
4305task flatten {
4306  input {
4307    Array[Array[Int]] nested_ints = [[1, 2], [3, 4], [5]]
4308  }
4309
4310  output {
4311    Array[Int] flattened_ints = flatten(nested_ints) # [1, 2, 3, 4, 5]
4312  }
4313}
4314```
4315"#
4316                        )
4317                        .build(),
4318                )
4319                .into(),
4320            )
4321            .is_none()
4322    );
4323
4324    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#select_first
4325    assert!(
4326        functions
4327            .insert(
4328                "select_first",
4329                // This differs from the definition of `select_first` in that we can have a single
4330                // signature of `X select_first(Array[X?], [X])`.
4331                MonomorphicFunction::new(
4332                    FunctionSignature::builder()
4333                        .any_type_parameter("X")
4334                        .required(1)
4335                        .parameter(
4336                            "array",
4337                            GenericArrayType::new(GenericType::Parameter("X")),
4338                            "Non-empty `Array` of optional values.",
4339                        )
4340                        .parameter("default", GenericType::UnqualifiedParameter("X"), "(Optional) The default value.")
4341                        .ret(GenericType::UnqualifiedParameter("X"))
4342                        .definition(
4343                            r#"
4344Given an `Array[X?]` `a`, returns the first non-`None` element in `a`. If all elements are `None`, an error is raised.
4345
4346**Parameters**
4347
43481. `Array[X?]`: The array to search.
4349
4350**Returns**: The first non-`None` element.
4351
4352Example: select_first_task.wdl
4353
4354```wdl
4355version 1.2
4356
4357task select_first {
4358  input {
4359    Array[Int?] ints = [None, 1, None, 2]
4360  }
4361
4362  output {
4363    Int first_int = select_first(ints) # 1
4364  }
4365}
4366```
4367"#
4368                        )
4369                        .build(),
4370                )
4371                .into(),
4372            )
4373            .is_none()
4374    );
4375
4376    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#select_all
4377    assert!(
4378        functions
4379            .insert(
4380                "select_all",
4381                MonomorphicFunction::new(
4382                    FunctionSignature::builder()
4383                        .any_type_parameter("X")
4384                        .parameter(
4385                            "array",
4386                            GenericArrayType::new(GenericType::Parameter("X")),
4387                            "`Array` of optional values.",
4388                        )
4389                        .ret(GenericArrayType::new(GenericType::UnqualifiedParameter(
4390                            "X"
4391                        )))
4392                        .definition(
4393                            r#"
4394Given 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.
4395
4396**Parameters**
4397
43981. `Array[X?]`: The array to filter.
4399
4400**Returns**: A new `Array[X]` with all the non-`None` elements.
4401
4402Example: select_all_task.wdl
4403
4404```wdl
4405version 1.2
4406
4407task select_all {
4408  input {
4409    Array[Int?] ints = [None, 1, None, 2]
4410  }
4411
4412  output {
4413    Array[Int] all_ints = select_all(ints) # [1, 2]
4414  }
4415}
4416```
4417"#
4418                        )
4419                        .build(),
4420                )
4421                .into(),
4422            )
4423            .is_none()
4424    );
4425
4426    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#as_pairs
4427    assert!(
4428        functions
4429            .insert(
4430                "as_pairs",
4431                MonomorphicFunction::new(
4432                    FunctionSignature::builder()
4433                        .min_version(SupportedVersion::V1(V1::One))
4434                        .type_parameter("K", PrimitiveTypeConstraint)
4435                        .any_type_parameter("V")
4436                        .parameter(
4437                            "map",
4438                            GenericMapType::new(
4439                                GenericType::Parameter("K"),
4440                                GenericType::Parameter("V"),
4441                            ),
4442                            "`Map` to convert to `Pairs`.",
4443                        )
4444                        .ret(GenericArrayType::new(GenericPairType::new(
4445                            GenericType::Parameter("K"),
4446                            GenericType::Parameter("V")
4447                        )))
4448                        .definition(
4449                            r#"
4450Given 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`.
4451
4452If `m` is empty, an empty array is returned.
4453
4454**Parameters**
4455
44561. `Map[K, V]`: The map to convert.
4457
4458**Returns**: A new `Array[Pair[K, V]]` with the key-value pairs.
4459
4460Example: as_pairs_task.wdl
4461
4462```wdl
4463version 1.2
4464
4465task as_pairs {
4466  input {
4467    Map[String, Int] map = {"a": 1, "b": 2}
4468  }
4469
4470  output {
4471    Array[Pair[String, Int]] pairs = as_pairs(map) # [("a", 1), ("b", 2)]
4472  }
4473}
4474```
4475"#
4476                        )
4477                        .build(),
4478                )
4479                .into(),
4480            )
4481            .is_none()
4482    );
4483
4484    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#as_map
4485    assert!(
4486        functions
4487            .insert(
4488                "as_map",
4489                MonomorphicFunction::new(
4490                    FunctionSignature::builder()
4491                        .min_version(SupportedVersion::V1(V1::One))
4492                        .type_parameter("K", PrimitiveTypeConstraint)
4493                        .any_type_parameter("V")
4494                        .parameter(
4495                            "pairs",
4496                            GenericArrayType::new(GenericPairType::new(
4497                                GenericType::Parameter("K"),
4498                                GenericType::Parameter("V"),
4499                            )),
4500                            "`Array` of `Pairs` to convert to a `Map`.",
4501                        )
4502                        .ret(GenericMapType::new(
4503                            GenericType::Parameter("K"),
4504                            GenericType::Parameter("V")
4505                        ))
4506                        .definition(
4507                            r#"
4508Given 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.
4509
4510If there are any duplicate keys in `a`, an error is raised.
4511
4512**Parameters**
4513
45141. `Array[Pair[K, V]]`: The array of pairs to convert.
4515
4516**Returns**: A new `Map[K, V]` with the key-value pairs.
4517
4518Example: as_map_task.wdl
4519
4520```wdl
4521version 1.2
4522
4523task as_map {
4524  input {
4525    Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2)]
4526  }
4527
4528  output {
4529    Map[String, Int] map = as_map(pairs) # {"a": 1, "b": 2}
4530  }
4531}
4532```
4533"#
4534                        )
4535                        .build(),
4536                )
4537                .into(),
4538            )
4539            .is_none()
4540    );
4541
4542    const KEYS_DEFINITION: &str = r#"
4543Given 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`.
4544
4545If `m` is empty, an empty array is returned.
4546
4547**Parameters**
4548
45491. `Map[K, V]`: The map to get the keys from.
4550
4551**Returns**: A new `Array[K]` with the keys.
4552
4553Example: keys_map_task.wdl
4554
4555```wdl
4556version 1.2
4557
4558task keys_map {
4559  input {
4560    Map[String, Int] map = {"a": 1, "b": 2}
4561  }
4562
4563  output {
4564    Array[String] keys = keys(map) # ["a", "b"]
4565  }
4566}
4567```
4568"#;
4569
4570    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#keys
4571    assert!(
4572        functions
4573            .insert(
4574                "keys",
4575                PolymorphicFunction::new(vec![
4576                    FunctionSignature::builder()
4577                        .min_version(SupportedVersion::V1(V1::One))
4578                        .type_parameter("K", PrimitiveTypeConstraint)
4579                        .any_type_parameter("V")
4580                        .parameter(
4581                            "map",
4582                            GenericMapType::new(
4583                                GenericType::Parameter("K"),
4584                                GenericType::Parameter("V"),
4585                            ),
4586                            "Collection from which to extract keys.",
4587                        )
4588                        .ret(GenericArrayType::new(GenericType::Parameter("K")))
4589                        .definition(KEYS_DEFINITION)
4590                        .build(),
4591                    FunctionSignature::builder()
4592                        .min_version(SupportedVersion::V1(V1::Two))
4593                        .type_parameter("S", StructConstraint)
4594                        .parameter(
4595                            "struct",
4596                            GenericType::Parameter("S"),
4597                            "Collection from which to extract keys.",
4598                        )
4599                        .ret(array_string.clone())
4600                        .definition(KEYS_DEFINITION)
4601                        .build(),
4602                    FunctionSignature::builder()
4603                        .min_version(SupportedVersion::V1(V1::Two))
4604                        .parameter(
4605                            "object",
4606                            Type::Object,
4607                            "Collection from which to extract keys.",
4608                        )
4609                        .ret(array_string.clone())
4610                        .definition(KEYS_DEFINITION)
4611                        .build(),
4612                ])
4613                .into(),
4614            )
4615            .is_none()
4616    );
4617
4618    const CONTAINS_KEY_DEFINITION: &str = r#"
4619Given a `Map[K, V]` `m` and a key `k` of type `K`, returns `true` if `k` is present in `m`, otherwise `false`.
4620
4621**Parameters**
4622
46231. `Map[K, V]`: The map to search.
46242. `K`: The key to search for.
4625
4626**Returns**: `true` if `k` is present in `m`, otherwise `false`.
4627
4628Example: contains_key_map_task.wdl
4629
4630```wdl
4631version 1.2
4632
4633task contains_key_map {
4634  input {
4635    Map[String, Int] map = {"a": 1, "b": 2}
4636  }
4637
4638  output {
4639    Boolean contains_a = contains_key(map, "a") # true
4640    Boolean contains_c = contains_key(map, "c") # false
4641  }
4642}
4643```
4644"#;
4645
4646    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#contains_key
4647    assert!(
4648        functions
4649            .insert(
4650                "contains_key",
4651                PolymorphicFunction::new(vec![
4652                        FunctionSignature::builder()
4653                            .min_version(SupportedVersion::V1(V1::Two))
4654                            .type_parameter("K", PrimitiveTypeConstraint)
4655                            .any_type_parameter("V")
4656                            .parameter(
4657                                "map",
4658                                GenericMapType::new(
4659                                    GenericType::Parameter("K"),
4660                                    GenericType::Parameter("V"),
4661                                ),
4662                                "Collection to search for the key.",
4663                            )
4664                            .parameter(
4665                                "key",
4666                                GenericType::Parameter("K"),
4667                                "The key to search for. If the first argument is a `Map`, then \
4668                                 the key must be of the same type as the `Map`'s key type. If the \
4669                                 `Map`'s key type is optional then the key may also be optional. \
4670                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4671                                 `Object`, then the key may be either a `String` or \
4672                                 `Array[String]`."
4673                            )
4674                            .ret(PrimitiveType::Boolean)
4675                            .definition(CONTAINS_KEY_DEFINITION)
4676                            .build(),
4677                        FunctionSignature::builder()
4678                            .min_version(SupportedVersion::V1(V1::Two))
4679                            .parameter("object", Type::Object, "Collection to search for the key.")
4680                            .parameter(
4681                                "key",
4682                                PrimitiveType::String,
4683                                "The key to search for. If the first argument is a `Map`, then \
4684                                 the key must be of the same type as the `Map`'s key type. If the \
4685                                 `Map`'s key type is optional then the key may also be optional. \
4686                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4687                                 `Object`, then the key may be either a `String` or \
4688                                 `Array[String]`."
4689                            )
4690                            .ret(PrimitiveType::Boolean)
4691                            .definition(CONTAINS_KEY_DEFINITION)
4692                            .build(),
4693                        FunctionSignature::builder()
4694                            .min_version(SupportedVersion::V1(V1::Two))
4695                            .any_type_parameter("V")
4696                            .parameter(
4697                                "map",
4698                                GenericMapType::new(
4699                                    PrimitiveType::String,
4700                                    GenericType::Parameter("V"),
4701                                ),
4702                                "Collection to search for the key.",
4703                            )
4704                            .parameter(
4705                                "keys",
4706                                array_string.clone(),
4707                                "The key to search for. If the first argument is a `Map`, then \
4708                                 the key must be of the same type as the `Map`'s key type. If the \
4709                                 `Map`'s key type is optional then the key may also be optional. \
4710                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4711                                 `Object`, then the key may be either a `String` or \
4712                                 `Array[String]`."
4713                            )
4714                            .ret(PrimitiveType::Boolean)
4715                            .definition(CONTAINS_KEY_DEFINITION)
4716                            .build(),
4717                        FunctionSignature::builder()
4718                            .min_version(SupportedVersion::V1(V1::Two))
4719                            .type_parameter("S", StructConstraint)
4720                            .parameter(
4721                                "struct",
4722                                GenericType::Parameter("S"),
4723                                "Collection to search for the key.",
4724                            )
4725                            .parameter(
4726                                "keys",
4727                                array_string.clone(),
4728                                "The key to search for. If the first argument is a `Map`, then \
4729                                 the key must be of the same type as the `Map`'s key type. If the \
4730                                 `Map`'s key type is optional then the key may also be optional. \
4731                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4732                                 `Object`, then the key may be either a `String` or \
4733                                 `Array[String]`."
4734                            )
4735                            .ret(PrimitiveType::Boolean)
4736                            .definition(CONTAINS_KEY_DEFINITION)
4737                            .build(),
4738                        FunctionSignature::builder()
4739                            .min_version(SupportedVersion::V1(V1::Two))
4740                            .parameter("object", Type::Object, "Collection to search for the key.")
4741                            .parameter(
4742                                "keys",
4743                                array_string.clone(),
4744                                "The key to search for. If the first argument is a `Map`, then \
4745                                 the key must be of the same type as the `Map`'s key type. If the \
4746                                 `Map`'s key type is optional then the key may also be optional. \
4747                                 If the first argument is a `Map[String, Y]`, `Struct`, or \
4748                                 `Object`, then the key may be either a `String` or \
4749                                 `Array[String]`."
4750                            )
4751                            .ret(PrimitiveType::Boolean)
4752                            .definition(CONTAINS_KEY_DEFINITION)
4753                            .build(),
4754                    ])
4755                .into(),
4756            )
4757            .is_none()
4758    );
4759
4760    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-values
4761    assert!(
4762        functions
4763            .insert(
4764                "values",
4765                MonomorphicFunction::new(
4766                    FunctionSignature::builder()
4767                        .min_version(SupportedVersion::V1(V1::Two))
4768                        .type_parameter("K", PrimitiveTypeConstraint)
4769                        .any_type_parameter("V")
4770                        .parameter(
4771                            "map",
4772                            GenericMapType::new(
4773                                GenericType::Parameter("K"),
4774                                GenericType::Parameter("V"),
4775                            ),
4776                            "`Map` from which to extract values.",
4777                        )
4778                        .ret(GenericArrayType::new(GenericType::Parameter("V")))
4779                        .definition(
4780                            r#"
4781Given 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`.
4782
4783If `m` is empty, an empty array is returned.
4784
4785**Parameters**
4786
47871. `Map[K, V]`: The map to get the values from.
4788
4789**Returns**: A new `Array[V]` with the values.
4790
4791Example: values_map_task.wdl
4792
4793```wdl
4794version 1.2
4795
4796task values_map {
4797  input {
4798    Map[String, Int] map = {"a": 1, "b": 2}
4799  }
4800
4801  output {
4802    Array[Int] values = values(map) # [1, 2]
4803  }
4804}
4805```
4806"#
4807                        )
4808                        .build(),
4809                )
4810                .into(),
4811            )
4812            .is_none()
4813    );
4814
4815    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#collect_by_key
4816    assert!(
4817        functions
4818            .insert(
4819                "collect_by_key",
4820                MonomorphicFunction::new(
4821                    FunctionSignature::builder()
4822                        .min_version(SupportedVersion::V1(V1::One))
4823                        .type_parameter("K", PrimitiveTypeConstraint)
4824                        .any_type_parameter("V")
4825                        .parameter(
4826                            "pairs",
4827                            GenericArrayType::new(GenericPairType::new(
4828                                GenericType::Parameter("K"),
4829                                GenericType::Parameter("V"),
4830                            )),
4831                            "`Array` of `Pairs` to group.",
4832                        )
4833                        .ret(GenericMapType::new(
4834                            GenericType::Parameter("K"),
4835                            GenericArrayType::new(GenericType::Parameter("V"))
4836                        ))
4837                        .definition(
4838                            r#"
4839Given 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`.
4840
4841If `a` is empty, an empty map is returned.
4842
4843**Parameters**
4844
48451. `Array[Pair[K, V]]`: The array of pairs to collect.
4846
4847**Returns**: A new `Map[K, Array[V]]` with the collected values.
4848
4849Example: collect_by_key_task.wdl
4850
4851```wdl
4852version 1.2
4853
4854task collect_by_key {
4855  input {
4856    Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2), ("a", 3)]
4857  }
4858
4859  output {
4860    Map[String, Array[Int]] collected = collect_by_key(pairs) # {"a": [1, 3], "b": 2}
4861  }
4862}
4863```
4864"#
4865                        )
4866                        .build(),
4867                )
4868                .into(),
4869            )
4870            .is_none()
4871    );
4872
4873    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#defined
4874    assert!(
4875        functions
4876            .insert(
4877                "defined",
4878                MonomorphicFunction::new(
4879                    FunctionSignature::builder()
4880                        .any_type_parameter("X")
4881                        .parameter(
4882                            "value",
4883                            GenericType::Parameter("X"),
4884                            "Optional value of any type."
4885                        )
4886                        .ret(PrimitiveType::Boolean)
4887                        .definition(
4888                            r#"
4889Given an optional value `x`, returns `true` if `x` is defined (i.e., not `None`), otherwise `false`.
4890
4891**Parameters**
4892
48931. `X?`: The optional value to check.
4894
4895**Returns**: `true` if `x` is defined, otherwise `false`.
4896
4897Example: defined_task.wdl
4898
4899```wdl
4900version 1.2
4901
4902task defined {
4903  input {
4904    Int? x = 1
4905    Int? y = None
4906  }
4907
4908  output {
4909    Boolean x_defined = defined(x) # true
4910    Boolean y_defined = defined(y) # false
4911  }
4912}
4913```
4914"#
4915                        )
4916                        .build(),
4917                )
4918                .into(),
4919            )
4920            .is_none()
4921    );
4922
4923    const LENGTH_DEFINITION: &str = r#"
4924Given an `Array[X]` `a`, returns the number of elements in `a`. If `a` is empty, `0` is returned.
4925
4926**Parameters**
4927
49281. `Array[X]`: The array to get the length from.
4929
4930**Returns**: The number of elements in the array as an `Int`.
4931
4932Example: length_array_task.wdl
4933
4934```wdl
4935version 1.2
4936
4937task length_array {
4938  input {
4939    Array[Int] ints = [1, 2, 3]
4940  }
4941
4942  output {
4943    Int len = length(ints) # 3
4944  }
4945}
4946```
4947"#;
4948
4949    // https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#length
4950    assert!(
4951        functions
4952            .insert(
4953                "length",
4954                PolymorphicFunction::new(vec![
4955                    FunctionSignature::builder()
4956                        .any_type_parameter("X")
4957                        .parameter(
4958                            "array",
4959                            GenericArrayType::new(GenericType::Parameter("X")),
4960                            "A collection or string whose elements are to be counted.",
4961                        )
4962                        .ret(PrimitiveType::Integer)
4963                        .definition(LENGTH_DEFINITION)
4964                        .build(),
4965                    FunctionSignature::builder()
4966                        .any_type_parameter("K")
4967                        .any_type_parameter("V")
4968                        .parameter(
4969                            "map",
4970                            GenericMapType::new(
4971                                GenericType::Parameter("K"),
4972                                GenericType::Parameter("V"),
4973                            ),
4974                            "A collection or string whose elements are to be counted.",
4975                        )
4976                        .ret(PrimitiveType::Integer)
4977                        .definition(LENGTH_DEFINITION)
4978                        .build(),
4979                    FunctionSignature::builder()
4980                        .parameter(
4981                            "object",
4982                            Type::Object,
4983                            "A collection or string whose elements are to be counted.",
4984                        )
4985                        .ret(PrimitiveType::Integer)
4986                        .definition(LENGTH_DEFINITION)
4987                        .build(),
4988                    FunctionSignature::builder()
4989                        .parameter(
4990                            "string",
4991                            PrimitiveType::String,
4992                            "A collection or string whose elements are to be counted.",
4993                        )
4994                        .ret(PrimitiveType::Integer)
4995                        .definition(LENGTH_DEFINITION)
4996                        .build(),
4997                ])
4998                .into(),
4999            )
5000            .is_none()
5001    );
5002
5003    StandardLibrary {
5004        functions,
5005        array_int,
5006        array_string,
5007        array_file,
5008        array_object,
5009        array_string_non_empty,
5010        array_array_string,
5011        map_string_string,
5012        map_string_int,
5013    }
5014});
5015
5016#[cfg(test)]
5017mod test {
5018    use pretty_assertions::assert_eq;
5019
5020    use super::*;
5021
5022    #[test]
5023    fn verify_stdlib_signatures() {
5024        let mut signatures = Vec::new();
5025        for (name, f) in STDLIB.functions() {
5026            match f {
5027                Function::Monomorphic(f) => {
5028                    let params = TypeParameters::new(&f.signature.type_parameters);
5029                    signatures.push(format!("{name}{sig}", sig = f.signature.display(&params)));
5030                }
5031                Function::Polymorphic(f) => {
5032                    for signature in &f.signatures {
5033                        let params = TypeParameters::new(&signature.type_parameters);
5034                        signatures.push(format!("{name}{sig}", sig = signature.display(&params)));
5035                    }
5036                }
5037            }
5038        }
5039
5040        assert_eq!(
5041            signatures,
5042            [
5043                "floor(value: Float) -> Int",
5044                "ceil(value: Float) -> Int",
5045                "round(value: Float) -> Int",
5046                "min(a: Int, b: Int) -> Int",
5047                "min(a: Int, b: Float) -> Float",
5048                "min(a: Float, b: Int) -> Float",
5049                "min(a: Float, b: Float) -> Float",
5050                "max(a: Int, b: Int) -> Int",
5051                "max(a: Int, b: Float) -> Float",
5052                "max(a: Float, b: Int) -> Float",
5053                "max(a: Float, b: Float) -> Float",
5054                "find(input: String, pattern: String) -> String?",
5055                "matches(input: String, pattern: String) -> Boolean",
5056                "sub(input: String, pattern: String, replace: String) -> String",
5057                "split(input: String, delimiter: String) -> Array[String]",
5058                "basename(path: File, <suffix: String>) -> String",
5059                "basename(path: String, <suffix: String>) -> String",
5060                "basename(path: Directory, <suffix: String>) -> String",
5061                "join_paths(base: File, relative: String) -> File",
5062                "join_paths(base: File, relative: Array[String]+) -> File",
5063                "join_paths(paths: Array[String]+) -> File",
5064                "glob(pattern: String) -> Array[File]",
5065                "size(value: None, <unit: String>) -> Float",
5066                "size(value: File?, <unit: String>) -> Float",
5067                "size(value: String?, <unit: String>) -> Float",
5068                "size(value: Directory?, <unit: String>) -> Float",
5069                "size(value: X, <unit: String>) -> Float where `X`: any compound type that \
5070                 recursively contains a `File` or `Directory`",
5071                "stdout() -> File",
5072                "stderr() -> File",
5073                "read_string(file: File) -> String",
5074                "read_int(file: File) -> Int",
5075                "read_float(file: File) -> Float",
5076                "read_boolean(file: File) -> Boolean",
5077                "read_lines(file: File) -> Array[String]",
5078                "write_lines(array: Array[String]) -> File",
5079                "read_tsv(file: File) -> Array[Array[String]]",
5080                "read_tsv(file: File, header: Boolean) -> Array[Object]",
5081                "read_tsv(file: File, header: Boolean, columns: Array[String]) -> Array[Object]",
5082                "write_tsv(data: Array[Array[String]]) -> File",
5083                "write_tsv(data: Array[Array[String]], header: Boolean, columns: Array[String]) \
5084                 -> File",
5085                "write_tsv(data: Array[S], <header: Boolean>, <columns: Array[String]>) -> File \
5086                 where `S`: any structure containing only primitive types",
5087                "read_map(file: File) -> Map[String, String]",
5088                "write_map(map: Map[String, String]) -> File",
5089                "read_json(file: File) -> Union",
5090                "write_json(value: X) -> File where `X`: any JSON-serializable type",
5091                "read_object(file: File) -> Object",
5092                "read_objects(file: File) -> Array[Object]",
5093                "write_object(object: Object) -> File",
5094                "write_object(object: S) -> File where `S`: any structure containing only \
5095                 primitive types",
5096                "write_objects(objects: Array[Object]) -> File",
5097                "write_objects(objects: Array[S]) -> File where `S`: any structure containing \
5098                 only primitive types",
5099                "prefix(prefix: String, array: Array[P]) -> Array[String] where `P`: any \
5100                 primitive type",
5101                "suffix(suffix: String, array: Array[P]) -> Array[String] where `P`: any \
5102                 primitive type",
5103                "quote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5104                "squote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5105                "sep(separator: String, array: Array[P]) -> String where `P`: any primitive type",
5106                "range(n: Int) -> Array[Int]",
5107                "transpose(array: Array[Array[X]]) -> Array[Array[X]]",
5108                "cross(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5109                "zip(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5110                "unzip(array: Array[Pair[X, Y]]) -> Pair[Array[X], Array[Y]]",
5111                "contains(array: Array[P], value: P) -> Boolean where `P`: any primitive type",
5112                "chunk(array: Array[X], size: Int) -> Array[Array[X]]",
5113                "flatten(array: Array[Array[X]]) -> Array[X]",
5114                "select_first(array: Array[X], <default: X>) -> X",
5115                "select_all(array: Array[X]) -> Array[X]",
5116                "as_pairs(map: Map[K, V]) -> Array[Pair[K, V]] where `K`: any primitive type",
5117                "as_map(pairs: Array[Pair[K, V]]) -> Map[K, V] where `K`: any primitive type",
5118                "keys(map: Map[K, V]) -> Array[K] where `K`: any primitive type",
5119                "keys(struct: S) -> Array[String] where `S`: any structure",
5120                "keys(object: Object) -> Array[String]",
5121                "contains_key(map: Map[K, V], key: K) -> Boolean where `K`: any primitive type",
5122                "contains_key(object: Object, key: String) -> Boolean",
5123                "contains_key(map: Map[String, V], keys: Array[String]) -> Boolean",
5124                "contains_key(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
5125                "contains_key(object: Object, keys: Array[String]) -> Boolean",
5126                "values(map: Map[K, V]) -> Array[V] where `K`: any primitive type",
5127                "collect_by_key(pairs: Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any \
5128                 primitive type",
5129                "defined(value: X) -> Boolean",
5130                "length(array: Array[X]) -> Int",
5131                "length(map: Map[K, V]) -> Int",
5132                "length(object: Object) -> Int",
5133                "length(string: String) -> Int",
5134            ]
5135        );
5136    }
5137
5138    #[test]
5139    fn it_binds_a_simple_function() {
5140        let f = STDLIB.function("floor").expect("should have function");
5141        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5142
5143        let e = f
5144            .bind(SupportedVersion::V1(V1::Zero), &[])
5145            .expect_err("bind should fail");
5146        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5147
5148        let e = f
5149            .bind(
5150                SupportedVersion::V1(V1::One),
5151                &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5152            )
5153            .expect_err("bind should fail");
5154        assert_eq!(e, FunctionBindError::TooManyArguments(1));
5155
5156        // Check for a string argument (should be a type mismatch)
5157        let e = f
5158            .bind(
5159                SupportedVersion::V1(V1::Two),
5160                &[PrimitiveType::String.into()],
5161            )
5162            .expect_err("bind should fail");
5163        assert_eq!(
5164            e,
5165            FunctionBindError::ArgumentTypeMismatch {
5166                index: 0,
5167                expected: "`Float`".into()
5168            }
5169        );
5170
5171        // Check for Union (i.e. indeterminate)
5172        let binding = f
5173            .bind(SupportedVersion::V1(V1::Zero), &[Type::Union])
5174            .expect("bind should succeed");
5175        assert_eq!(binding.index(), 0);
5176        assert_eq!(binding.return_type().to_string(), "Int");
5177
5178        // Check for a float argument
5179        let binding = f
5180            .bind(
5181                SupportedVersion::V1(V1::One),
5182                &[PrimitiveType::Float.into()],
5183            )
5184            .expect("bind should succeed");
5185        assert_eq!(binding.index(), 0);
5186        assert_eq!(binding.return_type().to_string(), "Int");
5187
5188        // Check for an integer argument (should coerce)
5189        let binding = f
5190            .bind(
5191                SupportedVersion::V1(V1::Two),
5192                &[PrimitiveType::Integer.into()],
5193            )
5194            .expect("bind should succeed");
5195        assert_eq!(binding.index(), 0);
5196        assert_eq!(binding.return_type().to_string(), "Int");
5197    }
5198
5199    #[test]
5200    fn it_binds_a_generic_function() {
5201        let f = STDLIB.function("values").expect("should have function");
5202        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Two));
5203
5204        let e = f
5205            .bind(SupportedVersion::V1(V1::Zero), &[])
5206            .expect_err("bind should fail");
5207        assert_eq!(
5208            e,
5209            FunctionBindError::RequiresVersion(SupportedVersion::V1(V1::Two))
5210        );
5211
5212        let e = f
5213            .bind(SupportedVersion::V1(V1::Two), &[])
5214            .expect_err("bind should fail");
5215        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5216
5217        let e = f
5218            .bind(
5219                SupportedVersion::V1(V1::Two),
5220                &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5221            )
5222            .expect_err("bind should fail");
5223        assert_eq!(e, FunctionBindError::TooManyArguments(1));
5224
5225        // Check for a string argument (should be a type mismatch)
5226        let e = f
5227            .bind(
5228                SupportedVersion::V1(V1::Two),
5229                &[PrimitiveType::String.into()],
5230            )
5231            .expect_err("bind should fail");
5232        assert_eq!(
5233            e,
5234            FunctionBindError::ArgumentTypeMismatch {
5235                index: 0,
5236                expected: "`Map[K, V]` where `K`: any primitive type".into()
5237            }
5238        );
5239
5240        // Check for Union (i.e. indeterminate)
5241        let binding = f
5242            .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5243            .expect("bind should succeed");
5244        assert_eq!(binding.index(), 0);
5245        assert_eq!(binding.return_type().to_string(), "Array[Union]");
5246
5247        // Check for a Map[String, String]
5248        let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
5249        let binding = f
5250            .bind(SupportedVersion::V1(V1::Two), &[ty])
5251            .expect("bind should succeed");
5252        assert_eq!(binding.index(), 0);
5253        assert_eq!(binding.return_type().to_string(), "Array[String]");
5254
5255        // Check for a Map[String, Object]
5256        let ty: Type = MapType::new(PrimitiveType::String, Type::Object).into();
5257        let binding = f
5258            .bind(SupportedVersion::V1(V1::Two), &[ty])
5259            .expect("bind should succeed");
5260        assert_eq!(binding.index(), 0);
5261        assert_eq!(binding.return_type().to_string(), "Array[Object]");
5262
5263        // Check for a map with an optional primitive type
5264        let ty: Type = MapType::new(
5265            Type::from(PrimitiveType::String).optional(),
5266            PrimitiveType::Boolean,
5267        )
5268        .into();
5269        let binding = f
5270            .bind(SupportedVersion::V1(V1::Two), &[ty])
5271            .expect("bind should succeed");
5272        assert_eq!(binding.index(), 0);
5273        assert_eq!(binding.return_type().to_string(), "Array[Boolean]");
5274    }
5275
5276    #[test]
5277    fn it_removes_qualifiers() {
5278        let f = STDLIB.function("select_all").expect("should have function");
5279        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5280
5281        // Check for a Array[String]
5282        let array_string: Type = ArrayType::new(PrimitiveType::String).into();
5283        let binding = f
5284            .bind(SupportedVersion::V1(V1::One), &[array_string])
5285            .expect("bind should succeed");
5286        assert_eq!(binding.index(), 0);
5287        assert_eq!(binding.return_type().to_string(), "Array[String]");
5288
5289        // Check for a Array[String?] -> Array[String]
5290        let array_optional_string: Type =
5291            ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5292        let binding = f
5293            .bind(SupportedVersion::V1(V1::One), &[array_optional_string])
5294            .expect("bind should succeed");
5295        assert_eq!(binding.index(), 0);
5296        assert_eq!(binding.return_type().to_string(), "Array[String]");
5297
5298        // Check for Union (i.e. indeterminate)
5299        let binding = f
5300            .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5301            .expect("bind should succeed");
5302        assert_eq!(binding.index(), 0);
5303        assert_eq!(binding.return_type().to_string(), "Array[Union]");
5304
5305        // Check for a Array[Array[String]?] -> Array[Array[String]]
5306        let array_string = Type::from(ArrayType::new(PrimitiveType::String)).optional();
5307        let array_array_string = ArrayType::new(array_string).into();
5308        let binding = f
5309            .bind(SupportedVersion::V1(V1::Zero), &[array_array_string])
5310            .expect("bind should succeed");
5311        assert_eq!(binding.index(), 0);
5312        assert_eq!(binding.return_type().to_string(), "Array[Array[String]]");
5313    }
5314
5315    #[test]
5316    fn it_binds_concrete_overloads() {
5317        let f = STDLIB.function("max").expect("should have function");
5318        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::One));
5319
5320        let e = f
5321            .bind(SupportedVersion::V1(V1::One), &[])
5322            .expect_err("bind should fail");
5323        assert_eq!(e, FunctionBindError::TooFewArguments(2));
5324
5325        let e = f
5326            .bind(
5327                SupportedVersion::V1(V1::Two),
5328                &[
5329                    PrimitiveType::String.into(),
5330                    PrimitiveType::Boolean.into(),
5331                    PrimitiveType::File.into(),
5332                ],
5333            )
5334            .expect_err("bind should fail");
5335        assert_eq!(e, FunctionBindError::TooManyArguments(2));
5336
5337        // Check for `(Int, Int)`
5338        let binding = f
5339            .bind(
5340                SupportedVersion::V1(V1::One),
5341                &[PrimitiveType::Integer.into(), PrimitiveType::Integer.into()],
5342            )
5343            .expect("binding should succeed");
5344        assert_eq!(binding.index(), 0);
5345        assert_eq!(binding.return_type().to_string(), "Int");
5346
5347        // Check for `(Int, Float)`
5348        let binding = f
5349            .bind(
5350                SupportedVersion::V1(V1::Two),
5351                &[PrimitiveType::Integer.into(), PrimitiveType::Float.into()],
5352            )
5353            .expect("binding should succeed");
5354        assert_eq!(binding.index(), 1);
5355        assert_eq!(binding.return_type().to_string(), "Float");
5356
5357        // Check for `(Float, Int)`
5358        let binding = f
5359            .bind(
5360                SupportedVersion::V1(V1::One),
5361                &[PrimitiveType::Float.into(), PrimitiveType::Integer.into()],
5362            )
5363            .expect("binding should succeed");
5364        assert_eq!(binding.index(), 2);
5365        assert_eq!(binding.return_type().to_string(), "Float");
5366
5367        // Check for `(Float, Float)`
5368        let binding = f
5369            .bind(
5370                SupportedVersion::V1(V1::Two),
5371                &[PrimitiveType::Float.into(), PrimitiveType::Float.into()],
5372            )
5373            .expect("binding should succeed");
5374        assert_eq!(binding.index(), 3);
5375        assert_eq!(binding.return_type().to_string(), "Float");
5376
5377        // Check for `(String, Int)`
5378        let e = f
5379            .bind(
5380                SupportedVersion::V1(V1::One),
5381                &[PrimitiveType::String.into(), PrimitiveType::Integer.into()],
5382            )
5383            .expect_err("binding should fail");
5384        assert_eq!(
5385            e,
5386            FunctionBindError::ArgumentTypeMismatch {
5387                index: 0,
5388                expected: "`Int` or `Float`".into()
5389            }
5390        );
5391
5392        // Check for `(Int, String)`
5393        let e = f
5394            .bind(
5395                SupportedVersion::V1(V1::Two),
5396                &[PrimitiveType::Integer.into(), PrimitiveType::String.into()],
5397            )
5398            .expect_err("binding should fail");
5399        assert_eq!(
5400            e,
5401            FunctionBindError::ArgumentTypeMismatch {
5402                index: 1,
5403                expected: "`Int` or `Float`".into()
5404            }
5405        );
5406
5407        // Check for `(String, Float)`
5408        let e = f
5409            .bind(
5410                SupportedVersion::V1(V1::One),
5411                &[PrimitiveType::String.into(), PrimitiveType::Float.into()],
5412            )
5413            .expect_err("binding should fail");
5414        assert_eq!(
5415            e,
5416            FunctionBindError::ArgumentTypeMismatch {
5417                index: 0,
5418                expected: "`Int` or `Float`".into()
5419            }
5420        );
5421
5422        // Check for `(Float, String)`
5423        let e = f
5424            .bind(
5425                SupportedVersion::V1(V1::Two),
5426                &[PrimitiveType::Float.into(), PrimitiveType::String.into()],
5427            )
5428            .expect_err("binding should fail");
5429        assert_eq!(
5430            e,
5431            FunctionBindError::ArgumentTypeMismatch {
5432                index: 1,
5433                expected: "`Int` or `Float`".into()
5434            }
5435        );
5436    }
5437
5438    #[test]
5439    fn it_binds_generic_overloads() {
5440        let f = STDLIB
5441            .function("select_first")
5442            .expect("should have function");
5443        assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5444
5445        let e = f
5446            .bind(SupportedVersion::V1(V1::Zero), &[])
5447            .expect_err("bind should fail");
5448        assert_eq!(e, FunctionBindError::TooFewArguments(1));
5449
5450        let e = f
5451            .bind(
5452                SupportedVersion::V1(V1::One),
5453                &[
5454                    PrimitiveType::String.into(),
5455                    PrimitiveType::Boolean.into(),
5456                    PrimitiveType::File.into(),
5457                ],
5458            )
5459            .expect_err("bind should fail");
5460        assert_eq!(e, FunctionBindError::TooManyArguments(2));
5461
5462        // Check `Int`
5463        let e = f
5464            .bind(
5465                SupportedVersion::V1(V1::Two),
5466                &[PrimitiveType::Integer.into()],
5467            )
5468            .expect_err("binding should fail");
5469        assert_eq!(
5470            e,
5471            FunctionBindError::ArgumentTypeMismatch {
5472                index: 0,
5473                expected: "`Array[X]`".into()
5474            }
5475        );
5476
5477        // Check `Array[String?]+`
5478        let array: Type = ArrayType::non_empty(Type::from(PrimitiveType::String).optional()).into();
5479        let binding = f
5480            .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5481            .expect("binding should succeed");
5482        assert_eq!(binding.index(), 0);
5483        assert_eq!(binding.return_type().to_string(), "String");
5484
5485        // Check (`Array[String?]+`, `String`)
5486        let binding = f
5487            .bind(
5488                SupportedVersion::V1(V1::One),
5489                &[array.clone(), PrimitiveType::String.into()],
5490            )
5491            .expect("binding should succeed");
5492        assert_eq!(binding.index(), 0);
5493        assert_eq!(binding.return_type().to_string(), "String");
5494
5495        // Check (`Array[String?]+`, `Int`)
5496        let e = f
5497            .bind(
5498                SupportedVersion::V1(V1::Two),
5499                &[array.clone(), PrimitiveType::Integer.into()],
5500            )
5501            .expect_err("binding should fail");
5502        assert_eq!(
5503            e,
5504            FunctionBindError::ArgumentTypeMismatch {
5505                index: 1,
5506                expected: "`String`".into()
5507            }
5508        );
5509
5510        // Check `Array[String?]`
5511        let array: Type = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5512        let binding = f
5513            .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5514            .expect("binding should succeed");
5515        assert_eq!(binding.index(), 0);
5516        assert_eq!(binding.return_type().to_string(), "String");
5517
5518        // Check (`Array[String?]`, `String`)
5519        let binding = f
5520            .bind(
5521                SupportedVersion::V1(V1::One),
5522                &[array.clone(), PrimitiveType::String.into()],
5523            )
5524            .expect("binding should succeed");
5525        assert_eq!(binding.index(), 0);
5526        assert_eq!(binding.return_type().to_string(), "String");
5527
5528        // Check (`Array[String?]`, `Int`)
5529        let e = f
5530            .bind(
5531                SupportedVersion::V1(V1::Two),
5532                &[array, PrimitiveType::Integer.into()],
5533            )
5534            .expect_err("binding should fail");
5535        assert_eq!(
5536            e,
5537            FunctionBindError::ArgumentTypeMismatch {
5538                index: 1,
5539                expected: "`String`".into()
5540            }
5541        );
5542    }
5543}