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