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}
425
426impl Type {
427 pub fn tuple(elements: Vec<Type>) -> Self {
428 Self::Tuple(elements.into())
429 }
430
431 pub fn is_structural_object(&self) -> bool {
433 match self.peel() {
434 Self::Object { .. } | Self::InterfaceRef { .. } => true,
435 Self::Union(members) => members.iter().all(Self::is_structural_object),
436 _ => false,
437 }
438 }
439
440 pub fn interface_ref(
445 package: Package,
446 name: impl Into<String>,
447 mangled: MangledName,
448 args: Vec<Type>,
449 ) -> Type {
450 Type::InterfaceRef {
451 mangled,
452 package,
453 name: name.into(),
454 args,
455 }
456 }
457
458 pub fn class_ref(
461 package: Package,
462 name: impl Into<String>,
463 mangled: MangledName,
464 args: Vec<Type>,
465 ) -> Type {
466 Type::ClassRef {
467 mangled,
468 package,
469 name: name.into(),
470 args,
471 }
472 }
473
474 pub fn number_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
475 Type::NumberEnum {
476 mangled,
477 package,
478 name: name.into(),
479 member: None,
480 }
481 }
482
483 pub fn string_enum(package: Package, name: impl Into<String>, mangled: MangledName) -> Type {
484 Type::StringEnum {
485 mangled,
486 package,
487 name: name.into(),
488 member: None,
489 }
490 }
491
492 pub fn with_enum_member(
497 &self,
498 member_name: &str,
499 value: EnumValue,
500 member_count: usize,
501 ) -> Type {
502 if member_count < 2 {
503 return self.without_enum_member();
504 }
505 match (self.clone(), value) {
506 (
507 Type::NumberEnum {
508 mangled,
509 package,
510 name,
511 ..
512 },
513 EnumValue::Number(value),
514 ) => Type::NumberEnum {
515 mangled,
516 package,
517 name,
518 member: Some(EnumMember::new(member_name, value, member_count)),
519 },
520 (
521 Type::StringEnum {
522 mangled,
523 package,
524 name,
525 ..
526 },
527 EnumValue::String(value),
528 ) => Type::StringEnum {
529 mangled,
530 package,
531 name,
532 member: Some(EnumMember::new(member_name, value, member_count)),
533 },
534 (ty, _) => ty,
535 }
536 }
537
538 pub fn without_enum_member(&self) -> Type {
540 match self {
541 Type::NumberEnum {
542 mangled,
543 package,
544 name,
545 member: Some(_),
546 } => Type::number_enum(package.clone(), name.clone(), mangled.clone()),
547 Type::StringEnum {
548 mangled,
549 package,
550 name,
551 member: Some(_),
552 } => Type::string_enum(package.clone(), name.clone(), mangled.clone()),
553 _ => self.clone(),
554 }
555 }
556
557 pub fn is_literal_type(&self) -> bool {
560 matches!(
561 self,
562 Type::NumberLiteral(_)
563 | Type::StringLiteral(_)
564 | Type::BooleanLiteral(_)
565 | Type::BigIntLiteral(_)
566 ) || self.is_enum_member()
567 }
568
569 pub fn is_enum_member(&self) -> bool {
571 self.enum_member_name().is_some()
572 }
573
574 pub fn enum_member_name(&self) -> Option<(&MangledName, &str, usize)> {
576 match self {
577 Type::NumberEnum {
578 mangled,
579 member: Some(member),
580 ..
581 } => Some((mangled, &member.name, member.member_count)),
582 Type::StringEnum {
583 mangled,
584 member: Some(member),
585 ..
586 } => Some((mangled, &member.name, member.member_count)),
587 _ => None,
588 }
589 }
590
591 pub fn enum_member_value(&self) -> Option<EnumValue> {
593 match self.peel() {
594 Type::NumberEnum {
595 member: Some(member),
596 ..
597 } => Some(EnumValue::Number(member.value)),
598 Type::StringEnum {
599 member: Some(member),
600 ..
601 } => Some(EnumValue::String(member.value.clone())),
602 _ => None,
603 }
604 }
605
606 pub fn alias_ref(
607 package: Package,
608 name: impl Into<String>,
609 mangled: MangledName,
610 args: Vec<Type>,
611 ) -> Type {
612 Type::AliasRef {
613 mangled,
614 package,
615 name: name.into(),
616 args,
617 }
618 }
619
620 pub fn alias_ty(
621 package: Package,
622 name: impl Into<String>,
623 mangled: MangledName,
624 args: Vec<Type>,
625 ty: Box<Type>,
626 ) -> Type {
627 Type::Alias {
628 mangled,
629 package,
630 name: name.into(),
631 args,
632 ty,
633 }
634 }
635
636 pub fn prelude_interface(name: impl Into<String>, args: Vec<Type>) -> Type {
641 let name = name.into();
642 let mangled = crate::mangle::prelude(&name);
643 Type::interface_ref(Package::prelude(), name, mangled, args)
644 }
645
646 pub fn prelude_error_class() -> Type {
649 Type::class_ref(
650 Package::prelude(),
651 "Error",
652 crate::mangle::prelude("Error"),
653 Vec::new(),
654 )
655 }
656
657 pub fn prelude_range_error_class() -> Type {
660 Type::class_ref(
661 Package::prelude(),
662 "RangeError",
663 crate::mangle::prelude("RangeError"),
664 Vec::new(),
665 )
666 }
667
668 pub fn without_aliases(&self) -> &Type {
670 match self {
671 Type::Alias { ty, .. } => ty.without_aliases(),
672 _ => self,
673 }
674 }
675
676 pub fn holds_every_value(&self) -> bool {
680 let members: &[Type] = match self.without_aliases() {
681 Type::Unknown => return true,
682 Type::Union(members) => members,
683 _ => return false,
684 };
685 members.iter().any(Type::is_empty_object)
688 && members
689 .iter()
690 .any(|m| matches!(m.without_aliases(), Type::Null))
691 && members
692 .iter()
693 .any(|m| matches!(m.without_aliases(), Type::Undefined))
694 }
695
696 pub fn refinement_pieces(&self) -> Option<Vec<Type>> {
699 let Type::Refined { original, ty } = self.without_aliases() else {
700 return None;
701 };
702 let Type::Union(parts) = ty.peel() else {
703 return None;
704 };
705 Some(
706 parts
707 .iter()
708 .map(|part| match part.peel() {
709 Type::Null | Type::Undefined | Type::Void => part.clone(),
710 _ => Type::Refined {
711 original: original.clone(),
712 ty: Box::new(part.clone()),
713 },
714 })
715 .collect(),
716 )
717 }
718
719 pub fn refines_type_parameter(&self) -> bool {
721 matches!(self.without_aliases(), Type::Refined { original, .. }
722 if matches!(original.peel(), Type::GenericParam { .. }))
723 }
724
725 pub fn peel(&self) -> &Type {
726 let mut t = self;
727 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) = t {
728 t = ty;
729 }
730 t
731 }
732
733 pub fn any_object() -> Type {
737 Type::Object {
738 index: Some(IndexSignature {
739 value: Box::new(Type::Unknown),
740 readonly: false,
741 }),
742 fields: BTreeMap::new(),
743 }
744 }
745
746 pub fn empty_object() -> Type {
748 Type::Object {
749 fields: BTreeMap::new(),
750 index: None,
751 }
752 }
753
754 pub fn is_empty_object(&self) -> bool {
759 let mut t = self;
760 while let Type::Alias { ty, .. } | Type::Readonly(ty) = t {
761 t = ty;
762 }
763 matches!(t, Type::Object { fields, index: None } if fields.is_empty())
764 }
765
766 pub fn lowered(&self) -> &Type {
772 let peeled = self.peel();
773 if peeled.is_empty_object() {
774 return &Type::Unknown;
775 }
776 peeled
777 }
778
779 pub fn peel_preserving_readonly(&self) -> &Type {
782 let mut t = self;
783 while let Type::Alias { ty, .. } | Type::Refined { ty, .. } = t {
784 t = ty;
785 }
786 t
787 }
788
789 pub fn is_readonly_array(&self) -> bool {
792 matches!(self.peel_preserving_readonly(), Type::Readonly(_))
793 }
794
795 pub fn rest_element(&self) -> Option<&Type> {
799 match self {
800 Type::Array(element) => Some(element),
801 Type::Readonly(inner) => match inner.as_ref() {
802 Type::Array(element) => Some(element),
803 _ => None,
804 },
805 _ => None,
806 }
807 }
808
809 pub fn rest_array_ignoring_readonly(&self) -> &Type {
814 match self {
815 Type::Readonly(inner) if matches!(inner.as_ref(), Type::Array(_)) => inner,
816 _ => self,
817 }
818 }
819
820 pub fn is_array_like_union(&self) -> bool {
823 match self.peel() {
824 Type::Union(members) => members
825 .iter()
826 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_))),
827 _ => false,
828 }
829 }
830
831 pub fn array_like_union_element(&self) -> Option<Type> {
836 let Type::Union(members) = self.peel() else {
837 return None;
838 };
839 let elements = members
840 .iter()
841 .map(|member| match member.peel() {
842 Type::Array(element) => Some((**element).clone()),
843 Type::Tuple(positions) => Some(Type::union(positions.elements.clone())),
844 _ => None,
845 })
846 .collect::<Option<Vec<_>>>()?;
847 Some(Type::union(elements))
848 }
849
850 pub fn string_or_array_union_arrays(&self) -> Option<Type> {
855 let Type::Union(members) = self.peel() else {
856 return None;
857 };
858 let (strings, arrays): (Vec<Type>, Vec<Type>) = members
859 .iter()
860 .cloned()
861 .partition(|member| member.peel().is_string_shaped());
862 let all_arrays = arrays
863 .iter()
864 .all(|member| matches!(member.peel(), Type::Array(_) | Type::Tuple(_)));
865 (!strings.is_empty() && !arrays.is_empty() && all_arrays).then(|| Type::union(arrays))
866 }
867
868 pub fn array_like_union_view(&self) -> Option<Type> {
871 self.array_like_union_element()
872 .map(|element| Type::Array(Box::new(element)))
873 }
874
875 pub fn is_void(&self) -> bool {
886 matches!(self.peel(), Type::Void)
887 }
888
889 pub fn function_arity_fits(actual: usize, expected: usize, has_rest: bool) -> bool {
895 actual == expected || (!has_rest && actual < expected)
896 }
897
898 pub fn carries_void(&self) -> bool {
908 match self.peel() {
909 Type::Void => true,
910 Type::Union(members) => members.iter().any(|m| matches!(m.peel(), Type::Void)),
911 _ => false,
912 }
913 }
914
915 pub fn primitive_behavior(&self) -> &Type {
918 match self.peel() {
919 Type::NumberEnum { .. } => &Type::Number,
920 Type::StringEnum { .. } => &Type::String,
921 Type::BooleanLiteral(_) => &Type::Boolean,
922 Type::BigIntLiteral(_) => &Type::BigInt,
923 Type::Union(members)
924 if !members.is_empty()
925 && members.iter().all(|member| {
926 matches!(
927 member.primitive_behavior(),
928 Type::Number | Type::NumberLiteral(_)
929 )
930 }) =>
931 {
932 &Type::Number
933 }
934 Type::Union(members)
935 if !members.is_empty()
936 && members
937 .iter()
938 .all(|member| matches!(member.primitive_behavior(), Type::BigInt)) =>
939 {
940 &Type::BigInt
941 }
942 Type::Union(members)
943 if !members.is_empty() && members.iter().all(Type::is_string_shaped) =>
944 {
945 &Type::String
946 }
947 ty => ty,
948 }
949 }
950
951 pub fn is_bigint(&self) -> bool {
954 matches!(self.primitive_behavior(), Type::BigInt)
955 }
956
957 pub fn widen_literal(&self) -> Type {
970 match self {
971 Type::NumberLiteral(_) => Type::Number,
972 Type::StringLiteral(_) => Type::String,
973 Type::BooleanLiteral(_) => Type::Boolean,
974 Type::BigIntLiteral(_) => Type::BigInt,
975 _ if self.is_enum_member() => self.without_enum_member(),
976 Type::Union(members) => Type::union(without_members_of_empty_object(
980 members.iter().map(Type::widen_literal).collect(),
981 )),
982 _ => self.clone(),
983 }
984 }
985
986 pub fn literal_base(&self) -> Option<Type> {
990 let widened = self.widen_literal();
991 (widened != *self && matches!(widened, Type::Number | Type::String | Type::Boolean))
992 .then_some(widened)
993 }
994
995 pub fn is_string_shaped(&self) -> bool {
1004 match self.peel() {
1005 Type::String | Type::StringLiteral(_) | Type::StringEnum { .. } => true,
1006 Type::Union(members) => members.iter().all(Type::is_string_shaped),
1007 _ => false,
1008 }
1009 }
1010
1011 pub fn contains_string(&self) -> bool {
1015 match self.peel() {
1016 Type::String | Type::StringLiteral(_) | Type::StringEnum { .. } => true,
1017 Type::Union(members) => members.iter().any(Type::contains_string),
1018 _ => false,
1019 }
1020 }
1021
1022 pub fn any_member(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1027 match self.peel() {
1028 Type::Union(members) => members.iter().any(|member| member.any_member(leaf)),
1029 member => leaf(member),
1030 }
1031 }
1032
1033 pub fn all_members(&self, leaf: &dyn Fn(&Type) -> bool) -> bool {
1036 !self.any_member(&|member| !leaf(member))
1037 }
1038
1039 pub fn spells_null(&self) -> bool {
1042 self.any_member(&|member| matches!(member, Type::Null))
1043 }
1044
1045 pub fn spells_undefined(&self) -> bool {
1048 self.any_member(&|member| matches!(member, Type::Undefined | Type::Void))
1049 }
1050
1051 pub fn stringifies_to_undefined(leaf: &Type) -> bool {
1055 matches!(
1057 leaf.lowered(),
1058 Type::Undefined
1059 | Type::Void
1060 | Type::Unknown
1061 | Type::GenericParam { .. }
1062 | Type::TypeVar(_)
1063 | Type::Function { .. }
1064 )
1065 }
1066
1067 pub fn union(members: Vec<Type>) -> Type {
1084 if members.iter().any(|m| matches!(m.peel(), Type::Error)) {
1085 return Type::Error;
1086 }
1087 if members.iter().any(|m| matches!(m.peel(), Type::Unknown)) {
1089 return Type::Unknown;
1090 }
1091 let members: Vec<Type> = members
1093 .into_iter()
1094 .filter(|m| !matches!(m.peel(), Type::Never))
1095 .collect();
1096 let mut flat: Vec<Type> = Vec::new();
1097 for m in members {
1098 match m.without_aliases() {
1099 Type::Union(inner) => flat.extend(inner.iter().cloned()),
1100 _ => flat.push(m),
1101 }
1102 }
1103 let alias_rank = |t: &Type| u8::from(!matches!(t, Type::Alias { .. }));
1107 flat.sort_by(|a, b| {
1108 a.without_aliases()
1109 .cmp(b.without_aliases())
1110 .then_with(|| alias_rank(a).cmp(&alias_rank(b)))
1111 });
1112 flat.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1113 fold_boolean_literals(&mut flat);
1114 fold_enum_members(&mut flat);
1115 let refines_a_member = |member: &Type| {
1117 matches!(member, Type::Refined { original, .. }
1118 if flat.iter().any(|other| other.without_aliases() == original.without_aliases()))
1119 };
1120 let kept: Vec<Type> = flat
1121 .iter()
1122 .filter(|member| !refines_a_member(member))
1123 .cloned()
1124 .collect();
1125 flat = kept;
1126 if flat.len() > 1 {
1127 return Type::Union(flat);
1128 }
1129 match flat.into_iter().next() {
1130 Some(member) => member,
1131 None => Type::Never,
1132 }
1133 }
1134
1135 pub fn interface_routing(&self) -> Option<(MangledName, &str, &str, Vec<Type>)> {
1140 let prelude = crate::mangle::PRELUDE_PACKAGE;
1141 match self.primitive_behavior() {
1142 Type::Number | Type::NumberLiteral(_) => Some((
1147 crate::mangle::prelude("Number"),
1148 prelude,
1149 "Number",
1150 Vec::new(),
1151 )),
1152 Type::BigInt => Some((
1153 crate::mangle::prelude("BigInt"),
1154 prelude,
1155 "BigInt",
1156 Vec::new(),
1157 )),
1158 Type::Boolean => Some((
1159 crate::mangle::prelude("Boolean"),
1160 prelude,
1161 "Boolean",
1162 Vec::new(),
1163 )),
1164 Type::String | Type::StringLiteral(_) => Some((
1165 crate::mangle::prelude("String"),
1166 prelude,
1167 "String",
1168 Vec::new(),
1169 )),
1170 Type::Uint8Array => Some((
1171 crate::mangle::prelude("Uint8Array"),
1172 prelude,
1173 "Uint8Array",
1174 Vec::new(),
1175 )),
1176 Type::Array(elem) => Some((
1177 crate::mangle::prelude("Array"),
1178 prelude,
1179 "Array",
1180 vec![(**elem).clone()],
1181 )),
1182 Type::Tuple(elements) => Some((
1186 crate::mangle::prelude("Array"),
1187 prelude,
1188 "Array",
1189 vec![Type::union(elements.elements.clone())],
1190 )),
1191 Type::Object { .. } | Type::TypeVar(_) | Type::GenericParam { .. } => Some((
1192 crate::mangle::prelude("Object"),
1193 prelude,
1194 "Object",
1195 Vec::new(),
1196 )),
1197 Type::InterfaceRef {
1198 mangled,
1199 package,
1200 name,
1201 args,
1202 ..
1203 }
1204 | Type::ClassRef {
1205 mangled,
1206 package,
1207 name,
1208 args,
1209 ..
1210 } => Some((
1211 mangled.clone(),
1212 package.as_str(),
1213 name.as_str(),
1214 args.clone(),
1215 )),
1216 Type::NumberEnum { .. } | Type::StringEnum { .. } => Some((
1218 crate::mangle::prelude("Object"),
1219 prelude,
1220 "Object",
1221 Vec::new(),
1222 )),
1223 Type::Unknown => Some((
1225 crate::mangle::prelude("Object"),
1226 prelude,
1227 "Object",
1228 Vec::new(),
1229 )),
1230 _ => None,
1231 }
1232 }
1233}
1234
1235pub(crate) fn shown_beside_optional_marker(ty: &Type) -> Type {
1241 match ty.peel() {
1242 Type::Union(members) => Type::union(
1243 members
1244 .iter()
1245 .filter(|member| !matches!(member.peel(), Type::Undefined))
1246 .cloned()
1247 .collect(),
1248 ),
1249 _ => ty.clone(),
1250 }
1251}
1252
1253pub(crate) fn escape_string_literal(s: &str) -> String {
1256 let mut out = String::with_capacity(s.len());
1257 let mut chars = crate::literal_units::literal_chars(s).peekable();
1258 while let Some(c) = chars.next() {
1259 let c = match c {
1260 Ok(c) => c,
1261 Err(lone) => {
1262 out.push_str(&format!("\\u{lone:04X}"));
1263 continue;
1264 }
1265 };
1266 match c {
1267 '"' => out.push_str("\\\""),
1268 '\\' => out.push_str("\\\\"),
1269 '\n' => out.push_str("\\n"),
1270 '\r' => out.push_str("\\r"),
1271 '\t' => out.push_str("\\t"),
1272 '\u{8}' => out.push_str("\\b"),
1273 '\u{b}' => out.push_str("\\v"),
1274 '\u{c}' => out.push_str("\\f"),
1275 '\0' if chars
1277 .peek()
1278 .is_some_and(|next| next.is_ok_and(|d| d.is_ascii_digit())) =>
1279 {
1280 out.push_str("\\x00");
1281 }
1282 '\0' => out.push_str("\\0"),
1283 '\u{0}'..='\u{1f}' | '\u{85}' | '\u{2028}' | '\u{2029}' => {
1284 out.push_str(&format!("\\u{:04X}", u32::from(c)));
1285 }
1286 c => out.push(c),
1287 }
1288 }
1289 out
1290}
1291
1292pub fn bigint_literal_type(digits: &str) -> Type {
1295 Type::BigIntLiteral(canonical_bigint_digits(digits))
1296}
1297
1298pub fn negate_bigint_digits(digits: &str) -> String {
1300 let canonical = canonical_bigint_digits(digits);
1301 if canonical == "0" {
1302 return canonical;
1303 }
1304 match canonical.strip_prefix('-') {
1305 Some(magnitude) => magnitude.to_string(),
1306 None => format!("-{canonical}"),
1307 }
1308}
1309
1310fn canonical_bigint_digits(digits: &str) -> String {
1312 let (sign, magnitude) = match digits.strip_prefix('-') {
1313 Some(magnitude) => ("-", magnitude),
1314 None => ("", digits),
1315 };
1316 match magnitude.trim_start_matches('0') {
1317 "" => "0".to_string(),
1318 trimmed => format!("{sign}{trimmed}"),
1319 }
1320}
1321
1322pub fn without_members_of_empty_object(members: Vec<Type>) -> Vec<Type> {
1327 if !members.iter().any(Type::is_empty_object) {
1328 return members;
1329 }
1330 members
1331 .into_iter()
1332 .filter(|member| member.is_empty_object() || may_hold_values_outside_empty_object(member))
1333 .collect()
1334}
1335
1336fn may_hold_values_outside_empty_object(ty: &Type) -> bool {
1339 match ty.peel() {
1340 Type::Null
1341 | Type::Undefined
1342 | Type::Void
1343 | Type::Unknown
1344 | Type::GenericParam { .. }
1345 | Type::TypeVar(_) => true,
1346 Type::Union(members) => members.iter().any(may_hold_values_outside_empty_object),
1347 _ => false,
1348 }
1349}
1350
1351fn fold_boolean_literals(members: &mut Vec<Type>) {
1355 let is_boolean = |m: &Type| matches!(m.without_aliases(), Type::Boolean);
1356 let is_literal = |m: &Type| matches!(m.without_aliases(), Type::BooleanLiteral(_));
1357 let literals = members.iter().filter(|m| is_literal(m)).count();
1358 let has_boolean = members.iter().any(is_boolean);
1359 if literals == 0 || (literals == 1 && !has_boolean) {
1360 return;
1361 }
1362 members.retain(|m| !is_literal(m));
1363 if !has_boolean {
1364 let at = members.partition_point(|m| m.without_aliases() < &Type::Boolean);
1365 members.insert(at, Type::Boolean);
1366 }
1367}
1368
1369fn fold_enum_members(members: &mut Vec<Type>) {
1374 absorb_enums_into_primitives(members);
1375 let complete = complete_enums(members);
1376 if complete.is_empty() {
1377 return;
1378 }
1379 for member in members.iter_mut() {
1380 if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } =
1381 member.without_aliases()
1382 && complete.contains(mangled)
1383 {
1384 *member = member.without_aliases().without_enum_member();
1385 }
1386 }
1387 members.sort_by(|a, b| a.without_aliases().cmp(b.without_aliases()));
1388 members.dedup_by(|a, b| a.without_aliases() == b.without_aliases());
1389}
1390
1391fn absorb_enums_into_primitives(members: &mut Vec<Type>) {
1393 let has = |base: &Type| members.iter().any(|m| m.without_aliases() == base);
1394 let (has_number, has_string) = (has(&Type::Number), has(&Type::String));
1395 members.retain(|m| match m.without_aliases() {
1396 Type::NumberEnum { .. } => !has_number,
1397 Type::StringEnum { .. } => !has_string,
1398 _ => true,
1399 });
1400}
1401
1402fn complete_enums(members: &[Type]) -> BTreeSet<MangledName> {
1405 struct NamedMembers<'a> {
1406 member_count: usize,
1407 names: BTreeSet<&'a str>,
1408 }
1409 let mut named: BTreeMap<&MangledName, NamedMembers> = BTreeMap::new();
1410 let mut whole: BTreeSet<&MangledName> = BTreeSet::new();
1411 for member in members {
1412 let ty = member.without_aliases();
1413 if let Some((mangled, name, member_count)) = ty.enum_member_name() {
1414 named
1415 .entry(mangled)
1416 .or_insert_with(|| NamedMembers {
1417 member_count,
1418 names: BTreeSet::new(),
1419 })
1420 .names
1421 .insert(name);
1422 } else if let Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } = ty {
1423 whole.insert(mangled);
1424 }
1425 }
1426 named
1427 .into_iter()
1428 .filter(|(mangled, named)| {
1429 whole.contains(mangled) || named.names.len() >= named.member_count
1430 })
1431 .map(|(mangled, _)| mangled.clone())
1432 .collect()
1433}
1434
1435impl Type {
1436 pub fn render_checked(
1437 &self,
1438 limits: crate::rendering::RenderLimits,
1439 ) -> Result<crate::rendering::RenderedText, crate::rendering::RenderError> {
1440 crate::type_rendering::render(self, limits)
1441 }
1442}
1443
1444pub fn union_display_order(members: &[Type]) -> Vec<&Type> {
1448 let mut ordered: Vec<&Type> = members.iter().collect();
1449 ordered.sort_by_key(|m| match m {
1450 Type::Null => 1,
1451 Type::Undefined => 2,
1452 _ => 0,
1453 });
1454 ordered
1455}
1456
1457impl fmt::Display for Type {
1458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1459 match self.render_checked(crate::rendering::RenderLimits::default()) {
1460 Ok(rendered) => f.write_str(&rendered.text),
1461 Err(_) => f.write_str("[diagnostic type unavailable]"),
1462 }
1463 }
1464}
1465
1466#[cfg(test)]
1467mod tests {
1468 use super::{Package, Type};
1469
1470 #[test]
1471 fn tuple_mapping_preserves_optional_positions() {
1472 let tuple = super::TupleType {
1473 elements: vec![Type::NumberLiteral(super::LiteralF64(1.0)), Type::Undefined],
1474 optional: 1,
1475 };
1476 let mapped = tuple.map(Type::widen_literal);
1477 assert_eq!(mapped.optional, 1);
1478 assert_eq!(mapped.required_len(), 1);
1479 assert_eq!(mapped.elements, vec![Type::Number, Type::Undefined]);
1480 }
1481
1482 #[test]
1483 fn optional_fields_read_as_undefined() {
1484 let field = super::ObjectField::optional(Type::String);
1485 assert_eq!(
1486 field.read_ty(),
1487 Type::union(vec![Type::String, Type::Undefined])
1488 );
1489 }
1490
1491 #[test]
1492 fn optional_function_and_tuple_display_preserve_omission() {
1493 let ty = Type::Function {
1494 params: vec![Type::union(vec![Type::String, Type::Undefined])],
1495 optional: 1,
1496 ret: Box::new(Type::Void),
1497 predicate: None,
1498 has_rest: false,
1499 };
1500 assert_eq!(ty.to_string(), "(arg0?: string) => void");
1502 let tuple = Type::Tuple(super::TupleType {
1503 elements: vec![
1504 Type::Number,
1505 Type::union(vec![Type::String, Type::Undefined]),
1506 ],
1507 optional: 1,
1508 });
1509 assert_eq!(tuple.to_string(), "[number, (string | undefined)?]");
1510 }
1511
1512 #[test]
1513 fn atomic_display() {
1514 assert_eq!(Type::Number.to_string(), "number");
1515 assert_eq!(Type::String.to_string(), "string");
1516 assert_eq!(Type::Boolean.to_string(), "boolean");
1517 assert_eq!(Type::Null.to_string(), "null");
1518 assert_eq!(Type::Void.to_string(), "void");
1519 assert_eq!(Type::Error.to_string(), "<error>");
1520 }
1521
1522 #[test]
1523 fn readonly_display_matches_typescript() {
1524 let numbers = Type::Array(Box::new(Type::Number));
1525 let readonly = Type::Readonly(Box::new(numbers.clone()));
1526 assert_eq!(readonly.to_string(), "readonly number[]");
1527 assert_eq!(
1528 Type::Array(Box::new(readonly.clone())).to_string(),
1529 "(readonly number[])[]"
1530 );
1531 assert_eq!(
1532 Type::Readonly(Box::new(Type::Array(Box::new(numbers)))).to_string(),
1533 "readonly number[][]"
1534 );
1535 assert_eq!(
1536 Type::Readonly(Box::new(Type::Tuple(
1537 vec![Type::Number, Type::String].into()
1538 )))
1539 .to_string(),
1540 "readonly [number, string]"
1541 );
1542 assert!(readonly.is_readonly_array());
1543 assert_eq!(readonly.peel(), &Type::Array(Box::new(Type::Number)));
1544 }
1545
1546 #[test]
1547 fn string_literal_display_escapes_like_typescript() {
1548 let cases = [
1550 ("G\"HI\\", r#""G\"HI\\""#),
1551 ("a\nb\r\t", r#""a\nb\r\t""#),
1552 ("\u{8}\u{b}\u{c}", r#""\b\v\f""#),
1553 ("\0x", r#""\0x""#),
1554 ("\u{0}1", r#""\x001""#),
1555 ("\u{7}\u{1b}", r#""\u0007\u001B""#),
1556 ("\u{85}\u{2028}\u{2029}", r#""\u0085\u2028\u2029""#),
1557 ("\u{7f}é", "\"\u{7f}é\""),
1558 ];
1559 for (value, printed) in cases {
1560 assert_eq!(Type::StringLiteral(value.into()).to_string(), printed);
1561 }
1562 }
1563
1564 #[test]
1565 fn structural_equality() {
1566 assert_eq!(Type::Number, Type::Number);
1567 assert_ne!(Type::Number, Type::String);
1568
1569 let a = Type::Function {
1570 params: vec![Type::Number, Type::String],
1571 ret: Box::new(Type::Boolean),
1572 predicate: None,
1573 has_rest: false,
1574 optional: 0,
1575 };
1576 let b = Type::Function {
1577 params: vec![Type::Number, Type::String],
1578 ret: Box::new(Type::Boolean),
1579 predicate: None,
1580 has_rest: false,
1581 optional: 0,
1582 };
1583 let c = Type::Function {
1584 params: vec![Type::Number],
1585 ret: Box::new(Type::Boolean),
1586 predicate: None,
1587 has_rest: false,
1588 optional: 0,
1589 };
1590 assert_eq!(a, b);
1591 assert_ne!(a, c);
1592 }
1593
1594 #[test]
1595 fn function_display_no_params() {
1596 let t = Type::Function {
1597 params: vec![],
1598 ret: Box::new(Type::Number),
1599 predicate: None,
1600 has_rest: false,
1601 optional: 0,
1602 };
1603 assert_eq!(t.to_string(), "() => number");
1604 }
1605
1606 #[test]
1607 fn function_display_one_param() {
1608 let t = Type::Function {
1609 params: vec![Type::Number],
1610 ret: Box::new(Type::Boolean),
1611 predicate: None,
1612 has_rest: false,
1613 optional: 0,
1614 };
1615 assert_eq!(t.to_string(), "(arg0: number) => boolean");
1616 }
1617
1618 #[test]
1619 fn function_display_multi_param() {
1620 let t = Type::Function {
1621 params: vec![Type::Number, Type::String],
1622 ret: Box::new(Type::Void),
1623 predicate: None,
1624 has_rest: false,
1625 optional: 0,
1626 };
1627 assert_eq!(t.to_string(), "(arg0: number, arg1: string) => void");
1628 }
1629
1630 #[test]
1631 fn function_display_nested() {
1632 let inner = Type::Function {
1633 params: vec![],
1634 ret: Box::new(Type::Number),
1635 predicate: None,
1636 has_rest: false,
1637 optional: 0,
1638 };
1639 let outer = Type::Function {
1640 params: vec![],
1641 ret: Box::new(inner),
1642 predicate: None,
1643 has_rest: false,
1644 optional: 0,
1645 };
1646 assert_eq!(outer.to_string(), "() => () => number");
1647 }
1648
1649 #[test]
1650 fn clone_roundtrip() {
1651 let t = Type::Function {
1652 params: vec![Type::Number, Type::String],
1653 ret: Box::new(Type::Boolean),
1654 predicate: None,
1655 has_rest: false,
1656 optional: 0,
1657 };
1658 assert_eq!(t.clone(), t);
1659 }
1660
1661 fn point_type() -> Type {
1662 let mut fields = std::collections::BTreeMap::new();
1663 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1664 fields.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1665 Type::Object {
1666 index: None,
1667 fields,
1668 }
1669 }
1670
1671 #[test]
1672 fn object_structural_equality_ignores_field_insertion_order() {
1673 let mut a = std::collections::BTreeMap::new();
1674 a.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1675 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1676
1677 let mut b = std::collections::BTreeMap::new();
1678 b.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1679 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1680
1681 assert_eq!(
1682 Type::Object {
1683 index: None,
1684 fields: a
1685 },
1686 Type::Object {
1687 index: None,
1688 fields: b
1689 }
1690 );
1691 }
1692
1693 #[test]
1694 fn object_inequality_on_different_field_set() {
1695 let mut a = std::collections::BTreeMap::new();
1696 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1697 let mut b = std::collections::BTreeMap::new();
1698 b.insert("y".to_string(), crate::ObjectField::required(Type::Number));
1699 assert_ne!(
1700 Type::Object {
1701 index: None,
1702 fields: a
1703 },
1704 Type::Object {
1705 index: None,
1706 fields: b
1707 }
1708 );
1709 }
1710
1711 #[test]
1712 fn object_display() {
1713 assert_eq!(point_type().to_string(), "{ x: number; y: number }");
1714 let empty = Type::Object {
1715 index: None,
1716 fields: std::collections::BTreeMap::new(),
1717 };
1718 assert_eq!(empty.to_string(), "{}");
1719 }
1720
1721 #[test]
1722 fn object_display_with_optional_field() {
1723 let mut fields = std::collections::BTreeMap::new();
1724 fields.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1725 fields.insert("y".to_string(), crate::ObjectField::optional(Type::String));
1726 let t = Type::Object {
1727 index: None,
1728 fields,
1729 };
1730 assert_eq!(t.to_string(), "{ x: number; y?: string }");
1731 }
1732
1733 #[test]
1734 fn object_inequality_required_vs_optional() {
1735 let mut a = std::collections::BTreeMap::new();
1736 a.insert("x".to_string(), crate::ObjectField::required(Type::Number));
1737 let mut b = std::collections::BTreeMap::new();
1738 b.insert("x".to_string(), crate::ObjectField::optional(Type::Number));
1739 assert_ne!(
1740 Type::Object {
1741 index: None,
1742 fields: a
1743 },
1744 Type::Object {
1745 index: None,
1746 fields: b
1747 }
1748 );
1749 }
1750
1751 #[test]
1752 fn array_equality() {
1753 assert_eq!(
1754 Type::Array(Box::new(Type::Number)),
1755 Type::Array(Box::new(Type::Number))
1756 );
1757 assert_ne!(
1758 Type::Array(Box::new(Type::Number)),
1759 Type::Array(Box::new(Type::String))
1760 );
1761 }
1762
1763 #[test]
1764 fn array_display() {
1765 assert_eq!(Type::Array(Box::new(Type::Number)).to_string(), "number[]");
1766 assert_eq!(
1767 Type::Array(Box::new(Type::Array(Box::new(Type::Number)))).to_string(),
1768 "number[][]"
1769 );
1770 }
1771
1772 #[test]
1773 fn nested_object_in_array_display() {
1774 let arr = Type::Array(Box::new(point_type()));
1775 assert_eq!(arr.to_string(), "{ x: number; y: number }[]");
1776 }
1777
1778 #[test]
1779 fn type_var_display_uses_source_name() {
1780 assert_eq!(Type::TypeVar("T".to_string()).to_string(), "T");
1781 assert_eq!(Type::TypeVar("Key".to_string()).to_string(), "Key");
1782 }
1783
1784 #[test]
1785 fn type_var_structural_equality_by_name() {
1786 let t = Type::TypeVar("T".to_string());
1787 let t2 = Type::TypeVar("T".to_string());
1788 let u = Type::TypeVar("U".to_string());
1789 assert_eq!(t, t2);
1790 assert_ne!(t, u);
1791 assert_ne!(t, Type::Number);
1792 }
1793
1794 #[test]
1795 fn type_var_inside_array_and_function_displays_through() {
1796 let arr = Type::Array(Box::new(Type::TypeVar("T".to_string())));
1797 assert_eq!(arr.to_string(), "T[]");
1798 let func = Type::Function {
1799 params: vec![Type::TypeVar("T".to_string())],
1800 ret: Box::new(Type::TypeVar("U".to_string())),
1801 predicate: None,
1802 has_rest: false,
1803 optional: 0,
1804 };
1805 assert_eq!(func.to_string(), "(arg0: T) => U");
1806 }
1807
1808 #[test]
1809 fn generic_param_display_uses_name_not_id() {
1810 let gp = Type::GenericParam {
1811 id: 42,
1812 name: "T".to_string(),
1813 };
1814 assert_eq!(gp.to_string(), "T");
1815 }
1816
1817 #[test]
1818 fn generic_param_equality_by_id() {
1819 let a = Type::GenericParam {
1820 id: 5,
1821 name: "T".to_string(),
1822 };
1823 let b = Type::GenericParam {
1824 id: 5,
1825 name: "T".to_string(),
1826 };
1827 assert_eq!(a, b);
1828
1829 let c = Type::GenericParam {
1830 id: 6,
1831 name: "T".to_string(),
1832 };
1833 assert_ne!(a, c);
1834 }
1835
1836 #[test]
1837 fn union_canonical_dedup() {
1838 let t = Type::union(vec![Type::Number, Type::Null, Type::Number]);
1839 let Type::Union(members) = t else {
1840 panic!("expected Union, got {t:?}");
1841 };
1842 assert_eq!(members, vec![Type::Number, Type::Null]);
1843 }
1844
1845 #[test]
1846 fn union_canonical_order_independent() {
1847 let a = Type::union(vec![Type::Number, Type::Null]);
1848 let b = Type::union(vec![Type::Null, Type::Number]);
1849 assert_eq!(a, b);
1850 }
1851
1852 #[test]
1853 fn union_collapses_to_single_member() {
1854 let t = Type::union(vec![Type::Number, Type::Number]);
1855 assert_eq!(t, Type::Number);
1856 }
1857
1858 #[test]
1859 fn union_flattens_nested() {
1860 let inner = Type::union(vec![Type::String, Type::Null]);
1861 let outer = Type::union(vec![Type::Number, inner]);
1862 let expected = Type::union(vec![Type::Number, Type::String, Type::Null]);
1863 assert_eq!(outer, expected);
1864 }
1865
1866 #[test]
1867 fn union_error_member_collapses_to_error() {
1868 let t = Type::union(vec![Type::Number, Type::Error]);
1869 assert_eq!(t, Type::Error);
1870 }
1871
1872 #[test]
1873 fn union_display_basic() {
1874 let t = Type::union(vec![Type::Number, Type::String]);
1875 assert_eq!(t.to_string(), "number | string");
1876 }
1877
1878 #[test]
1879 fn union_display_with_null() {
1880 let t = Type::union(vec![Type::Number, Type::Null]);
1881 assert_eq!(t.to_string(), "number | null");
1882 let t2 = Type::union(vec![Type::Null, Type::Number]);
1883 assert_eq!(t2.to_string(), "number | null");
1884 }
1885
1886 #[test]
1887 fn union_display_wraps_function_member() {
1888 let fn_ty = Type::Function {
1889 params: vec![],
1890 ret: Box::new(Type::String),
1891 predicate: None,
1892 has_rest: false,
1893 optional: 0,
1894 };
1895 let t = Type::union(vec![fn_ty, Type::Boolean]);
1896 assert_eq!(t.to_string(), "boolean | (() => string)");
1897 }
1898
1899 use super::LiteralF64;
1900
1901 #[test]
1902 fn string_literal_display_is_quoted() {
1903 let t = Type::StringLiteral("north".to_string());
1904 assert_eq!(t.to_string(), "\"north\"");
1905 }
1906
1907 #[test]
1908 fn number_literal_display_is_unquoted_with_whole_number_shape() {
1909 let whole = Type::NumberLiteral(LiteralF64(42.0));
1910 assert_eq!(whole.to_string(), "42");
1911 let frac = Type::NumberLiteral(LiteralF64(4.5));
1912 assert_eq!(frac.to_string(), "4.5");
1913 }
1914
1915 #[test]
1916 fn string_literal_equality_by_value() {
1917 let a = Type::StringLiteral("hi".to_string());
1918 let b = Type::StringLiteral("hi".to_string());
1919 let c = Type::StringLiteral("bye".to_string());
1920 assert_eq!(a, b);
1921 assert_ne!(a, c);
1922 }
1923
1924 #[test]
1925 fn number_literal_equality_by_value() {
1926 let a = Type::NumberLiteral(LiteralF64(42.0));
1927 let b = Type::NumberLiteral(LiteralF64(42.0));
1928 let c = Type::NumberLiteral(LiteralF64(43.0));
1929 assert_eq!(a, b);
1930 assert_ne!(a, c);
1931 }
1932
1933 #[test]
1934 fn union_sorts_literals_next_to_base_types() {
1935 let t = Type::union(vec![
1937 Type::String,
1938 Type::StringLiteral("hi".to_string()),
1939 Type::Number,
1940 Type::NumberLiteral(LiteralF64(42.0)),
1941 ]);
1942 assert_eq!(t.to_string(), "number | 42 | string | \"hi\"");
1943 }
1944
1945 #[test]
1946 fn widen_literal_replaces_a_literal_with_its_base() {
1947 assert_eq!(
1948 Type::NumberLiteral(LiteralF64(1.0)).widen_literal(),
1949 Type::Number
1950 );
1951 assert_eq!(
1952 Type::StringLiteral("hi".to_string()).widen_literal(),
1953 Type::String
1954 );
1955 }
1956
1957 #[test]
1960 fn widen_literal_collapses_a_union_of_literals() {
1961 let t = Type::union(vec![
1962 Type::NumberLiteral(LiteralF64(1.0)),
1963 Type::NumberLiteral(LiteralF64(2.0)),
1964 ]);
1965 assert_eq!(t.widen_literal(), Type::Number);
1966 }
1967
1968 #[test]
1971 fn widen_literal_leaves_everything_else_alone() {
1972 assert_eq!(Type::Number.widen_literal(), Type::Number);
1973 assert_eq!(Type::Boolean.widen_literal(), Type::Boolean);
1974 let arr = Type::Array(Box::new(Type::NumberLiteral(LiteralF64(1.0))));
1975 assert_eq!(arr.widen_literal(), arr);
1976 }
1977
1978 #[test]
1979 fn union_of_two_string_literals_sorts_by_value() {
1980 let t = Type::union(vec![
1981 Type::StringLiteral("south".to_string()),
1982 Type::StringLiteral("north".to_string()),
1983 ]);
1984 assert_eq!(t.to_string(), "\"north\" | \"south\"");
1985 }
1986
1987 #[test]
1988 fn literal_f64_equality_distinguishes_negative_zero_at_bit_level() {
1989 let pos = LiteralF64(0.0);
1992 let neg = LiteralF64(-0.0);
1993 assert_ne!(pos, neg);
1994 }
1995
1996 #[test]
1997 fn type_var_and_generic_param_are_distinct_types() {
1998 let tv = Type::TypeVar("T".to_string());
2001 let gp = Type::GenericParam {
2002 id: 0,
2003 name: "T".to_string(),
2004 };
2005 assert_ne!(tv, gp);
2006 }
2007
2008 #[test]
2009 fn alias_display_renders_name_not_body() {
2010 let ty = user_alias("Circle", point_type());
2011 assert_eq!(ty.to_string(), "Circle");
2012 }
2013
2014 #[test]
2015 fn peel_walks_through_alias_to_underlying() {
2016 let inner = Type::Number;
2017 let aliased = user_alias("ID", inner.clone());
2018 assert_eq!(aliased.peel(), &inner);
2019 }
2020
2021 #[test]
2022 fn peel_is_idempotent_through_nested_aliases() {
2023 let nested = user_alias("A", user_alias("B", Type::Number));
2024 assert_eq!(nested.peel(), &Type::Number);
2025 assert_eq!(nested.peel().peel(), &Type::Number);
2026 }
2027
2028 #[test]
2029 fn alias_is_not_structurally_equal_to_underlying() {
2030 let aliased = user_alias("ID", Type::Number);
2031 assert_ne!(aliased, Type::Number);
2032 assert_eq!(aliased.peel(), &Type::Number);
2033 }
2034
2035 #[test]
2036 fn alias_routes_to_underlying_interface() {
2037 let aliased = user_alias("ID", Type::Number);
2038 let (_mangled, _pkg, iface, _args) = aliased.interface_routing().expect("number routes");
2039 assert_eq!(iface, "Number");
2040 }
2041
2042 #[test]
2043 fn union_of_error_alias_collapses_to_error() {
2044 let alias_err = user_alias("Bad", Type::Error);
2045 let t = Type::union(vec![Type::Number, alias_err]);
2046 assert_eq!(t, Type::Error);
2047 }
2048
2049 #[test]
2050 fn union_preserves_distinct_alias_members_for_display() {
2051 let circle = user_alias("Circle", point_type());
2052 let rect = user_alias("Rectangle", Type::Number);
2053 let t = Type::union(vec![circle, rect]);
2054 assert_eq!(t.to_string(), "Rectangle | Circle");
2057 }
2058
2059 #[test]
2060 fn union_dedups_an_alias_against_its_own_body_keeping_the_label() {
2061 let n = user_alias("N", Type::Number);
2062 assert_eq!(Type::union(vec![n.clone(), Type::Number]), n);
2063 assert_eq!(Type::union(vec![Type::Number, n.clone()]), n);
2064 }
2065
2066 #[test]
2067 fn union_dedups_aliases_that_share_one_body() {
2068 let circle = user_alias("Circle", point_type());
2069 let rect = user_alias("Rectangle", point_type());
2070 assert_eq!(Type::union(vec![circle.clone(), rect]), circle);
2071 }
2072
2073 #[test]
2074 fn union_flattens_an_alias_whose_body_is_a_union() {
2075 let nullable = user_alias("MN", Type::union(vec![Type::Number, Type::Null]));
2076 assert_eq!(
2077 Type::union(vec![nullable, Type::Number]),
2078 Type::union(vec![Type::Number, Type::Null])
2079 );
2080 }
2081
2082 #[test]
2083 fn generic_alias_display_includes_args() {
2084 let ty = user_alias_args("Box", vec![Type::Number], Type::Number);
2085 assert_eq!(ty.to_string(), "Box<number>");
2086 }
2087
2088 fn iface(package: &str, name: &str) -> Type {
2089 Type::interface_ref(
2090 Package(package.to_string()),
2091 name,
2092 crate::mangle::package_symbol(package, name),
2093 Vec::new(),
2094 )
2095 }
2096
2097 fn user_alias(name: &str, ty: Type) -> Type {
2098 user_alias_args(name, Vec::new(), ty)
2099 }
2100
2101 fn user_alias_args(name: &str, args: Vec<Type>, ty: Type) -> Type {
2102 Type::alias_ty(
2103 Package::user(),
2104 name,
2105 crate::mangle::package_symbol(crate::mangle::USER_PACKAGE, name),
2106 args,
2107 Box::new(ty),
2108 )
2109 }
2110
2111 #[test]
2112 fn mangled_name_keys_type_identity() {
2113 assert_eq!(iface("main", "Response"), iface("main", "Response"));
2118 let http = iface("submilli:http", "Response");
2119 let main = iface("main", "Response");
2120 assert_ne!(
2121 http, main,
2122 "different package ⇒ different mangled ⇒ distinct"
2123 );
2124 assert_ne!(
2125 http.cmp(&main),
2126 std::cmp::Ordering::Equal,
2127 "Ord must distinguish distinct mangled names"
2128 );
2129 assert_ne!(
2130 iface("main", "Response"),
2131 iface("main", "Other"),
2132 "different names stay distinct"
2133 );
2134 }
2135
2136 #[test]
2137 fn same_name_different_module_not_equal_nor_assignable() {
2138 let root = iface("main", "Logger");
2142 let sibling = Type::InterfaceRef {
2143 mangled: crate::mangle::package_module_symbol("main", "util", "Logger"),
2144 package: Package::user(),
2145 name: "Logger".to_string(),
2146 args: Vec::new(),
2147 };
2148 assert_ne!(root, sibling);
2149 let root2 = iface("main", "Logger");
2151 assert_eq!(root, root2);
2152 }
2153
2154 #[test]
2155 fn union_dedups_interface_refs_by_mangled() {
2156 let same = Type::union(vec![iface("main", "Response"), iface("main", "Response")]);
2158 assert!(
2159 !matches!(same, Type::Union(_)),
2160 "identical refs collapse to one member, got {same:?}"
2161 );
2162 let distinct = Type::union(vec![
2163 iface("submilli:http", "Response"),
2164 iface("main", "Response"),
2165 ]);
2166 assert!(
2167 matches!(distinct, Type::Union(ref ms) if ms.len() == 2),
2168 "distinct-package refs stay a 2-member union, got {distinct:?}"
2169 );
2170 }
2171
2172 #[test]
2173 fn generic_alias_display_multiple_args() {
2174 let ty = user_alias_args("Pair", vec![Type::String, Type::Number], Type::Number);
2175 assert_eq!(ty.to_string(), "Pair<string, number>");
2176 }
2177}