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