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