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(Arc<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.into())
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: Type,
677    /// Whether or not the array type is non-empty.
678    ///
679    /// This is `None` for literal arrays so that the array may coerce to both
680    /// empty and non-empty types.
681    non_empty: bool,
682}
683
684impl ArrayType {
685    /// Constructs a new array type.
686    pub fn new(element_type: impl Into<Type>) -> Self {
687        Self {
688            element_type: element_type.into(),
689            non_empty: false,
690        }
691    }
692
693    /// Constructs a new non-empty array type.
694    pub fn non_empty(element_type: impl Into<Type>) -> Self {
695        Self {
696            element_type: element_type.into(),
697            non_empty: true,
698        }
699    }
700
701    /// Gets the array's element type.
702    pub fn element_type(&self) -> &Type {
703        &self.element_type
704    }
705
706    /// Determines if the array type is non-empty.
707    pub fn is_non_empty(&self) -> bool {
708        self.non_empty
709    }
710}
711
712impl fmt::Display for ArrayType {
713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714        write!(f, "Array[{ty}]", ty = self.element_type)?;
715
716        if self.non_empty {
717            write!(f, "+")?;
718        }
719
720        Ok(())
721    }
722}
723
724impl Coercible for ArrayType {
725    fn is_coercible_to(&self, target: &Self) -> bool {
726        self.element_type.is_coercible_to(&target.element_type)
727    }
728}
729
730/// Represents the type of a `Pair`.
731#[derive(Debug, Clone, PartialEq, Eq)]
732pub struct PairType {
733    /// The type of the left element of the pair.
734    left_type: Type,
735    /// The type of the right element of the pair.
736    right_type: Type,
737}
738
739impl PairType {
740    /// Constructs a new pair type.
741    pub fn new(left_type: impl Into<Type>, right_type: impl Into<Type>) -> Self {
742        Self {
743            left_type: left_type.into(),
744            right_type: right_type.into(),
745        }
746    }
747
748    /// Gets the pairs's left type.
749    pub fn left_type(&self) -> &Type {
750        &self.left_type
751    }
752
753    /// Gets the pairs's right type.
754    pub fn right_type(&self) -> &Type {
755        &self.right_type
756    }
757}
758
759impl fmt::Display for PairType {
760    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761        write!(
762            f,
763            "Pair[{left}, {right}]",
764            left = self.left_type,
765            right = self.right_type
766        )?;
767
768        Ok(())
769    }
770}
771
772impl Coercible for PairType {
773    fn is_coercible_to(&self, target: &Self) -> bool {
774        self.left_type.is_coercible_to(&target.left_type)
775            && self.right_type.is_coercible_to(&target.right_type)
776    }
777}
778
779/// Represents the type of a `Map`.
780#[derive(Debug, Clone, PartialEq, Eq)]
781pub struct MapType {
782    /// The key type of the map.
783    key_type: Type,
784    /// The value type of the map.
785    value_type: Type,
786}
787
788impl MapType {
789    /// Constructs a new map type.
790    pub fn new(key_type: impl Into<Type>, value_type: impl Into<Type>) -> Self {
791        Self {
792            key_type: key_type.into(),
793            value_type: value_type.into(),
794        }
795    }
796
797    /// Gets the maps's key type.
798    pub fn key_type(&self) -> &Type {
799        &self.key_type
800    }
801
802    /// Gets the maps's value type.
803    pub fn value_type(&self) -> &Type {
804        &self.value_type
805    }
806}
807
808impl fmt::Display for MapType {
809    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
810        write!(
811            f,
812            "Map[{key}, {value}]",
813            key = self.key_type,
814            value = self.value_type
815        )?;
816
817        Ok(())
818    }
819}
820
821impl Coercible for MapType {
822    fn is_coercible_to(&self, target: &Self) -> bool {
823        self.key_type.is_coercible_to(&target.key_type)
824            && self.value_type.is_coercible_to(&target.value_type)
825    }
826}
827
828/// Represents the type of a struct.
829#[derive(Debug, PartialEq, Eq)]
830pub struct StructType {
831    /// The name of the struct.
832    name: Arc<String>,
833    /// The members of the struct.
834    members: IndexMap<String, Type>,
835}
836
837impl StructType {
838    /// Constructs a new struct type definition.
839    pub fn new<N, T>(name: impl Into<String>, members: impl IntoIterator<Item = (N, T)>) -> Self
840    where
841        N: Into<String>,
842        T: Into<Type>,
843    {
844        Self {
845            name: Arc::new(name.into()),
846            members: members
847                .into_iter()
848                .map(|(n, ty)| (n.into(), ty.into()))
849                .collect(),
850        }
851    }
852
853    /// Gets the name of the struct.
854    pub fn name(&self) -> &Arc<String> {
855        &self.name
856    }
857
858    /// Gets the members of the struct.
859    pub fn members(&self) -> &IndexMap<String, Type> {
860        &self.members
861    }
862}
863
864impl fmt::Display for StructType {
865    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
866        write!(f, "{name}", name = self.name)
867    }
868}
869
870impl Coercible for StructType {
871    fn is_coercible_to(&self, target: &Self) -> bool {
872        if self.members.len() != target.members.len() {
873            return false;
874        }
875
876        self.members.iter().all(|(k, v)| {
877            target
878                .members
879                .get(k)
880                .map(|target| v.is_coercible_to(target))
881                .unwrap_or(false)
882        })
883    }
884}
885
886/// The kind of call for a call type.
887#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
888pub enum CallKind {
889    /// The call is to a task.
890    Task,
891    /// The call is to a workflow.
892    Workflow,
893}
894
895impl fmt::Display for CallKind {
896    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
897        match self {
898            Self::Task => write!(f, "task"),
899            Self::Workflow => write!(f, "workflow"),
900        }
901    }
902}
903
904/// Represents the type of a call.
905#[derive(Debug, Clone, Eq)]
906pub struct CallType {
907    /// The call kind.
908    kind: CallKind,
909    /// The namespace of the call.
910    namespace: Option<Arc<String>>,
911    /// The name of the task or workflow that was called.
912    name: Arc<String>,
913    /// The set of specified inputs in the call.
914    specified: Arc<HashSet<String>>,
915    /// The input types to the call.
916    inputs: Arc<IndexMap<String, Input>>,
917    /// The output types from the call.
918    outputs: Arc<IndexMap<String, Output>>,
919}
920
921impl CallType {
922    /// Constructs a new call type given the task or workflow name being called.
923    pub fn new(
924        kind: CallKind,
925        name: impl Into<String>,
926        specified: Arc<HashSet<String>>,
927        inputs: Arc<IndexMap<String, Input>>,
928        outputs: Arc<IndexMap<String, Output>>,
929    ) -> Self {
930        Self {
931            kind,
932            namespace: None,
933            name: Arc::new(name.into()),
934            specified,
935            inputs,
936            outputs,
937        }
938    }
939
940    /// Constructs a new call type given namespace and the task or workflow name
941    /// being called.
942    pub fn namespaced(
943        kind: CallKind,
944        namespace: impl Into<String>,
945        name: impl Into<String>,
946        specified: Arc<HashSet<String>>,
947        inputs: Arc<IndexMap<String, Input>>,
948        outputs: Arc<IndexMap<String, Output>>,
949    ) -> Self {
950        Self {
951            kind,
952            namespace: Some(Arc::new(namespace.into())),
953            name: Arc::new(name.into()),
954            specified,
955            inputs,
956            outputs,
957        }
958    }
959
960    /// Gets the kind of the call.
961    pub fn kind(&self) -> CallKind {
962        self.kind
963    }
964
965    /// Gets the namespace of the call target.
966    ///
967    /// Returns `None` if the call is local to the current document.
968    pub fn namespace(&self) -> Option<&str> {
969        self.namespace.as_ref().map(|ns| ns.as_str())
970    }
971
972    /// Gets the name of the call target.
973    pub fn name(&self) -> &str {
974        &self.name
975    }
976
977    /// Gets the set of inputs specified in the call.
978    pub fn specified(&self) -> &HashSet<String> {
979        &self.specified
980    }
981
982    /// Gets the inputs of the called workflow or task.
983    pub fn inputs(&self) -> &IndexMap<String, Input> {
984        &self.inputs
985    }
986
987    /// Gets the outputs of the called workflow or task.
988    pub fn outputs(&self) -> &IndexMap<String, Output> {
989        &self.outputs
990    }
991
992    /// Promotes the call type into a parent scope.
993    pub fn promote(&self, kind: PromotionKind) -> Self {
994        let mut ty = self.clone();
995        for output in Arc::make_mut(&mut ty.outputs).values_mut() {
996            *output = Output::new(output.ty().promote(kind), output.name_span());
997        }
998
999        ty
1000    }
1001}
1002
1003impl Coercible for CallType {
1004    fn is_coercible_to(&self, _: &Self) -> bool {
1005        // Calls are not coercible to other types
1006        false
1007    }
1008}
1009
1010impl fmt::Display for CallType {
1011    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012        if let Some(ns) = &self.namespace {
1013            write!(
1014                f,
1015                "call to {kind} `{ns}.{name}`",
1016                kind = self.kind,
1017                name = self.name,
1018            )
1019        } else {
1020            write!(
1021                f,
1022                "call to {kind} `{name}`",
1023                kind = self.kind,
1024                name = self.name,
1025            )
1026        }
1027    }
1028}
1029
1030impl PartialEq for CallType {
1031    fn eq(&self, other: &Self) -> bool {
1032        // Each call type instance is unique, so just compare pointer
1033        std::ptr::eq(self, other)
1034    }
1035}
1036
1037#[cfg(test)]
1038mod test {
1039    use pretty_assertions::assert_eq;
1040
1041    use super::*;
1042
1043    #[test]
1044    fn primitive_type_display() {
1045        assert_eq!(PrimitiveType::Boolean.to_string(), "Boolean");
1046        assert_eq!(PrimitiveType::Integer.to_string(), "Int");
1047        assert_eq!(PrimitiveType::Float.to_string(), "Float");
1048        assert_eq!(PrimitiveType::String.to_string(), "String");
1049        assert_eq!(PrimitiveType::File.to_string(), "File");
1050        assert_eq!(PrimitiveType::Directory.to_string(), "Directory");
1051        assert_eq!(
1052            Type::from(PrimitiveType::Boolean).optional().to_string(),
1053            "Boolean?"
1054        );
1055        assert_eq!(
1056            Type::from(PrimitiveType::Integer).optional().to_string(),
1057            "Int?"
1058        );
1059        assert_eq!(
1060            Type::from(PrimitiveType::Float).optional().to_string(),
1061            "Float?"
1062        );
1063        assert_eq!(
1064            Type::from(PrimitiveType::String).optional().to_string(),
1065            "String?"
1066        );
1067        assert_eq!(
1068            Type::from(PrimitiveType::File).optional().to_string(),
1069            "File?"
1070        );
1071        assert_eq!(
1072            Type::from(PrimitiveType::Directory).optional().to_string(),
1073            "Directory?"
1074        );
1075    }
1076
1077    #[test]
1078    fn array_type_display() {
1079        assert_eq!(
1080            ArrayType::new(PrimitiveType::String).to_string(),
1081            "Array[String]"
1082        );
1083        assert_eq!(
1084            ArrayType::non_empty(PrimitiveType::String).to_string(),
1085            "Array[String]+"
1086        );
1087
1088        let ty: Type = ArrayType::new(ArrayType::new(PrimitiveType::String)).into();
1089        assert_eq!(ty.to_string(), "Array[Array[String]]");
1090
1091        let ty = Type::from(ArrayType::non_empty(
1092            Type::from(ArrayType::non_empty(
1093                Type::from(PrimitiveType::String).optional(),
1094            ))
1095            .optional(),
1096        ))
1097        .optional();
1098        assert_eq!(ty.to_string(), "Array[Array[String?]+?]+?");
1099    }
1100
1101    #[test]
1102    fn pair_type_display() {
1103        assert_eq!(
1104            PairType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
1105            "Pair[String, Boolean]"
1106        );
1107
1108        let ty: Type = PairType::new(
1109            ArrayType::new(PrimitiveType::String),
1110            ArrayType::new(PrimitiveType::String),
1111        )
1112        .into();
1113        assert_eq!(ty.to_string(), "Pair[Array[String], Array[String]]");
1114
1115        let ty = Type::from(PairType::new(
1116            Type::from(ArrayType::non_empty(
1117                Type::from(PrimitiveType::File).optional(),
1118            ))
1119            .optional(),
1120            Type::from(ArrayType::non_empty(
1121                Type::from(PrimitiveType::File).optional(),
1122            ))
1123            .optional(),
1124        ))
1125        .optional();
1126        assert_eq!(ty.to_string(), "Pair[Array[File?]+?, Array[File?]+?]?");
1127    }
1128
1129    #[test]
1130    fn map_type_display() {
1131        assert_eq!(
1132            MapType::new(PrimitiveType::String, PrimitiveType::Boolean).to_string(),
1133            "Map[String, Boolean]"
1134        );
1135
1136        let ty: Type = MapType::new(
1137            PrimitiveType::Boolean,
1138            ArrayType::new(PrimitiveType::String),
1139        )
1140        .into();
1141        assert_eq!(ty.to_string(), "Map[Boolean, Array[String]]");
1142
1143        let ty: Type = Type::from(MapType::new(
1144            PrimitiveType::String,
1145            Type::from(ArrayType::non_empty(
1146                Type::from(PrimitiveType::File).optional(),
1147            ))
1148            .optional(),
1149        ))
1150        .optional();
1151        assert_eq!(ty.to_string(), "Map[String, Array[File?]+?]?");
1152    }
1153
1154    #[test]
1155    fn struct_type_display() {
1156        assert_eq!(
1157            StructType::new("Foobar", std::iter::empty::<(String, Type)>()).to_string(),
1158            "Foobar"
1159        );
1160    }
1161
1162    #[test]
1163    fn object_type_display() {
1164        assert_eq!(Type::Object.to_string(), "Object");
1165        assert_eq!(Type::OptionalObject.to_string(), "Object?");
1166    }
1167
1168    #[test]
1169    fn union_type_display() {
1170        assert_eq!(Type::Union.to_string(), "Union");
1171    }
1172
1173    #[test]
1174    fn none_type_display() {
1175        assert_eq!(Type::None.to_string(), "None");
1176    }
1177
1178    #[test]
1179    fn primitive_type_coercion() {
1180        // All types should be coercible to self, and required should coerce to optional
1181        // (but not vice versa)
1182        for ty in [
1183            Type::from(PrimitiveType::Boolean),
1184            PrimitiveType::Directory.into(),
1185            PrimitiveType::File.into(),
1186            PrimitiveType::Float.into(),
1187            PrimitiveType::Integer.into(),
1188            PrimitiveType::String.into(),
1189        ] {
1190            assert!(ty.is_coercible_to(&ty));
1191            assert!(ty.optional().is_coercible_to(&ty.optional()));
1192            assert!(ty.is_coercible_to(&ty.optional()));
1193            assert!(!ty.optional().is_coercible_to(&ty));
1194        }
1195
1196        // Check the valid coercions
1197        assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::File));
1198        assert!(PrimitiveType::String.is_coercible_to(&PrimitiveType::Directory));
1199        assert!(PrimitiveType::Integer.is_coercible_to(&PrimitiveType::Float));
1200        assert!(PrimitiveType::File.is_coercible_to(&PrimitiveType::String));
1201        assert!(PrimitiveType::Directory.is_coercible_to(&PrimitiveType::String));
1202        assert!(!PrimitiveType::Float.is_coercible_to(&PrimitiveType::Integer));
1203    }
1204
1205    #[test]
1206    fn object_type_coercion() {
1207        assert!(Type::Object.is_coercible_to(&Type::Object));
1208        assert!(Type::Object.is_coercible_to(&Type::OptionalObject));
1209        assert!(Type::OptionalObject.is_coercible_to(&Type::OptionalObject));
1210        assert!(!Type::OptionalObject.is_coercible_to(&Type::Object));
1211
1212        // Object -> Map[String, X]
1213        let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1214        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1215
1216        // Object -> Map[Int, X] (not a string key)
1217        let ty = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1218        assert!(!Type::Object.is_coercible_to(&ty));
1219
1220        // Object -> Map[String, X]?
1221        let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1222        assert!(Type::Object.is_coercible_to(&ty));
1223
1224        // Object? -> Map[String, X]?
1225        let ty = Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1226        assert!(Type::OptionalObject.is_coercible_to(&ty));
1227
1228        // Object? -> Map[String, X]
1229        let ty = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1230        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1231
1232        // Object -> Struct
1233        let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1234        assert!(Type::Object.is_coercible_to(&ty));
1235
1236        // Object -> Struct?
1237        let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1238        assert!(Type::Object.is_coercible_to(&ty));
1239
1240        // Object? -> Struct?
1241        let ty = Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1242        assert!(Type::OptionalObject.is_coercible_to(&ty));
1243
1244        // Object? -> Struct
1245        let ty = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1246        assert!(!Type::OptionalObject.is_coercible_to(&ty));
1247    }
1248
1249    #[test]
1250    fn array_type_coercion() {
1251        // Array[X] -> Array[Y]
1252        assert!(
1253            ArrayType::new(PrimitiveType::String)
1254                .is_coercible_to(&ArrayType::new(PrimitiveType::String))
1255        );
1256        assert!(
1257            ArrayType::new(PrimitiveType::File)
1258                .is_coercible_to(&ArrayType::new(PrimitiveType::String))
1259        );
1260        assert!(
1261            ArrayType::new(PrimitiveType::String)
1262                .is_coercible_to(&ArrayType::new(PrimitiveType::File))
1263        );
1264
1265        // Array[X] -> Array[Y?]
1266        let type1: Type = ArrayType::new(PrimitiveType::String).into();
1267        let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
1268        assert!(type1.is_coercible_to(&type2));
1269        assert!(!type2.is_coercible_to(&type1));
1270
1271        // Array[Array[X]] -> Array[Array[Y]]
1272        let type1: Type = ArrayType::new(type1).into();
1273        let type2 = ArrayType::new(type2).into();
1274        assert!(type1.is_coercible_to(&type2));
1275        assert!(!type2.is_coercible_to(&type1));
1276
1277        // Array[X]+ -> Array[Y]
1278        let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
1279        let type2 = ArrayType::new(Type::from(PrimitiveType::File).optional()).into();
1280        assert!(type1.is_coercible_to(&type2));
1281        assert!(!type2.is_coercible_to(&type1));
1282
1283        // Array[X]+ -> Array[X?]
1284        let type1: Type = ArrayType::non_empty(PrimitiveType::String).into();
1285        let type2 = ArrayType::new(Type::from(PrimitiveType::String).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::new(PrimitiveType::String).into();
1291        let type2 = ArrayType::new(PrimitiveType::String).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::from(ArrayType::new(PrimitiveType::String)).optional();
1297        let type2 = Type::from(ArrayType::new(PrimitiveType::String)).optional();
1298        assert!(type1.is_coercible_to(&type2));
1299        assert!(type2.is_coercible_to(&type1));
1300
1301        // Array[X] -> Array[X]?
1302        let type1: Type = ArrayType::new(PrimitiveType::String).into();
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
1308    #[test]
1309    fn pair_type_coercion() {
1310        // Pair[W, X] -> Pair[Y, Z]
1311        assert!(
1312            PairType::new(PrimitiveType::String, PrimitiveType::String)
1313                .is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
1314        );
1315        assert!(
1316            PairType::new(PrimitiveType::String, PrimitiveType::String).is_coercible_to(
1317                &PairType::new(PrimitiveType::File, PrimitiveType::Directory)
1318            )
1319        );
1320        assert!(
1321            PairType::new(PrimitiveType::File, PrimitiveType::Directory)
1322                .is_coercible_to(&PairType::new(PrimitiveType::String, PrimitiveType::String))
1323        );
1324
1325        // Pair[W, X] -> Pair[Y?, Z?]
1326        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1327        let type2 = PairType::new(
1328            Type::from(PrimitiveType::File).optional(),
1329            Type::from(PrimitiveType::Directory).optional(),
1330        )
1331        .into();
1332        assert!(type1.is_coercible_to(&type2));
1333        assert!(!type2.is_coercible_to(&type1));
1334
1335        // Pair[Pair[W, X], Pair[W, X]] -> Pair[Pair[Y, Z], Pair[Y, Z]]
1336        let type1: Type = PairType::new(type1.clone(), type1).into();
1337        let type2 = PairType::new(type2.clone(), type2).into();
1338        assert!(type1.is_coercible_to(&type2));
1339        assert!(!type2.is_coercible_to(&type1));
1340
1341        // Pair[W, X] -> Pair[W, X]
1342        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1343        let type2 = PairType::new(PrimitiveType::String, PrimitiveType::String).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 =
1349            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1350        let type2 =
1351            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1352        assert!(type1.is_coercible_to(&type2));
1353        assert!(type2.is_coercible_to(&type1));
1354
1355        // Pair[W, X] -> Pair[W, X]?
1356        let type1: Type = PairType::new(PrimitiveType::String, PrimitiveType::String).into();
1357        let type2 =
1358            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1359        assert!(type1.is_coercible_to(&type2));
1360        assert!(!type2.is_coercible_to(&type1));
1361    }
1362
1363    #[test]
1364    fn map_type_coercion() {
1365        // Map[W, X] -> Map[Y, Z]
1366        assert!(
1367            MapType::new(PrimitiveType::String, PrimitiveType::String)
1368                .is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
1369        );
1370        assert!(
1371            MapType::new(PrimitiveType::String, PrimitiveType::String)
1372                .is_coercible_to(&MapType::new(PrimitiveType::File, PrimitiveType::Directory))
1373        );
1374        assert!(
1375            MapType::new(PrimitiveType::File, PrimitiveType::Directory)
1376                .is_coercible_to(&MapType::new(PrimitiveType::String, PrimitiveType::String))
1377        );
1378
1379        // Map[W, X] -> Map[Y?, Z?]
1380        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1381        let type2 = MapType::new(
1382            Type::from(PrimitiveType::File).optional(),
1383            Type::from(PrimitiveType::Directory).optional(),
1384        )
1385        .into();
1386        assert!(type1.is_coercible_to(&type2));
1387        assert!(!type2.is_coercible_to(&type1));
1388
1389        // Map[P, Map[W, X]] -> Map[Q, Map[Y, Z]]
1390        let type1: Type = MapType::new(PrimitiveType::String, type1).into();
1391        let type2 = MapType::new(PrimitiveType::Directory, type2).into();
1392        assert!(type1.is_coercible_to(&type2));
1393        assert!(!type2.is_coercible_to(&type1));
1394
1395        // Map[W, X] -> Map[W, X]
1396        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1397        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).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 =
1403            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1404        let type2: Type =
1405            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1406        assert!(type1.is_coercible_to(&type2));
1407        assert!(type2.is_coercible_to(&type1));
1408
1409        // Map[W, X] -> Map[W, X]?
1410        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1411        let type2 =
1412            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::String)).optional();
1413        assert!(type1.is_coercible_to(&type2));
1414        assert!(!type2.is_coercible_to(&type1));
1415
1416        // Map[String, X] -> Struct
1417        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1418        let type2 = StructType::new(
1419            "Foo",
1420            [
1421                ("foo", PrimitiveType::Integer),
1422                ("bar", PrimitiveType::Integer),
1423                ("baz", PrimitiveType::Integer),
1424            ],
1425        )
1426        .into();
1427        assert!(type1.is_coercible_to(&type2));
1428
1429        // Map[String, X] -> Struct (mismatched fields)
1430        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1431        let type2 = StructType::new(
1432            "Foo",
1433            [
1434                ("foo", PrimitiveType::Integer),
1435                ("bar", PrimitiveType::String),
1436                ("baz", PrimitiveType::Integer),
1437            ],
1438        )
1439        .into();
1440        assert!(!type1.is_coercible_to(&type2));
1441
1442        // Map[Int, X] -> Struct
1443        let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
1444        let type2 = StructType::new(
1445            "Foo",
1446            [
1447                ("foo", PrimitiveType::Integer),
1448                ("bar", PrimitiveType::Integer),
1449                ("baz", PrimitiveType::Integer),
1450            ],
1451        )
1452        .into();
1453        assert!(!type1.is_coercible_to(&type2));
1454
1455        // Map[String, X] -> Object
1456        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1457        assert!(type1.is_coercible_to(&Type::Object));
1458
1459        // Map[String, X] -> Object?
1460        let type1: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1461        assert!(type1.is_coercible_to(&Type::OptionalObject));
1462
1463        // Map[String, X]? -> Object?
1464        let type1: Type =
1465            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1466        assert!(type1.is_coercible_to(&Type::OptionalObject));
1467
1468        // Map[String, X]? -> Object
1469        let type1: Type =
1470            Type::from(MapType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1471        assert!(!type1.is_coercible_to(&Type::Object));
1472
1473        // Map[Integer, X] -> Object
1474        let type1: Type = MapType::new(PrimitiveType::Integer, PrimitiveType::Integer).into();
1475        assert!(!type1.is_coercible_to(&Type::Object));
1476    }
1477
1478    #[test]
1479    fn struct_type_coercion() {
1480        // S -> S (identical)
1481        let type1: Type = StructType::new(
1482            "Foo",
1483            [
1484                ("foo", PrimitiveType::String),
1485                ("bar", PrimitiveType::String),
1486                ("baz", PrimitiveType::Integer),
1487            ],
1488        )
1489        .into();
1490        let type2 = StructType::new(
1491            "Foo",
1492            [
1493                ("foo", PrimitiveType::String),
1494                ("bar", PrimitiveType::String),
1495                ("baz", PrimitiveType::Integer),
1496            ],
1497        )
1498        .into();
1499        assert!(type1.is_coercible_to(&type2));
1500        assert!(type2.is_coercible_to(&type1));
1501
1502        // S -> S?
1503        let type1: Type = StructType::new(
1504            "Foo",
1505            [
1506                ("foo", PrimitiveType::String),
1507                ("bar", PrimitiveType::String),
1508                ("baz", PrimitiveType::Integer),
1509            ],
1510        )
1511        .into();
1512        let type2 = Type::from(StructType::new(
1513            "Foo",
1514            [
1515                ("foo", PrimitiveType::String),
1516                ("bar", PrimitiveType::String),
1517                ("baz", PrimitiveType::Integer),
1518            ],
1519        ))
1520        .optional();
1521        assert!(type1.is_coercible_to(&type2));
1522        assert!(!type2.is_coercible_to(&type1));
1523
1524        // S? -> S?
1525        let type1: Type = Type::from(StructType::new(
1526            "Foo",
1527            [
1528                ("foo", PrimitiveType::String),
1529                ("bar", PrimitiveType::String),
1530                ("baz", PrimitiveType::Integer),
1531            ],
1532        ))
1533        .optional();
1534        let type2 = Type::from(StructType::new(
1535            "Foo",
1536            [
1537                ("foo", PrimitiveType::String),
1538                ("bar", PrimitiveType::String),
1539                ("baz", PrimitiveType::Integer),
1540            ],
1541        ))
1542        .optional();
1543        assert!(type1.is_coercible_to(&type2));
1544        assert!(type2.is_coercible_to(&type1));
1545
1546        // S -> S (coercible fields)
1547        let type1: Type = StructType::new(
1548            "Foo",
1549            [
1550                ("foo", PrimitiveType::String),
1551                ("bar", PrimitiveType::String),
1552                ("baz", PrimitiveType::Integer),
1553            ],
1554        )
1555        .into();
1556        let type2 = StructType::new(
1557            "Bar",
1558            [
1559                ("foo", PrimitiveType::File),
1560                ("bar", PrimitiveType::Directory),
1561                ("baz", PrimitiveType::Float),
1562            ],
1563        )
1564        .into();
1565        assert!(type1.is_coercible_to(&type2));
1566        assert!(!type2.is_coercible_to(&type1));
1567
1568        // S -> S (mismatched fields)
1569        let type1: Type = StructType::new(
1570            "Foo",
1571            [
1572                ("foo", PrimitiveType::String),
1573                ("bar", PrimitiveType::String),
1574                ("baz", PrimitiveType::Integer),
1575            ],
1576        )
1577        .into();
1578        let type2 = StructType::new("Bar", [("baz", PrimitiveType::Float)]).into();
1579        assert!(!type1.is_coercible_to(&type2));
1580        assert!(!type2.is_coercible_to(&type1));
1581
1582        // Struct -> Map[String, X]
1583        let type1: Type = StructType::new(
1584            "Foo",
1585            [
1586                ("foo", PrimitiveType::String),
1587                ("bar", PrimitiveType::String),
1588                ("baz", PrimitiveType::String),
1589            ],
1590        )
1591        .into();
1592        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1593        assert!(type1.is_coercible_to(&type2));
1594
1595        // Struct -> Map[String, X] (mismatched types)
1596        let type1: Type = StructType::new(
1597            "Foo",
1598            [
1599                ("foo", PrimitiveType::String),
1600                ("bar", PrimitiveType::Integer),
1601                ("baz", PrimitiveType::String),
1602            ],
1603        )
1604        .into();
1605        let type2 = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1606        assert!(!type1.is_coercible_to(&type2));
1607
1608        // Struct -> Map[Int, X] (not a string key)
1609        let type1: Type = StructType::new(
1610            "Foo",
1611            [
1612                ("foo", PrimitiveType::String),
1613                ("bar", PrimitiveType::String),
1614                ("baz", PrimitiveType::String),
1615            ],
1616        )
1617        .into();
1618        let type2 = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1619        assert!(!type1.is_coercible_to(&type2));
1620
1621        // Struct -> Object
1622        assert!(type1.is_coercible_to(&Type::Object));
1623
1624        // Struct -> Object?
1625        assert!(type1.is_coercible_to(&Type::OptionalObject));
1626
1627        // Struct? -> Object?
1628        let type1: Type =
1629            Type::from(StructType::new("Foo", [("foo", PrimitiveType::String)])).optional();
1630        assert!(type1.is_coercible_to(&Type::OptionalObject));
1631
1632        // Struct? -> Object
1633        assert!(!type1.is_coercible_to(&Type::Object));
1634    }
1635
1636    #[test]
1637    fn union_type_coercion() {
1638        // Union -> anything (ok)
1639        for ty in [
1640            Type::from(PrimitiveType::Boolean),
1641            PrimitiveType::Directory.into(),
1642            PrimitiveType::File.into(),
1643            PrimitiveType::Float.into(),
1644            PrimitiveType::Integer.into(),
1645            PrimitiveType::String.into(),
1646        ] {
1647            assert!(Type::Union.is_coercible_to(&ty));
1648            assert!(Type::Union.is_coercible_to(&ty.optional()));
1649            assert!(ty.is_coercible_to(&Type::Union));
1650        }
1651
1652        for optional in [true, false] {
1653            // Union -> Array[X], Union -> Array[X]?
1654            let ty: Type = ArrayType::new(PrimitiveType::String).into();
1655            let ty = if optional { ty.optional() } else { ty };
1656
1657            let coercible = Type::Union.is_coercible_to(&ty);
1658            assert!(coercible);
1659
1660            // Union -> Pair[X, Y], Union -> Pair[X, Y]?
1661            let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1662            let ty = if optional { ty.optional() } else { ty };
1663            let coercible = Type::Union.is_coercible_to(&ty);
1664            assert!(coercible);
1665
1666            // Union -> Map[X, Y], Union -> Map[X, Y]?
1667            let ty: Type = MapType::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 -> Struct, Union -> Struct?
1673            let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1674            let ty = if optional { ty.optional() } else { ty };
1675            let coercible = Type::Union.is_coercible_to(&ty);
1676            assert!(coercible);
1677        }
1678    }
1679
1680    #[test]
1681    fn none_type_coercion() {
1682        // None -> optional type (ok)
1683        for ty in [
1684            Type::from(PrimitiveType::Boolean),
1685            PrimitiveType::Directory.into(),
1686            PrimitiveType::File.into(),
1687            PrimitiveType::Float.into(),
1688            PrimitiveType::Integer.into(),
1689            PrimitiveType::String.into(),
1690        ] {
1691            assert!(!Type::None.is_coercible_to(&ty));
1692            assert!(Type::None.is_coercible_to(&ty.optional()));
1693            assert!(!ty.is_coercible_to(&Type::None));
1694        }
1695
1696        for optional in [true, false] {
1697            // None -> Array[X], None -> Array[X]?
1698            let ty: Type = ArrayType::new(PrimitiveType::String).into();
1699            let ty = if optional { ty.optional() } else { ty };
1700            let coercible = Type::None.is_coercible_to(&ty);
1701            if optional {
1702                assert!(coercible);
1703            } else {
1704                assert!(!coercible);
1705            }
1706
1707            // None -> Pair[X, Y], None -> Pair[X, Y]?
1708            let ty: Type = PairType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1709            let ty = if optional { ty.optional() } else { ty };
1710            let coercible = Type::None.is_coercible_to(&ty);
1711            if optional {
1712                assert!(coercible);
1713            } else {
1714                assert!(!coercible);
1715            }
1716
1717            // None -> Map[X, Y], None -> Map[X, Y]?
1718            let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::Boolean).into();
1719            let ty = if optional { ty.optional() } else { ty };
1720            let coercible = Type::None.is_coercible_to(&ty);
1721            if optional {
1722                assert!(coercible);
1723            } else {
1724                assert!(!coercible);
1725            }
1726
1727            // None -> Struct, None -> Struct?
1728            let ty: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1729            let ty = if optional { ty.optional() } else { ty };
1730            let coercible = Type::None.is_coercible_to(&ty);
1731            if optional {
1732                assert!(coercible);
1733            } else {
1734                assert!(!coercible);
1735            }
1736        }
1737    }
1738
1739    #[test]
1740    fn primitive_equality() {
1741        for ty in [
1742            Type::from(PrimitiveType::Boolean),
1743            PrimitiveType::Directory.into(),
1744            PrimitiveType::File.into(),
1745            PrimitiveType::Float.into(),
1746            PrimitiveType::Integer.into(),
1747            PrimitiveType::String.into(),
1748        ] {
1749            assert!(ty.eq(&ty));
1750            assert!(!ty.optional().eq(&ty));
1751            assert!(!ty.eq(&ty.optional()));
1752            assert!(ty.optional().eq(&ty.optional()));
1753            assert!(!ty.eq(&Type::Object));
1754            assert!(!ty.eq(&Type::OptionalObject));
1755            assert!(!ty.eq(&Type::Union));
1756            assert!(!ty.eq(&Type::None));
1757        }
1758    }
1759
1760    #[test]
1761    fn array_equality() {
1762        // Array[String] == Array[String]
1763        let a: Type = ArrayType::new(PrimitiveType::String).into();
1764        let b: Type = ArrayType::new(PrimitiveType::String).into();
1765        assert!(a.eq(&b));
1766        assert!(!a.optional().eq(&b));
1767        assert!(!a.eq(&b.optional()));
1768        assert!(a.optional().eq(&b.optional()));
1769
1770        // Array[Array[String]] == Array[Array[String]
1771        let a: Type = ArrayType::new(a).into();
1772        let b: Type = ArrayType::new(b).into();
1773        assert!(a.eq(&b));
1774
1775        // Array[Array[Array[String]]]+ == Array[Array[Array[String]]+
1776        let a: Type = ArrayType::non_empty(a).into();
1777        let b: Type = ArrayType::non_empty(b).into();
1778        assert!(a.eq(&b));
1779
1780        // Array[String] != Array[String]+
1781        let a: Type = ArrayType::new(PrimitiveType::String).into();
1782        let b: Type = ArrayType::non_empty(PrimitiveType::String).into();
1783        assert!(!a.eq(&b));
1784
1785        // Array[String] != Array[Int]
1786        let a: Type = ArrayType::new(PrimitiveType::String).into();
1787        let b: Type = ArrayType::new(PrimitiveType::Integer).into();
1788        assert!(!a.eq(&b));
1789
1790        assert!(!a.eq(&Type::Object));
1791        assert!(!a.eq(&Type::OptionalObject));
1792        assert!(!a.eq(&Type::Union));
1793        assert!(!a.eq(&Type::None));
1794    }
1795
1796    #[test]
1797    fn pair_equality() {
1798        // Pair[String, Int] == Pair[String, Int]
1799        let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1800        let b: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1801        assert!(a.eq(&b));
1802        assert!(!a.optional().eq(&b));
1803        assert!(!a.eq(&b.optional()));
1804        assert!(a.optional().eq(&b.optional()));
1805
1806        // Pair[Pair[String, Int], Pair[String, Int]] == Pair[Pair[String, Int],
1807        // Pair[String, Int]]
1808        let a: Type = PairType::new(a.clone(), a).into();
1809        let b: Type = PairType::new(b.clone(), b).into();
1810        assert!(a.eq(&b));
1811
1812        // Pair[String, Int] != Pair[String, Int]?
1813        let a: Type = PairType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1814        let b: Type =
1815            Type::from(PairType::new(PrimitiveType::String, PrimitiveType::Integer)).optional();
1816        assert!(!a.eq(&b));
1817
1818        assert!(!a.eq(&Type::Object));
1819        assert!(!a.eq(&Type::OptionalObject));
1820        assert!(!a.eq(&Type::Union));
1821        assert!(!a.eq(&Type::None));
1822    }
1823
1824    #[test]
1825    fn map_equality() {
1826        // Map[String, Int] == Map[String, Int]
1827        let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1828        let b = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1829        assert!(a.eq(&b));
1830        assert!(!a.optional().eq(&b));
1831        assert!(!a.eq(&b.optional()));
1832        assert!(a.optional().eq(&b.optional()));
1833
1834        // Map[File, Map[String, Int]] == Map[File, Map[String, Int]]
1835        let a: Type = MapType::new(PrimitiveType::File, a).into();
1836        let b = MapType::new(PrimitiveType::File, b).into();
1837        assert!(a.eq(&b));
1838
1839        // Map[String, Int] != Map[Int, String]
1840        let a: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1841        let b = MapType::new(PrimitiveType::Integer, PrimitiveType::String).into();
1842        assert!(!a.eq(&b));
1843
1844        assert!(!a.eq(&Type::Object));
1845        assert!(!a.eq(&Type::OptionalObject));
1846        assert!(!a.eq(&Type::Union));
1847        assert!(!a.eq(&Type::None));
1848    }
1849
1850    #[test]
1851    fn struct_equality() {
1852        let a: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1853        assert!(a.eq(&a));
1854        assert!(!a.optional().eq(&a));
1855        assert!(!a.eq(&a.optional()));
1856        assert!(a.optional().eq(&a.optional()));
1857
1858        let b: Type = StructType::new("Foo", [("foo", PrimitiveType::String)]).into();
1859        assert!(a.eq(&b));
1860        let b: Type = StructType::new("Bar", [("foo", PrimitiveType::String)]).into();
1861        assert!(!a.eq(&b));
1862    }
1863
1864    #[test]
1865    fn object_equality() {
1866        assert!(Type::Object.eq(&Type::Object));
1867        assert!(!Type::OptionalObject.eq(&Type::Object));
1868        assert!(!Type::Object.eq(&Type::OptionalObject));
1869        assert!(Type::OptionalObject.eq(&Type::OptionalObject));
1870    }
1871
1872    #[test]
1873    fn union_equality() {
1874        assert!(Type::Union.eq(&Type::Union));
1875        assert!(!Type::None.eq(&Type::Union));
1876        assert!(!Type::Union.eq(&Type::None));
1877        assert!(Type::None.eq(&Type::None));
1878    }
1879}