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