Skip to main content

wdl_analysis/
stdlib.rs

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