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