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 #[serde(default)]
187 pub string_keyof_includes_number: bool,
188 #[serde(default = "string_index_key")]
189 pub key: Box<Type>,
190 pub value: Box<Type>,
191 pub readonly: bool,
192}
193
194fn string_index_key() -> Box<Type> {
195 Box::new(Type::String)
196}
197
198impl IndexSignature {
199 pub fn map_types(&self, mut transform: impl FnMut(&Type) -> Type) -> Self {
200 Self {
201 string_keyof_includes_number: self.string_keyof_includes_number,
202 key: Box::new(transform(&self.key)),
203 value: Box::new(transform(&self.value)),
204 readonly: self.readonly,
205 }
206 }
207
208 pub fn try_map_types<E>(
210 &self,
211 mut transform: impl FnMut(&Type) -> Result<Type, E>,
212 ) -> Result<Self, E> {
213 Ok(Self {
214 string_keyof_includes_number: self.string_keyof_includes_number,
215 key: Box::new(transform(&self.key)?),
216 value: Box::new(transform(&self.value)?),
217 readonly: self.readonly,
218 })
219 }
220
221 pub fn read_ty(&self) -> Type {
227 (*self.value).clone()
228 }
229}
230
231#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
232pub struct TypePredicate {
233 pub parameter_index: u32,
234 pub asserted_type: Type,
235}
236
237#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
240pub struct TupleType {
241 pub elements: Vec<Type>,
242 pub optional: usize,
243}
244
245impl TupleType {
246 pub fn required_len(&self) -> usize {
247 self.elements.len().saturating_sub(self.optional)
248 }
249
250 pub fn map(&self, transform: impl FnMut(&Type) -> Type) -> Self {
251 Self {
252 elements: self.elements.iter().map(transform).collect(),
253 optional: self.optional,
254 }
255 }
256
257 pub fn try_map<E>(&self, transform: impl FnMut(&Type) -> Result<Type, E>) -> Result<Self, E> {
258 Ok(Self {
259 elements: self
260 .elements
261 .iter()
262 .map(transform)
263 .collect::<Result<_, _>>()?,
264 optional: self.optional,
265 })
266 }
267}
268
269impl From<Vec<Type>> for TupleType {
270 fn from(elements: Vec<Type>) -> Self {
271 Self {
272 elements,
273 optional: 0,
274 }
275 }
276}
277
278impl std::ops::Deref for TupleType {
279 type Target = [Type];
280 fn deref(&self) -> &Self::Target {
281 &self.elements
282 }
283}
284
285impl IntoIterator for TupleType {
286 type Item = Type;
287 type IntoIter = std::vec::IntoIter<Type>;
288 fn into_iter(self) -> Self::IntoIter {
289 self.elements.into_iter()
290 }
291}
292
293impl<'a> IntoIterator for &'a TupleType {
294 type Item = &'a Type;
295 type IntoIter = std::slice::Iter<'a, Type>;
296 fn into_iter(self) -> Self::IntoIter {
297 self.elements.iter()
298 }
299}
300
301#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
302pub enum Type {
303 Number,
304 NumberLiteral(LiteralF64),
306 BigInt,
307 BigIntLiteral(String),
311 String,
312 StringLiteral(String),
313 TemplateLiteral {
314 parts: Vec<String>,
315 substitutions: Vec<Type>,
316 },
317 Uint8Array,
318 Boolean,
319 BooleanLiteral(bool),
323 Null,
324 Undefined,
326 Void,
327 Unknown,
330 Function {
331 params: Vec<Type>,
332 optional: usize,
334 ret: Box<Type>,
335 predicate: Option<Box<TypePredicate>>,
338 has_rest: bool,
340 },
341 Object {
343 fields: BTreeMap<String, ObjectField>,
344 index: Option<IndexSignature>,
345 },
346 Array(Box<Type>),
347 Tuple(TupleType),
350 Readonly(Box<Type>),
357 Error,
359 Never,
361 TypeVar(String),
364 GenericParam {
367 id: u32,
368 name: String,
369 },
370 Refined {
373 original: Box<Type>,
374 ty: Box<Type>,
375 },
376 InterfaceRef {
382 mangled: MangledName,
383 package: Package,
384 name: String,
385 args: Vec<Type>,
386 },
387 ClassRef {
393 mangled: MangledName,
394 package: Package,
395 name: String,
396 args: Vec<Type>,
397 },
398 NumberEnum {
404 mangled: MangledName,
405 package: Package,
406 name: String,
407 member: Option<EnumMember<LiteralF64>>,
408 },
409 StringEnum {
410 mangled: MangledName,
411 package: Package,
412 name: String,
413 member: Option<EnumMember<String>>,
414 },
415 Union(Vec<Type>),
418 Alias {
422 mangled: MangledName,
423 package: Package,
424 name: String,
425 args: Vec<Type>,
426 ty: Box<Type>,
427 },
428 AliasRef {
440 mangled: MangledName,
441 package: Package,
442 name: String,
443 args: Vec<Type>,
444 },
445 NonPrimitive,
449 KeyOf(Box<Type>),
454 IndexedAccess {
459 object: Box<Type>,
460 index: Box<Type>,
461 },
462}
463
464impl Type {
465 pub fn tuple(elements: Vec<Type>) -> Self {
466 Self::Tuple(elements.into())
467 }
468
469 pub fn is_regexp_match_array(&self) -> bool {
470 matches!(self.peel(), Self::InterfaceRef { mangled, .. }
471 if *mangled == crate::mangle::prelude("RegExpMatchArray")
472 || *mangled == crate::mangle::prelude("RegExpExecArray"))
473 }
474
475 pub fn holds_undefined(&self) -> bool {
482 match self.peel() {
483 Type::Undefined | Type::Void | Type::Unknown => true,
484 Type::Union(members) => members.iter().any(Type::holds_undefined),
485 _ => false,
486 }
487 }
488
489 pub fn has_type_parameter_member(&self) -> bool {
493 match self.peel() {
494 Type::GenericParam { .. } | Type::TypeVar(_) => true,
495 form if form.is_type_operator() => true,
496 Type::Union(members) => members.iter().any(Type::has_type_parameter_member),
497 _ => false,
498 }
499 }
500
501 pub fn names_undefined(&self) -> bool {
508 fn has_unknown(ty: &Type) -> bool {
509 match ty.peel() {
510 Type::Unknown => true,
511 Type::Union(members) => members.iter().any(has_unknown),
512 _ => false,
513 }
514 }
515 fn names(ty: &Type) -> bool {
516 match ty.peel() {
517 Type::Undefined | Type::Void => true,
518 Type::Union(members) => members.iter().any(names),
519 _ => false,
520 }
521 }
522 names(self) && !has_unknown(self)
523 }
524
525 pub fn regexp_element() -> Type {
526 Type::union(vec![Type::String, Type::Undefined])
527 }
528
529 pub fn indexed_access(object: Type, index: Type) -> Type {
531 Type::IndexedAccess {
532 object: Box::new(object),
533 index: Box::new(index),
534 }
535 }
536
537 pub fn indexes_by_name(&self) -> bool {
541 let refined = match self.without_aliases() {
546 Type::Refined { original, ty } => {
547 let erased = match original.without_aliases() {
548 Type::Union(members) => members.iter().all(|member| {
549 member.lowers_to_erased_slot()
550 || matches!(member.peel(), Type::Null | Type::Undefined)
551 }),
552 original => original.lowers_to_erased_slot(),
553 };
554 erased && !ty.indexes_by_element()
555 }
556 _ => false,
557 };
558 refined || self.is_structural_object() || self.lowers_to_erased_slot()
559 }
560
561 fn indexes_by_element(&self) -> bool {
564 match self.peel() {
565 Type::Union(members) => members.iter().all(Type::indexes_by_element),
566 Type::Array(_)
567 | Type::Tuple(_)
568 | Type::Uint8Array
569 | Type::String
570 | Type::StringLiteral(_)
571 | Type::TemplateLiteral { .. } => true,
572 _ => false,
573 }
574 }
575
576 pub fn is_structural_object(&self) -> bool {
578 if self.is_regexp_match_array() {
579 return false;
580 }
581 match self.peel() {
582 Self::Object { .. } | Self::InterfaceRef { .. } => true,
583 Self::Union(members) => members.iter().all(Self::is_structural_object),
584 _ => false,
585 }
586 }
587
588 pub fn interface_ref(
593 package: Package,
594 name: impl Into<String>,
595 mangled: MangledName,
596 args: Vec<Type>,
597 ) -> Type {
598 Type::InterfaceRef {
599 mangled,
600 package,
601 name: name.into(),
602 args,
603 }
604 }
605
606 pub fn class_ref(
609 package: Package,
610 name: impl Into<String>,
611 mangled: MangledName,
612 args: Vec<Type>,
613 ) -> Type {
614 Type::ClassRef {
615 mangled,
616 package,
617 name: name.into(),
618 args,
619 }
620 }
621
622 pub fn number_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
623 Type::NumberEnum {
624 mangled,
625 package,
626 name: name.into(),
627 member: None,
628 }
629 }
630
631 pub fn string_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
632 Type::StringEnum {
633 mangled,
634 package,
635 name: name.into(),
636 member: None,
637 }
638 }
639
640 pub fn with_enum_member(
645 &self,
646 member_name: &str,
647 value: EnumValue,
648 member_count: usize,
649 ) -> Type {
650 if member_count < 2 {
651 return self.without_enum_member();
652 }
653 match (self.clone(), value) {
654 (
655 Type::NumberEnum {
656 mangled,
657 package,
658 name,
659 ..
660 },
661 EnumValue::Number(value),
662 ) => Type::NumberEnum {
663 mangled,
664 package,
665 name,
666 member: Some(EnumMember::new(member_name, value, member_count)),
667 },
668 (
669 Type::StringEnum {
670 mangled,
671 package,
672 name,
673 ..
674 },
675 EnumValue::String(value),
676 ) => Type::StringEnum {
677 mangled,
678 package,
679 name,
680 member: Some(EnumMember::new(member_name, value, member_count)),
681 },
682 (ty, _) => ty,
683 }
684 }
685
686 pub fn without_enum_member(&self) -> Type {
688 match self {
689 Type::NumberEnum {
690 mangled,
691 package,
692 name,
693 member: Some(_),
694 } => Type::number_enum(package.clone(), name.clone(), mangled.clone()),
695 Type::StringEnum {
696 mangled,
697 package,
698 name,
699 member: Some(_),
700 } => Type::string_enum(package.clone(), name.clone(), mangled.clone()),
701 _ => self.clone(),
702 }
703 }
704
705 pub fn is_literal_type(&self) -> bool {
708 matches!(
709 self,
710 Type::NumberLiteral(_)
711 | Type::StringLiteral(_)
712 | Type::BooleanLiteral(_)
713 | Type::BigIntLiteral(_)
714 ) || self.is_enum_member()
715 }
716
717 pub fn is_enum_member(&self) -> bool {
719 self.enum_member_name().is_some()
720 }
721
722 pub fn enum_member_name(&self) -> Option<(&MangledName, &str, usize)> {
724 match self {
725 Type::NumberEnum {
726 mangled,
727 member: Some(member),
728 ..
729 } => Some((mangled, &member.name, member.member_count)),
730 Type::StringEnum {
731 mangled,
732 member: Some(member),
733 ..
734 } => Some((mangled, &member.name, member.member_count)),
735 _ => None,
736 }
737 }
738
739 pub fn enum_member_value(&self) -> Option<EnumValue> {
741 match self.peel() {
742 Type::NumberEnum {
743 member: Some(member),
744 ..
745 } => Some(EnumValue::Number(member.value)),
746 Type::StringEnum {
747 member: Some(member),
748 ..
749 } => Some(EnumValue::String(member.value.clone())),
750 _ => None,
751 }
752 }
753
754 pub fn alias_ref(
755 package: Package,
756 name: impl Into<String>,
757 mangled: MangledName,
758 args: Vec<Type>,
759 ) -> Type {
760 Type::AliasRef {
761 mangled,
762 package,
763 name: name.into(),
764 args,
765 }
766 }
767
768 pub fn alias_ty(
769 package: Package,
770 name: impl Into<String>,
771 mangled: MangledName,
772 args: Vec<Type>,
773 ty: Box<Type>,
774 ) -> Type {
775 Type::Alias {
776 mangled,
777 package,
778 name: name.into(),
779 args,
780 ty,
781 }
782 }
783
784 pub fn prelude_interface(name: impl Into<String>, args: Vec<Type>) -> Type {
789 let name = name.into();
790 let mangled = crate::mangle::prelude(&name);
791 Type::interface_ref(Package::prelude(), name, mangled, args)
792 }
793
794 pub fn prelude_error_class() -> Type {
797 Type::class_ref(
798 Package::prelude(),
799 "Error",
800 crate::mangle::prelude("Error"),
801 Vec::new(),
802 )
803 }
804
805 pub fn prelude_range_error_class() -> Type {
808 Type::class_ref(
809 Package::prelude(),
810 "RangeError",
811 crate::mangle::prelude("RangeError"),
812 Vec::new(),
813 )
814 }
815
816 pub fn lowers_to_erased_slot(&self) -> bool {
820 let ty = self.peel();
821 matches!(ty, Type::TypeVar(_) | Type::GenericParam { .. }) || ty.is_type_operator()
822 }
823
824 pub fn is_utility_application(&self) -> bool {
828 matches!(self, Type::AliasRef { mangled, .. }
829 if crate::typechecker::infer::utility_types::utility(mangled).is_some())
830 }
831
832 pub fn is_type_operator(&self) -> bool {
836 matches!(self, Type::KeyOf(_) | Type::IndexedAccess { .. })
837 || crate::typechecker::infer::utility_types::deferred_application(self).is_some()
838 }
839
840 pub fn without_aliases(&self) -> &Type {
842 match self {
843 Type::Alias { ty, .. } => ty.without_aliases(),
844 _ => self,
845 }
846 }
847
848 pub fn holds_every_value(&self) -> bool {
852 let members: &[Type] = match self.without_aliases() {
853 Type::Unknown => return true,
854 Type::Union(members) => members,
855 _ => return false,
856 };
857 members.iter().any(Type::is_empty_object)
860 && members
861 .iter()
862 .any(|m| matches!(m.without_aliases(), Type::Null))
863 && members
864 .iter()
865 .any(|m| matches!(m.without_aliases(), Type::Undefined))
866 }
867
868 pub fn refinement_pieces(&self) -> Option<Vec<Type>> {
871 let Type::Refined { original, ty } = self.without_aliases() else {
872 return None;
873 };
874 let Type::Union(parts) = ty.peel() else {
875 return None;
876 };
877 Some(
878 parts
879 .iter()
880 .map(|part| match part.peel() {
881 Type::Null | Type::Undefined | Type::Void => part.clone(),
882 _ => Type::Refined {
883 original: original.clone(),
884 ty: Box::new(part.clone()),
885 },
886 })
887 .collect(),
888 )
889 }
890
891 pub fn refines_type_parameter(&self) -> bool {
893 matches!(self.without_aliases(), Type::Refined { original, .. }
894 if matches!(original.peel(), Type::GenericParam { .. }))
895 }
896
897 pub fn peel(&self) -> &Type {
898 let mut t = self;
899 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) = t {
900 t = ty;
901 }
902 t
903 }
904
905 pub fn any_object() -> Type {
909 Type::Object {
910 index: Some(IndexSignature {
911 string_keyof_includes_number: false,
912 key: Box::new(crate::Type::String),
913 value: Box::new(Type::Unknown),
914 readonly: false,
915 }),
916 fields: BTreeMap::new(),
917 }
918 }
919
920 pub fn empty_object() -> Type {
922 Type::Object {
923 fields: BTreeMap::new(),
924 index: None,
925 }
926 }
927
928 pub fn is_empty_object(&self) -> bool {
933 let mut t = self;
934 while let Type::Alias { ty, .. } | Type::Readonly(ty) = t {
935 t = ty;
936 }
937 matches!(t, Type::Object { fields, index: None } if fields.is_empty())
938 }
939
940 pub fn lowered(&self) -> &Type {
946 let peeled = self.peel();
947 if peeled.is_empty_object() || matches!(peeled, Type::NonPrimitive) {
948 return &Type::Unknown;
949 }
950 peeled
951 }
952
953 pub fn peel_preserving_readonly(&self) -> &Type {
956 let mut t = self;
957 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } = t {
958 t = ty;
959 }
960 t
961 }
962
963 pub fn is_readonly_array(&self) -> bool {
966 matches!(self.peel_preserving_readonly(), Type::Readonly(_))
967 }
968
969 pub fn rest_element(&self) -> Option<&Type> {
973 match self {
974 Type::Array(element) => Some(element),
975 Type::Readonly(inner) => match inner.as_ref() {
976 Type::Array(element) => Some(element),
977 _ => None,
978 },
979 _ => None,
980 }
981 }
982
983 pub fn rest_array_ignoring_readonly(&self) -> &Type {
988 match self {
989 Type::Readonly(inner) if matches!(inner.as_ref(), Type::Array(_)) => inner,
990 _ => self,
991 }
992 }
993
994 pub fn holds_no_element(&self) -> bool {
997 match self.peel() {
998 Type::Array(element) => {
999 matches!(element.peel(), Type::Never) || element.holds_no_element()
1000 }
1001 _ => false,
1002 }
1003 }
1004
1005 pub fn is_array_like_union(&self) -> bool {
1008 match self.peel() {
1009 Type::Union(members) => members
1010 .iter()
1011 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_))),
1012 _ => false,
1013 }
1014 }
1015
1016 pub fn array_like_union_element(&self) -> Option<Type> {
1021 let Type::Union(members) = self.peel() else {
1022 return None;
1023 };
1024 let elements = members
1025 .iter()
1026 .map(|member| match member.peel() {
1027 Type::Array(element) => Some((**element).clone()),
1028 Type::Tuple(positions) => Some(Type::union(positions.elements.clone())),
1029 _ => None,
1030 })
1031 .collect::<Option<Vec<_>>>()?;
1032 Some(Type::union(elements))
1033 }
1034
1035 pub fn string_or_array_union_arrays(&self) -> Option<Type> {
1040 let Type::Union(members) = self.peel() else {
1041 return None;
1042 };
1043 let (strings, arrays): (Vec<Type>, Vec<Type>) = members
1044 .iter()
1045 .cloned()
1046 .partition(|member| member.peel().is_string_shaped());
1047 let all_arrays = arrays
1048 .iter()
1049 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_)));
1050 (!strings.is_empty() && !arrays.is_empty() && all_arrays).then(|| Type::union(arrays))
1051 }
1052
1053 pub fn array_like_union_view(&self) -> Option<Type> {
1056 self.array_like_union_element()
1057 .map(|element| Type::Array(Box::new(element)))
1058 }
1059
1060 pub fn is_void(&self) -> bool {
1071 matches!(self.peel(), Type::Void)
1072 }
1073
1074 pub fn function_arity_fits(actual: usize, expected: usize, has_rest: bool) -> bool {
1080 actual == expected || (!has_rest && actual < expected)
1081 }
1082
1083 pub fn carries_void(&self) -> bool {
1093 match self.peel() {
1094 Type::Void => true,
1095 Type::Union(members) => members.iter().any(|m| matches!(m.peel(), Type::Void)),
1096 _ => false,
1097 }
1098 }
1099
1100 pub fn primitive_behavior(&self) -> &Type {
1103 match self.peel() {
1104 Type::NumberEnum { .. } => &Type::Number,
1105 Type::StringEnum { .. } | Type::TemplateLiteral { .. } => &Type::String,
1106 Type::BooleanLiteral(_) => &Type::Boolean,
1107 Type::BigIntLiteral(_) => &Type::BigInt,
1108 Type::Union(members)
1109 if !members.is_empty()
1110 && members.iter().all(|member| {
1111 matches!(
1112 member.primitive_behavior(),
1113 Type::Number | Type::NumberLiteral(_)
1114 )
1115 }) =>
1116 {
1117 &Type::Number
1118 }
1119 Type::Union(members)
1120 if !members.is_empty()
1121 && members
1122 .iter()
1123 .all(|member| matches!(member.primitive_behavior(), Type::BigInt)) =>
1124 {
1125 &Type::BigInt
1126 }
1127 Type::Union(members)
1128 if !members.is_empty() && members.iter().all(Type::is_string_shaped) =>
1129 {
1130 &Type::String
1131 }
1132 ty => ty,
1133 }
1134 }
1135
1136 pub fn is_bigint(&self) -> bool {
1139 matches!(self.primitive_behavior(), Type::BigInt)
1140 }
1141
1142 pub fn widen_literal(&self) -> Type {
1155 match self {
1156 Type::NumberLiteral(_) => Type::Number,
1157 Type::StringLiteral(_) => Type::String,
1158 Type::BooleanLiteral(_) => Type::Boolean,
1159 Type::BigIntLiteral(_) => Type::BigInt,
1160 _ if self.is_enum_member() => self.without_enum_member(),
1161 Type::Union(members) => Type::union(without_members_of_empty_object(
1165 members.iter().map(Type::widen_literal).collect(),
1166 )),
1167 _ => self.clone(),
1168 }
1169 }
1170
1171 pub fn literal_base(&self) -> Option<Type> {
1175 let widened = self.widen_literal();
1176 (widened != *self && matches!(widened, Type::Number | Type::String | Type::Boolean))
1177 .then_some(widened)
1178 }
1179
1180 pub fn is_string_shaped(&self) -> bool {
1189 match self.peel() {
1190 Type::String
1191 | Type::TemplateLiteral { .. }
1192 | Type::StringLiteral(_)
1193 | Type::StringEnum { .. } => true,
1194 Type::Union(members) => members.iter().all(Type::is_string_shaped),
1195 _ => false,
1196 }
1197 }
1198
1199 pub fn contains_string(&self) -> bool {
1203 match self.peel() {
1204 Type::String
1205 | Type::TemplateLiteral { .. }
1206 | Type::StringLiteral(_)
1207 | Type::StringEnum { .. } => true,
1208 Type::Union(members) => members.iter().any(Type::contains_string),
1209 _ => false,
1210 }
1211 }
1212
1213 pub fn any_member(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1218 match self.peel() {
1219 Type::Union(members) => members.iter().any(|member| member.any_member(leaf)),
1220 member => leaf(member),
1221 }
1222 }
1223
1224 pub fn all_members(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1227 !self.any_member(&|member| !leaf(member))
1228 }
1229
1230 pub fn spells_null(&self) -> bool {
1233 self.any_member(&|member| matches!(member, Type::Null))
1234 }
1235
1236 pub fn spells_undefined(&self) -> bool {
1239 self.any_member(&|member| matches!(member, Type::Undefined | Type::Void))
1240 }
1241
1242 pub fn stringifies_to_undefined(leaf: &Type) -> bool {
1247 matches!(
1249 leaf.lowered(),
1250 Type::Undefined
1251 | Type::Void
1252 | Type::Unknown
1253 | Type::GenericParam { .. }
1254 | Type::TypeVar(_)
1255 | Type::IndexedAccess { .. }
1256 | Type::Function { .. }
1257 )
1258 }
1259
1260 pub fn union(members: Vec<Type>) -> Type {
1277 if members.iter().any(|m| matches!(m.peel(), Type::Error)) {
1278 return Type::Error;
1279 }
1280 if members.iter().any(|m| matches!(m.peel(), Type::Unknown)) {
1282 return Type::Unknown;
1283 }
1284 let members: Vec<Type> = members
1286 .into_iter()
1287 .filter(|m| !matches!(m.peel(), Type::Never))
1288 .collect();
1289 let mut flat: Vec<Type> = Vec::new();
1290 for m in members {
1291 match m.without_aliases() {
1292 Type::Union(inner) => flat.extend(inner.iter().cloned()),
1293 _ => flat.push(m),
1294 }
1295 }
1296 let alias_rank = |t: &Type| u8::from(!matches!(t, Type::Alias { .. }));
1300 flat.sort_by(|a, b| {
1301 a.without_aliases()
1302 .cmp(b.without_aliases())
1303 .then_with(|| alias_rank(a).cmp(&alias_rank(b)))
1304 });
1305 flat.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1306 fold_boolean_literals(&mut flat);
1307 fold_enum_members(&mut flat);
1308 let refines_a_member = |member: &Type| {
1310 matches!(member, Type::Refined { original, .. }
1311 if flat.iter().any(|other| other.without_aliases() == original.without_aliases()))
1312 };
1313 let kept: Vec<Type> = flat
1314 .iter()
1315 .filter(|member| !refines_a_member(member))
1316 .cloned()
1317 .collect();
1318 flat = kept;
1319 if flat.iter().any(Type::is_empty_object) {
1321 flat.retain(|member| !matches!(member.peel(), Type::NonPrimitive));
1322 }
1323 if flat.len() > 1 {
1324 return Type::Union(flat);
1325 }
1326 match flat.into_iter().next() {
1327 Some(member) => member,
1328 None => Type::Never,
1329 }
1330 }
1331
1332 pub(crate) fn primitive_union_members(&self) -> Option<Vec<Type>> {
1335 let Type::Union(members) = self.peel() else {
1336 return None;
1337 };
1338 let mut primitives = std::collections::BTreeSet::new();
1339 for member in members {
1340 primitives.insert(match member.primitive_behavior() {
1341 Type::Number | Type::NumberLiteral(_) => Type::Number,
1342 Type::String | Type::StringLiteral(_) => Type::String,
1343 Type::Boolean => Type::Boolean,
1344 Type::BigInt => Type::BigInt,
1345 _ => return None,
1346 });
1347 }
1348 (!primitives.is_empty()).then(|| primitives.into_iter().collect())
1349 }
1350
1351 pub fn interface_routing(&self) -> Option<(MangledName, &str, &str, Vec<Type>)> {
1356 let prelude = crate::mangle::PRELUDE_PACKAGE;
1357 match self.primitive_behavior() {
1358 Type::Number | Type::NumberLiteral(_) => Some((
1363 crate::mangle::prelude("Number"),
1364 prelude,
1365 "Number",
1366 Vec::new(),
1367 )),
1368 Type::BigInt => Some((
1369 crate::mangle::prelude("BigInt"),
1370 prelude,
1371 "BigInt",
1372 Vec::new(),
1373 )),
1374 Type::Boolean => Some((
1375 crate::mangle::prelude("Boolean"),
1376 prelude,
1377 "Boolean",
1378 Vec::new(),
1379 )),
1380 Type::String | Type::StringLiteral(_) => Some((
1381 crate::mangle::prelude("String"),
1382 prelude,
1383 "String",
1384 Vec::new(),
1385 )),
1386 Type::Uint8Array => Some((
1387 crate::mangle::prelude("Uint8Array"),
1388 prelude,
1389 "Uint8Array",
1390 Vec::new(),
1391 )),
1392 Type::Array(elem) => Some((
1393 crate::mangle::prelude("Array"),
1394 prelude,
1395 "Array",
1396 vec![(**elem).clone()],
1397 )),
1398 Type::Tuple(elements) => Some((
1402 crate::mangle::prelude("Array"),
1403 prelude,
1404 "Array",
1405 vec![Type::union(elements.elements.clone())],
1406 )),
1407 Type::Object { .. }
1408 | Type::NonPrimitive
1409 | Type::TypeVar(_)
1410 | Type::GenericParam { .. } => Some((
1411 crate::mangle::prelude("Object"),
1412 prelude,
1413 "Object",
1414 Vec::new(),
1415 )),
1416 Type::InterfaceRef {
1417 mangled,
1418 package,
1419 name,
1420 args,
1421 ..
1422 }
1423 | Type::ClassRef {
1424 mangled,
1425 package,
1426 name,
1427 args,
1428 ..
1429 } => Some((
1430 mangled.clone(),
1431 package.as_str(),
1432 name.as_str(),
1433 args.clone(),
1434 )),
1435 Type::NumberEnum { .. } | Type::StringEnum { .. } => Some((
1437 crate::mangle::prelude("Object"),
1438 prelude,
1439 "Object",
1440 Vec::new(),
1441 )),
1442 Type::Unknown => Some((
1444 crate::mangle::prelude("Object"),
1445 prelude,
1446 "Object",
1447 Vec::new(),
1448 )),
1449 _ => None,
1450 }
1451 }
1452}
1453
1454pub(crate) fn shown_beside_optional_marker(ty: &Type) -> Type {
1460 match ty.peel() {
1461 Type::Union(members) => Type::union(
1462 members
1463 .iter()
1464 .filter(|member| !matches!(member.peel(), Type::Undefined))
1465 .cloned()
1466 .collect(),
1467 ),
1468 _ => ty.clone(),
1469 }
1470}
1471
1472pub(crate) fn escape_string_literal(s: &str) -> String {
1475 let mut out = String::with_capacity(s.len());
1476 let mut chars = crate::literal_units::literal_chars(s).peekable();
1477 while let Some(c) = chars.next() {
1478 let c = match c {
1479 Ok(c) => c,
1480 Err(lone) => {
1481 out.push_str(&format!("\\u{lone:04X}"));
1482 continue;
1483 }
1484 };
1485 match c {
1486 '"' => out.push_str("\\\""),
1487 '\\' => out.push_str("\\\\"),
1488 '\n' => out.push_str("\\n"),
1489 '\r' => out.push_str("\\r"),
1490 '\t' => out.push_str("\\t"),
1491 '\u{8}' => out.push_str("\\b"),
1492 '\u{b}' => out.push_str("\\v"),
1493 '\u{c}' => out.push_str("\\f"),
1494 '\0' if chars
1496 .peek()
1497 .is_some_and(|next| next.is_ok_and(|d| d.is_ascii_digit())) =>
1498 {
1499 out.push_str("\\x00");
1500 }
1501 '\0' => out.push_str("\\0"),
1502 '\u{0}'..='\u{1f}' | '\u{85}' | '\u{2028}' | '\u{2029}' => {
1503 out.push_str(&format!("\\u{:04X}", u32::from(c)));
1504 }
1505 c => out.push(c),
1506 }
1507 }
1508 out
1509}
1510
1511pub fn bigint_literal_type(digits: &str) -> Type {
1514 Type::BigIntLiteral(canonical_bigint_digits(digits))
1515}
1516
1517pub fn negate_bigint_digits(digits: &str) -> String {
1519 let canonical = canonical_bigint_digits(digits);
1520 if canonical == "0" {
1521 return canonical;
1522 }
1523 match canonical.strip_prefix('-') {
1524 Some(magnitude) => magnitude.to_string(),
1525 None => format!("-{canonical}"),
1526 }
1527}
1528
1529fn canonical_bigint_digits(digits: &str) -> String {
1531 let (sign, magnitude) = match digits.strip_prefix('-') {
1532 Some(magnitude) => ("-", magnitude),
1533 None => ("", digits),
1534 };
1535 match magnitude.trim_start_matches('0') {
1536 "" => "0".to_string(),
1537 trimmed => format!("{sign}{trimmed}"),
1538 }
1539}
1540
1541pub fn without_members_of_empty_object(members: Vec<Type>) -> Vec<Type> {
1546 if !members.iter().any(Type::is_empty_object) {
1547 return members;
1548 }
1549 members
1550 .into_iter()
1551 .filter(|member| member.is_empty_object() || may_hold_values_outside_empty_object(member))
1552 .collect()
1553}
1554
1555fn may_hold_values_outside_empty_object(ty: &Type) -> bool {
1558 match ty.peel() {
1559 Type::Null
1560 | Type::Undefined
1561 | Type::Void
1562 | Type::Unknown
1563 | Type::GenericParam { .. }
1564 | Type::TypeVar(_) => true,
1565 Type::Union(members) => members.iter().any(may_hold_values_outside_empty_object),
1566 _ => false,
1567 }
1568}
1569
1570fn fold_boolean_literals(members: &mut Vec<Type>) {
1574 let is_boolean = |m: &Type| matches!(m.without_aliases(), Type::Boolean);
1575 let is_literal = |m: &Type| matches!(m.without_aliases(), Type::BooleanLiteral(_));
1576 let literals = members.iter().filter(|m| is_literal(m)).count();
1577 let has_boolean = members.iter().any(is_boolean);
1578 if literals == 0 || (literals == 1 && !has_boolean) {
1579 return;
1580 }
1581 members.retain(|m| !is_literal(m));
1582 if !has_boolean {
1583 let at = members.partition_point(|m| m.without_aliases() < &Type::Boolean);
1584 members.insert(at, Type::Boolean);
1585 }
1586}
1587
1588fn fold_enum_members(members: &mut Vec<Type>) {
1593 absorb_enums_into_primitives(members);
1594 let complete = complete_enums(members);
1595 if complete.is_empty() {
1596 return;
1597 }
1598 for member in members.iter_mut() {
1599 if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } =
1600 member.without_aliases()
1601 && complete.contains(mangled)
1602 {
1603 *member = member.without_aliases().without_enum_member();
1604 }
1605 }
1606 members.sort_by(|a, b| a.without_aliases().cmp(b.without_aliases()));
1607 members.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1608}
1609
1610fn absorb_enums_into_primitives(members: &mut Vec<Type>) {
1612 let has = |base: &Type| members.iter().any(|m| m.without_aliases() == base);
1613 let (has_number, has_string) = (has(&Type::Number), has(&Type::String));
1614 members.retain(|m| match m.without_aliases() {
1615 Type::NumberEnum { .. } => !has_number,
1616 Type::StringEnum { .. } => !has_string,
1617 _ => true,
1618 });
1619}
1620
1621fn complete_enums(members: &[Type]) -> BTreeSet<MangledName> {
1624 struct NamedMembers<'a> {
1625 member_count: usize,
1626 names: BTreeSet<&'a str>,
1627 }
1628 let mut named: BTreeMap<&MangledName, NamedMembers> = BTreeMap::new();
1629 let mut whole: BTreeSet<&MangledName> = BTreeSet::new();
1630 for member in members {
1631 let ty = member.without_aliases();
1632 if let Some((mangled, name, member_count)) = ty.enum_member_name() {
1633 named
1634 .entry(mangled)
1635 .or_insert_with(|| NamedMembers {
1636 member_count,
1637 names: BTreeSet::new(),
1638 })
1639 .names
1640 .insert(name);
1641 } else if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } = ty {
1642 whole.insert(mangled);
1643 }
1644 }
1645 named
1646 .into_iter()
1647 .filter(|(mangled, named)| {
1648 whole.contains(mangled) || named.names.len() >= named.member_count
1649 })
1650 .map(|(mangled, _)| mangled.clone())
1651 .collect()
1652}
1653
1654impl Type {
1655 pub fn render_checked(
1656 &self,
1657 limits: crate::rendering::RenderLimits,
1658 ) -> Result<crate::rendering::RenderedText, crate::rendering::RenderError> {
1659 crate::type_rendering::render(self, limits)
1660 }
1661}
1662
1663pub fn union_display_order(members: &[Type]) -> Vec<&Type> {
1667 let mut ordered: Vec<&Type> = members.iter().collect();
1668 ordered.sort_by_key(|m| match m {
1669 Type::Null => 1,
1670 Type::Undefined => 2,
1671 _ => 0,
1672 });
1673 ordered
1674}
1675
1676impl fmt::Display for Type {
1677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1678 match self.render_checked(crate::rendering::RenderLimits::default()) {
1679 Ok(rendered) => f.write_str(&rendered.text),
1680 Err(_) => f.write_str("[diagnostic type unavailable]"),
1681 }
1682 }
1683}
1684
1685#[cfg(test)]
1686mod tests {
1687 use super::{Package, Type};
1688
1689 #[test]
1690 fn tuple_mapping_preserves_optional_positions() {
1691 let tuple = super::TupleType {
1692 elements: vec![Type::NumberLiteral(super::LiteralF64(1.0)), Type::Undefined],
1693 optional: 1,
1694 };
1695 let mapped = tuple.map(Type::widen_literal);
1696 assert_eq!(mapped.optional, 1);
1697 assert_eq!(mapped.required_len(), 1);
1698 assert_eq!(mapped.elements, vec![Type::Number, Type::Undefined]);
1699 }
1700
1701 #[test]
1702 fn optional_fields_read_as_undefined() {
1703 let field = super::ObjectField::optional(Type::String);
1704 assert_eq!(
1705 field.read_ty(),
1706 Type::union(vec![Type::String, Type::Undefined])
1707 );
1708 }
1709
1710 #[test]
1711 fn optional_function_and_tuple_display_preserve_omission() {
1712 let ty = Type::Function {
1713 params: vec![Type::union(vec![Type::String, Type::Undefined])],
1714 optional: 1,
1715 ret: Box::new(Type::Void),
1716 predicate: None,
1717 has_rest: false,
1718 };
1719 assert_eq!(ty.to_string(), "(arg0?: string) => void");
1721 let tuple = Type::Tuple(super::TupleType {
1722 elements: vec![
1723 Type::Number,
1724 Type::union(vec![Type::String, Type::Undefined]),
1725 ],
1726 optional: 1,
1727 });
1728 assert_eq!(tuple.to_string(), "[number, (string | undefined)?]");
1729 }
1730
1731 #[test]
1732 fn atomic_display() {
1733 assert_eq!(Type::Number.to_string(), "number");
1734 assert_eq!(Type::String.to_string(), "string");
1735 assert_eq!(Type::Boolean.to_string(), "boolean");
1736 assert_eq!(Type::Null.to_string(), "null");
1737 assert_eq!(Type::Void.to_string(), "void");
1738 assert_eq!(Type::Error.to_string(), "<error>");
1739 }
1740
1741 #[test]
1742 fn readonly_display_matches_typescript() {
1743 let numbers = Type::Array(Box::new(Type::Number));
1744 let readonly = Type::Readonly(Box::new(numbers.clone()));
1745 assert_eq!(readonly.to_string(), "readonly number[]");
1746 assert_eq!(
1747 Type::Array(Box::new(readonly.clone())).to_string(),
1748 "(readonly number[])[]"
1749 );
1750 assert_eq!(
1751 Type::Readonly(Box::new(Type::Array(Box::new(numbers)))).to_string(),
1752 "readonly number[][]"
1753 );
1754 assert_eq!(
1755 Type::Readonly(Box::new(Type::Tuple(
1756 vec![Type::Number, Type::String].into()
1757 )))
1758 .to_string(),
1759 "readonly [number, string]"
1760 );
1761 assert!(readonly.is_readonly_array());
1762 assert_eq!(readonly.peel(), &Type::Array(Box::new(Type::Number)));
1763 }
1764
1765 #[test]
1766 fn string_literal_display_escapes_like_typescript() {
1767 let cases = [
1769 ("G\"HI\\", r#""G\"HI\\""#),
1770 ("a\nb\r\t", r#""a\nb\r\t""#),
1771 ("\u{8}\u{b}\u{c}", r#""\b\v\f""#),
1772 ("\0x", r#""\0x""#),
1773 ("\u{0}1", r#""\x001""#),
1774 ("\u{7}\u{1b}", r#""\u0007\u001B""#),
1775 ("\u{85}\u{2028}\u{2029}", r#""\u0085\u2028\u2029""#),
1776 ("\u{7f}é", "\"\u{7f}é\""),
1777 ];
1778 for (value, printed) in cases {
1779 assert_eq!(Type::StringLiteral(value.into()).to_string(), printed);
1780 }
1781 }
1782
1783 #[test]
1784 fn structural_equality() {
1785 assert_eq!(Type::Number, Type::Number);
1786 assert_ne!(Type::Number, Type::String);
1787
1788 let a = Type::Function {
1789 params: vec![Type::Number, Type::String],
1790 ret: Box::new(Type::Boolean),
1791 predicate: None,
1792 has_rest: false,
1793 optional: 0,
1794 };
1795 let b = Type::Function {
1796 params: vec![Type::Number, Type::String],
1797 ret: Box::new(Type::Boolean),
1798 predicate: None,
1799 has_rest: false,
1800 optional: 0,
1801 };
1802 let c = Type::Function {
1803 params: vec![Type::Number],
1804 ret: Box::new(Type::Boolean),
1805 predicate: None,
1806 has_rest: false,
1807 optional: 0,
1808 };
1809 assert_eq!(a, b);
1810 assert_ne!(a, c);
1811 }
1812
1813 #[test]
1814 fn function_display_no_params() {
1815 let t = Type::Function {
1816 params: vec![],
1817 ret: Box::new(Type::Number),
1818 predicate: None,
1819 has_rest: false,
1820 optional: 0,
1821 };
1822 assert_eq!(t.to_string(), "() => number");
1823 }
1824
1825 #[test]
1826 fn function_display_one_param() {
1827 let t = Type::Function {
1828 params: vec![Type::Number],
1829 ret: Box::new(Type::Boolean),
1830 predicate: None,
1831 has_rest: false,
1832 optional: 0,
1833 };
1834 assert_eq!(t.to_string(), "(arg0: number) => boolean");
1835 }
1836
1837 #[test]
1838 fn function_display_multi_param() {
1839 let t = Type::Function {
1840 params: vec![Type::Number, Type::String],
1841 ret: Box::new(Type::Void),
1842 predicate: None,
1843 has_rest: false,
1844 optional: 0,
1845 };
1846 assert_eq!(t.to_string(), "(arg0: number, arg1: string) => void");
1847 }
1848
1849 #[test]
1850 fn function_display_nested() {
1851 let inner = Type::Function {
1852 params: vec![],
1853 ret: Box::new(Type::Number),
1854 predicate: None,
1855 has_rest: false,
1856 optional: 0,
1857 };
1858 let outer = Type::Function {
1859 params: vec![],
1860 ret: Box::new(inner),
1861 predicate: None,
1862 has_rest: false,
1863 optional: 0,
1864 };
1865 assert_eq!(outer.to_string(), "() => () => number");
1866 }
1867
1868 #[test]
1869 fn clone_roundtrip() {
1870 let t = Type::Function {
1871 params: vec![Type::Number, Type::String],
1872 ret: Box::new(Type::Boolean),
1873 predicate: None,
1874 has_rest: false,
1875 optional: 0,
1876 };
1877 assert_eq!(t.clone(), t);
1878 }
1879
1880 fn point_type() -> Type {
1881 let mut fields = std::collections::BTreeMap::new();
1882 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1883 fields.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1884 Type::Object {
1885 index: None,
1886 fields,
1887 }
1888 }
1889
1890 #[test]
1891 fn object_structural_equality_ignores_field_insertion_order() {
1892 let mut a = std::collections::BTreeMap::new();
1893 a.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1894 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1895
1896 let mut b = std::collections::BTreeMap::new();
1897 b.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1898 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1899
1900 assert_eq!(
1901 Type::Object {
1902 index: None,
1903 fields: a
1904 },
1905 Type::Object {
1906 index: None,
1907 fields: b
1908 }
1909 );
1910 }
1911
1912 #[test]
1913 fn object_inequality_on_different_field_set() {
1914 let mut a = std::collections::BTreeMap::new();
1915 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1916 let mut b = std::collections::BTreeMap::new();
1917 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1918 assert_ne!(
1919 Type::Object {
1920 index: None,
1921 fields: a
1922 },
1923 Type::Object {
1924 index: None,
1925 fields: b
1926 }
1927 );
1928 }
1929
1930 #[test]
1931 fn object_display() {
1932 assert_eq!(point_type().to_string(), "{ x: number; y: number }");
1933 let empty = Type::Object {
1934 index: None,
1935 fields: std::collections::BTreeMap::new(),
1936 };
1937 assert_eq!(empty.to_string(), "{}");
1938 }
1939
1940 #[test]
1941 fn object_display_with_optional_field() {
1942 let mut fields = std::collections::BTreeMap::new();
1943 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1944 fields.insert("y".to_string(), crate::ObjectField::optional(Type::String));
1945 let t = Type::Object {
1946 index: None,
1947 fields,
1948 };
1949 assert_eq!(t.to_string(), "{ x: number; y?: string }");
1950 }
1951
1952 #[test]
1953 fn object_inequality_required_vs_optional() {
1954 let mut a = std::collections::BTreeMap::new();
1955 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1956 let mut b = std::collections::BTreeMap::new();
1957 b.insert("x".to_string(), crate::ObjectField::optional(Type::Number));
1958 assert_ne!(
1959 Type::Object {
1960 index: None,
1961 fields: a
1962 },
1963 Type::Object {
1964 index: None,
1965 fields: b
1966 }
1967 );
1968 }
1969
1970 #[test]
1971 fn array_equality() {
1972 assert_eq!(
1973 Type::Array(Box::new(Type::Number)),
1974 Type::Array(Box::new(Type::Number))
1975 );
1976 assert_ne!(
1977 Type::Array(Box::new(Type::Number)),
1978 Type::Array(Box::new(Type::String))
1979 );
1980 }
1981
1982 #[test]
1983 fn array_display() {
1984 assert_eq!(Type::Array(Box::new(Type::Number)).to_string(), "number[]");
1985 assert_eq!(
1986 Type::Array(Box::new(Type::Array(Box::new(Type::Number)))).to_string(),
1987 "number[][]"
1988 );
1989 }
1990
1991 #[test]
1992 fn nested_object_in_array_display() {
1993 let arr = Type::Array(Box::new(point_type()));
1994 assert_eq!(arr.to_string(), "{ x: number; y: number }[]");
1995 }
1996
1997 #[test]
1998 fn type_var_display_uses_source_name() {
1999 assert_eq!(Type::TypeVar("T".to_string()).to_string(), "T");
2000 assert_eq!(Type::TypeVar("Key".to_string()).to_string(), "Key");
2001 }
2002
2003 #[test]
2004 fn type_var_structural_equality_by_name() {
2005 let t = Type::TypeVar("T".to_string());
2006 let t2 = Type::TypeVar("T".to_string());
2007 let u = Type::TypeVar("U".to_string());
2008 assert_eq!(t, t2);
2009 assert_ne!(t, u);
2010 assert_ne!(t, Type::Number);
2011 }
2012
2013 #[test]
2014 fn type_var_inside_array_and_function_displays_through() {
2015 let arr = Type::Array(Box::new(Type::TypeVar("T".to_string())));
2016 assert_eq!(arr.to_string(), "T[]");
2017 let func = Type::Function {
2018 params: vec![Type::TypeVar("T".to_string())],
2019 ret: Box::new(Type::TypeVar("U".to_string())),
2020 predicate: None,
2021 has_rest: false,
2022 optional: 0,
2023 };
2024 assert_eq!(func.to_string(), "(arg0: T) => U");
2025 }
2026
2027 #[test]
2028 fn generic_param_display_uses_name_not_id() {
2029 let gp = Type::GenericParam {
2030 id: 42,
2031 name: "T".to_string(),
2032 };
2033 assert_eq!(gp.to_string(), "T");
2034 }
2035
2036 #[test]
2037 fn generic_param_equality_by_id() {
2038 let a = Type::GenericParam {
2039 id: 5,
2040 name: "T".to_string(),
2041 };
2042 let b = Type::GenericParam {
2043 id: 5,
2044 name: "T".to_string(),
2045 };
2046 assert_eq!(a, b);
2047
2048 let c = Type::GenericParam {
2049 id: 6,
2050 name: "T".to_string(),
2051 };
2052 assert_ne!(a, c);
2053 }
2054
2055 #[test]
2056 fn union_canonical_dedup() {
2057 let t = Type::union(vec![Type::Number, Type::Null, Type::Number]);
2058 let Type::Union(members) = t else {
2059 panic!("expected Union, got {t:?}");
2060 };
2061 assert_eq!(members, vec![Type::Number, Type::Null]);
2062 }
2063
2064 #[test]
2065 fn union_canonical_order_independent() {
2066 let a = Type::union(vec![Type::Number, Type::Null]);
2067 let b = Type::union(vec![Type::Null, Type::Number]);
2068 assert_eq!(a, b);
2069 }
2070
2071 #[test]
2072 fn union_collapses_to_single_member() {
2073 let t = Type::union(vec![Type::Number, Type::Number]);
2074 assert_eq!(t, Type::Number);
2075 }
2076
2077 #[test]
2078 fn union_flattens_nested() {
2079 let inner = Type::union(vec![Type::String, Type::Null]);
2080 let outer = Type::union(vec![Type::Number, inner]);
2081 let expected = Type::union(vec![Type::Number, Type::String, Type::Null]);
2082 assert_eq!(outer, expected);
2083 }
2084
2085 #[test]
2086 fn union_error_member_collapses_to_error() {
2087 let t = Type::union(vec![Type::Number, Type::Error]);
2088 assert_eq!(t, Type::Error);
2089 }
2090
2091 #[test]
2092 fn union_display_basic() {
2093 let t = Type::union(vec![Type::Number, Type::String]);
2094 assert_eq!(t.to_string(), "number | string");
2095 }
2096
2097 #[test]
2098 fn union_display_with_null() {
2099 let t = Type::union(vec![Type::Number, Type::Null]);
2100 assert_eq!(t.to_string(), "number | null");
2101 let t2 = Type::union(vec![Type::Null, Type::Number]);
2102 assert_eq!(t2.to_string(), "number | null");
2103 }
2104
2105 #[test]
2106 fn union_display_wraps_function_member() {
2107 let fn_ty = Type::Function {
2108 params: vec![],
2109 ret: Box::new(Type::String),
2110 predicate: None,
2111 has_rest: false,
2112 optional: 0,
2113 };
2114 let t = Type::union(vec![fn_ty, Type::Boolean]);
2115 assert_eq!(t.to_string(), "boolean | (() => string)");
2116 }
2117
2118 use super::LiteralF64;
2119
2120 #[test]
2121 fn string_literal_display_is_quoted() {
2122 let t = Type::StringLiteral("north".to_string());
2123 assert_eq!(t.to_string(), "\"north\"");
2124 }
2125
2126 #[test]
2127 fn number_literal_display_is_unquoted_with_whole_number_shape() {
2128 let whole = Type::NumberLiteral(LiteralF64(42.0));
2129 assert_eq!(whole.to_string(), "42");
2130 let frac = Type::NumberLiteral(LiteralF64(4.5));
2131 assert_eq!(frac.to_string(), "4.5");
2132 }
2133
2134 #[test]
2135 fn string_literal_equality_by_value() {
2136 let a = Type::StringLiteral("hi".to_string());
2137 let b = Type::StringLiteral("hi".to_string());
2138 let c = Type::StringLiteral("bye".to_string());
2139 assert_eq!(a, b);
2140 assert_ne!(a, c);
2141 }
2142
2143 #[test]
2144 fn number_literal_equality_by_value() {
2145 let a = Type::NumberLiteral(LiteralF64(42.0));
2146 let b = Type::NumberLiteral(LiteralF64(42.0));
2147 let c = Type::NumberLiteral(LiteralF64(43.0));
2148 assert_eq!(a, b);
2149 assert_ne!(a, c);
2150 }
2151
2152 #[test]
2153 fn union_sorts_literals_next_to_base_types() {
2154 let t = Type::union(vec![
2156 Type::String,
2157 Type::StringLiteral("hi".to_string()),
2158 Type::Number,
2159 Type::NumberLiteral(LiteralF64(42.0)),
2160 ]);
2161 assert_eq!(t.to_string(), "number | 42 | string | \"hi\"");
2162 }
2163
2164 #[test]
2165 fn widen_literal_replaces_a_literal_with_its_base() {
2166 assert_eq!(
2167 Type::NumberLiteral(LiteralF64(1.0)).widen_literal(),
2168 Type::Number
2169 );
2170 assert_eq!(
2171 Type::StringLiteral("hi".to_string()).widen_literal(),
2172 Type::String
2173 );
2174 }
2175
2176 #[test]
2179 fn widen_literal_collapses_a_union_of_literals() {
2180 let t = Type::union(vec![
2181 Type::NumberLiteral(LiteralF64(1.0)),
2182 Type::NumberLiteral(LiteralF64(2.0)),
2183 ]);
2184 assert_eq!(t.widen_literal(), Type::Number);
2185 }
2186
2187 #[test]
2190 fn widen_literal_leaves_everything_else_alone() {
2191 assert_eq!(Type::Number.widen_literal(), Type::Number);
2192 assert_eq!(Type::Boolean.widen_literal(), Type::Boolean);
2193 let arr = Type::Array(Box::new(Type::NumberLiteral(LiteralF64(1.0))));
2194 assert_eq!(arr.widen_literal(), arr);
2195 }
2196
2197 #[test]
2198 fn union_of_two_string_literals_sorts_by_value() {
2199 let t = Type::union(vec![
2200 Type::StringLiteral("south".to_string()),
2201 Type::StringLiteral("north".to_string()),
2202 ]);
2203 assert_eq!(t.to_string(), "\"north\" | \"south\"");
2204 }
2205
2206 #[test]
2207 fn literal_f64_equality_distinguishes_negative_zero_at_bit_level() {
2208 let pos = LiteralF64(0.0);
2211 let neg = LiteralF64(-0.0);
2212 assert_ne!(pos, neg);
2213 }
2214
2215 #[test]
2216 fn type_var_and_generic_param_are_distinct_types() {
2217 let tv = Type::TypeVar("T".to_string());
2220 let gp = Type::GenericParam {
2221 id: 0,
2222 name: "T".to_string(),
2223 };
2224 assert_ne!(tv, gp);
2225 }
2226
2227 #[test]
2228 fn alias_display_renders_name_not_body() {
2229 let ty = user_alias("Circle", point_type());
2230 assert_eq!(ty.to_string(), "Circle");
2231 }
2232
2233 #[test]
2234 fn peel_walks_through_alias_to_underlying() {
2235 let inner = Type::Number;
2236 let aliased = user_alias("ID", inner.clone());
2237 assert_eq!(aliased.peel(), &inner);
2238 }
2239
2240 #[test]
2241 fn peel_is_idempotent_through_nested_aliases() {
2242 let nested = user_alias("A", user_alias("B", Type::Number));
2243 assert_eq!(nested.peel(), &Type::Number);
2244 assert_eq!(nested.peel().peel(), &Type::Number);
2245 }
2246
2247 #[test]
2248 fn alias_is_not_structurally_equal_to_underlying() {
2249 let aliased = user_alias("ID", Type::Number);
2250 assert_ne!(aliased, Type::Number);
2251 assert_eq!(aliased.peel(), &Type::Number);
2252 }
2253
2254 #[test]
2255 fn alias_routes_to_underlying_interface() {
2256 let aliased = user_alias("ID", Type::Number);
2257 let (_mangled, _pkg, iface, _args) = aliased.interface_routing().expect("number routes");
2258 assert_eq!(iface, "Number");
2259 }
2260
2261 #[test]
2262 fn union_of_error_alias_collapses_to_error() {
2263 let alias_err = user_alias("Bad", Type::Error);
2264 let t = Type::union(vec![Type::Number, alias_err]);
2265 assert_eq!(t, Type::Error);
2266 }
2267
2268 #[test]
2269 fn union_preserves_distinct_alias_members_for_display() {
2270 let circle = user_alias("Circle", point_type());
2271 let rect = user_alias("Rectangle", Type::Number);
2272 let t = Type::union(vec![circle, rect]);
2273 assert_eq!(t.to_string(), "Rectangle | Circle");
2276 }
2277
2278 #[test]
2279 fn union_dedups_an_alias_against_its_own_body_keeping_the_label() {
2280 let n = user_alias("N", Type::Number);
2281 assert_eq!(Type::union(vec![n.clone(), Type::Number]), n);
2282 assert_eq!(Type::union(vec![Type::Number, n.clone()]), n);
2283 }
2284
2285 #[test]
2286 fn union_dedups_aliases_that_share_one_body() {
2287 let circle = user_alias("Circle", point_type());
2288 let rect = user_alias("Rectangle", point_type());
2289 assert_eq!(Type::union(vec![circle.clone(), rect]), circle);
2290 }
2291
2292 #[test]
2293 fn union_flattens_an_alias_whose_body_is_a_union() {
2294 let nullable = user_alias("MN", Type::union(vec![Type::Number, Type::Null]));
2295 assert_eq!(
2296 Type::union(vec![nullable, Type::Number]),
2297 Type::union(vec![Type::Number, Type::Null])
2298 );
2299 }
2300
2301 #[test]
2302 fn generic_alias_display_includes_args() {
2303 let ty = user_alias_args("Box", vec![Type::Number], Type::Number);
2304 assert_eq!(ty.to_string(), "Box<number>");
2305 }
2306
2307 fn iface(package: &str, name: &str) -> Type {
2308 Type::interface_ref(
2309 Package(package.to_string()),
2310 name,
2311 crate::mangle::package_symbol(package, name),
2312 Vec::new(),
2313 )
2314 }
2315
2316 fn user_alias(name: &str, ty: Type) -> Type {
2317 user_alias_args(name, Vec::new(), ty)
2318 }
2319
2320 fn user_alias_args(name: &str, args: Vec<Type>, ty: Type) -> Type {
2321 Type::alias_ty(
2322 Package::user(),
2323 name,
2324 crate::mangle::package_symbol(crate::mangle::USER_PACKAGE, name),
2325 args,
2326 Box::new(ty),
2327 )
2328 }
2329
2330 #[test]
2331 fn mangled_name_keys_type_identity() {
2332 assert_eq!(iface("main", "Response"), iface("main", "Response"));
2337 let http = iface("submilli:http", "Response");
2338 let main = iface("main", "Response");
2339 assert_ne!(
2340 http, main,
2341 "different package ⇒ different mangled ⇒ distinct"
2342 );
2343 assert_ne!(
2344 http.cmp(&main),
2345 std::cmp::Ordering::Equal,
2346 "Ord must distinguish distinct mangled names"
2347 );
2348 assert_ne!(
2349 iface("main", "Response"),
2350 iface("main", "Other"),
2351 "different names stay distinct"
2352 );
2353 }
2354
2355 #[test]
2356 fn same_name_different_module_not_equal_nor_assignable() {
2357 let root = iface("main", "Logger");
2361 let sibling = Type::InterfaceRef {
2362 mangled: crate::mangle::package_module_symbol("main", "util", "Logger"),
2363 package: Package::user(),
2364 name: "Logger".to_string(),
2365 args: Vec::new(),
2366 };
2367 assert_ne!(root, sibling);
2368 let root2 = iface("main", "Logger");
2370 assert_eq!(root, root2);
2371 }
2372
2373 #[test]
2374 fn union_dedups_interface_refs_by_mangled() {
2375 let same = Type::union(vec![iface("main", "Response"), iface("main", "Response")]);
2377 assert!(
2378 !matches!(same, Type::Union(_)),
2379 "identical refs collapse to one member, got {same:?}"
2380 );
2381 let distinct = Type::union(vec![
2382 iface("submilli:http", "Response"),
2383 iface("main", "Response"),
2384 ]);
2385 assert!(
2386 matches!(distinct, Type::Union(ref ms) if ms.len() == 2),
2387 "distinct-package refs stay a 2-member union, got {distinct:?}"
2388 );
2389 }
2390
2391 #[test]
2392 fn generic_alias_display_multiple_args() {
2393 let ty = user_alias_args("Pair", vec![Type::String, Type::Number], Type::Number);
2394 assert_eq!(ty.to_string(), "Pair<string, number>");
2395 }
2396}