Skip to main content

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