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