wdl_analysis/
types.rs

1//! Representation of the WDL type system.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::sync::Arc;
6
7use indexmap::IndexMap;
8use wdl_ast::Diagnostic;
9use wdl_ast::Span;
10
11use crate::document::Input;
12use crate::document::Output;
13
14pub mod v1;
15
16/// Used to display a slice of types.
17pub fn display_types(slice: &[Type]) -> impl fmt::Display + use<'_> {
18    /// Used to display a slice of types.
19    struct Display<'a>(&'a [Type]);
20
21    impl fmt::Display for Display<'_> {
22        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23            for (i, ty) in self.0.iter().enumerate() {
24                if i > 0 {
25                    if self.0.len() == 2 {
26                        write!(f, " ")?;
27                    } else {
28                        write!(f, ", ")?;
29                    }
30
31                    if i == self.0.len() - 1 {
32                        write!(f, "or ")?;
33                    }
34                }
35
36                write!(f, "type `{ty}`")?;
37            }
38
39            Ok(())
40        }
41    }
42
43    Display(slice)
44}
45
46/// A trait implemented on type name resolvers.
47pub trait TypeNameResolver {
48    /// Resolves the given type name to a type.
49    fn resolve(&mut self, name: &str, span: Span) -> Result<Type, Diagnostic>;
50}
51
52/// A trait implemented on types that may be optional.
53pub trait Optional {
54    /// Determines if the type is optional.
55    fn is_optional(&self) -> bool;
56
57    /// Makes the type optional if it isn't already optional.
58    fn optional(&self) -> Self;
59
60    /// Makes the type required if it isn't already required.
61    fn require(&self) -> Self;
62}
63
64/// A trait implemented on types that are coercible to other types.
65pub trait Coercible {
66    /// Determines if the type is coercible to the target type.
67    fn is_coercible_to(&self, target: &Self) -> bool;
68}
69
70/// Represents a primitive WDL type.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72pub enum PrimitiveType {
73    /// The type is a `Boolean`.
74    Boolean,
75    /// The type is an `Int`.
76    Integer,
77    /// The type is a `Float`.
78    Float,
79    /// The type is a `String`.
80    String,
81    /// The type is a `File`.
82    File,
83    /// The type is a `Directory`.
84    Directory,
85}
86
87impl Coercible for PrimitiveType {
88    fn is_coercible_to(&self, target: &Self) -> bool {
89        if self == target {
90            return true;
91        }
92
93        match (self, target) {
94            // String -> File
95            (Self::String, Self::File) |
96            // String -> Directory
97            (Self::String, Self::Directory) |
98            // Int -> Float
99            (Self::Integer, Self::Float) |
100            // File -> String
101            (Self::File, Self::String) |
102            // Directory -> String
103            (Self::Directory, Self::String)
104            => true,
105
106            // Not coercible
107            _ => false
108        }
109    }
110}
111
112impl fmt::Display for PrimitiveType {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::Boolean => write!(f, "Boolean")?,
116            Self::Integer => write!(f, "Int")?,
117            Self::Float => write!(f, "Float")?,
118            Self::String => write!(f, "String")?,
119            Self::File => write!(f, "File")?,
120            Self::Directory => write!(f, "Directory")?,
121        }
122
123        Ok(())
124    }
125}
126
127/// Represents a hidden type in WDL.
128///
129/// Hidden types are special types used internally for type checking but
130/// are not directly expressible in WDL source code.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum HiddenType {
133    /// A hidden type for `hints` that is available in task hints sections.
134    Hints,
135    /// A hidden type for `input` that is available in task hints sections.
136    Input,
137    /// A hidden type for `output` that is available in task hints sections.
138    Output,
139    /// A hidden type for `task` that is available in requirements,
140    /// hints, and runtime sections before constraint evaluation.
141    TaskPreEvaluation,
142    /// A hidden type for `task` that is available in command and output
143    /// sections after constraint evaluation.
144    TaskPostEvaluation,
145    /// A hidden type for `task.previous` that contains the previous
146    /// attempt's computed requirements. Available in WDL 1.3+ in both
147    /// pre-evaluation (requirements, hints, runtime) and post-evaluation
148    /// (command, output) contexts.
149    PreviousTaskData,
150}
151
152impl fmt::Display for HiddenType {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            Self::Hints => write!(f, "hints"),
156            Self::Input => write!(f, "input"),
157            Self::Output => write!(f, "output"),
158            Self::TaskPreEvaluation | Self::TaskPostEvaluation => write!(f, "task"),
159            Self::PreviousTaskData => write!(f, "task.previous"),
160        }
161    }
162}
163
164/// Represents a WDL type.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub enum Type {
167    /// The type is a primitive type.
168    ///
169    /// The second field is whether or not the primitive type is optional.
170    Primitive(PrimitiveType, bool),
171    /// The type is a compound type.
172    ///
173    /// The second field is whether or not the compound type is optional.
174    Compound(CompoundType, bool),
175    /// The type is `Object`.
176    Object,
177    /// The type is `Object?`.
178    OptionalObject,
179    /// A special hidden type for a value that may have any one of several
180    /// concrete types.
181    ///
182    /// This variant is also used to convey an "indeterminate" type; an
183    /// indeterminate type may result from a previous type error.
184    Union,
185    /// A special type that behaves like an optional `Union`.
186    None,
187    /// A special hidden type that is not directly expressible in WDL source.
188    ///
189    /// Hidden types are used for type checking special values like `task`,
190    /// `task.previous`, `hints`, `input`, and `output`.
191    Hidden(HiddenType),
192    /// The type is a call output.
193    Call(CallType),
194}
195
196impl Type {
197    /// Casts the type to a primitive type.
198    ///
199    /// Returns `None` if the type is not primitive.
200    pub fn as_primitive(&self) -> Option<PrimitiveType> {
201        match self {
202            Self::Primitive(ty, _) => Some(*ty),
203            _ => None,
204        }
205    }
206
207    /// Casts the type to a compound type.
208    ///
209    /// Returns `None` if the type is not a compound type.
210    pub fn as_compound(&self) -> Option<&CompoundType> {
211        match self {
212            Self::Compound(ty, _) => Some(ty),
213            _ => None,
214        }
215    }
216
217    /// Converts the type to an array type.
218    ///
219    /// Returns `None` if the type is not an array type.
220    pub fn as_array(&self) -> Option<&ArrayType> {
221        match self {
222            Self::Compound(CompoundType::Array(ty), _) => Some(ty),
223            _ => None,
224        }
225    }
226
227    /// Converts the type to a pair type.
228    ///
229    /// Returns `None` if the type is not a pair type.
230    pub fn as_pair(&self) -> Option<&PairType> {
231        match self {
232            Self::Compound(CompoundType::Pair(ty), _) => Some(ty),
233            _ => None,
234        }
235    }
236
237    /// Converts the type to a map type.
238    ///
239    /// Returns `None` if the type is not a map type.
240    pub fn as_map(&self) -> Option<&MapType> {
241        match self {
242            Self::Compound(CompoundType::Map(ty), _) => Some(ty),
243            _ => None,
244        }
245    }
246
247    /// Converts the type to a struct type.
248    ///
249    /// Returns `None` if the type is not a struct type.
250    pub fn as_struct(&self) -> Option<&StructType> {
251        match self {
252            Self::Compound(CompoundType::Struct(ty), _) => Some(ty),
253            _ => None,
254        }
255    }
256
257    /// Converts the type to a call type
258    ///
259    /// Returns `None` if the type if not a call type.
260    pub fn as_call(&self) -> Option<&CallType> {
261        match self {
262            Self::Call(ty) => Some(ty),
263            _ => None,
264        }
265    }
266
267    /// Determines if the type is `Union`.
268    pub fn is_union(&self) -> bool {
269        matches!(self, Type::Union)
270    }
271
272    /// Determines if the type is `None`.
273    pub fn is_none(&self) -> bool {
274        matches!(self, Type::None)
275    }
276
277    /// Promotes a type from a scatter statement into the parent scope.
278    ///
279    /// For most types, this wraps them in an array. For call types, this
280    /// promotes each output type into an array.
281    pub fn promote_scatter(&self) -> Self {
282        // For calls, the outputs of the call are promoted instead of the call itself
283        if let Self::Call(ty) = self {
284            return Self::Call(ty.promote_scatter());
285        }
286
287        Type::Compound(ArrayType::new(self.clone()).into(), false)
288    }
289
290    /// Calculates a common type between this type and the given type.
291    ///
292    /// Returns `None` if the types have no common type.
293    pub fn common_type(&self, other: &Type) -> Option<Type> {
294        // If the other type is union, then the common type would be this type
295        if other.is_union() {
296            return Some(self.clone());
297        }
298
299        // If this type is union, then the common type would be the other type
300        if self.is_union() {
301            return Some(other.clone());
302        }
303
304        // If the other type is `None`, then the common type would be an optional this
305        // type
306        if other.is_none() {
307            return Some(self.optional());
308        }
309
310        // If this type is `None`, then the common type would be an optional other type
311        if self.is_none() {
312            return Some(other.optional());
313        }
314
315        // Check for the other type being coercible to this type
316        if other.is_coercible_to(self) {
317            return Some(self.clone());
318        }
319
320        // Check for this type being coercible to the other type
321        if self.is_coercible_to(other) {
322            return Some(other.clone());
323        }
324
325        // Check for a compound type that might have a common type within it
326        if let (Some(this), Some(other)) = (self.as_compound(), other.as_compound())
327            && let Some(ty) = this.common_type(other)
328        {
329            return Some(Self::Compound(ty, self.is_optional()));
330        }
331
332        // Check for a call type to have a common type with itself
333        if let (Some(this), Some(other)) = (self.as_call(), self.as_call())
334            && this == other
335        {
336            return Some(Self::Call(this.clone()));
337        }
338
339        None
340    }
341}
342
343impl fmt::Display for Type {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        match self {
346            Self::Primitive(ty, optional) => {
347                ty.fmt(f)?;
348                if *optional { write!(f, "?") } else { Ok(()) }
349            }
350            Self::Compound(ty, optional) => {
351                ty.fmt(f)?;
352                if *optional { write!(f, "?") } else { Ok(()) }
353            }
354            Self::Object => {
355                write!(f, "Object")
356            }
357            Self::OptionalObject => {
358                write!(f, "Object?")
359            }
360            Self::Union => write!(f, "Union"),
361            Self::None => write!(f, "None"),
362            Self::Hidden(ty) => ty.fmt(f),
363            Self::Call(ty) => ty.fmt(f),
364        }
365    }
366}
367
368impl Optional for Type {
369    fn is_optional(&self) -> bool {
370        match self {
371            Self::Primitive(_, optional) => *optional,
372            Self::Compound(_, optional) => *optional,
373            Self::OptionalObject | Self::None => true,
374            Self::Object | Self::Union | Self::Hidden(_) | Self::Call(_) => false,
375        }
376    }
377
378    fn optional(&self) -> Self {
379        match self {
380            Self::Primitive(ty, _) => Self::Primitive(*ty, true),
381            Self::Compound(ty, _) => Self::Compound(ty.clone(), true),
382            Self::Object => Self::OptionalObject,
383            Self::Union => Self::None,
384            Self::Call(ty) => Self::Call(ty.optional()),
385            ty => ty.clone(),
386        }
387    }
388
389    fn require(&self) -> Self {
390        match self {
391            Self::Primitive(ty, _) => Self::Primitive(*ty, false),
392            Self::Compound(ty, _) => Self::Compound(ty.clone(), false),
393            Self::OptionalObject => Self::Object,
394            Self::None => Self::Union,
395            ty => ty.clone(),
396        }
397    }
398}
399
400impl Coercible for Type {
401    fn is_coercible_to(&self, target: &Self) -> bool {
402        if self.eq(target) {
403            return true;
404        }
405
406        match (self, target) {
407            (Self::Primitive(src, src_opt), Self::Primitive(target, target_opt)) => {
408                // An optional type cannot coerce into a required type
409                if *src_opt && !*target_opt {
410                    return false;
411                }
412
413                src.is_coercible_to(target)
414            }
415            (Self::Compound(src, src_opt), Self::Compound(target, target_opt)) => {
416                // An optional type cannot coerce into a required type
417                if *src_opt && !*target_opt {
418                    return false;
419                }
420
421                src.is_coercible_to(target)
422            }
423
424            // Object -> Object, Object -> Object?, Object? -> Object?
425            (Self::Object, Self::Object)
426            | (Self::Object, Self::OptionalObject)
427            | (Self::OptionalObject, Self::OptionalObject) => true,
428
429            // Map[X, Y] -> Object, Map[X, Y] -> Object?, Map[X, Y]? -> Object? where: X -> String
430            //
431            // Struct -> Object, Struct -> Object?, Struct? -> Object?
432            (Self::Compound(src, false), Self::Object)
433            | (Self::Compound(src, false), Self::OptionalObject)
434            | (Self::Compound(src, _), Self::OptionalObject) => match src {
435                CompoundType::Map(src) => {
436                    src.key_type.is_coercible_to(&PrimitiveType::String.into())
437                }
438                CompoundType::Struct(_) => true,
439                _ => false,
440            },
441
442            // Object -> Map[X, Y], Object -> Map[X, Y]?, Object? -> Map[X, Y]? where: String -> X
443            // and all object members are coercible to Y
444            //
445            // Object -> Struct, Object -> Struct?, Object? -> Struct? where: object keys match
446            // struct member names and object values are coercible to struct member types
447            (Self::Object, Self::Compound(target, _))
448            | (Self::OptionalObject, Self::Compound(target, true)) => {
449                match target {
450                    CompoundType::Map(target) => {
451                        Type::from(PrimitiveType::String).is_coercible_to(&target.key_type)
452                    }
453                    CompoundType::Struct(_) => {
454                        // Note: checking object keys and values is a runtime constraint
455                        true
456                    }
457                    _ => false,
458                }
459            }
460
461            // Union is always coercible to the target (and vice versa)
462            (Self::Union, _) | (_, Self::Union) => true,
463
464            // None is coercible to an optional type
465            (Self::None, ty) if ty.is_optional() => true,
466
467            // Not coercible
468            _ => false,
469        }
470    }
471}
472
473impl From<PrimitiveType> for Type {
474    fn from(value: PrimitiveType) -> Self {
475        Self::Primitive(value, false)
476    }
477}
478
479impl From<CompoundType> for Type {
480    fn from(value: CompoundType) -> Self {
481        Self::Compound(value, false)
482    }
483}
484
485impl From<ArrayType> for Type {
486    fn from(value: ArrayType) -> Self {
487        Self::Compound(value.into(), false)
488    }
489}
490
491impl From<PairType> for Type {
492    fn from(value: PairType) -> Self {
493        Self::Compound(value.into(), false)
494    }
495}
496
497impl From<MapType> for Type {
498    fn from(value: MapType) -> Self {
499        Self::Compound(value.into(), false)
500    }
501}
502
503impl From<StructType> for Type {
504    fn from(value: StructType) -> Self {
505        Self::Compound(value.into(), false)
506    }
507}
508
509impl From<CallType> for Type {
510    fn from(value: CallType) -> Self {
511        Self::Call(value)
512    }
513}
514
515/// Represents a compound type definition.
516#[derive(Debug, Clone, PartialEq, Eq)]
517pub enum CompoundType {
518    /// The type is an `Array`.
519    Array(ArrayType),
520    /// The type is a `Pair`.
521    Pair(Arc<PairType>),
522    /// The type is a `Map`.
523    Map(Arc<MapType>),
524    /// The type is a struct (e.g. `Foo`).
525    Struct(Arc<StructType>),
526}
527
528impl CompoundType {
529    /// Converts the compound type to an array type.
530    ///
531    /// Returns `None` if the compound type is not an array type.
532    pub fn as_array(&self) -> Option<&ArrayType> {
533        match self {
534            Self::Array(ty) => Some(ty),
535            _ => None,
536        }
537    }
538
539    /// Converts the compound type to a pair type.
540    ///
541    /// Returns `None` if the compound type is not a pair type.
542    pub fn as_pair(&self) -> Option<&PairType> {
543        match self {
544            Self::Pair(ty) => Some(ty),
545            _ => None,
546        }
547    }
548
549    /// Converts the compound type to a map type.
550    ///
551    /// Returns `None` if the compound type is not a map type.
552    pub fn as_map(&self) -> Option<&MapType> {
553        match self {
554            Self::Map(ty) => Some(ty),
555            _ => None,
556        }
557    }
558
559    /// Converts the compound type to a struct type.
560    ///
561    /// Returns `None` if the compound type is not a struct type.
562    pub fn as_struct(&self) -> Option<&StructType> {
563        match self {
564            Self::Struct(ty) => Some(ty),
565            _ => None,
566        }
567    }
568
569    /// Calculates a common type between two compound types.
570    ///
571    /// This method does not attempt coercion; it only attempts to find common
572    /// inner types for the same outer type.
573    fn common_type(&self, other: &Self) -> Option<CompoundType> {
574        // Check to see if the types are both `Array`, `Pair`, or `Map`; if so, attempt
575        // to find a common type for their inner types
576        match (self, other) {
577            (Self::Array(this), Self::Array(other)) => {
578                let element_type = this.element_type.common_type(&other.element_type)?;
579                Some(ArrayType::new(element_type).into())
580            }
581            (Self::Pair(this), Self::Pair(other)) => {
582                let left_type = this.left_type.common_type(&other.left_type)?;
583                let right_type = this.right_type.common_type(&other.right_type)?;
584                Some(PairType::new(left_type, right_type).into())
585            }
586            (Self::Map(this), Self::Map(other)) => {
587                let key_type = this.key_type.common_type(&other.key_type)?;
588                let value_type = this.value_type.common_type(&other.value_type)?;
589                Some(MapType::new(key_type, value_type).into())
590            }
591            _ => None,
592        }
593    }
594}
595
596impl fmt::Display for CompoundType {
597    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598        match self {
599            Self::Array(ty) => ty.fmt(f),
600            Self::Pair(ty) => ty.fmt(f),
601            Self::Map(ty) => ty.fmt(f),
602            Self::Struct(ty) => ty.fmt(f),
603        }
604    }
605}
606
607impl Coercible for CompoundType {
608    fn is_coercible_to(&self, target: &Self) -> bool {
609        match (self, target) {
610            // Array[X] -> Array[Y], Array[X] -> Array[Y]?, Array[X]? -> Array[Y]?, Array[X]+ ->
611            // Array[Y] where: X -> Y
612            (Self::Array(src), Self::Array(target)) => src.is_coercible_to(target),
613
614            // Pair[W, X] -> Pair[Y, Z], Pair[W, X] -> Pair[Y, Z]?, Pair[W, X]? -> Pair[Y, Z]?
615            // where: W -> Y and X -> Z
616            (Self::Pair(src), Self::Pair(target)) => src.is_coercible_to(target),
617
618            // Map[W, X] -> Map[Y, Z], Map[W, X] -> Map[Y, Z]?, Map[W, X]? -> Map[Y, Z]? where: W ->
619            // Y and X -> Z
620            (Self::Map(src), Self::Map(target)) => src.is_coercible_to(target),
621
622            // Struct -> Struct, Struct -> Struct?, Struct? -> Struct? where: all member names match
623            // and all member types coerce
624            (Self::Struct(src), Self::Struct(target)) => src.is_coercible_to(target),
625
626            // Map[X, Y] -> Struct, Map[X, Y] -> Struct?, Map[X, Y]? -> Struct? where: X -> String,
627            // keys match member names, and Y -> member type
628            (Self::Map(src), Self::Struct(target)) => {
629                if !src.key_type.is_coercible_to(&PrimitiveType::String.into()) {
630                    return false;
631                }
632
633                // Ensure the value type is coercible to every struct member type
634                if !target
635                    .members
636                    .values()
637                    .all(|ty| src.value_type.is_coercible_to(ty))
638                {
639                    return false;
640                }
641
642                // Note: checking map keys is a runtime value constraint
643                true
644            }
645
646            // Struct -> Map[X, Y], Struct -> Map[X, Y]?, Struct? -> Map[X, Y]? where: String -> X
647            // and member types -> Y
648            (Self::Struct(src), Self::Map(target)) => {
649                if !Type::from(PrimitiveType::String).is_coercible_to(&target.key_type) {
650                    return false;
651                }
652
653                // Ensure all the struct members are coercible to the value type
654                if !src
655                    .members
656                    .values()
657                    .all(|ty| ty.is_coercible_to(&target.value_type))
658                {
659                    return false;
660                }
661
662                true
663            }
664
665            _ => false,
666        }
667    }
668}
669
670impl From<ArrayType> for CompoundType {
671    fn from(value: ArrayType) -> Self {
672        Self::Array(value)
673    }
674}
675
676impl From<PairType> for CompoundType {
677    fn from(value: PairType) -> Self {
678        Self::Pair(value.into())
679    }
680}
681
682impl From<MapType> for CompoundType {
683    fn from(value: MapType) -> Self {
684        Self::Map(value.into())
685    }
686}
687
688impl From<StructType> for CompoundType {
689    fn from(value: StructType) -> Self {
690        Self::Struct(value.into())
691    }
692}
693
694/// Represents the type of an `Array`.
695#[derive(Debug, Clone, PartialEq, Eq)]
696pub struct ArrayType {
697    /// The element type of the array.
698    element_type: Arc<Type>,
699    /// Whether or not the array type is non-empty.
700    non_empty: bool,
701}
702
703impl ArrayType {
704    /// Constructs a new array type.
705    pub fn new(element_type: impl Into<Type>) -> Self {
706        Self {
707            element_type: Arc::new(element_type.into()),
708            non_empty: false,
709        }
710    }
711
712    /// Constructs a new non-empty array type.
713    pub fn non_empty(element_type: impl Into<Type>) -> Self {
714        Self {
715            element_type: Arc::new(element_type.into()),
716            non_empty: true,
717        }
718    }
719
720    /// Gets the array's element type.
721    pub fn element_type(&self) -> &Type {
722        &self.element_type
723    }
724
725    /// Determines if the array type is non-empty.
726    pub fn is_non_empty(&self) -> bool {
727        self.non_empty
728    }
729
730    /// Consumes the array type and removes the non-empty (`+`) qualifier.
731    pub fn unqualified(self) -> ArrayType {
732        Self {
733            element_type: self.element_type,
734            non_empty: false,
735        }
736    }
737}
738
739impl fmt::Display for ArrayType {
740    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741        write!(f, "Array[{ty}]", ty = self.element_type)?;
742
743        if self.non_empty {
744            write!(f, "+")?;
745        }
746
747        Ok(())
748    }
749}
750
751impl Coercible for ArrayType {
752    fn is_coercible_to(&self, target: &Self) -> bool {
753        // Note: non-empty constraints are enforced at runtime and are not checked here.
754        self.element_type.is_coercible_to(&target.element_type)
755    }
756}
757
758/// Represents the type of a `Pair`.
759#[derive(Debug, Clone, PartialEq, Eq)]
760pub struct PairType {
761    /// The type of the left element of the pair.
762    left_type: Type,
763    /// The type of the right element of the pair.
764    right_type: Type,
765}
766
767impl PairType {
768    /// Constructs a new pair type.
769    pub fn new(left_type: impl Into<Type>, right_type: impl Into<Type>) -> Self {
770        Self {
771            left_type: left_type.into(),
772            right_type: right_type.into(),
773        }
774    }
775
776    /// Gets the pairs's left type.
777    pub fn left_type(&self) -> &Type {
778        &self.left_type
779    }
780
781    /// Gets the pairs's right type.
782    pub fn right_type(&self) -> &Type {
783        &self.right_type
784    }
785}
786
787impl fmt::Display for PairType {
788    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789        write!(
790            f,
791            "Pair[{left}, {right}]",
792            left = self.left_type,
793            right = self.right_type
794        )?;
795
796        Ok(())
797    }
798}
799
800impl Coercible for PairType {
801    fn is_coercible_to(&self, target: &Self) -> bool {
802        self.left_type.is_coercible_to(&target.left_type)
803            && self.right_type.is_coercible_to(&target.right_type)
804    }
805}
806
807/// Represents the type of a `Map`.
808#[derive(Debug, Clone, PartialEq, Eq)]
809pub struct MapType {
810    /// The key type of the map.
811    key_type: Type,
812    /// The value type of the map.
813    value_type: Type,
814}
815
816impl MapType {
817    /// Constructs a new map type.
818    pub fn new(key_type: impl Into<Type>, value_type: impl Into<Type>) -> Self {
819        Self {
820            key_type: key_type.into(),
821            value_type: value_type.into(),
822        }
823    }
824
825    /// Gets the maps's key type.
826    pub fn key_type(&self) -> &Type {
827        &self.key_type
828    }
829
830    /// Gets the maps's value type.
831    pub fn value_type(&self) -> &Type {
832        &self.value_type
833    }
834}
835
836impl fmt::Display for MapType {
837    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
838        write!(
839            f,
840            "Map[{key}, {value}]",
841            key = self.key_type,
842            value = self.value_type
843        )?;
844
845        Ok(())
846    }
847}
848
849impl Coercible for MapType {
850    fn is_coercible_to(&self, target: &Self) -> bool {
851        self.key_type.is_coercible_to(&target.key_type)
852            && self.value_type.is_coercible_to(&target.value_type)
853    }
854}
855
856/// Represents the type of a struct.
857#[derive(Debug, Clone, PartialEq, Eq)]
858pub struct StructType {
859    /// The name of the struct.
860    name: Arc<String>,
861    /// The members of the struct.
862    members: IndexMap<String, Type>,
863}
864
865impl StructType {
866    /// Constructs a new struct type definition.
867    pub fn new<N, T>(name: impl Into<String>, members: impl IntoIterator<Item = (N, T)>) -> Self
868    where
869        N: Into<String>,
870        T: Into<Type>,
871    {
872        Self {
873            name: Arc::new(name.into()),
874            members: members
875                .into_iter()
876                .map(|(n, ty)| (n.into(), ty.into()))
877                .collect(),
878        }
879    }
880
881    /// Gets the name of the struct.
882    pub fn name(&self) -> &Arc<String> {
883        &self.name
884    }
885
886    /// Gets the members of the struct.
887    pub fn members(&self) -> &IndexMap<String, Type> {
888        &self.members
889    }
890}
891
892impl fmt::Display for StructType {
893    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
894        write!(f, "{name}", name = self.name)
895    }
896}
897
898impl Coercible for StructType {
899    fn is_coercible_to(&self, target: &Self) -> bool {
900        if self.members.len() != target.members.len() {
901            return false;
902        }
903
904        self.members.iter().all(|(k, v)| {
905            target
906                .members
907                .get(k)
908                .map(|target| v.is_coercible_to(target))
909                .unwrap_or(false)
910        })
911    }
912}
913
914/// The kind of call for a call type.
915#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
916pub enum CallKind {
917    /// The call is to a task.
918    Task,
919    /// The call is to a workflow.
920    Workflow,
921}
922
923impl fmt::Display for CallKind {
924    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
925        match self {
926            Self::Task => write!(f, "task"),
927            Self::Workflow => write!(f, "workflow"),
928        }
929    }
930}
931
932/// Represents the type of a call.
933#[derive(Debug, Clone, Eq)]
934pub struct CallType {
935    /// The call kind.
936    kind: CallKind,
937    /// The namespace of the call.
938    namespace: Option<Arc<String>>,
939    /// The name of the task or workflow that was called.
940    name: Arc<String>,
941    /// The set of specified inputs in the call.
942    specified: Arc<HashSet<String>>,
943    /// The input types to the call.
944    inputs: Arc<IndexMap<String, Input>>,
945    /// The output types from the call.
946    outputs: Arc<IndexMap<String, Output>>,
947}
948
949impl CallType {
950    /// Constructs a new call type given the task or workflow name being called.
951    pub fn new(
952        kind: CallKind,
953        name: impl Into<String>,
954        specified: Arc<HashSet<String>>,
955        inputs: Arc<IndexMap<String, Input>>,
956        outputs: Arc<IndexMap<String, Output>>,
957    ) -> Self {
958        Self {
959            kind,
960            namespace: None,
961            name: Arc::new(name.into()),
962            specified,
963            inputs,
964            outputs,
965        }
966    }
967
968    /// Constructs a new call type given namespace and the task or workflow name
969    /// being called.
970    pub fn namespaced(
971        kind: CallKind,
972        namespace: impl Into<String>,
973        name: impl Into<String>,
974        specified: Arc<HashSet<String>>,
975        inputs: Arc<IndexMap<String, Input>>,
976        outputs: Arc<IndexMap<String, Output>>,
977    ) -> Self {
978        Self {
979            kind,
980            namespace: Some(Arc::new(namespace.into())),
981            name: Arc::new(name.into()),
982            specified,
983            inputs,
984            outputs,
985        }
986    }
987
988    /// Gets the kind of the call.
989    pub fn kind(&self) -> CallKind {
990        self.kind
991    }
992
993    /// Gets the namespace of the call target.
994    ///
995    /// Returns `None` if the call is local to the current document.
996    pub fn namespace(&self) -> Option<&str> {
997        self.namespace.as_ref().map(|ns| ns.as_str())
998    }
999
1000    /// Gets the name of the call target.
1001    pub fn name(&self) -> &str {
1002        &self.name
1003    }
1004
1005    /// Gets the set of inputs specified in the call.
1006    pub fn specified(&self) -> &HashSet<String> {
1007        &self.specified
1008    }
1009
1010    /// Gets the inputs of the called workflow or task.
1011    pub fn inputs(&self) -> &IndexMap<String, Input> {
1012        &self.inputs
1013    }
1014
1015    /// Gets the outputs of the called workflow or task.
1016    pub fn outputs(&self) -> &IndexMap<String, Output> {
1017        &self.outputs
1018    }
1019
1020    /// Makes all outputs of the call type optional.
1021    pub fn optional(&self) -> Self {
1022        let mut ty = self.clone();
1023        for output in Arc::make_mut(&mut ty.outputs).values_mut() {
1024            *output = Output::new(output.ty().optional(), output.name_span());
1025        }
1026
1027        ty
1028    }
1029
1030    /// Promotes the call type into a scatter statement.
1031    pub fn promote_scatter(&self) -> Self {
1032        let mut ty = self.clone();
1033        for output in Arc::make_mut(&mut ty.outputs).values_mut() {
1034            *output = Output::new(output.ty().promote_scatter(), output.name_span());
1035        }
1036
1037        ty
1038    }
1039}
1040
1041impl Coercible for CallType {
1042    fn is_coercible_to(&self, _: &Self) -> bool {
1043        // Calls are not coercible to other types
1044        false
1045    }
1046}
1047
1048impl fmt::Display for CallType {
1049    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1050        if let Some(ns) = &self.namespace {
1051            write!(
1052                f,
1053                "call to {kind} `{ns}.{name}`",
1054                kind = self.kind,
1055                name = self.name,
1056            )
1057        } else {
1058            write!(
1059                f,
1060                "call to {kind} `{name}`",
1061                kind = self.kind,
1062                name = self.name,
1063            )
1064        }
1065    }
1066}
1067
1068impl PartialEq for CallType {
1069    fn eq(&self, other: &Self) -> bool {
1070        // Each call type instance is unique, so just compare pointer
1071        std::ptr::eq(self, other)
1072    }
1073}
1074
1075#[cfg(test)]
1076mod test {
1077    use pretty_assertions::assert_eq;
1078
1079    use super::*;
1080
1081    #[test]
1082    fn primitive_type_display() {
1083        assert_eq!(PrimitiveType::Boolean.to_string(), "Boolean");
1084        assert_eq!(PrimitiveType::Integer.to_string(), "Int");
1085        assert_eq!(PrimitiveType::Float.to_string(), "Float");
1086        assert_eq!(PrimitiveType::String.to_string(), "String");
1087        assert_eq!(PrimitiveType::File.to_string(), "File");
1088        assert_eq!(PrimitiveType::Directory.to_string(), "Directory");
1089        assert_eq!(
1090            Type::from(PrimitiveType::Boolean).optional().to_string(),
1091            "Boolean?"
1092        );
1093        assert_eq!(
1094            Type::from(PrimitiveType::Integer).optional().to_string(),
1095            "Int?"
1096        );
1097        assert_eq!(
1098            Type::from(PrimitiveType::Float).optional().to_string(),
1099            "Float?"
1100        );
1101        assert_eq!(
1102            Type::from(PrimitiveType::String).optional().to_string(),
1103            "String?"
1104        );
1105        assert_eq!(
1106            Type::from(PrimitiveType::File).optional().to_string(),
1107            "File?"
1108        );
1109        assert_eq!(
1110            Type::from(PrimitiveType::Directory).optional().to_string(),
1111            "Directory?"
1112        );
1113    }
1114
1115    #[test]
1116    fn array_type_display() {
1117        assert_eq!(
1118            ArrayType::new(PrimitiveType::String).to_string(),
1119            "Array[String]"
1120        );
1121        assert_eq!(
1122            ArrayType::non_empty(PrimitiveType::String).to_string(),
1123            "Array[String]+"
1124        );
1125
1126        let ty: Type = ArrayType::new(ArrayType::new(PrimitiveType::String)).into();
1127        assert_eq!(ty.to_string(), "Array[Array[String]]");
1128
1129        let ty = Type::from(ArrayType::non_empty(
1130            Type::from(ArrayType::non_empty(
1131                Type::from(PrimitiveType::String).optional(),
1132            ))
1133            .optional(),
1134        ))
1135        .optional();
1136        assert_eq!(ty.to_string(), "Array[Array[String?]+?]+?");
1137    }
1138
1139    #[test]
1140    fn pair_type_display() {
1141        assert_eq!(
1142            PairType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
1143            "Pair[String, Boolean]"
1144        );
1145
1146        let ty: Type = PairType::new(
1147            ArrayType::new(PrimitiveType::String),
1148            ArrayType::new(PrimitiveType::String),
1149        )
1150        .into();
1151        assert_eq!(ty.to_string(), "Pair[Array[String], Array[String]]");
1152
1153        let ty = Type::from(PairType::new(
1154            Type::from(ArrayType::non_empty(
1155                Type::from(PrimitiveType::File).optional(),
1156            ))
1157            .optional(),
1158            Type::from(ArrayType::non_empty(
1159                Type::from(PrimitiveType::File).optional(),
1160            ))
1161            .optional(),
1162        ))
1163        .optional();
1164        assert_eq!(ty.to_string(), "Pair[Array[File?]+?, Array[File?]+?]?");
1165    }
1166
1167    #[test]
1168    fn map_type_display() {
1169        assert_eq!(
1170            MapType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
1171            "Map[String, Boolean]"
1172        );
1173
1174        let ty: Type = MapType::new(
1175            PrimitiveType::Boolean,
1176            ArrayType::new(PrimitiveType::String),
1177        )
1178        .into();
1179        assert_eq!(ty.to_string(), "Map[Boolean, Array[String]]");
1180
1181        let ty: Type = Type::from(MapType::new(
1182            PrimitiveType::String,
1183            Type::from(ArrayType::non_empty(
1184                Type::from(PrimitiveType::File).optional(),
1185            ))
1186            .optional(),
1187        ))
1188        .optional();
1189        assert_eq!(ty.to_string(), "Map[String, Array[File?]+?]?");
1190    }
1191
1192    #[test]
1193    fn struct_type_display() {
1194        assert_eq!(
1195            StructType::new("Foobar", std::iter::empty::<(String, Type)>()).to_string(),
1196            "Foobar"
1197        );
1198    }
1199
1200    #[test]
1201    fn object_type_display() {
1202        assert_eq!(Type::Object.to_string(), "Object");
1203        assert_eq!(Type::OptionalObject.to_string(), "Object?");
1204    }
1205
1206    #[test]
1207    fn union_type_display() {
1208        assert_eq!(Type::Union.to_string(), "Union");
1209    }
1210
1211    #[test]
1212    fn none_type_display() {
1213        assert_eq!(Type::None.to_string(), "None");
1214    }
1215
1216    #[test]
1217    fn primitive_type_coercion() {
1218        // All types should be coercible to self, and required should coerce to optional
1219        // (but not vice versa)
1220        for ty in [
1221            Type::from(PrimitiveType::Boolean),
1222            PrimitiveType::Directory.into(),
1223            PrimitiveType::File.into(),
1224            PrimitiveType::Float.into(),
1225            PrimitiveType::Integer.into(),
1226            PrimitiveType::String.into(),
1227        ] {
1228            assert!(ty.is_coercible_to(&ty));
1229            assert!(ty.optional().is_coercible_to(&ty.optional()));
1230            assert!(ty.is_coercible_to(&ty.optional()));
1231            assert!(!ty.optional().is_coercible_to(&ty));
1232        }
1233
1234        // Check the valid coercions
1235        assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::File));
1236        assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::Directory));
1237        assert!(PrimitiveType::Integer.is_coercible_to(&PrimitiveType::Float));
1238        assert!(PrimitiveType::File.is_coercible_to(&PrimitiveType::String));
1239        assert!(PrimitiveType::Directory.is_coercible_to(&PrimitiveType::String));
1240        assert!(!PrimitiveType::Float.is_coercible_to(&PrimitiveType::Integer));
1241    }
1242
1243    #[test]
1244    fn object_type_coercion() {
1245        assert!(Type::Object.is_coercible_to(&Type::Object));
1246        assert!(Type::Object.is_coercible_to(&Type::OptionalObject));
1247        assert!(Type::OptionalObject.is_coercible_to(&Type::OptionalObject));
1248        assert!(!Type::OptionalObject.is_coercible_to(&Type::Object));
1249
1250        // Object? -> Map[String, X]
1251        let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1252        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1253
1254        // Object? -> Map[File, X]
1255        let ty = MapType::new(PrimitiveType::File, PrimitiveType::String).into();
1256        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1257
1258        // Object -> Map[Int, X] (key not coercible from string)
1259        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1260        assert!(!Type::Object.is_coercible_to(&ty));
1261
1262        // Object -> Map[String, X]?
1263        let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1264        assert!(Type::Object.is_coercible_to(&ty));
1265
1266        // Object -> Map[File, X]?
1267        let ty = Type::from(MapType::new(PrimitiveType::File, PrimitiveType::String)).optional();
1268        assert!(Type::Object.is_coercible_to(&ty));
1269
1270        // Object? -> Map[String, X]?
1271        let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1272        assert!(Type::OptionalObject.is_coercible_to(&ty));
1273
1274        // Object? -> Map[String, X]
1275        let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1276        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1277
1278        // Object -> Struct
1279        let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1280        assert!(Type::Object.is_coercible_to(&ty));
1281
1282        // Object -> Struct?
1283        let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1284        assert!(Type::Object.is_coercible_to(&ty));
1285
1286        // Object? -> Struct?
1287        let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1288        assert!(Type::OptionalObject.is_coercible_to(&ty));
1289
1290        // Object? -> Struct
1291        let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1292        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1293    }
1294
1295    #[test]
1296    fn array_type_coercion() {
1297        // Array[X] -> Array[Y]
1298        assert!(
1299            ArrayType::new(PrimitiveType::String)
1300                .is_coercible_to(&ArrayType::new(PrimitiveType::String))
1301        );
1302        assert!(
1303            ArrayType::new(PrimitiveType::File)
1304                .is_coercible_to(&ArrayType::new(PrimitiveType::String))
1305        );
1306        assert!(
1307            ArrayType::new(PrimitiveType::String)
1308                .is_coercible_to(&ArrayType::new(PrimitiveType::File))
1309        );
1310
1311        // Array[X] -> Array[Y?]
1312        let type1: Type = ArrayType::new(PrimitiveType::String).into();
1313        let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
1314        assert!(type1.is_coercible_to(&type2));
1315        assert!(!type2.is_coercible_to(&type1));
1316
1317        // Array[Array[X]] -> Array[Array[Y]]
1318        let type1: Type = ArrayType::new(type1).into();
1319        let type2 = ArrayType::new(type2).into();
1320        assert!(type1.is_coercible_to(&type2));
1321        assert!(!type2.is_coercible_to(&type1));
1322
1323        // Array[X]+ -> Array[Y]
1324        let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
1325        let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
1326        assert!(type1.is_coercible_to(&type2));
1327        assert!(!type2.is_coercible_to(&type1));
1328
1329        // Array[X]+ -> Array[X?]
1330        let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
1331        let type2 = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
1332        assert!(type1.is_coercible_to(&type2));
1333        assert!(!type2.is_coercible_to(&type1));
1334
1335        // Array[X] -> Array[X]
1336        let type1: Type = ArrayType::new(PrimitiveType::String).into();
1337        let type2 = ArrayType::new(PrimitiveType::String).into();
1338        assert!(type1.is_coercible_to(&type2));
1339        assert!(type2.is_coercible_to(&type1));
1340
1341        // Array[X]? -> Array[X]?
1342        let type1 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
1343        let type2 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
1344        assert!(type1.is_coercible_to(&type2));
1345        assert!(type2.is_coercible_to(&type1));
1346
1347        // Array[X] -> Array[X]?
1348        let type1: Type = ArrayType::new(PrimitiveType::String).into();
1349        let type2 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
1350        assert!(type1.is_coercible_to(&type2));
1351        assert!(!type2.is_coercible_to(&type1));
1352    }
1353
1354    #[test]
1355    fn pair_type_coercion() {
1356        // Pair[W, X] -> Pair[Y, Z]
1357        assert!(
1358            PairType::new(PrimitiveType::String, PrimitiveType::String)
1359                .is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
1360        );
1361        assert!(
1362            PairType::new(PrimitiveType::String, PrimitiveType::String).is_coercible_to(
1363                &PairType::new(PrimitiveType::File, PrimitiveType::Directory)
1364            )
1365        );
1366        assert!(
1367            PairType::new(PrimitiveType::File, PrimitiveType::Directory)
1368                .is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
1369        );
1370
1371        // Pair[W, X] -> Pair[Y?, Z?]
1372        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1373        let type2 = PairType::new(
1374            Type::from(PrimitiveType::File).optional(),
1375            Type::from(PrimitiveType::Directory).optional(),
1376        )
1377        .into();
1378        assert!(type1.is_coercible_to(&type2));
1379        assert!(!type2.is_coercible_to(&type1));
1380
1381        // Pair[Pair[W, X], Pair[W, X]] -> Pair[Pair[Y, Z], Pair[Y, Z]]
1382        let type1: Type = PairType::new(type1.clone(), type1).into();
1383        let type2 = PairType::new(type2.clone(), type2).into();
1384        assert!(type1.is_coercible_to(&type2));
1385        assert!(!type2.is_coercible_to(&type1));
1386
1387        // Pair[W, X] -> Pair[W, X]
1388        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1389        let type2 = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1390        assert!(type1.is_coercible_to(&type2));
1391        assert!(type2.is_coercible_to(&type1));
1392
1393        // Pair[W, X]? -> Pair[W, X]?
1394        let type1 =
1395            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1396        let type2 =
1397            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1398        assert!(type1.is_coercible_to(&type2));
1399        assert!(type2.is_coercible_to(&type1));
1400
1401        // Pair[W, X] -> Pair[W, X]?
1402        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1403        let type2 =
1404            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1405        assert!(type1.is_coercible_to(&type2));
1406        assert!(!type2.is_coercible_to(&type1));
1407    }
1408
1409    #[test]
1410    fn map_type_coercion() {
1411        // Map[W, X] -> Map[Y, Z]
1412        assert!(
1413            MapType::new(PrimitiveType::String, PrimitiveType::String)
1414                .is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
1415        );
1416        assert!(
1417            MapType::new(PrimitiveType::String, PrimitiveType::String)
1418                .is_coercible_to(&MapType::new(PrimitiveType::File, PrimitiveType::Directory))
1419        );
1420        assert!(
1421            MapType::new(PrimitiveType::File, PrimitiveType::Directory)
1422                .is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
1423        );
1424
1425        // Map[W, X] -> Map[Y?, Z?]
1426        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1427        let type2 = MapType::new(
1428            Type::from(PrimitiveType::File).optional(),
1429            Type::from(PrimitiveType::Directory).optional(),
1430        )
1431        .into();
1432        assert!(type1.is_coercible_to(&type2));
1433        assert!(!type2.is_coercible_to(&type1));
1434
1435        // Map[P, Map[W, X]] -> Map[Q, Map[Y, Z]]
1436        let type1: Type = MapType::new(PrimitiveType::String, type1).into();
1437        let type2 = MapType::new(PrimitiveType::Directory, type2).into();
1438        assert!(type1.is_coercible_to(&type2));
1439        assert!(!type2.is_coercible_to(&type1));
1440
1441        // Map[W, X] -> Map[W, X]
1442        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1443        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1444        assert!(type1.is_coercible_to(&type2));
1445        assert!(type2.is_coercible_to(&type1));
1446
1447        // Map[W, X]? -> Map[W, X]?
1448        let type1: Type =
1449            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1450        let type2: Type =
1451            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1452        assert!(type1.is_coercible_to(&type2));
1453        assert!(type2.is_coercible_to(&type1));
1454
1455        // Map[W, X] -> Map[W, X]?
1456        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1457        let type2 =
1458            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1459        assert!(type1.is_coercible_to(&type2));
1460        assert!(!type2.is_coercible_to(&type1));
1461
1462        // Map[String, Int] -> Struct
1463        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1464        let type2 = StructType::new(
1465            "Foo",
1466            [
1467                ("foo", PrimitiveType::Integer),
1468                ("bar", PrimitiveType::Integer),
1469                ("baz", PrimitiveType::Integer),
1470            ],
1471        )
1472        .into();
1473        assert!(type1.is_coercible_to(&type2));
1474
1475        // Map[File, Int] -> Struct
1476        let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
1477        let type2 = StructType::new(
1478            "Foo",
1479            [
1480                ("foo", PrimitiveType::Integer),
1481                ("bar", PrimitiveType::Integer),
1482                ("baz", PrimitiveType::Integer),
1483            ],
1484        )
1485        .into();
1486        assert!(type1.is_coercible_to(&type2));
1487
1488        // Map[String, Int] -> Struct (mismatched fields)
1489        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1490        let type2 = StructType::new(
1491            "Foo",
1492            [
1493                ("foo", PrimitiveType::Integer),
1494                ("bar", PrimitiveType::String),
1495                ("baz", PrimitiveType::Integer),
1496            ],
1497        )
1498        .into();
1499        assert!(!type1.is_coercible_to(&type2));
1500
1501        // Map[Int, Int] -> Struct
1502        let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
1503        let type2 = StructType::new(
1504            "Foo",
1505            [
1506                ("foo", PrimitiveType::Integer),
1507                ("bar", PrimitiveType::Integer),
1508                ("baz", PrimitiveType::Integer),
1509            ],
1510        )
1511        .into();
1512        assert!(!type1.is_coercible_to(&type2));
1513
1514        // Map[String, Int] -> Object
1515        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1516        assert!(type1.is_coercible_to(&Type::Object));
1517
1518        // Map[String, Int] -> Object?
1519        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1520        assert!(type1.is_coercible_to(&Type::OptionalObject));
1521
1522        // Map[String, Int]? -> Object?
1523        let type1: Type =
1524            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1525        assert!(type1.is_coercible_to(&Type::OptionalObject));
1526
1527        // Map[File, Int] -> Object
1528        let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
1529        assert!(type1.is_coercible_to(&Type::Object));
1530
1531        // Map[File, Int] -> Object?
1532        let type1: Type = MapType::new(PrimitiveType::File, PrimitiveType::Integer).into();
1533        assert!(type1.is_coercible_to(&Type::OptionalObject));
1534
1535        // Map[File, Int]? -> Object?
1536        let type1: Type =
1537            Type::from(MapType::new(PrimitiveType::File, PrimitiveType::Integer)).optional();
1538        assert!(type1.is_coercible_to(&Type::OptionalObject));
1539
1540        // Map[String, Int]? -> Object
1541        let type1: Type =
1542            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1543        assert!(!type1.is_coercible_to(&Type::Object));
1544
1545        // Map[File, Int]? -> Object
1546        let type1: Type =
1547            Type::from(MapType::new(PrimitiveType::File, PrimitiveType::Integer)).optional();
1548        assert!(!type1.is_coercible_to(&Type::Object));
1549
1550        // Map[Integer, Int] -> Object
1551        let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
1552        assert!(!type1.is_coercible_to(&Type::Object));
1553    }
1554
1555    #[test]
1556    fn struct_type_coercion() {
1557        // S -> S (identical)
1558        let type1: Type = StructType::new(
1559            "Foo",
1560            [
1561                ("foo", PrimitiveType::String),
1562                ("bar", PrimitiveType::String),
1563                ("baz", PrimitiveType::Integer),
1564            ],
1565        )
1566        .into();
1567        let type2 = StructType::new(
1568            "Foo",
1569            [
1570                ("foo", PrimitiveType::String),
1571                ("bar", PrimitiveType::String),
1572                ("baz", PrimitiveType::Integer),
1573            ],
1574        )
1575        .into();
1576        assert!(type1.is_coercible_to(&type2));
1577        assert!(type2.is_coercible_to(&type1));
1578
1579        // S -> S?
1580        let type1: Type = StructType::new(
1581            "Foo",
1582            [
1583                ("foo", PrimitiveType::String),
1584                ("bar", PrimitiveType::String),
1585                ("baz", PrimitiveType::Integer),
1586            ],
1587        )
1588        .into();
1589        let type2 = Type::from(StructType::new(
1590            "Foo",
1591            [
1592                ("foo", PrimitiveType::String),
1593                ("bar", PrimitiveType::String),
1594                ("baz", PrimitiveType::Integer),
1595            ],
1596        ))
1597        .optional();
1598        assert!(type1.is_coercible_to(&type2));
1599        assert!(!type2.is_coercible_to(&type1));
1600
1601        // S? -> S?
1602        let type1: Type = Type::from(StructType::new(
1603            "Foo",
1604            [
1605                ("foo", PrimitiveType::String),
1606                ("bar", PrimitiveType::String),
1607                ("baz", PrimitiveType::Integer),
1608            ],
1609        ))
1610        .optional();
1611        let type2 = Type::from(StructType::new(
1612            "Foo",
1613            [
1614                ("foo", PrimitiveType::String),
1615                ("bar", PrimitiveType::String),
1616                ("baz", PrimitiveType::Integer),
1617            ],
1618        ))
1619        .optional();
1620        assert!(type1.is_coercible_to(&type2));
1621        assert!(type2.is_coercible_to(&type1));
1622
1623        // S -> S (coercible fields)
1624        let type1: Type = StructType::new(
1625            "Foo",
1626            [
1627                ("foo", PrimitiveType::String),
1628                ("bar", PrimitiveType::String),
1629                ("baz", PrimitiveType::Integer),
1630            ],
1631        )
1632        .into();
1633        let type2 = StructType::new(
1634            "Bar",
1635            [
1636                ("foo", PrimitiveType::File),
1637                ("bar", PrimitiveType::Directory),
1638                ("baz", PrimitiveType::Float),
1639            ],
1640        )
1641        .into();
1642        assert!(type1.is_coercible_to(&type2));
1643        assert!(!type2.is_coercible_to(&type1));
1644
1645        // S -> S (mismatched fields)
1646        let type1: Type = StructType::new(
1647            "Foo",
1648            [
1649                ("foo", PrimitiveType::String),
1650                ("bar", PrimitiveType::String),
1651                ("baz", PrimitiveType::Integer),
1652            ],
1653        )
1654        .into();
1655        let type2 = StructType::new("Bar", [("baz", PrimitiveType::Float)]).into();
1656        assert!(!type1.is_coercible_to(&type2));
1657        assert!(!type2.is_coercible_to(&type1));
1658
1659        // Struct -> Map[String, String]
1660        let type1: Type = StructType::new(
1661            "Foo",
1662            [
1663                ("foo", PrimitiveType::String),
1664                ("bar", PrimitiveType::String),
1665                ("baz", PrimitiveType::String),
1666            ],
1667        )
1668        .into();
1669        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1670        assert!(type1.is_coercible_to(&type2));
1671
1672        // Struct -> Map[File, String]
1673        let type1: Type = StructType::new(
1674            "Foo",
1675            [
1676                ("foo", PrimitiveType::String),
1677                ("bar", PrimitiveType::String),
1678                ("baz", PrimitiveType::String),
1679            ],
1680        )
1681        .into();
1682        let type2 = MapType::new(PrimitiveType::File, PrimitiveType::String).into();
1683        assert!(type1.is_coercible_to(&type2));
1684
1685        // Struct -> Map[String, X] (mismatched types)
1686        let type1: Type = StructType::new(
1687            "Foo",
1688            [
1689                ("foo", PrimitiveType::String),
1690                ("bar", PrimitiveType::Integer),
1691                ("baz", PrimitiveType::String),
1692            ],
1693        )
1694        .into();
1695        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1696        assert!(!type1.is_coercible_to(&type2));
1697
1698        // Struct -> Map[Int, String] (key not coercible from String)
1699        let type1: Type = StructType::new(
1700            "Foo",
1701            [
1702                ("foo", PrimitiveType::String),
1703                ("bar", PrimitiveType::String),
1704                ("baz", PrimitiveType::String),
1705            ],
1706        )
1707        .into();
1708        let type2 = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1709        assert!(!type1.is_coercible_to(&type2));
1710
1711        // Struct -> Object
1712        assert!(type1.is_coercible_to(&Type::Object));
1713
1714        // Struct -> Object?
1715        assert!(type1.is_coercible_to(&Type::OptionalObject));
1716
1717        // Struct? -> Object?
1718        let type1: Type =
1719            Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1720        assert!(type1.is_coercible_to(&Type::OptionalObject));
1721
1722        // Struct? -> Object
1723        assert!(!type1.is_coercible_to(&Type::Object));
1724    }
1725
1726    #[test]
1727    fn union_type_coercion() {
1728        // Union -> anything (ok)
1729        for ty in [
1730            Type::from(PrimitiveType::Boolean),
1731            PrimitiveType::Directory.into(),
1732            PrimitiveType::File.into(),
1733            PrimitiveType::Float.into(),
1734            PrimitiveType::Integer.into(),
1735            PrimitiveType::String.into(),
1736        ] {
1737            assert!(Type::Union.is_coercible_to(&ty));
1738            assert!(Type::Union.is_coercible_to(&ty.optional()));
1739            assert!(ty.is_coercible_to(&Type::Union));
1740        }
1741
1742        for optional in [true, false] {
1743            // Union -> Array[X], Union -> Array[X]?
1744            let ty: Type = ArrayType::new(PrimitiveType::String).into();
1745            let ty = if optional { ty.optional() } else { ty };
1746
1747            let coercible = Type::Union.is_coercible_to(&ty);
1748            assert!(coercible);
1749
1750            // Union -> Pair[X, Y], Union -> Pair[X, Y]?
1751            let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1752            let ty = if optional { ty.optional() } else { ty };
1753            let coercible = Type::Union.is_coercible_to(&ty);
1754            assert!(coercible);
1755
1756            // Union -> Map[X, Y], Union -> Map[X, Y]?
1757            let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1758            let ty = if optional { ty.optional() } else { ty };
1759            let coercible = Type::Union.is_coercible_to(&ty);
1760            assert!(coercible);
1761
1762            // Union -> Struct, Union -> Struct?
1763            let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1764            let ty = if optional { ty.optional() } else { ty };
1765            let coercible = Type::Union.is_coercible_to(&ty);
1766            assert!(coercible);
1767        }
1768    }
1769
1770    #[test]
1771    fn none_type_coercion() {
1772        // None -> optional type (ok)
1773        for ty in [
1774            Type::from(PrimitiveType::Boolean),
1775            PrimitiveType::Directory.into(),
1776            PrimitiveType::File.into(),
1777            PrimitiveType::Float.into(),
1778            PrimitiveType::Integer.into(),
1779            PrimitiveType::String.into(),
1780        ] {
1781            assert!(!Type::None.is_coercible_to(&ty));
1782            assert!(Type::None.is_coercible_to(&ty.optional()));
1783            assert!(!ty.is_coercible_to(&Type::None));
1784        }
1785
1786        for optional in [true, false] {
1787            // None -> Array[X], None -> Array[X]?
1788            let ty: Type = ArrayType::new(PrimitiveType::String).into();
1789            let ty = if optional { ty.optional() } else { ty };
1790            let coercible = Type::None.is_coercible_to(&ty);
1791            if optional {
1792                assert!(coercible);
1793            } else {
1794                assert!(!coercible);
1795            }
1796
1797            // None -> Pair[X, Y], None -> Pair[X, Y]?
1798            let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1799            let ty = if optional { ty.optional() } else { ty };
1800            let coercible = Type::None.is_coercible_to(&ty);
1801            if optional {
1802                assert!(coercible);
1803            } else {
1804                assert!(!coercible);
1805            }
1806
1807            // None -> Map[X, Y], None -> Map[X, Y]?
1808            let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1809            let ty = if optional { ty.optional() } else { ty };
1810            let coercible = Type::None.is_coercible_to(&ty);
1811            if optional {
1812                assert!(coercible);
1813            } else {
1814                assert!(!coercible);
1815            }
1816
1817            // None -> Struct, None -> Struct?
1818            let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1819            let ty = if optional { ty.optional() } else { ty };
1820            let coercible = Type::None.is_coercible_to(&ty);
1821            if optional {
1822                assert!(coercible);
1823            } else {
1824                assert!(!coercible);
1825            }
1826        }
1827    }
1828
1829    #[test]
1830    fn primitive_equality() {
1831        for ty in [
1832            Type::from(PrimitiveType::Boolean),
1833            PrimitiveType::Directory.into(),
1834            PrimitiveType::File.into(),
1835            PrimitiveType::Float.into(),
1836            PrimitiveType::Integer.into(),
1837            PrimitiveType::String.into(),
1838        ] {
1839            assert!(ty.eq(&ty));
1840            assert!(!ty.optional().eq(&ty));
1841            assert!(!ty.eq(&ty.optional()));
1842            assert!(ty.optional().eq(&ty.optional()));
1843            assert!(!ty.eq(&Type::Object));
1844            assert!(!ty.eq(&Type::OptionalObject));
1845            assert!(!ty.eq(&Type::Union));
1846            assert!(!ty.eq(&Type::None));
1847        }
1848    }
1849
1850    #[test]
1851    fn array_equality() {
1852        // Array[String] == Array[String]
1853        let a: Type = ArrayType::new(PrimitiveType::String).into();
1854        let b: Type = ArrayType::new(PrimitiveType::String).into();
1855        assert!(a.eq(&b));
1856        assert!(!a.optional().eq(&b));
1857        assert!(!a.eq(&b.optional()));
1858        assert!(a.optional().eq(&b.optional()));
1859
1860        // Array[Array[String]] == Array[Array[String]
1861        let a: Type = ArrayType::new(a).into();
1862        let b: Type = ArrayType::new(b).into();
1863        assert!(a.eq(&b));
1864
1865        // Array[Array[Array[String]]]+ == Array[Array[Array[String]]+
1866        let a: Type = ArrayType::non_empty(a).into();
1867        let b: Type = ArrayType::non_empty(b).into();
1868        assert!(a.eq(&b));
1869
1870        // Array[String] != Array[String]+
1871        let a: Type = ArrayType::new(PrimitiveType::String).into();
1872        let b: Type = ArrayType::non_empty(PrimitiveType::String).into();
1873        assert!(!a.eq(&b));
1874
1875        // Array[String] != Array[Int]
1876        let a: Type = ArrayType::new(PrimitiveType::String).into();
1877        let b: Type = ArrayType::new(PrimitiveType::Integer).into();
1878        assert!(!a.eq(&b));
1879
1880        assert!(!a.eq(&Type::Object));
1881        assert!(!a.eq(&Type::OptionalObject));
1882        assert!(!a.eq(&Type::Union));
1883        assert!(!a.eq(&Type::None));
1884    }
1885
1886    #[test]
1887    fn pair_equality() {
1888        // Pair[String, Int] == Pair[String, Int]
1889        let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1890        let b: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1891        assert!(a.eq(&b));
1892        assert!(!a.optional().eq(&b));
1893        assert!(!a.eq(&b.optional()));
1894        assert!(a.optional().eq(&b.optional()));
1895
1896        // Pair[Pair[String, Int], Pair[String, Int]] == Pair[Pair[String, Int],
1897        // Pair[String, Int]]
1898        let a: Type = PairType::new(a.clone(), a).into();
1899        let b: Type = PairType::new(b.clone(), b).into();
1900        assert!(a.eq(&b));
1901
1902        // Pair[String, Int] != Pair[String, Int]?
1903        let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1904        let b: Type =
1905            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1906        assert!(!a.eq(&b));
1907
1908        assert!(!a.eq(&Type::Object));
1909        assert!(!a.eq(&Type::OptionalObject));
1910        assert!(!a.eq(&Type::Union));
1911        assert!(!a.eq(&Type::None));
1912    }
1913
1914    #[test]
1915    fn map_equality() {
1916        // Map[String, Int] == Map[String, Int]
1917        let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1918        let b = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1919        assert!(a.eq(&b));
1920        assert!(!a.optional().eq(&b));
1921        assert!(!a.eq(&b.optional()));
1922        assert!(a.optional().eq(&b.optional()));
1923
1924        // Map[File, Map[String, Int]] == Map[File, Map[String, Int]]
1925        let a: Type = MapType::new(PrimitiveType::File, a).into();
1926        let b = MapType::new(PrimitiveType::File, b).into();
1927        assert!(a.eq(&b));
1928
1929        // Map[String, Int] != Map[Int, String]
1930        let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1931        let b = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1932        assert!(!a.eq(&b));
1933
1934        assert!(!a.eq(&Type::Object));
1935        assert!(!a.eq(&Type::OptionalObject));
1936        assert!(!a.eq(&Type::Union));
1937        assert!(!a.eq(&Type::None));
1938    }
1939
1940    #[test]
1941    fn struct_equality() {
1942        let a: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1943        assert!(a.eq(&a));
1944        assert!(!a.optional().eq(&a));
1945        assert!(!a.eq(&a.optional()));
1946        assert!(a.optional().eq(&a.optional()));
1947
1948        let b: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1949        assert!(a.eq(&b));
1950        let b: Type = StructType::new("Bar", [("foo", PrimitiveType::String)]).into();
1951        assert!(!a.eq(&b));
1952    }
1953
1954    #[test]
1955    fn object_equality() {
1956        assert!(Type::Object.eq(&Type::Object));
1957        assert!(!Type::OptionalObject.eq(&Type::Object));
1958        assert!(!Type::Object.eq(&Type::OptionalObject));
1959        assert!(Type::OptionalObject.eq(&Type::OptionalObject));
1960    }
1961
1962    #[test]
1963    fn union_equality() {
1964        assert!(Type::Union.eq(&Type::Union));
1965        assert!(!Type::None.eq(&Type::Union));
1966        assert!(!Type::Union.eq(&Type::None));
1967        assert!(Type::None.eq(&Type::None));
1968    }
1969}