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