1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3
4use crate::mangle::MangledName;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct EnumMember<V> {
10 pub name: String,
11 pub value: V,
12 pub member_count: usize,
15}
16
17impl<V> EnumMember<V> {
18 pub fn new(name: &str, value: V, member_count: usize) -> Self {
19 EnumMember {
20 name: name.to_string(),
21 value,
22 member_count,
23 }
24 }
25}
26
27#[derive(Clone, Debug, PartialEq)]
29pub enum EnumValue {
30 Number(LiteralF64),
31 String(String),
32}
33
34impl EnumValue {
35 pub fn literal_type(&self) -> Type {
37 match self {
38 EnumValue::Number(value) => Type::NumberLiteral(*value),
39 EnumValue::String(value) => Type::StringLiteral(value.clone()),
40 }
41 }
42}
43
44#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
48pub struct LiteralF64(#[serde(with = "crate::artifact_f64")] pub f64);
49
50impl PartialEq for LiteralF64 {
51 fn eq(&self, other: &Self) -> bool {
52 self.0.to_bits() == other.0.to_bits()
53 }
54}
55
56impl Eq for LiteralF64 {}
57
58impl PartialOrd for LiteralF64 {
59 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
60 Some(self.cmp(other))
61 }
62}
63
64impl Ord for LiteralF64 {
65 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
66 self.0.total_cmp(&other.0)
67 }
68}
69
70impl std::hash::Hash for LiteralF64 {
71 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
72 self.0.to_bits().hash(state);
73 }
74}
75
76#[derive(Clone, Debug, Serialize, Deserialize)]
87pub struct Package(pub String);
88
89impl Package {
90 pub fn prelude() -> Self {
91 Self(crate::mangle::PRELUDE_PACKAGE.to_string())
92 }
93
94 pub fn user() -> Self {
95 Self(crate::mangle::USER_PACKAGE.to_string())
96 }
97
98 pub fn as_str(&self) -> &str {
99 &self.0
100 }
101}
102
103impl PartialEq for Package {
104 fn eq(&self, _: &Self) -> bool {
105 true
106 }
107}
108
109impl Eq for Package {}
110
111impl PartialOrd for Package {
112 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
113 Some(self.cmp(other))
114 }
115}
116
117impl Ord for Package {
118 fn cmp(&self, _: &Self) -> std::cmp::Ordering {
119 std::cmp::Ordering::Equal
120 }
121}
122
123impl std::hash::Hash for Package {
124 fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
125}
126
127#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
131pub struct ObjectField {
132 pub ty: Type,
133 pub optional: bool,
134 pub readonly: bool,
139 #[serde(default)]
142 pub method: bool,
143}
144
145impl ObjectField {
146 pub fn required(ty: Type) -> Self {
147 Self {
148 ty,
149 optional: false,
150 readonly: false,
151 method: false,
152 }
153 }
154 pub fn optional(ty: Type) -> Self {
155 Self {
156 ty,
157 optional: true,
158 readonly: false,
159 method: false,
160 }
161 }
162
163 pub fn read_ty(&self) -> Type {
169 Self::widen_optional(self.optional, self.ty.clone())
170 }
171
172 pub fn widen_optional(optional: bool, ty: Type) -> Type {
175 if optional {
176 Type::union(vec![ty, Type::Undefined])
177 } else {
178 ty
179 }
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
185pub struct IndexSignature {
186 pub value: Box<Type>,
187 pub readonly: bool,
188}
189
190impl IndexSignature {
191 pub fn map_value(&self, transform: impl FnOnce(&Type) -> Type) -> Self {
192 Self {
193 value: Box::new(transform(&self.value)),
194 readonly: self.readonly,
195 }
196 }
197
198 pub fn try_map_value<E>(
200 &self,
201 transform: impl FnOnce(&Type) -> Result<Type, E>,
202 ) -> Result<Self, E> {
203 Ok(Self {
204 value: Box::new(transform(&self.value)?),
205 readonly: self.readonly,
206 })
207 }
208
209 pub fn read_ty(&self) -> Type {
210 Type::union(vec![(*self.value).clone(), Type::Undefined])
211 }
212}
213
214#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
215pub struct TypePredicate {
216 pub parameter_index: u32,
217 pub asserted_type: Type,
218}
219
220#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
223pub struct TupleType {
224 pub elements: Vec<Type>,
225 pub optional: usize,
226}
227
228impl TupleType {
229 pub fn required_len(&self) -> usize {
230 self.elements.len().saturating_sub(self.optional)
231 }
232
233 pub fn map(&self, transform: impl FnMut(&Type) -> Type) -> Self {
234 Self {
235 elements: self.elements.iter().map(transform).collect(),
236 optional: self.optional,
237 }
238 }
239
240 pub fn try_map<E>(&self, transform: impl FnMut(&Type) -> Result<Type, E>) -> Result<Self, E> {
241 Ok(Self {
242 elements: self
243 .elements
244 .iter()
245 .map(transform)
246 .collect::<Result<_, _>>()?,
247 optional: self.optional,
248 })
249 }
250}
251
252impl From<Vec<Type>> for TupleType {
253 fn from(elements: Vec<Type>) -> Self {
254 Self {
255 elements,
256 optional: 0,
257 }
258 }
259}
260
261impl std::ops::Deref for TupleType {
262 type Target = [Type];
263 fn deref(&self) -> &Self::Target {
264 &self.elements
265 }
266}
267
268impl IntoIterator for TupleType {
269 type Item = Type;
270 type IntoIter = std::vec::IntoIter<Type>;
271 fn into_iter(self) -> Self::IntoIter {
272 self.elements.into_iter()
273 }
274}
275
276impl<'a> IntoIterator for &'a TupleType {
277 type Item = &'a Type;
278 type IntoIter = std::slice::Iter<'a, Type>;
279 fn into_iter(self) -> Self::IntoIter {
280 self.elements.iter()
281 }
282}
283
284#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
285pub enum Type {
286 Number,
287 NumberLiteral(LiteralF64),
289 BigInt,
290 BigIntLiteral(String),
294 String,
295 StringLiteral(String),
296 Uint8Array,
297 Boolean,
298 BooleanLiteral(bool),
302 Null,
303 Undefined,
305 Void,
306 Unknown,
309 Function {
310 params: Vec<Type>,
311 optional: usize,
313 ret: Box<Type>,
314 predicate: Option<Box<TypePredicate>>,
317 has_rest: bool,
319 },
320 Object {
322 fields: BTreeMap<String, ObjectField>,
323 index: Option<IndexSignature>,
324 },
325 Array(Box<Type>),
326 Tuple(TupleType),
329 Readonly(Box<Type>),
336 Error,
338 Never,
340 TypeVar(String),
343 GenericParam {
346 id: u32,
347 name: String,
348 },
349 Refined {
352 original: Box<Type>,
353 ty: Box<Type>,
354 },
355 InterfaceRef {
361 mangled: MangledName,
362 package: Package,
363 name: String,
364 args: Vec<Type>,
365 },
366 ClassRef {
372 mangled: MangledName,
373 package: Package,
374 name: String,
375 args: Vec<Type>,
376 },
377 NumberEnum {
383 mangled: MangledName,
384 package: Package,
385 name: String,
386 member: Option<EnumMember<LiteralF64>>,
387 },
388 StringEnum {
389 mangled: MangledName,
390 package: Package,
391 name: String,
392 member: Option<EnumMember<String>>,
393 },
394 Union(Vec<Type>),
397 Alias {
401 mangled: MangledName,
402 package: Package,
403 name: String,
404 args: Vec<Type>,
405 ty: Box<Type>,
406 },
407 AliasRef {
419 mangled: MangledName,
420 package: Package,
421 name: String,
422 args: Vec<Type>,
423 },
424 NonPrimitive,
428}
429
430impl Type {
431 pub fn tuple(elements: Vec<Type>) -> Self {
432 Self::Tuple(elements.into())
433 }
434
435 pub fn is_structural_object(&self) -> bool {
437 match self.peel() {
438 Self::Object { .. } | Self::InterfaceRef { .. } => true,
439 Self::Union(members) => members.iter().all(Self::is_structural_object),
440 _ => false,
441 }
442 }
443
444 pub fn interface_ref(
449 package: Package,
450 name: impl Into<String>,
451 mangled: MangledName,
452 args: Vec<Type>,
453 ) -> Type {
454 Type::InterfaceRef {
455 mangled,
456 package,
457 name: name.into(),
458 args,
459 }
460 }
461
462 pub fn class_ref(
465 package: Package,
466 name: impl Into<String>,
467 mangled: MangledName,
468 args: Vec<Type>,
469 ) -> Type {
470 Type::ClassRef {
471 mangled,
472 package,
473 name: name.into(),
474 args,
475 }
476 }
477
478 pub fn number_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
479 Type::NumberEnum {
480 mangled,
481 package,
482 name: name.into(),
483 member: None,
484 }
485 }
486
487 pub fn string_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
488 Type::StringEnum {
489 mangled,
490 package,
491 name: name.into(),
492 member: None,
493 }
494 }
495
496 pub fn with_enum_member(
501 &self,
502 member_name: &str,
503 value: EnumValue,
504 member_count: usize,
505 ) -> Type {
506 if member_count < 2 {
507 return self.without_enum_member();
508 }
509 match (self.clone(), value) {
510 (
511 Type::NumberEnum {
512 mangled,
513 package,
514 name,
515 ..
516 },
517 EnumValue::Number(value),
518 ) => Type::NumberEnum {
519 mangled,
520 package,
521 name,
522 member: Some(EnumMember::new(member_name, value, member_count)),
523 },
524 (
525 Type::StringEnum {
526 mangled,
527 package,
528 name,
529 ..
530 },
531 EnumValue::String(value),
532 ) => Type::StringEnum {
533 mangled,
534 package,
535 name,
536 member: Some(EnumMember::new(member_name, value, member_count)),
537 },
538 (ty, _) => ty,
539 }
540 }
541
542 pub fn without_enum_member(&self) -> Type {
544 match self {
545 Type::NumberEnum {
546 mangled,
547 package,
548 name,
549 member: Some(_),
550 } => Type::number_enum(package.clone(), name.clone(), mangled.clone()),
551 Type::StringEnum {
552 mangled,
553 package,
554 name,
555 member: Some(_),
556 } => Type::string_enum(package.clone(), name.clone(), mangled.clone()),
557 _ => self.clone(),
558 }
559 }
560
561 pub fn is_literal_type(&self) -> bool {
564 matches!(
565 self,
566 Type::NumberLiteral(_)
567 | Type::StringLiteral(_)
568 | Type::BooleanLiteral(_)
569 | Type::BigIntLiteral(_)
570 ) || self.is_enum_member()
571 }
572
573 pub fn is_enum_member(&self) -> bool {
575 self.enum_member_name().is_some()
576 }
577
578 pub fn enum_member_name(&self) -> Option<(&MangledName, &str, usize)> {
580 match self {
581 Type::NumberEnum {
582 mangled,
583 member: Some(member),
584 ..
585 } => Some((mangled, &member.name, member.member_count)),
586 Type::StringEnum {
587 mangled,
588 member: Some(member),
589 ..
590 } => Some((mangled, &member.name, member.member_count)),
591 _ => None,
592 }
593 }
594
595 pub fn enum_member_value(&self) -> Option<EnumValue> {
597 match self.peel() {
598 Type::NumberEnum {
599 member: Some(member),
600 ..
601 } => Some(EnumValue::Number(member.value)),
602 Type::StringEnum {
603 member: Some(member),
604 ..
605 } => Some(EnumValue::String(member.value.clone())),
606 _ => None,
607 }
608 }
609
610 pub fn alias_ref(
611 package: Package,
612 name: impl Into<String>,
613 mangled: MangledName,
614 args: Vec<Type>,
615 ) -> Type {
616 Type::AliasRef {
617 mangled,
618 package,
619 name: name.into(),
620 args,
621 }
622 }
623
624 pub fn alias_ty(
625 package: Package,
626 name: impl Into<String>,
627 mangled: MangledName,
628 args: Vec<Type>,
629 ty: Box<Type>,
630 ) -> Type {
631 Type::Alias {
632 mangled,
633 package,
634 name: name.into(),
635 args,
636 ty,
637 }
638 }
639
640 pub fn prelude_interface(name: impl Into<String>, args: Vec<Type>) -> Type {
645 let name = name.into();
646 let mangled = crate::mangle::prelude(&name);
647 Type::interface_ref(Package::prelude(), name, mangled, args)
648 }
649
650 pub fn prelude_error_class() -> Type {
653 Type::class_ref(
654 Package::prelude(),
655 "Error",
656 crate::mangle::prelude("Error"),
657 Vec::new(),
658 )
659 }
660
661 pub fn prelude_range_error_class() -> Type {
664 Type::class_ref(
665 Package::prelude(),
666 "RangeError",
667 crate::mangle::prelude("RangeError"),
668 Vec::new(),
669 )
670 }
671
672 pub fn without_aliases(&self) -> &Type {
674 match self {
675 Type::Alias { ty, .. } => ty.without_aliases(),
676 _ => self,
677 }
678 }
679
680 pub fn holds_every_value(&self) -> bool {
684 let members: &[Type] = match self.without_aliases() {
685 Type::Unknown => return true,
686 Type::Union(members) => members,
687 _ => return false,
688 };
689 members.iter().any(Type::is_empty_object)
692 && members
693 .iter()
694 .any(|m| matches!(m.without_aliases(), Type::Null))
695 && members
696 .iter()
697 .any(|m| matches!(m.without_aliases(), Type::Undefined))
698 }
699
700 pub fn refinement_pieces(&self) -> Option<Vec<Type>> {
703 let Type::Refined { original, ty } = self.without_aliases() else {
704 return None;
705 };
706 let Type::Union(parts) = ty.peel() else {
707 return None;
708 };
709 Some(
710 parts
711 .iter()
712 .map(|part| match part.peel() {
713 Type::Null | Type::Undefined | Type::Void => part.clone(),
714 _ => Type::Refined {
715 original: original.clone(),
716 ty: Box::new(part.clone()),
717 },
718 })
719 .collect(),
720 )
721 }
722
723 pub fn refines_type_parameter(&self) -> bool {
725 matches!(self.without_aliases(), Type::Refined { original, .. }
726 if matches!(original.peel(), Type::GenericParam { .. }))
727 }
728
729 pub fn peel(&self) -> &Type {
730 let mut t = self;
731 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) = t {
732 t = ty;
733 }
734 t
735 }
736
737 pub fn any_object() -> Type {
741 Type::Object {
742 index: Some(IndexSignature {
743 value: Box::new(Type::Unknown),
744 readonly: false,
745 }),
746 fields: BTreeMap::new(),
747 }
748 }
749
750 pub fn empty_object() -> Type {
752 Type::Object {
753 fields: BTreeMap::new(),
754 index: None,
755 }
756 }
757
758 pub fn is_empty_object(&self) -> bool {
763 let mut t = self;
764 while let Type::Alias { ty, .. } | Type::Readonly(ty) = t {
765 t = ty;
766 }
767 matches!(t, Type::Object { fields, index: None } if fields.is_empty())
768 }
769
770 pub fn lowered(&self) -> &Type {
776 let peeled = self.peel();
777 if peeled.is_empty_object() || matches!(peeled, Type::NonPrimitive) {
778 return &Type::Unknown;
779 }
780 peeled
781 }
782
783 pub fn peel_preserving_readonly(&self) -> &Type {
786 let mut t = self;
787 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } = t {
788 t = ty;
789 }
790 t
791 }
792
793 pub fn is_readonly_array(&self) -> bool {
796 matches!(self.peel_preserving_readonly(), Type::Readonly(_))
797 }
798
799 pub fn rest_element(&self) -> Option<&Type> {
803 match self {
804 Type::Array(element) => Some(element),
805 Type::Readonly(inner) => match inner.as_ref() {
806 Type::Array(element) => Some(element),
807 _ => None,
808 },
809 _ => None,
810 }
811 }
812
813 pub fn rest_array_ignoring_readonly(&self) -> &Type {
818 match self {
819 Type::Readonly(inner) if matches!(inner.as_ref(), Type::Array(_)) => inner,
820 _ => self,
821 }
822 }
823
824 pub fn holds_no_element(&self) -> bool {
827 match self.peel() {
828 Type::Array(element) => {
829 matches!(element.peel(), Type::Never) || element.holds_no_element()
830 }
831 _ => false,
832 }
833 }
834
835 pub fn is_array_like_union(&self) -> bool {
838 match self.peel() {
839 Type::Union(members) => members
840 .iter()
841 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_))),
842 _ => false,
843 }
844 }
845
846 pub fn array_like_union_element(&self) -> Option<Type> {
851 let Type::Union(members) = self.peel() else {
852 return None;
853 };
854 let elements = members
855 .iter()
856 .map(|member| match member.peel() {
857 Type::Array(element) => Some((**element).clone()),
858 Type::Tuple(positions) => Some(Type::union(positions.elements.clone())),
859 _ => None,
860 })
861 .collect::<Option<Vec<_>>>()?;
862 Some(Type::union(elements))
863 }
864
865 pub fn string_or_array_union_arrays(&self) -> Option<Type> {
870 let Type::Union(members) = self.peel() else {
871 return None;
872 };
873 let (strings, arrays): (Vec<Type>, Vec<Type>) = members
874 .iter()
875 .cloned()
876 .partition(|member| member.peel().is_string_shaped());
877 let all_arrays = arrays
878 .iter()
879 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_)));
880 (!strings.is_empty() && !arrays.is_empty() && all_arrays).then(|| Type::union(arrays))
881 }
882
883 pub fn array_like_union_view(&self) -> Option<Type> {
886 self.array_like_union_element()
887 .map(|element| Type::Array(Box::new(element)))
888 }
889
890 pub fn is_void(&self) -> bool {
901 matches!(self.peel(), Type::Void)
902 }
903
904 pub fn function_arity_fits(actual: usize, expected: usize, has_rest: bool) -> bool {
910 actual == expected || (!has_rest && actual < expected)
911 }
912
913 pub fn carries_void(&self) -> bool {
923 match self.peel() {
924 Type::Void => true,
925 Type::Union(members) => members.iter().any(|m| matches!(m.peel(), Type::Void)),
926 _ => false,
927 }
928 }
929
930 pub fn primitive_behavior(&self) -> &Type {
933 match self.peel() {
934 Type::NumberEnum { .. } => &Type::Number,
935 Type::StringEnum { .. } => &Type::String,
936 Type::BooleanLiteral(_) => &Type::Boolean,
937 Type::BigIntLiteral(_) => &Type::BigInt,
938 Type::Union(members)
939 if !members.is_empty()
940 && members.iter().all(|member| {
941 matches!(
942 member.primitive_behavior(),
943 Type::Number | Type::NumberLiteral(_)
944 )
945 }) =>
946 {
947 &Type::Number
948 }
949 Type::Union(members)
950 if !members.is_empty()
951 && members
952 .iter()
953 .all(|member| matches!(member.primitive_behavior(), Type::BigInt)) =>
954 {
955 &Type::BigInt
956 }
957 Type::Union(members)
958 if !members.is_empty() && members.iter().all(Type::is_string_shaped) =>
959 {
960 &Type::String
961 }
962 ty => ty,
963 }
964 }
965
966 pub fn is_bigint(&self) -> bool {
969 matches!(self.primitive_behavior(), Type::BigInt)
970 }
971
972 pub fn widen_literal(&self) -> Type {
985 match self {
986 Type::NumberLiteral(_) => Type::Number,
987 Type::StringLiteral(_) => Type::String,
988 Type::BooleanLiteral(_) => Type::Boolean,
989 Type::BigIntLiteral(_) => Type::BigInt,
990 _ if self.is_enum_member() => self.without_enum_member(),
991 Type::Union(members) => Type::union(without_members_of_empty_object(
995 members.iter().map(Type::widen_literal).collect(),
996 )),
997 _ => self.clone(),
998 }
999 }
1000
1001 pub fn literal_base(&self) -> Option<Type> {
1005 let widened = self.widen_literal();
1006 (widened != *self && matches!(widened, Type::Number | Type::String | Type::Boolean))
1007 .then_some(widened)
1008 }
1009
1010 pub fn is_string_shaped(&self) -> bool {
1019 match self.peel() {
1020 Type::String | Type::StringLiteral(_) | Type::StringEnum { .. } => true,
1021 Type::Union(members) => members.iter().all(Type::is_string_shaped),
1022 _ => false,
1023 }
1024 }
1025
1026 pub fn contains_string(&self) -> bool {
1030 match self.peel() {
1031 Type::String | Type::StringLiteral(_) | Type::StringEnum { .. } => true,
1032 Type::Union(members) => members.iter().any(Type::contains_string),
1033 _ => false,
1034 }
1035 }
1036
1037 pub fn any_member(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1042 match self.peel() {
1043 Type::Union(members) => members.iter().any(|member| member.any_member(leaf)),
1044 member => leaf(member),
1045 }
1046 }
1047
1048 pub fn all_members(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1051 !self.any_member(&|member| !leaf(member))
1052 }
1053
1054 pub fn spells_null(&self) -> bool {
1057 self.any_member(&|member| matches!(member, Type::Null))
1058 }
1059
1060 pub fn spells_undefined(&self) -> bool {
1063 self.any_member(&|member| matches!(member, Type::Undefined | Type::Void))
1064 }
1065
1066 pub fn stringifies_to_undefined(leaf: &Type) -> bool {
1070 matches!(
1072 leaf.lowered(),
1073 Type::Undefined
1074 | Type::Void
1075 | Type::Unknown
1076 | Type::GenericParam { .. }
1077 | Type::TypeVar(_)
1078 | Type::Function { .. }
1079 )
1080 }
1081
1082 pub fn union(members: Vec<Type>) -> Type {
1099 if members.iter().any(|m| matches!(m.peel(), Type::Error)) {
1100 return Type::Error;
1101 }
1102 if members.iter().any(|m| matches!(m.peel(), Type::Unknown)) {
1104 return Type::Unknown;
1105 }
1106 let members: Vec<Type> = members
1108 .into_iter()
1109 .filter(|m| !matches!(m.peel(), Type::Never))
1110 .collect();
1111 let mut flat: Vec<Type> = Vec::new();
1112 for m in members {
1113 match m.without_aliases() {
1114 Type::Union(inner) => flat.extend(inner.iter().cloned()),
1115 _ => flat.push(m),
1116 }
1117 }
1118 let alias_rank = |t: &Type| u8::from(!matches!(t, Type::Alias { .. }));
1122 flat.sort_by(|a, b| {
1123 a.without_aliases()
1124 .cmp(b.without_aliases())
1125 .then_with(|| alias_rank(a).cmp(&alias_rank(b)))
1126 });
1127 flat.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1128 fold_boolean_literals(&mut flat);
1129 fold_enum_members(&mut flat);
1130 let refines_a_member = |member: &Type| {
1132 matches!(member, Type::Refined { original, .. }
1133 if flat.iter().any(|other| other.without_aliases() == original.without_aliases()))
1134 };
1135 let kept: Vec<Type> = flat
1136 .iter()
1137 .filter(|member| !refines_a_member(member))
1138 .cloned()
1139 .collect();
1140 flat = kept;
1141 if flat.iter().any(Type::is_empty_object) {
1143 flat.retain(|member| !matches!(member.peel(), Type::NonPrimitive));
1144 }
1145 if flat.len() > 1 {
1146 return Type::Union(flat);
1147 }
1148 match flat.into_iter().next() {
1149 Some(member) => member,
1150 None => Type::Never,
1151 }
1152 }
1153
1154 pub fn interface_routing(&self) -> Option<(MangledName, &str, &str, Vec<Type>)> {
1159 let prelude = crate::mangle::PRELUDE_PACKAGE;
1160 match self.primitive_behavior() {
1161 Type::Number | Type::NumberLiteral(_) => Some((
1166 crate::mangle::prelude("Number"),
1167 prelude,
1168 "Number",
1169 Vec::new(),
1170 )),
1171 Type::BigInt => Some((
1172 crate::mangle::prelude("BigInt"),
1173 prelude,
1174 "BigInt",
1175 Vec::new(),
1176 )),
1177 Type::Boolean => Some((
1178 crate::mangle::prelude("Boolean"),
1179 prelude,
1180 "Boolean",
1181 Vec::new(),
1182 )),
1183 Type::String | Type::StringLiteral(_) => Some((
1184 crate::mangle::prelude("String"),
1185 prelude,
1186 "String",
1187 Vec::new(),
1188 )),
1189 Type::Uint8Array => Some((
1190 crate::mangle::prelude("Uint8Array"),
1191 prelude,
1192 "Uint8Array",
1193 Vec::new(),
1194 )),
1195 Type::Array(elem) => Some((
1196 crate::mangle::prelude("Array"),
1197 prelude,
1198 "Array",
1199 vec![(**elem).clone()],
1200 )),
1201 Type::Tuple(elements) => Some((
1205 crate::mangle::prelude("Array"),
1206 prelude,
1207 "Array",
1208 vec![Type::union(elements.elements.clone())],
1209 )),
1210 Type::Object { .. }
1211 | Type::NonPrimitive
1212 | Type::TypeVar(_)
1213 | Type::GenericParam { .. } => Some((
1214 crate::mangle::prelude("Object"),
1215 prelude,
1216 "Object",
1217 Vec::new(),
1218 )),
1219 Type::InterfaceRef {
1220 mangled,
1221 package,
1222 name,
1223 args,
1224 ..
1225 }
1226 | Type::ClassRef {
1227 mangled,
1228 package,
1229 name,
1230 args,
1231 ..
1232 } => Some((
1233 mangled.clone(),
1234 package.as_str(),
1235 name.as_str(),
1236 args.clone(),
1237 )),
1238 Type::NumberEnum { .. } | Type::StringEnum { .. } => Some((
1240 crate::mangle::prelude("Object"),
1241 prelude,
1242 "Object",
1243 Vec::new(),
1244 )),
1245 Type::Unknown => Some((
1247 crate::mangle::prelude("Object"),
1248 prelude,
1249 "Object",
1250 Vec::new(),
1251 )),
1252 _ => None,
1253 }
1254 }
1255}
1256
1257pub(crate) fn shown_beside_optional_marker(ty: &Type) -> Type {
1263 match ty.peel() {
1264 Type::Union(members) => Type::union(
1265 members
1266 .iter()
1267 .filter(|member| !matches!(member.peel(), Type::Undefined))
1268 .cloned()
1269 .collect(),
1270 ),
1271 _ => ty.clone(),
1272 }
1273}
1274
1275pub(crate) fn escape_string_literal(s: &str) -> String {
1278 let mut out = String::with_capacity(s.len());
1279 let mut chars = crate::literal_units::literal_chars(s).peekable();
1280 while let Some(c) = chars.next() {
1281 let c = match c {
1282 Ok(c) => c,
1283 Err(lone) => {
1284 out.push_str(&format!("\\u{lone:04X}"));
1285 continue;
1286 }
1287 };
1288 match c {
1289 '"' => out.push_str("\\\""),
1290 '\\' => out.push_str("\\\\"),
1291 '\n' => out.push_str("\\n"),
1292 '\r' => out.push_str("\\r"),
1293 '\t' => out.push_str("\\t"),
1294 '\u{8}' => out.push_str("\\b"),
1295 '\u{b}' => out.push_str("\\v"),
1296 '\u{c}' => out.push_str("\\f"),
1297 '\0' if chars
1299 .peek()
1300 .is_some_and(|next| next.is_ok_and(|d| d.is_ascii_digit())) =>
1301 {
1302 out.push_str("\\x00");
1303 }
1304 '\0' => out.push_str("\\0"),
1305 '\u{0}'..='\u{1f}' | '\u{85}' | '\u{2028}' | '\u{2029}' => {
1306 out.push_str(&format!("\\u{:04X}", u32::from(c)));
1307 }
1308 c => out.push(c),
1309 }
1310 }
1311 out
1312}
1313
1314pub fn bigint_literal_type(digits: &str) -> Type {
1317 Type::BigIntLiteral(canonical_bigint_digits(digits))
1318}
1319
1320pub fn negate_bigint_digits(digits: &str) -> String {
1322 let canonical = canonical_bigint_digits(digits);
1323 if canonical == "0" {
1324 return canonical;
1325 }
1326 match canonical.strip_prefix('-') {
1327 Some(magnitude) => magnitude.to_string(),
1328 None => format!("-{canonical}"),
1329 }
1330}
1331
1332fn canonical_bigint_digits(digits: &str) -> String {
1334 let (sign, magnitude) = match digits.strip_prefix('-') {
1335 Some(magnitude) => ("-", magnitude),
1336 None => ("", digits),
1337 };
1338 match magnitude.trim_start_matches('0') {
1339 "" => "0".to_string(),
1340 trimmed => format!("{sign}{trimmed}"),
1341 }
1342}
1343
1344pub fn without_members_of_empty_object(members: Vec<Type>) -> Vec<Type> {
1349 if !members.iter().any(Type::is_empty_object) {
1350 return members;
1351 }
1352 members
1353 .into_iter()
1354 .filter(|member| member.is_empty_object() || may_hold_values_outside_empty_object(member))
1355 .collect()
1356}
1357
1358fn may_hold_values_outside_empty_object(ty: &Type) -> bool {
1361 match ty.peel() {
1362 Type::Null
1363 | Type::Undefined
1364 | Type::Void
1365 | Type::Unknown
1366 | Type::GenericParam { .. }
1367 | Type::TypeVar(_) => true,
1368 Type::Union(members) => members.iter().any(may_hold_values_outside_empty_object),
1369 _ => false,
1370 }
1371}
1372
1373fn fold_boolean_literals(members: &mut Vec<Type>) {
1377 let is_boolean = |m: &Type| matches!(m.without_aliases(), Type::Boolean);
1378 let is_literal = |m: &Type| matches!(m.without_aliases(), Type::BooleanLiteral(_));
1379 let literals = members.iter().filter(|m| is_literal(m)).count();
1380 let has_boolean = members.iter().any(is_boolean);
1381 if literals == 0 || (literals == 1 && !has_boolean) {
1382 return;
1383 }
1384 members.retain(|m| !is_literal(m));
1385 if !has_boolean {
1386 let at = members.partition_point(|m| m.without_aliases() < &Type::Boolean);
1387 members.insert(at, Type::Boolean);
1388 }
1389}
1390
1391fn fold_enum_members(members: &mut Vec<Type>) {
1396 absorb_enums_into_primitives(members);
1397 let complete = complete_enums(members);
1398 if complete.is_empty() {
1399 return;
1400 }
1401 for member in members.iter_mut() {
1402 if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } =
1403 member.without_aliases()
1404 && complete.contains(mangled)
1405 {
1406 *member = member.without_aliases().without_enum_member();
1407 }
1408 }
1409 members.sort_by(|a, b| a.without_aliases().cmp(b.without_aliases()));
1410 members.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1411}
1412
1413fn absorb_enums_into_primitives(members: &mut Vec<Type>) {
1415 let has = |base: &Type| members.iter().any(|m| m.without_aliases() == base);
1416 let (has_number, has_string) = (has(&Type::Number), has(&Type::String));
1417 members.retain(|m| match m.without_aliases() {
1418 Type::NumberEnum { .. } => !has_number,
1419 Type::StringEnum { .. } => !has_string,
1420 _ => true,
1421 });
1422}
1423
1424fn complete_enums(members: &[Type]) -> BTreeSet<MangledName> {
1427 struct NamedMembers<'a> {
1428 member_count: usize,
1429 names: BTreeSet<&'a str>,
1430 }
1431 let mut named: BTreeMap<&MangledName, NamedMembers> = BTreeMap::new();
1432 let mut whole: BTreeSet<&MangledName> = BTreeSet::new();
1433 for member in members {
1434 let ty = member.without_aliases();
1435 if let Some((mangled, name, member_count)) = ty.enum_member_name() {
1436 named
1437 .entry(mangled)
1438 .or_insert_with(|| NamedMembers {
1439 member_count,
1440 names: BTreeSet::new(),
1441 })
1442 .names
1443 .insert(name);
1444 } else if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } = ty {
1445 whole.insert(mangled);
1446 }
1447 }
1448 named
1449 .into_iter()
1450 .filter(|(mangled, named)| {
1451 whole.contains(mangled) || named.names.len() >= named.member_count
1452 })
1453 .map(|(mangled, _)| mangled.clone())
1454 .collect()
1455}
1456
1457impl Type {
1458 pub fn render_checked(
1459 &self,
1460 limits: crate::rendering::RenderLimits,
1461 ) -> Result<crate::rendering::RenderedText, crate::rendering::RenderError> {
1462 crate::type_rendering::render(self, limits)
1463 }
1464}
1465
1466pub fn union_display_order(members: &[Type]) -> Vec<&Type> {
1470 let mut ordered: Vec<&Type> = members.iter().collect();
1471 ordered.sort_by_key(|m| match m {
1472 Type::Null => 1,
1473 Type::Undefined => 2,
1474 _ => 0,
1475 });
1476 ordered
1477}
1478
1479impl fmt::Display for Type {
1480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1481 match self.render_checked(crate::rendering::RenderLimits::default()) {
1482 Ok(rendered) => f.write_str(&rendered.text),
1483 Err(_) => f.write_str("[diagnostic type unavailable]"),
1484 }
1485 }
1486}
1487
1488#[cfg(test)]
1489mod tests {
1490 use super::{Package, Type};
1491
1492 #[test]
1493 fn tuple_mapping_preserves_optional_positions() {
1494 let tuple = super::TupleType {
1495 elements: vec![Type::NumberLiteral(super::LiteralF64(1.0)), Type::Undefined],
1496 optional: 1,
1497 };
1498 let mapped = tuple.map(Type::widen_literal);
1499 assert_eq!(mapped.optional, 1);
1500 assert_eq!(mapped.required_len(), 1);
1501 assert_eq!(mapped.elements, vec![Type::Number, Type::Undefined]);
1502 }
1503
1504 #[test]
1505 fn optional_fields_read_as_undefined() {
1506 let field = super::ObjectField::optional(Type::String);
1507 assert_eq!(
1508 field.read_ty(),
1509 Type::union(vec![Type::String, Type::Undefined])
1510 );
1511 }
1512
1513 #[test]
1514 fn optional_function_and_tuple_display_preserve_omission() {
1515 let ty = Type::Function {
1516 params: vec![Type::union(vec![Type::String, Type::Undefined])],
1517 optional: 1,
1518 ret: Box::new(Type::Void),
1519 predicate: None,
1520 has_rest: false,
1521 };
1522 assert_eq!(ty.to_string(), "(arg0?: string) => void");
1524 let tuple = Type::Tuple(super::TupleType {
1525 elements: vec![
1526 Type::Number,
1527 Type::union(vec![Type::String, Type::Undefined]),
1528 ],
1529 optional: 1,
1530 });
1531 assert_eq!(tuple.to_string(), "[number, (string | undefined)?]");
1532 }
1533
1534 #[test]
1535 fn atomic_display() {
1536 assert_eq!(Type::Number.to_string(), "number");
1537 assert_eq!(Type::String.to_string(), "string");
1538 assert_eq!(Type::Boolean.to_string(), "boolean");
1539 assert_eq!(Type::Null.to_string(), "null");
1540 assert_eq!(Type::Void.to_string(), "void");
1541 assert_eq!(Type::Error.to_string(), "<error>");
1542 }
1543
1544 #[test]
1545 fn readonly_display_matches_typescript() {
1546 let numbers = Type::Array(Box::new(Type::Number));
1547 let readonly = Type::Readonly(Box::new(numbers.clone()));
1548 assert_eq!(readonly.to_string(), "readonly number[]");
1549 assert_eq!(
1550 Type::Array(Box::new(readonly.clone())).to_string(),
1551 "(readonly number[])[]"
1552 );
1553 assert_eq!(
1554 Type::Readonly(Box::new(Type::Array(Box::new(numbers)))).to_string(),
1555 "readonly number[][]"
1556 );
1557 assert_eq!(
1558 Type::Readonly(Box::new(Type::Tuple(
1559 vec![Type::Number, Type::String].into()
1560 )))
1561 .to_string(),
1562 "readonly [number, string]"
1563 );
1564 assert!(readonly.is_readonly_array());
1565 assert_eq!(readonly.peel(), &Type::Array(Box::new(Type::Number)));
1566 }
1567
1568 #[test]
1569 fn string_literal_display_escapes_like_typescript() {
1570 let cases = [
1572 ("G\"HI\\", r#""G\"HI\\""#),
1573 ("a\nb\r\t", r#""a\nb\r\t""#),
1574 ("\u{8}\u{b}\u{c}", r#""\b\v\f""#),
1575 ("\0x", r#""\0x""#),
1576 ("\u{0}1", r#""\x001""#),
1577 ("\u{7}\u{1b}", r#""\u0007\u001B""#),
1578 ("\u{85}\u{2028}\u{2029}", r#""\u0085\u2028\u2029""#),
1579 ("\u{7f}é", "\"\u{7f}é\""),
1580 ];
1581 for (value, printed) in cases {
1582 assert_eq!(Type::StringLiteral(value.into()).to_string(), printed);
1583 }
1584 }
1585
1586 #[test]
1587 fn structural_equality() {
1588 assert_eq!(Type::Number, Type::Number);
1589 assert_ne!(Type::Number, Type::String);
1590
1591 let a = Type::Function {
1592 params: vec![Type::Number, Type::String],
1593 ret: Box::new(Type::Boolean),
1594 predicate: None,
1595 has_rest: false,
1596 optional: 0,
1597 };
1598 let b = Type::Function {
1599 params: vec![Type::Number, Type::String],
1600 ret: Box::new(Type::Boolean),
1601 predicate: None,
1602 has_rest: false,
1603 optional: 0,
1604 };
1605 let c = Type::Function {
1606 params: vec![Type::Number],
1607 ret: Box::new(Type::Boolean),
1608 predicate: None,
1609 has_rest: false,
1610 optional: 0,
1611 };
1612 assert_eq!(a, b);
1613 assert_ne!(a, c);
1614 }
1615
1616 #[test]
1617 fn function_display_no_params() {
1618 let t = Type::Function {
1619 params: vec![],
1620 ret: Box::new(Type::Number),
1621 predicate: None,
1622 has_rest: false,
1623 optional: 0,
1624 };
1625 assert_eq!(t.to_string(), "() => number");
1626 }
1627
1628 #[test]
1629 fn function_display_one_param() {
1630 let t = Type::Function {
1631 params: vec![Type::Number],
1632 ret: Box::new(Type::Boolean),
1633 predicate: None,
1634 has_rest: false,
1635 optional: 0,
1636 };
1637 assert_eq!(t.to_string(), "(arg0: number) => boolean");
1638 }
1639
1640 #[test]
1641 fn function_display_multi_param() {
1642 let t = Type::Function {
1643 params: vec![Type::Number, Type::String],
1644 ret: Box::new(Type::Void),
1645 predicate: None,
1646 has_rest: false,
1647 optional: 0,
1648 };
1649 assert_eq!(t.to_string(), "(arg0: number, arg1: string) => void");
1650 }
1651
1652 #[test]
1653 fn function_display_nested() {
1654 let inner = Type::Function {
1655 params: vec![],
1656 ret: Box::new(Type::Number),
1657 predicate: None,
1658 has_rest: false,
1659 optional: 0,
1660 };
1661 let outer = Type::Function {
1662 params: vec![],
1663 ret: Box::new(inner),
1664 predicate: None,
1665 has_rest: false,
1666 optional: 0,
1667 };
1668 assert_eq!(outer.to_string(), "() => () => number");
1669 }
1670
1671 #[test]
1672 fn clone_roundtrip() {
1673 let t = Type::Function {
1674 params: vec![Type::Number, Type::String],
1675 ret: Box::new(Type::Boolean),
1676 predicate: None,
1677 has_rest: false,
1678 optional: 0,
1679 };
1680 assert_eq!(t.clone(), t);
1681 }
1682
1683 fn point_type() -> Type {
1684 let mut fields = std::collections::BTreeMap::new();
1685 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1686 fields.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1687 Type::Object {
1688 index: None,
1689 fields,
1690 }
1691 }
1692
1693 #[test]
1694 fn object_structural_equality_ignores_field_insertion_order() {
1695 let mut a = std::collections::BTreeMap::new();
1696 a.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1697 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1698
1699 let mut b = std::collections::BTreeMap::new();
1700 b.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1701 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1702
1703 assert_eq!(
1704 Type::Object {
1705 index: None,
1706 fields: a
1707 },
1708 Type::Object {
1709 index: None,
1710 fields: b
1711 }
1712 );
1713 }
1714
1715 #[test]
1716 fn object_inequality_on_different_field_set() {
1717 let mut a = std::collections::BTreeMap::new();
1718 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1719 let mut b = std::collections::BTreeMap::new();
1720 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1721 assert_ne!(
1722 Type::Object {
1723 index: None,
1724 fields: a
1725 },
1726 Type::Object {
1727 index: None,
1728 fields: b
1729 }
1730 );
1731 }
1732
1733 #[test]
1734 fn object_display() {
1735 assert_eq!(point_type().to_string(), "{ x: number; y: number }");
1736 let empty = Type::Object {
1737 index: None,
1738 fields: std::collections::BTreeMap::new(),
1739 };
1740 assert_eq!(empty.to_string(), "{}");
1741 }
1742
1743 #[test]
1744 fn object_display_with_optional_field() {
1745 let mut fields = std::collections::BTreeMap::new();
1746 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1747 fields.insert("y".to_string(), crate::ObjectField::optional(Type::String));
1748 let t = Type::Object {
1749 index: None,
1750 fields,
1751 };
1752 assert_eq!(t.to_string(), "{ x: number; y?: string }");
1753 }
1754
1755 #[test]
1756 fn object_inequality_required_vs_optional() {
1757 let mut a = std::collections::BTreeMap::new();
1758 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1759 let mut b = std::collections::BTreeMap::new();
1760 b.insert("x".to_string(), crate::ObjectField::optional(Type::Number));
1761 assert_ne!(
1762 Type::Object {
1763 index: None,
1764 fields: a
1765 },
1766 Type::Object {
1767 index: None,
1768 fields: b
1769 }
1770 );
1771 }
1772
1773 #[test]
1774 fn array_equality() {
1775 assert_eq!(
1776 Type::Array(Box::new(Type::Number)),
1777 Type::Array(Box::new(Type::Number))
1778 );
1779 assert_ne!(
1780 Type::Array(Box::new(Type::Number)),
1781 Type::Array(Box::new(Type::String))
1782 );
1783 }
1784
1785 #[test]
1786 fn array_display() {
1787 assert_eq!(Type::Array(Box::new(Type::Number)).to_string(), "number[]");
1788 assert_eq!(
1789 Type::Array(Box::new(Type::Array(Box::new(Type::Number)))).to_string(),
1790 "number[][]"
1791 );
1792 }
1793
1794 #[test]
1795 fn nested_object_in_array_display() {
1796 let arr = Type::Array(Box::new(point_type()));
1797 assert_eq!(arr.to_string(), "{ x: number; y: number }[]");
1798 }
1799
1800 #[test]
1801 fn type_var_display_uses_source_name() {
1802 assert_eq!(Type::TypeVar("T".to_string()).to_string(), "T");
1803 assert_eq!(Type::TypeVar("Key".to_string()).to_string(), "Key");
1804 }
1805
1806 #[test]
1807 fn type_var_structural_equality_by_name() {
1808 let t = Type::TypeVar("T".to_string());
1809 let t2 = Type::TypeVar("T".to_string());
1810 let u = Type::TypeVar("U".to_string());
1811 assert_eq!(t, t2);
1812 assert_ne!(t, u);
1813 assert_ne!(t, Type::Number);
1814 }
1815
1816 #[test]
1817 fn type_var_inside_array_and_function_displays_through() {
1818 let arr = Type::Array(Box::new(Type::TypeVar("T".to_string())));
1819 assert_eq!(arr.to_string(), "T[]");
1820 let func = Type::Function {
1821 params: vec![Type::TypeVar("T".to_string())],
1822 ret: Box::new(Type::TypeVar("U".to_string())),
1823 predicate: None,
1824 has_rest: false,
1825 optional: 0,
1826 };
1827 assert_eq!(func.to_string(), "(arg0: T) => U");
1828 }
1829
1830 #[test]
1831 fn generic_param_display_uses_name_not_id() {
1832 let gp = Type::GenericParam {
1833 id: 42,
1834 name: "T".to_string(),
1835 };
1836 assert_eq!(gp.to_string(), "T");
1837 }
1838
1839 #[test]
1840 fn generic_param_equality_by_id() {
1841 let a = Type::GenericParam {
1842 id: 5,
1843 name: "T".to_string(),
1844 };
1845 let b = Type::GenericParam {
1846 id: 5,
1847 name: "T".to_string(),
1848 };
1849 assert_eq!(a, b);
1850
1851 let c = Type::GenericParam {
1852 id: 6,
1853 name: "T".to_string(),
1854 };
1855 assert_ne!(a, c);
1856 }
1857
1858 #[test]
1859 fn union_canonical_dedup() {
1860 let t = Type::union(vec![Type::Number, Type::Null, Type::Number]);
1861 let Type::Union(members) = t else {
1862 panic!("expected Union, got {t:?}");
1863 };
1864 assert_eq!(members, vec![Type::Number, Type::Null]);
1865 }
1866
1867 #[test]
1868 fn union_canonical_order_independent() {
1869 let a = Type::union(vec![Type::Number, Type::Null]);
1870 let b = Type::union(vec![Type::Null, Type::Number]);
1871 assert_eq!(a, b);
1872 }
1873
1874 #[test]
1875 fn union_collapses_to_single_member() {
1876 let t = Type::union(vec![Type::Number, Type::Number]);
1877 assert_eq!(t, Type::Number);
1878 }
1879
1880 #[test]
1881 fn union_flattens_nested() {
1882 let inner = Type::union(vec![Type::String, Type::Null]);
1883 let outer = Type::union(vec![Type::Number, inner]);
1884 let expected = Type::union(vec![Type::Number, Type::String, Type::Null]);
1885 assert_eq!(outer, expected);
1886 }
1887
1888 #[test]
1889 fn union_error_member_collapses_to_error() {
1890 let t = Type::union(vec![Type::Number, Type::Error]);
1891 assert_eq!(t, Type::Error);
1892 }
1893
1894 #[test]
1895 fn union_display_basic() {
1896 let t = Type::union(vec![Type::Number, Type::String]);
1897 assert_eq!(t.to_string(), "number | string");
1898 }
1899
1900 #[test]
1901 fn union_display_with_null() {
1902 let t = Type::union(vec![Type::Number, Type::Null]);
1903 assert_eq!(t.to_string(), "number | null");
1904 let t2 = Type::union(vec![Type::Null, Type::Number]);
1905 assert_eq!(t2.to_string(), "number | null");
1906 }
1907
1908 #[test]
1909 fn union_display_wraps_function_member() {
1910 let fn_ty = Type::Function {
1911 params: vec![],
1912 ret: Box::new(Type::String),
1913 predicate: None,
1914 has_rest: false,
1915 optional: 0,
1916 };
1917 let t = Type::union(vec![fn_ty, Type::Boolean]);
1918 assert_eq!(t.to_string(), "boolean | (() => string)");
1919 }
1920
1921 use super::LiteralF64;
1922
1923 #[test]
1924 fn string_literal_display_is_quoted() {
1925 let t = Type::StringLiteral("north".to_string());
1926 assert_eq!(t.to_string(), "\"north\"");
1927 }
1928
1929 #[test]
1930 fn number_literal_display_is_unquoted_with_whole_number_shape() {
1931 let whole = Type::NumberLiteral(LiteralF64(42.0));
1932 assert_eq!(whole.to_string(), "42");
1933 let frac = Type::NumberLiteral(LiteralF64(4.5));
1934 assert_eq!(frac.to_string(), "4.5");
1935 }
1936
1937 #[test]
1938 fn string_literal_equality_by_value() {
1939 let a = Type::StringLiteral("hi".to_string());
1940 let b = Type::StringLiteral("hi".to_string());
1941 let c = Type::StringLiteral("bye".to_string());
1942 assert_eq!(a, b);
1943 assert_ne!(a, c);
1944 }
1945
1946 #[test]
1947 fn number_literal_equality_by_value() {
1948 let a = Type::NumberLiteral(LiteralF64(42.0));
1949 let b = Type::NumberLiteral(LiteralF64(42.0));
1950 let c = Type::NumberLiteral(LiteralF64(43.0));
1951 assert_eq!(a, b);
1952 assert_ne!(a, c);
1953 }
1954
1955 #[test]
1956 fn union_sorts_literals_next_to_base_types() {
1957 let t = Type::union(vec![
1959 Type::String,
1960 Type::StringLiteral("hi".to_string()),
1961 Type::Number,
1962 Type::NumberLiteral(LiteralF64(42.0)),
1963 ]);
1964 assert_eq!(t.to_string(), "number | 42 | string | \"hi\"");
1965 }
1966
1967 #[test]
1968 fn widen_literal_replaces_a_literal_with_its_base() {
1969 assert_eq!(
1970 Type::NumberLiteral(LiteralF64(1.0)).widen_literal(),
1971 Type::Number
1972 );
1973 assert_eq!(
1974 Type::StringLiteral("hi".to_string()).widen_literal(),
1975 Type::String
1976 );
1977 }
1978
1979 #[test]
1982 fn widen_literal_collapses_a_union_of_literals() {
1983 let t = Type::union(vec![
1984 Type::NumberLiteral(LiteralF64(1.0)),
1985 Type::NumberLiteral(LiteralF64(2.0)),
1986 ]);
1987 assert_eq!(t.widen_literal(), Type::Number);
1988 }
1989
1990 #[test]
1993 fn widen_literal_leaves_everything_else_alone() {
1994 assert_eq!(Type::Number.widen_literal(), Type::Number);
1995 assert_eq!(Type::Boolean.widen_literal(), Type::Boolean);
1996 let arr = Type::Array(Box::new(Type::NumberLiteral(LiteralF64(1.0))));
1997 assert_eq!(arr.widen_literal(), arr);
1998 }
1999
2000 #[test]
2001 fn union_of_two_string_literals_sorts_by_value() {
2002 let t = Type::union(vec![
2003 Type::StringLiteral("south".to_string()),
2004 Type::StringLiteral("north".to_string()),
2005 ]);
2006 assert_eq!(t.to_string(), "\"north\" | \"south\"");
2007 }
2008
2009 #[test]
2010 fn literal_f64_equality_distinguishes_negative_zero_at_bit_level() {
2011 let pos = LiteralF64(0.0);
2014 let neg = LiteralF64(-0.0);
2015 assert_ne!(pos, neg);
2016 }
2017
2018 #[test]
2019 fn type_var_and_generic_param_are_distinct_types() {
2020 let tv = Type::TypeVar("T".to_string());
2023 let gp = Type::GenericParam {
2024 id: 0,
2025 name: "T".to_string(),
2026 };
2027 assert_ne!(tv, gp);
2028 }
2029
2030 #[test]
2031 fn alias_display_renders_name_not_body() {
2032 let ty = user_alias("Circle", point_type());
2033 assert_eq!(ty.to_string(), "Circle");
2034 }
2035
2036 #[test]
2037 fn peel_walks_through_alias_to_underlying() {
2038 let inner = Type::Number;
2039 let aliased = user_alias("ID", inner.clone());
2040 assert_eq!(aliased.peel(), &inner);
2041 }
2042
2043 #[test]
2044 fn peel_is_idempotent_through_nested_aliases() {
2045 let nested = user_alias("A", user_alias("B", Type::Number));
2046 assert_eq!(nested.peel(), &Type::Number);
2047 assert_eq!(nested.peel().peel(), &Type::Number);
2048 }
2049
2050 #[test]
2051 fn alias_is_not_structurally_equal_to_underlying() {
2052 let aliased = user_alias("ID", Type::Number);
2053 assert_ne!(aliased, Type::Number);
2054 assert_eq!(aliased.peel(), &Type::Number);
2055 }
2056
2057 #[test]
2058 fn alias_routes_to_underlying_interface() {
2059 let aliased = user_alias("ID", Type::Number);
2060 let (_mangled, _pkg, iface, _args) = aliased.interface_routing().expect("number routes");
2061 assert_eq!(iface, "Number");
2062 }
2063
2064 #[test]
2065 fn union_of_error_alias_collapses_to_error() {
2066 let alias_err = user_alias("Bad", Type::Error);
2067 let t = Type::union(vec![Type::Number, alias_err]);
2068 assert_eq!(t, Type::Error);
2069 }
2070
2071 #[test]
2072 fn union_preserves_distinct_alias_members_for_display() {
2073 let circle = user_alias("Circle", point_type());
2074 let rect = user_alias("Rectangle", Type::Number);
2075 let t = Type::union(vec![circle, rect]);
2076 assert_eq!(t.to_string(), "Rectangle | Circle");
2079 }
2080
2081 #[test]
2082 fn union_dedups_an_alias_against_its_own_body_keeping_the_label() {
2083 let n = user_alias("N", Type::Number);
2084 assert_eq!(Type::union(vec![n.clone(), Type::Number]), n);
2085 assert_eq!(Type::union(vec![Type::Number, n.clone()]), n);
2086 }
2087
2088 #[test]
2089 fn union_dedups_aliases_that_share_one_body() {
2090 let circle = user_alias("Circle", point_type());
2091 let rect = user_alias("Rectangle", point_type());
2092 assert_eq!(Type::union(vec![circle.clone(), rect]), circle);
2093 }
2094
2095 #[test]
2096 fn union_flattens_an_alias_whose_body_is_a_union() {
2097 let nullable = user_alias("MN", Type::union(vec![Type::Number, Type::Null]));
2098 assert_eq!(
2099 Type::union(vec![nullable, Type::Number]),
2100 Type::union(vec![Type::Number, Type::Null])
2101 );
2102 }
2103
2104 #[test]
2105 fn generic_alias_display_includes_args() {
2106 let ty = user_alias_args("Box", vec![Type::Number], Type::Number);
2107 assert_eq!(ty.to_string(), "Box<number>");
2108 }
2109
2110 fn iface(package: &str, name: &str) -> Type {
2111 Type::interface_ref(
2112 Package(package.to_string()),
2113 name,
2114 crate::mangle::package_symbol(package, name),
2115 Vec::new(),
2116 )
2117 }
2118
2119 fn user_alias(name: &str, ty: Type) -> Type {
2120 user_alias_args(name, Vec::new(), ty)
2121 }
2122
2123 fn user_alias_args(name: &str, args: Vec<Type>, ty: Type) -> Type {
2124 Type::alias_ty(
2125 Package::user(),
2126 name,
2127 crate::mangle::package_symbol(crate::mangle::USER_PACKAGE, name),
2128 args,
2129 Box::new(ty),
2130 )
2131 }
2132
2133 #[test]
2134 fn mangled_name_keys_type_identity() {
2135 assert_eq!(iface("main", "Response"), iface("main", "Response"));
2140 let http = iface("submilli:http", "Response");
2141 let main = iface("main", "Response");
2142 assert_ne!(
2143 http, main,
2144 "different package ⇒ different mangled ⇒ distinct"
2145 );
2146 assert_ne!(
2147 http.cmp(&main),
2148 std::cmp::Ordering::Equal,
2149 "Ord must distinguish distinct mangled names"
2150 );
2151 assert_ne!(
2152 iface("main", "Response"),
2153 iface("main", "Other"),
2154 "different names stay distinct"
2155 );
2156 }
2157
2158 #[test]
2159 fn same_name_different_module_not_equal_nor_assignable() {
2160 let root = iface("main", "Logger");
2164 let sibling = Type::InterfaceRef {
2165 mangled: crate::mangle::package_module_symbol("main", "util", "Logger"),
2166 package: Package::user(),
2167 name: "Logger".to_string(),
2168 args: Vec::new(),
2169 };
2170 assert_ne!(root, sibling);
2171 let root2 = iface("main", "Logger");
2173 assert_eq!(root, root2);
2174 }
2175
2176 #[test]
2177 fn union_dedups_interface_refs_by_mangled() {
2178 let same = Type::union(vec![iface("main", "Response"), iface("main", "Response")]);
2180 assert!(
2181 !matches!(same, Type::Union(_)),
2182 "identical refs collapse to one member, got {same:?}"
2183 );
2184 let distinct = Type::union(vec![
2185 iface("submilli:http", "Response"),
2186 iface("main", "Response"),
2187 ]);
2188 assert!(
2189 matches!(distinct, Type::Union(ref ms) if ms.len() == 2),
2190 "distinct-package refs stay a 2-member union, got {distinct:?}"
2191 );
2192 }
2193
2194 #[test]
2195 fn generic_alias_display_multiple_args() {
2196 let ty = user_alias_args("Pair", vec![Type::String, Type::Number], Type::Number);
2197 assert_eq!(ty.to_string(), "Pair<string, number>");
2198 }
2199}