1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::Atomic;
7use crate::symbol::Name;
8
9pub fn empty_type_params() -> Arc<[Type]> {
13 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
14 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
15}
16
17pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
20 if v.is_empty() {
21 empty_type_params()
22 } else {
23 Arc::from(v)
24 }
25}
26
27pub type AtomicVec = SmallVec<[Atomic; 2]>;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum CloneValidity {
33 Cloneable,
35 Invalid,
37 PossiblyInvalid,
39 Unknown,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub struct Type {
49 pub types: AtomicVec,
50 pub possibly_undefined: bool,
52 pub from_docblock: bool,
54 pub falsy_stripped: bool,
62 pub possibly_absent_offset: bool,
73}
74
75impl Type {
76 pub fn empty() -> Self {
79 Self {
80 types: SmallVec::new(),
81 possibly_undefined: false,
82 from_docblock: false,
83 falsy_stripped: false,
84 possibly_absent_offset: false,
85 }
86 }
87
88 pub fn single(atomic: Atomic) -> Self {
89 let mut types = SmallVec::new();
90 types.push(atomic);
91 Self {
92 types,
93 possibly_undefined: false,
94 from_docblock: false,
95 falsy_stripped: false,
96 possibly_absent_offset: false,
97 }
98 }
99
100 pub fn mixed() -> Self {
101 Self::single(Atomic::TMixed)
102 }
103
104 pub fn void() -> Self {
105 Self::single(Atomic::TVoid)
106 }
107
108 pub fn never() -> Self {
109 Self::single(Atomic::TNever)
110 }
111
112 pub fn null() -> Self {
113 Self::single(Atomic::TNull)
114 }
115
116 pub fn bool() -> Self {
117 Self::single(Atomic::TBool)
118 }
119
120 pub fn int() -> Self {
121 Self::single(Atomic::TInt)
122 }
123
124 pub fn float() -> Self {
125 Self::single(Atomic::TFloat)
126 }
127
128 pub fn string() -> Self {
129 Self::single(Atomic::TString)
130 }
131
132 pub fn array_key() -> Self {
135 let mut u = Self::single(Atomic::TInt);
136 u.add_type(Atomic::TString);
137 u
138 }
139
140 pub fn nullable(atomic: Atomic) -> Self {
142 if matches!(atomic, Atomic::TMixed) {
144 return Self::mixed();
145 }
146 let mut types = SmallVec::new();
147 types.push(atomic);
148 types.push(Atomic::TNull);
149 Self {
150 types,
151 possibly_undefined: false,
152 from_docblock: false,
153 falsy_stripped: false,
154 possibly_absent_offset: false,
155 }
156 }
157
158 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
160 let mut u = Self::empty();
161 for a in atomics {
162 u.add_type(a);
163 }
164 u
165 }
166
167 pub fn is_empty(&self) -> bool {
170 self.types.is_empty()
171 }
172
173 pub fn is_single(&self) -> bool {
174 self.types.len() == 1
175 }
176
177 pub fn is_nullable(&self) -> bool {
178 self.types.iter().any(|t| matches!(t, Atomic::TNull))
179 }
180
181 pub fn is_array_key(&self) -> bool {
186 self.types.len() == 2
187 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
188 && self.types.iter().any(|t| matches!(t, Atomic::TString))
189 }
190
191 pub fn is_mixed(&self) -> bool {
192 self.types.iter().any(|t| match t {
193 Atomic::TMixed => true,
194 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
195 _ => false,
196 })
197 }
198
199 pub fn is_mixed_not_template(&self) -> bool {
204 self.is_mixed()
205 && !self
206 .types
207 .iter()
208 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
209 }
210
211 pub fn is_never(&self) -> bool {
212 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
213 }
214
215 pub fn clone_validity(&self) -> CloneValidity {
218 if self.types.is_empty() {
219 return CloneValidity::Unknown;
220 }
221 let mut has_non_object = false;
222 let mut has_other = false; for t in &self.types {
224 match t {
225 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
226 CloneValidity::Invalid => has_non_object = true,
227 CloneValidity::PossiblyInvalid => {
228 has_non_object = true;
229 has_other = true;
230 }
231 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
232 },
233 other if other.is_definitely_non_object() => has_non_object = true,
234 _ => has_other = true,
235 }
236 }
237 match (has_non_object, has_other) {
238 (true, false) => CloneValidity::Invalid,
239 (true, true) => CloneValidity::PossiblyInvalid,
240 _ => CloneValidity::Cloneable,
241 }
242 }
243
244 pub fn is_void(&self) -> bool {
245 self.is_single() && matches!(self.types[0], Atomic::TVoid)
246 }
247
248 pub fn can_be_falsy(&self) -> bool {
249 self.types.iter().any(|t| t.can_be_falsy())
250 }
251
252 pub fn can_be_truthy(&self) -> bool {
253 self.types.iter().any(|t| t.can_be_truthy())
254 }
255
256 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
257 self.types.iter().any(f)
258 }
259
260 pub fn has_named_object(&self, fqcn: &str) -> bool {
261 self.types.iter().any(|t| match t {
262 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
263 _ => false,
264 })
265 }
266
267 pub fn add_type(&mut self, atomic: Atomic) {
272 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
274 return;
275 }
276
277 if matches!(atomic, Atomic::TMixed) {
279 self.types.clear();
280 self.types.push(Atomic::TMixed);
281 return;
282 }
283
284 let atomic = if let Atomic::TConditional { data } = &atomic {
287 let (if_true, if_false) = (&data.if_true, &data.if_false);
288 let mut simplified_true = Type::empty();
289 for t in &if_true.types {
290 simplified_true.add_type(t.clone());
291 }
292 let mut simplified_false = Type::empty();
293 for t in &if_false.types {
294 simplified_false.add_type(t.clone());
295 }
296 if simplified_true == simplified_false {
297 for t in simplified_true.types {
298 self.add_type(t);
299 }
300 return;
301 }
302 atomic
303 } else {
304 atomic
305 };
306
307 if self.types.contains(&atomic) {
309 return;
310 }
311
312 if let Atomic::TLiteralInt(_) = &atomic {
314 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
315 return;
316 }
317 }
318 if let Atomic::TLiteralString(_) = &atomic {
320 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
321 return;
322 }
323 }
324 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
326 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
327 {
328 return;
329 }
330 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
333 {
334 self.types.retain(|t| !matches!(t, Atomic::TFalse));
335 self.types.push(Atomic::TBool);
336 return;
337 }
338 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
339 {
340 self.types.retain(|t| !matches!(t, Atomic::TTrue));
341 self.types.push(Atomic::TBool);
342 return;
343 }
344 if matches!(atomic, Atomic::TInt) {
346 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
347 }
348 if matches!(atomic, Atomic::TString) {
350 self.types
351 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
352 }
353 if matches!(atomic, Atomic::TBool) {
355 self.types
356 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
357 }
358
359 if matches!(atomic, Atomic::TNever) {
361 if !self.types.is_empty() {
362 return;
363 }
364 } else {
365 self.types.retain(|t| !matches!(t, Atomic::TNever));
366 }
367
368 if let Atomic::TKeyedArray {
374 properties,
375 is_open,
376 ..
377 } = &atomic
378 {
379 if properties.is_empty() && !is_open {
380 for existing in &self.types {
381 match existing {
382 Atomic::TArray { .. }
383 | Atomic::TNonEmptyArray { .. }
384 | Atomic::TList { .. }
385 | Atomic::TNonEmptyList { .. } => {
386 return; }
388 _ => {}
389 }
390 }
391 }
392 }
393
394 let is_generic_array_or_list = matches!(
397 &atomic,
398 Atomic::TArray { .. }
399 | Atomic::TNonEmptyArray { .. }
400 | Atomic::TList { .. }
401 | Atomic::TNonEmptyList { .. }
402 );
403 if is_generic_array_or_list {
404 self.types.retain(|t| {
405 if let Atomic::TKeyedArray {
406 properties,
407 is_open,
408 ..
409 } = t
410 {
411 !properties.is_empty() || *is_open
412 } else {
413 true
414 }
415 });
416 }
417
418 self.types.push(atomic);
419 }
420
421 pub fn remove_null(&self) -> Type {
425 self.filter(|t| !matches!(t, Atomic::TNull))
426 }
427
428 pub fn remove_false(&self) -> Type {
431 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
432 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
433 result.add_type(Atomic::TTrue);
434 }
435 result
436 }
437
438 pub fn remove_true(&self) -> Type {
441 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
442 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
443 result.add_type(Atomic::TFalse);
444 }
445 result
446 }
447
448 pub fn core_type(&self) -> Type {
450 self.remove_null().remove_false()
451 }
452
453 pub fn narrow_to_truthy(&self) -> Type {
455 if self.is_mixed_not_template() {
456 return Type::mixed();
457 }
458 let mut result = Type::empty();
459 result.from_docblock = self.from_docblock;
460 for t in &self.types {
461 match t {
462 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
466 Atomic::TLiteralInt(0)
468 | Atomic::TLiteralFloat(0, 0)
469 | Atomic::TNull
470 | Atomic::TFalse => {}
471 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
472 Atomic::TBool => result.add_type(Atomic::TTrue),
474 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
476 key: key.clone(),
477 value: value.clone(),
478 }),
479 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
480 value: value.clone(),
481 }),
482 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
486 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
491 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
492 let atom = if max.is_none() {
493 Atomic::TPositiveInt
494 } else {
495 Atomic::TIntRange {
496 min: Some(1),
497 max: *max,
498 }
499 };
500 result.add_type(atom);
501 }
502 Atomic::TIntRange { min, max: Some(0) } => {
504 let atom = match min {
505 None => Atomic::TNegativeInt,
506 Some(n) if *n <= -1 => Atomic::TIntRange {
507 min: *min,
508 max: Some(-1),
509 },
510 _ => continue, };
512 result.add_type(atom);
513 }
514 t if !t.can_be_truthy() => {}
516 _ => result.add_type(t.clone()),
517 }
518 }
519 result
520 }
521
522 pub fn narrow_to_falsy(&self) -> Type {
524 if self.is_mixed_not_template() {
525 return Type::from_vec(vec![
526 Atomic::TNull,
527 Atomic::TFalse,
528 Atomic::TLiteralInt(0),
529 Atomic::TLiteralString("".into()),
530 ]);
531 }
532 let mut result = Type::empty();
533 result.from_docblock = self.from_docblock;
534 for t in &self.types {
535 match t {
536 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
541 Atomic::TBool => result.add_type(Atomic::TFalse),
543 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
545 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
547 Atomic::TString => {
549 result.add_type(Atomic::TLiteralString("".into()));
550 result.add_type(Atomic::TLiteralString("0".into()));
551 }
552 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
554 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
556 Atomic::TIntRange {
558 min: Some(0),
559 max: Some(_) | None,
560 } => result.add_type(Atomic::TLiteralInt(0)),
561 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
563 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
565 }
566 }
567 result
568 }
569
570 pub fn narrow_instanceof(&self, class: &str) -> Type {
576 let narrowed_ty = Atomic::TNamedObject {
577 fqcn: class.into(),
578 type_params: empty_type_params(),
579 };
580 let has_object = self.types.iter().any(|t| {
582 matches!(
583 t,
584 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
586 });
587 if has_object || self.is_empty() {
588 Type::single(narrowed_ty)
589 } else {
590 Type::single(narrowed_ty)
593 }
594 }
595
596 pub fn narrow_to_string(&self) -> Type {
600 self.filter_replacing(
601 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
602 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
603 Atomic::TString,
604 )
605 }
606
607 pub fn narrow_to_int(&self) -> Type {
609 self.filter_replacing(
610 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
611 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
612 Atomic::TInt,
613 )
614 }
615
616 pub fn narrow_to_float(&self) -> Type {
618 self.filter_replacing(
619 |t| {
620 matches!(
621 t,
622 Atomic::TFloat
623 | Atomic::TIntegralFloat
624 | Atomic::TLiteralFloat(..)
625 | Atomic::TTemplateParam { .. }
626 )
627 },
628 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
629 Atomic::TFloat,
630 )
631 }
632
633 pub fn narrow_to_bool(&self) -> Type {
635 self.filter_replacing(
636 |t| {
637 matches!(
638 t,
639 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
640 )
641 },
642 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
643 Atomic::TBool,
644 )
645 }
646
647 pub fn narrow_to_null(&self) -> Type {
649 self.filter_replacing(
650 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
651 |t| matches!(t, Atomic::TMixed),
652 Atomic::TNull,
653 )
654 }
655
656 pub fn narrow_to_array(&self) -> Type {
658 self.filter_replacing(
659 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
660 |t| matches!(t, Atomic::TMixed),
661 Atomic::TArray {
662 key: Box::new(Type::mixed()),
663 value: Box::new(Type::mixed()),
664 },
665 )
666 }
667
668 pub fn narrow_to_non_empty_collection(&self) -> Type {
670 let mut out = Type::empty();
671 out.from_docblock = self.from_docblock;
672 for t in &self.types {
673 match t {
674 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
675 key: key.clone(),
676 value: value.clone(),
677 }),
678 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
679 value: value.clone(),
680 }),
681 _ => out.add_type(t.clone()),
682 }
683 }
684 out
685 }
686
687 pub fn narrow_to_empty_collection(&self) -> Type {
697 let mut out = Type::empty();
698 out.from_docblock = self.from_docblock;
699 for t in &self.types {
700 match t {
701 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
702 Atomic::TArray { .. } | Atomic::TList { .. } => {
703 out.add_type(Atomic::TKeyedArray {
704 properties: Box::default(),
705 is_open: false,
706 is_list: true,
707 });
708 }
709 _ => out.add_type(t.clone()),
710 }
711 }
712 out
713 }
714
715 pub fn narrow_to_list(&self) -> Type {
727 let mut out = Type::empty();
728 out.from_docblock = self.from_docblock;
729 for t in &self.types {
730 match t {
731 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
732 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
738 out.add_type(Atomic::TList {
739 value: value.clone(),
740 });
741 }
742 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
743 out.add_type(Atomic::TNonEmptyList {
744 value: value.clone(),
745 });
746 }
747 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
748 Atomic::TMixed => out.add_type(Atomic::TList {
749 value: Box::new(Type::mixed()),
750 }),
751 _ => {}
752 }
753 }
754 if out.is_empty() {
755 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
756 } else {
757 out
758 }
759 }
760
761 pub fn narrow_to_object(&self) -> Type {
766 let mut out = Type::empty();
767 for t in &self.types {
768 if matches!(t, Atomic::TMixed) {
769 out.add_type(Atomic::TObject);
770 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
771 out.add_type(t.clone());
772 }
773 }
774 if out.types.is_empty() {
775 self.filter(|t| t.is_object())
776 } else {
777 out
778 }
779 }
780
781 pub fn narrow_to_callable(&self) -> Type {
788 let narrowed = self.filter(|t| {
789 t.is_callable()
790 || t.is_string()
791 || t.is_array()
792 || t.is_object()
793 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
794 });
795 let mut result = Type::empty();
802 result.possibly_undefined = narrowed.possibly_undefined;
803 result.from_docblock = narrowed.from_docblock;
804 for atomic in narrowed.types {
805 if matches!(atomic, Atomic::TObject) {
806 result.add_type(Atomic::TCallable {
807 params: None,
808 return_type: None,
809 });
810 } else {
811 result.add_type(atomic);
812 }
813 }
814 result
815 }
816
817 pub fn narrow_to_scalar(&self) -> Type {
819 self.filter_replacing(
820 |t| {
821 t.is_string()
822 || t.is_int()
823 || matches!(
824 t,
825 Atomic::TFloat
826 | Atomic::TIntegralFloat
827 | Atomic::TLiteralFloat(..)
828 | Atomic::TBool
829 | Atomic::TTrue
830 | Atomic::TFalse
831 | Atomic::TScalar
832 | Atomic::TNumeric
833 | Atomic::TNumericString
834 | Atomic::TTemplateParam { .. }
835 )
836 },
837 |t| matches!(t, Atomic::TMixed),
838 Atomic::TScalar,
839 )
840 }
841
842 pub fn narrow_to_iterable(&self) -> Type {
845 self.filter(|t| {
846 t.is_array()
847 || t.is_object()
848 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
849 })
850 }
851
852 pub fn narrow_to_countable(&self) -> Type {
855 self.filter(|t| {
856 t.is_array()
857 || t.is_object()
858 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
859 })
860 }
861
862 pub fn narrow_to_resource(&self) -> Type {
866 self.filter(|t| matches!(t, Atomic::TMixed))
868 }
869
870 pub fn narrow_to_class_string(&self) -> Type {
875 let mut out = Type::empty();
876 out.from_docblock = self.from_docblock;
877 for t in &self.types {
878 match t {
879 Atomic::TClassString(_) => out.add_type(t.clone()),
880 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
881 out.add_type(Atomic::TClassString(None));
882 }
883 _ => {}
884 }
885 }
886 out
887 }
888
889 pub fn narrow_to_interface_string(&self) -> Type {
894 let mut out = Type::empty();
895 out.from_docblock = self.from_docblock;
896 for t in &self.types {
897 match t {
898 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
899 Atomic::TClassString(name) => {
903 out.add_type(Atomic::TInterfaceString(*name));
904 }
905 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
906 out.add_type(Atomic::TInterfaceString(None));
907 }
908 _ => {}
909 }
910 }
911 out
912 }
913
914 pub fn merge(a: &Type, b: &Type) -> Type {
919 if b.types.is_empty() {
921 let mut result = a.clone();
922 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
923 return result;
924 }
925 if a.types.is_empty() {
927 let mut result = b.clone();
928 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
929 return result;
930 }
931 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
933 let mut result = a.clone();
934 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
935 return result;
936 }
937 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
939 return Type {
940 types: smallvec::smallvec![Atomic::TMixed],
941 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
942 from_docblock: a.from_docblock || b.from_docblock,
943 falsy_stripped: false,
944 possibly_absent_offset: false,
945 };
946 }
947 let mut result = a.clone();
948 result.merge_with(b);
949 result
950 }
951
952 pub fn merge_with(&mut self, other: &Type) {
954 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
955 self.possibly_undefined |= other.possibly_undefined;
956 return;
957 }
958 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
959 self.types.clear();
960 self.types.push(Atomic::TMixed);
961 self.possibly_undefined |= other.possibly_undefined;
962 return;
963 }
964 for atomic in &other.types {
965 self.add_type(atomic.clone());
966 }
967 self.possibly_undefined |= other.possibly_undefined;
968 }
969
970 pub fn intersect_with(&self, other: &Type) -> Type {
974 if self.is_mixed() {
975 return other.clone();
976 }
977 if other.is_mixed() {
978 return self.clone();
979 }
980 let mut result = Type::empty();
988 for a in &self.types {
989 for b in &other.types {
990 if a == b {
991 result.add_type(a.clone());
992 } else if atomic_subtype(b, a) {
993 result.add_type(b.clone());
994 } else if atomic_subtype(a, b) {
995 result.add_type(a.clone());
996 }
997 }
998 }
999 if result.is_empty() {
1000 Type::never()
1001 } else {
1002 result
1003 }
1004 }
1005
1006 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1010 if bindings.is_empty() {
1011 return self.clone();
1012 }
1013 if !self.types.iter().any(atomic_may_contain_templates) {
1016 return self.clone();
1017 }
1018 let mut result = Type::empty();
1019 result.possibly_undefined = self.possibly_undefined;
1020 result.from_docblock = self.from_docblock;
1021 for atomic in &self.types {
1022 match atomic {
1023 Atomic::TTemplateParam { name, .. } => {
1024 if let Some(resolved) = bindings.get(name) {
1025 for t in &resolved.types {
1026 result.add_type(t.clone());
1027 }
1028 } else {
1029 result.add_type(atomic.clone());
1030 }
1031 }
1032 Atomic::TArray { key, value } => {
1033 result.add_type(Atomic::TArray {
1034 key: Box::new(key.substitute_templates(bindings)),
1035 value: Box::new(value.substitute_templates(bindings)),
1036 });
1037 }
1038 Atomic::TList { value } => {
1039 result.add_type(Atomic::TList {
1040 value: Box::new(value.substitute_templates(bindings)),
1041 });
1042 }
1043 Atomic::TNonEmptyArray { key, value } => {
1044 result.add_type(Atomic::TNonEmptyArray {
1045 key: Box::new(key.substitute_templates(bindings)),
1046 value: Box::new(value.substitute_templates(bindings)),
1047 });
1048 }
1049 Atomic::TNonEmptyList { value } => {
1050 result.add_type(Atomic::TNonEmptyList {
1051 value: Box::new(value.substitute_templates(bindings)),
1052 });
1053 }
1054 Atomic::TKeyedArray {
1055 properties,
1056 is_open,
1057 is_list,
1058 } => {
1059 use crate::atomic::KeyedProperty;
1060 let new_props = properties
1061 .iter()
1062 .map(|(k, prop)| {
1063 (
1064 k.clone(),
1065 KeyedProperty {
1066 ty: prop.ty.substitute_templates(bindings),
1067 optional: prop.optional,
1068 },
1069 )
1070 })
1071 .collect();
1072 result.add_type(Atomic::TKeyedArray {
1073 properties: Box::new(new_props),
1074 is_open: *is_open,
1075 is_list: *is_list,
1076 });
1077 }
1078 Atomic::TCallable {
1079 params,
1080 return_type,
1081 } => {
1082 result.add_type(Atomic::TCallable {
1083 params: params.as_ref().map(|ps| {
1084 ps.iter()
1085 .map(|p| substitute_in_fn_param(p, bindings))
1086 .collect()
1087 }),
1088 return_type: return_type
1089 .as_ref()
1090 .map(|r| Box::new(r.substitute_templates(bindings))),
1091 });
1092 }
1093 Atomic::TClosure { data } => {
1094 result.add_type(Atomic::TClosure {
1095 data: Box::new(crate::atomic::ClosureData {
1096 params: data
1097 .params
1098 .iter()
1099 .map(|p| substitute_in_fn_param(p, bindings))
1100 .collect(),
1101 return_type: data.return_type.substitute_templates(bindings),
1102 this_type: data
1103 .this_type
1104 .as_ref()
1105 .map(|t| t.substitute_templates(bindings)),
1106 }),
1107 });
1108 }
1109 Atomic::TConditional { data } => {
1110 let param_name = &data.param_name;
1111 let new_subject = data.subject.substitute_templates(bindings);
1112 let new_if_true = data.if_true.substitute_templates(bindings);
1113 let new_if_false = data.if_false.substitute_templates(bindings);
1114
1115 let resolved = if let Some(name) = param_name {
1119 if let Some(bound) = bindings.get(name) {
1120 if new_subject.types.len() == 1 {
1121 resolve_conditional_branch(
1122 &new_subject.types[0],
1123 bound,
1124 &new_if_true,
1125 &new_if_false,
1126 )
1127 } else {
1128 None
1129 }
1130 } else {
1131 None
1132 }
1133 } else {
1134 None
1135 };
1136
1137 if let Some(branch) = resolved {
1138 for t in branch.types {
1139 result.add_type(t);
1140 }
1141 } else {
1142 result.add_type(Atomic::TConditional {
1143 data: Box::new(crate::atomic::ConditionalData {
1144 param_name: *param_name,
1145 subject: new_subject,
1146 if_true: new_if_true,
1147 if_false: new_if_false,
1148 }),
1149 });
1150 }
1151 }
1152 Atomic::TIntersection { parts } => {
1153 result.add_type(Atomic::TIntersection {
1154 parts: vec_to_type_params(
1155 parts
1156 .iter()
1157 .map(|p| p.substitute_templates(bindings))
1158 .collect(),
1159 ),
1160 });
1161 }
1162 Atomic::TNamedObject { fqcn, type_params } => {
1163 if type_params.is_empty() && !fqcn.contains('\\') {
1170 if let Some(resolved) = bindings.get(fqcn) {
1171 for t in &resolved.types {
1172 result.add_type(t.clone());
1173 }
1174 continue;
1175 }
1176 }
1177 let new_params: Vec<Type> = type_params
1178 .iter()
1179 .map(|p| p.substitute_templates(bindings))
1180 .collect();
1181 result.add_type(Atomic::TNamedObject {
1182 fqcn: *fqcn,
1183 type_params: vec_to_type_params(new_params),
1184 });
1185 }
1186 Atomic::TClassString(Some(param_name)) => {
1188 if let Some(resolved) = bindings.get(param_name) {
1189 for r_atomic in &resolved.types {
1190 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1191 Some(*fqcn)
1192 } else {
1193 None
1194 };
1195 result.add_type(Atomic::TClassString(cls_name));
1196 }
1197 } else {
1198 result.add_type(atomic.clone());
1199 }
1200 }
1201 Atomic::TInterfaceString(Some(param_name)) => {
1203 if let Some(resolved) = bindings.get(param_name) {
1204 for r_atomic in &resolved.types {
1205 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1206 Some(*fqcn)
1207 } else {
1208 None
1209 };
1210 result.add_type(Atomic::TInterfaceString(iface_name));
1211 }
1212 } else {
1213 result.add_type(atomic.clone());
1214 }
1215 }
1216 _ => {
1217 result.add_type(atomic.clone());
1218 }
1219 }
1220 }
1221 result
1222 }
1223
1224 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1230 where
1231 F: Fn(&str) -> Option<Type>,
1232 {
1233 self.resolve_conditional_inner(&lookup)
1234 }
1235
1236 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1237 where
1238 F: Fn(&str) -> Option<Type>,
1239 {
1240 let mut result = Type::empty();
1241 for atomic in self.types {
1242 match atomic {
1243 Atomic::TConditional { ref data } => {
1244 let (param_name, subject, if_true, if_false) = (
1245 &data.param_name,
1246 &data.subject,
1247 &data.if_true,
1248 &data.if_false,
1249 );
1250 let resolved = if subject.types.len() == 1 {
1251 if let Some(name) = param_name {
1252 if let Some(arg_ty) = lookup(name.as_ref()) {
1253 resolve_conditional_branch(
1254 &subject.types[0],
1255 &arg_ty,
1256 if_true,
1257 if_false,
1258 )
1259 } else {
1260 None
1261 }
1262 } else {
1263 None
1264 }
1265 } else {
1266 None
1267 };
1268
1269 if let Some(branch) = resolved {
1270 for t in branch.resolve_conditional_inner(lookup).types {
1272 result.add_type(t);
1273 }
1274 } else {
1275 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1278 result.add_type(t);
1279 }
1280 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1281 result.add_type(t);
1282 }
1283 }
1284 }
1285 other => result.add_type(other),
1286 }
1287 }
1288 result
1289 }
1290
1291 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1301 if other.is_mixed() {
1302 return true;
1303 }
1304 if self.is_never() {
1305 return true; }
1307 self.types
1308 .iter()
1309 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1310 }
1311
1312 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1316 if self.is_mixed() {
1317 return true;
1318 }
1319 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1320 }
1321
1322 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1325 let mut result = Type::empty();
1326 result.possibly_undefined = self.possibly_undefined;
1327 result.from_docblock = self.from_docblock;
1328 for atomic in &self.types {
1329 if f(atomic) {
1330 result.types.push(atomic.clone());
1331 }
1332 }
1333 result
1334 }
1335
1336 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1341 &self,
1342 keep: K,
1343 placeholder: P,
1344 replacement: Atomic,
1345 ) -> Type {
1346 let mut result = Type::empty();
1347 result.possibly_undefined = self.possibly_undefined;
1348 result.from_docblock = self.from_docblock;
1349 for atomic in &self.types {
1350 if keep(atomic) {
1351 result.add_type(atomic.clone());
1352 } else if placeholder(atomic) {
1353 result.add_type(replacement.clone());
1354 }
1355 }
1356 result
1357 }
1358
1359 pub fn possibly_undefined(mut self) -> Self {
1361 self.possibly_undefined = true;
1362 self
1363 }
1364
1365 pub fn from_docblock(mut self) -> Self {
1367 self.from_docblock = true;
1368 self
1369 }
1370
1371 pub fn falsy_stripped(mut self) -> Self {
1374 self.falsy_stripped = true;
1375 self
1376 }
1377
1378 pub fn possibly_absent_offset(mut self) -> Self {
1382 self.possibly_absent_offset = true;
1383 self
1384 }
1385}
1386
1387fn is_string_atomic(a: &Atomic) -> bool {
1392 matches!(
1393 a,
1394 Atomic::TString
1395 | Atomic::TNonEmptyString
1396 | Atomic::TLiteralString(_)
1397 | Atomic::TNumericString
1398 | Atomic::TClassString(_)
1399 | Atomic::TInterfaceString(_)
1400 | Atomic::TCallableString
1401 )
1402}
1403
1404fn is_array_atomic(a: &Atomic) -> bool {
1405 matches!(
1406 a,
1407 Atomic::TArray { .. }
1408 | Atomic::TNonEmptyArray { .. }
1409 | Atomic::TKeyedArray { .. }
1410 | Atomic::TList { .. }
1411 | Atomic::TNonEmptyList { .. }
1412 )
1413}
1414
1415fn is_list_atomic(a: &Atomic) -> bool {
1416 match a {
1417 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1418 Atomic::TKeyedArray { is_list, .. } => *is_list,
1419 _ => false,
1420 }
1421}
1422
1423fn is_float_atomic(a: &Atomic) -> bool {
1424 matches!(
1425 a,
1426 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1427 )
1428}
1429
1430fn is_bool_atomic(a: &Atomic) -> bool {
1431 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1432}
1433
1434fn resolve_conditional_branch(
1440 subject: &Atomic,
1441 arg_ty: &Type,
1442 if_true: &Type,
1443 if_false: &Type,
1444) -> Option<Type> {
1445 let predicate: fn(&Atomic) -> bool = match subject {
1446 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1447 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1448 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1449 Atomic::TString => is_string_atomic,
1450 Atomic::TList { .. } => is_list_atomic,
1451 Atomic::TArray { .. } => is_array_atomic,
1452 Atomic::TInt => Atomic::is_int,
1453 Atomic::TFloat => is_float_atomic,
1454 Atomic::TBool => is_bool_atomic,
1455 _ => return None,
1456 };
1457
1458 if arg_ty.types.is_empty() {
1459 return None;
1460 }
1461 let all_match = arg_ty.types.iter().all(&predicate);
1462 let none_match = !arg_ty.types.iter().any(predicate);
1463 if all_match {
1464 Some(if_true.clone())
1465 } else if none_match {
1466 Some(if_false.clone())
1467 } else {
1468 None
1469 }
1470}
1471
1472fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1480 match atomic {
1481 Atomic::TNamedObject { fqcn, type_params } => {
1485 !type_params.is_empty() || !fqcn.contains('\\')
1486 }
1487 Atomic::TTemplateParam { .. }
1488 | Atomic::TArray { .. }
1489 | Atomic::TList { .. }
1490 | Atomic::TNonEmptyArray { .. }
1491 | Atomic::TNonEmptyList { .. }
1492 | Atomic::TKeyedArray { .. }
1493 | Atomic::TCallable { .. }
1494 | Atomic::TClosure { .. }
1495 | Atomic::TConditional { .. }
1496 | Atomic::TIntersection { .. }
1497 | Atomic::TClassString(Some(_))
1498 | Atomic::TInterfaceString(Some(_)) => true,
1499 _ => false,
1500 }
1501}
1502
1503fn substitute_in_fn_param(
1504 p: &crate::atomic::FnParam,
1505 bindings: &FxHashMap<Name, Type>,
1506) -> crate::atomic::FnParam {
1507 crate::atomic::FnParam {
1508 name: p.name,
1509 ty: p.ty.as_ref().map(|t| {
1510 let u = t.to_union();
1511 let substituted = u.substitute_templates(bindings);
1512 crate::compact::SimpleType::from_union(substituted)
1513 }),
1514 out_ty: p.out_ty.as_ref().map(|t| {
1515 let u = t.to_union();
1516 let substituted = u.substitute_templates(bindings);
1517 crate::compact::SimpleType::from_union(substituted)
1518 }),
1519 default: p.default.as_ref().map(|d| {
1520 let u = d.to_union();
1521 let substituted = u.substitute_templates(bindings);
1522 crate::compact::SimpleType::from_union(substituted)
1523 }),
1524 is_variadic: p.is_variadic,
1525 is_byref: p.is_byref,
1526 is_optional: p.is_optional,
1527 }
1528}
1529
1530pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1536 if sub == sup {
1537 return true;
1538 }
1539 match (sub, sup) {
1540 (Atomic::TNever, _) => true,
1542 (_, Atomic::TMixed) => true,
1544 (Atomic::TMixed, _) => true,
1545 (_, Atomic::TTemplateParam { as_type, .. }) => {
1550 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1551 }
1552
1553 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1555 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1556 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1557 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1558 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1559 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1560 (Atomic::TPositiveInt, Atomic::TInt) => true,
1561 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1562 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1563 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1564 (Atomic::TNegativeInt, Atomic::TInt) => true,
1565 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1566 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1567 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1568 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1569 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1570 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1571 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1572 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1573 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1575 max.is_none() && min.is_none_or(|m| m <= 1)
1576 }
1577 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1579 min.is_none() && max.is_none_or(|m| m >= -1)
1580 }
1581 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1583 max.is_none() && min.is_none_or(|m| m <= 0)
1584 }
1585 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1587 sub_min.is_some_and(|lo| lo >= 1)
1588 }
1589 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1590 sub_min.is_some_and(|lo| lo >= 0)
1591 }
1592 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1593 sub_max.is_some_and(|hi| hi <= -1)
1594 }
1595 (
1597 Atomic::TIntRange {
1598 min: sub_min,
1599 max: sub_max,
1600 },
1601 Atomic::TIntRange {
1602 min: sup_min,
1603 max: sup_max,
1604 },
1605 ) => {
1606 let lower_ok = match (sub_min, sup_min) {
1607 (_, None) => true,
1608 (None, Some(_)) => false,
1609 (Some(sl), Some(su)) => sl >= su,
1610 };
1611 let upper_ok = match (sub_max, sup_max) {
1612 (None, None) | (Some(_), None) => true,
1613 (None, Some(_)) => false,
1614 (Some(sl), Some(su)) => sl <= su,
1615 };
1616 lower_ok && upper_ok
1617 }
1618
1619 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1620 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1621 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1622
1623 (Atomic::TLiteralString(s), Atomic::TString) => {
1624 let _ = s;
1625 true
1626 }
1627 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1628 let _ = s;
1629 true
1630 }
1631 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1632 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1633 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1636 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1639 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1640 (Atomic::TNonEmptyString, Atomic::TString) => true,
1641 (Atomic::TCallableString, Atomic::TString) => true,
1642 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1644 (Atomic::TNumericString, Atomic::TString) => true,
1645 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1649 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1650 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1651 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1652 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1653 (Atomic::TClassString(_), Atomic::TString) => true,
1654 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1655 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1660 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1661 (Atomic::TEnumString, Atomic::TString) => true,
1662 (Atomic::TTraitString, Atomic::TString) => true,
1663
1664 (Atomic::TTrue, Atomic::TBool) => true,
1665 (Atomic::TFalse, Atomic::TBool) => true,
1666
1667 (Atomic::TInt, Atomic::TNumeric) => true,
1668 (Atomic::TFloat, Atomic::TNumeric) => true,
1669 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1670 (Atomic::TNumericString, Atomic::TNumeric) => true,
1671
1672 (Atomic::TInt, Atomic::TScalar) => true,
1673 (Atomic::TFloat, Atomic::TScalar) => true,
1674 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1675 (Atomic::TString, Atomic::TScalar) => true,
1676 (Atomic::TBool, Atomic::TScalar) => true,
1677 (Atomic::TNumeric, Atomic::TScalar) => true,
1678 (Atomic::TTrue, Atomic::TScalar) => true,
1679 (Atomic::TFalse, Atomic::TScalar) => true,
1680 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1684 (Atomic::TNumericString, Atomic::TScalar) => true,
1685 (Atomic::TCallableString, Atomic::TScalar) => true,
1686 (Atomic::TClassString(_), Atomic::TScalar) => true,
1687 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1688 (Atomic::TEnumString, Atomic::TScalar) => true,
1689 (Atomic::TTraitString, Atomic::TScalar) => true,
1690
1691 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1693 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1694 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1695 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
1697 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1699 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1700 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1702 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1703 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
1706 enum_fqcn == fqcn
1707 }
1708 (
1712 Atomic::TNamedObject {
1713 fqcn: sub_fqcn,
1714 type_params: sub_params,
1715 },
1716 Atomic::TNamedObject {
1717 fqcn: sup_fqcn,
1718 type_params: sup_params,
1719 },
1720 ) => {
1721 sub_fqcn == sup_fqcn
1722 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1723 }
1724
1725 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1727
1728 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1730 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1731 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1732 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1733 (Atomic::TInt, Atomic::TFloat) => true,
1734 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1735
1736 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1738 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1739 }
1740
1741 (Atomic::TString, Atomic::TCallable { .. }) => true,
1743 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1744 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1745 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1746 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1747 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1748
1749 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1751 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1753 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1763 fn has_nominal_type(t: &Type) -> bool {
1764 t.types.iter().any(|a| {
1765 matches!(
1766 a,
1767 Atomic::TNamedObject { .. }
1768 | Atomic::TSelf { .. }
1769 | Atomic::TStaticObject { .. }
1770 | Atomic::TTemplateParam { .. }
1771 | Atomic::TClosure { .. }
1772 | Atomic::TCallable { .. }
1773 )
1774 })
1775 }
1776 let sub_required = sub
1777 .params
1778 .iter()
1779 .filter(|p| !p.is_optional && !p.is_variadic)
1780 .count();
1781 if sub_required > sup.params.len() {
1782 false
1783 } else {
1784 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1785 let Some(sub_param) = sub.params.get(i) else {
1786 return true;
1787 };
1788 if sub_param.is_optional || sub_param.is_variadic {
1789 return true;
1790 }
1791 let (Some(sub_ty), Some(sup_ty)) =
1792 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1793 else {
1794 return true;
1795 };
1796 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1797 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1798 return true;
1799 }
1800 sup_u.is_subtype_structural(&sub_u)
1803 });
1804 params_ok
1805 && (sub.return_type.is_mixed()
1806 || sup.return_type.is_mixed()
1807 || has_nominal_type(&sub.return_type)
1808 || has_nominal_type(&sup.return_type)
1809 || sub.return_type.is_subtype_structural(&sup.return_type))
1810 }
1811 }
1812 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1814 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1816 fqcn.as_ref().eq_ignore_ascii_case("closure")
1817 }
1818 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1819 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1821 fqcn.as_ref().eq_ignore_ascii_case("closure")
1822 }
1823 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1825 fqcn.as_ref().eq_ignore_ascii_case("closure")
1826 }
1827
1828 (
1836 Atomic::TIntersection { parts: sub_parts },
1837 Atomic::TIntersection { parts: sup_parts },
1838 ) => sup_parts.iter().all(|sup_part| {
1839 sub_parts
1840 .iter()
1841 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
1842 }),
1843
1844 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1846 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1847 }
1848 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1849 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1850 }
1851 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1852 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1853 }
1854 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1855 value.is_subtype_structural(lv)
1856 }
1857 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1859 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1860 && av.is_subtype_structural(lv)
1861 }
1862 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1863 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1864 && av.is_subtype_structural(lv)
1865 }
1866 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1867 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1868 && av.is_subtype_structural(lv)
1869 }
1870 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1871 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1872 && av.is_subtype_structural(lv)
1873 }
1874 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1876 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1877 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1878 }
1879
1880 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1882 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1883 }
1884
1885 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
1894 properties.iter().all(|(prop_key, prop)| {
1895 let key_atomic = match prop_key {
1896 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1897 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1898 };
1899 if !Type::single(key_atomic).is_subtype_structural(key) {
1900 return false; }
1902 let has_named_obj = prop.ty.types.iter().any(|a| {
1904 matches!(
1905 a,
1906 Atomic::TNamedObject { .. }
1907 | Atomic::TSelf { .. }
1908 | Atomic::TStaticObject { .. }
1909 | Atomic::TClosure { .. }
1910 | Atomic::TTemplateParam { .. }
1911 )
1912 });
1913 has_named_obj || prop.ty.is_subtype_structural(value)
1914 })
1915 }
1916 (
1917 Atomic::TKeyedArray {
1918 properties,
1919 is_open,
1920 ..
1921 },
1922 Atomic::TNonEmptyArray { key, value },
1923 ) => {
1924 (*is_open || properties.iter().any(|(_, p)| !p.optional))
1925 && properties.iter().all(|(prop_key, prop)| {
1926 let key_atomic = match prop_key {
1927 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1928 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1929 };
1930 if !Type::single(key_atomic).is_subtype_structural(key) {
1931 return false;
1932 }
1933 let has_named_obj = prop.ty.types.iter().any(|a| {
1934 matches!(
1935 a,
1936 Atomic::TNamedObject { .. }
1937 | Atomic::TSelf { .. }
1938 | Atomic::TStaticObject { .. }
1939 | Atomic::TClosure { .. }
1940 | Atomic::TTemplateParam { .. }
1941 )
1942 });
1943 has_named_obj || prop.ty.is_subtype_structural(value)
1944 })
1945 }
1946
1947 (
1949 Atomic::TKeyedArray {
1950 properties,
1951 is_list,
1952 ..
1953 },
1954 Atomic::TList { value: lv },
1955 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1956 (
1957 Atomic::TKeyedArray {
1958 properties,
1959 is_list,
1960 ..
1961 },
1962 Atomic::TNonEmptyList { value: lv },
1963 ) => {
1964 *is_list
1965 && !properties.is_empty()
1966 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1967 }
1968
1969 (
1974 Atomic::TKeyedArray {
1975 properties: sub_props,
1976 is_open: sub_open,
1977 ..
1978 },
1979 Atomic::TKeyedArray {
1980 properties: sup_props,
1981 is_open: sup_open,
1982 ..
1983 },
1984 ) => {
1985 let keys_satisfied = sup_props
1986 .iter()
1987 .all(|(key, sup_prop)| match sub_props.get(key) {
1988 Some(sub_prop) => {
1989 if !sup_prop.optional && sub_prop.optional {
1993 return false;
1994 }
1995 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
1996 matches!(
1997 a,
1998 Atomic::TNamedObject { .. }
1999 | Atomic::TSelf { .. }
2000 | Atomic::TStaticObject { .. }
2001 | Atomic::TClosure { .. }
2002 | Atomic::TTemplateParam { .. }
2003 )
2004 });
2005 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2006 }
2007 None => sup_prop.optional || *sub_open,
2008 });
2009 let no_undeclared_extras =
2010 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2011 keys_satisfied && no_undeclared_extras
2012 }
2013
2014 _ => false,
2015 }
2016}
2017
2018fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2024 if sub.len() != sup.len() {
2025 return false;
2026 }
2027 sub.iter()
2028 .zip(sup.iter())
2029 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2030}
2031
2032fn is_empty_array_literal(t: &Type) -> bool {
2035 !t.types.is_empty()
2036 && t.types.iter().all(
2037 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2038 )
2039}
2040
2041fn is_array_like(t: &Type) -> bool {
2043 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2044}
2045
2046#[cfg(test)]
2051mod tests {
2052 use std::sync::Arc;
2053
2054 use super::*;
2055
2056 fn conditional(
2057 param_name: Option<Name>,
2058 subject: Type,
2059 if_true: Type,
2060 if_false: Type,
2061 ) -> Atomic {
2062 Atomic::TConditional {
2063 data: Box::new(crate::atomic::ConditionalData {
2064 param_name,
2065 subject,
2066 if_true,
2067 if_false,
2068 }),
2069 }
2070 }
2071
2072 #[test]
2073 fn single_is_single() {
2074 let u = Type::single(Atomic::TString);
2075 assert!(u.is_single());
2076 assert!(!u.is_nullable());
2077 }
2078
2079 #[test]
2080 fn nullable_has_null() {
2081 let u = Type::nullable(Atomic::TString);
2082 assert!(u.is_nullable());
2083 assert_eq!(u.types.len(), 2);
2084 }
2085
2086 #[test]
2087 fn add_type_deduplicates() {
2088 let mut u = Type::single(Atomic::TString);
2089 u.add_type(Atomic::TString);
2090 assert_eq!(u.types.len(), 1);
2091 }
2092
2093 #[test]
2094 fn array_key_is_int_string() {
2095 let k = Type::array_key();
2096 assert!(k.is_array_key());
2097 assert_eq!(k.types.len(), 2);
2098 }
2099
2100 #[test]
2101 fn is_array_key_false_for_plain_int() {
2102 assert!(!Type::int().is_array_key());
2103 }
2104
2105 #[test]
2106 fn is_array_key_false_for_mixed() {
2107 assert!(!Type::mixed().is_array_key());
2108 }
2109
2110 #[test]
2111 fn is_array_key_false_for_int_string_null() {
2112 let mut u = Type::array_key();
2113 u.add_type(Atomic::TNull);
2114 assert!(!u.is_array_key());
2115 }
2116
2117 #[test]
2118 fn add_type_literal_subsumed_by_base() {
2119 let mut u = Type::single(Atomic::TInt);
2120 u.add_type(Atomic::TLiteralInt(42));
2121 assert_eq!(u.types.len(), 1);
2122 assert!(matches!(u.types[0], Atomic::TInt));
2123 }
2124
2125 #[test]
2126 fn true_then_false_merges_to_bool() {
2127 let mut u = Type::single(Atomic::TTrue);
2128 u.add_type(Atomic::TFalse);
2129 assert_eq!(u.types.len(), 1);
2130 assert!(matches!(u.types[0], Atomic::TBool));
2131 }
2132
2133 #[test]
2134 fn false_then_true_merges_to_bool() {
2135 let mut u = Type::single(Atomic::TFalse);
2136 u.add_type(Atomic::TTrue);
2137 assert_eq!(u.types.len(), 1);
2138 assert!(matches!(u.types[0], Atomic::TBool));
2139 }
2140
2141 #[test]
2142 fn true_alone_stays_true() {
2143 let u = Type::single(Atomic::TTrue);
2144 assert_eq!(u.types.len(), 1);
2145 assert!(matches!(u.types[0], Atomic::TTrue));
2146 }
2147
2148 #[test]
2149 fn true_false_merge_preserves_other_union_members() {
2150 let mut u = Type::single(Atomic::TTrue);
2151 u.add_type(Atomic::TNull);
2152 u.add_type(Atomic::TFalse);
2153 assert_eq!(u.types.len(), 2);
2154 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2155 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2156 }
2157
2158 #[test]
2159 fn add_type_base_widens_literals() {
2160 let mut u = Type::single(Atomic::TLiteralInt(1));
2161 u.add_type(Atomic::TLiteralInt(2));
2162 u.add_type(Atomic::TInt);
2163 assert_eq!(u.types.len(), 1);
2164 assert!(matches!(u.types[0], Atomic::TInt));
2165 }
2166
2167 #[test]
2168 fn mixed_subsumes_everything() {
2169 let mut u = Type::single(Atomic::TString);
2170 u.add_type(Atomic::TMixed);
2171 assert_eq!(u.types.len(), 1);
2172 assert!(u.is_mixed());
2173 }
2174
2175 #[test]
2176 fn remove_null() {
2177 let u = Type::nullable(Atomic::TString);
2178 let narrowed = u.remove_null();
2179 assert!(!narrowed.is_nullable());
2180 assert_eq!(narrowed.types.len(), 1);
2181 }
2182
2183 #[test]
2184 fn narrow_to_truthy_removes_null_false() {
2185 let mut u = Type::empty();
2186 u.add_type(Atomic::TString);
2187 u.add_type(Atomic::TNull);
2188 u.add_type(Atomic::TFalse);
2189 let truthy = u.narrow_to_truthy();
2190 assert!(!truthy.is_nullable());
2191 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2192 }
2193
2194 #[test]
2195 fn merge_combines_types() {
2196 let a = Type::single(Atomic::TString);
2197 let b = Type::single(Atomic::TInt);
2198 let merged = Type::merge(&a, &b);
2199 assert_eq!(merged.types.len(), 2);
2200 }
2201
2202 #[test]
2203 fn intersect_keeps_narrower_side_not_self() {
2204 let int_ty = Type::single(Atomic::TInt);
2208 let mut literals = Type::empty();
2209 literals.add_type(Atomic::TLiteralInt(1));
2210 literals.add_type(Atomic::TLiteralInt(2));
2211
2212 let narrowed = int_ty.intersect_with(&literals);
2213 assert_eq!(narrowed.types.len(), 2);
2214 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2215 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2216 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2217 }
2218
2219 #[test]
2220 fn subtype_literal_int_under_int() {
2221 let sub = Type::single(Atomic::TLiteralInt(5));
2222 let sup = Type::single(Atomic::TInt);
2223 assert!(sub.is_subtype_structural(&sup));
2224 }
2225
2226 #[test]
2227 fn subtype_never_is_bottom() {
2228 let never = Type::never();
2229 let string = Type::single(Atomic::TString);
2230 assert!(never.is_subtype_structural(&string));
2231 }
2232
2233 #[test]
2234 fn subtype_everything_under_mixed() {
2235 let string = Type::single(Atomic::TString);
2236 let mixed = Type::mixed();
2237 assert!(string.is_subtype_structural(&mixed));
2238 }
2239
2240 #[test]
2241 fn subtype_enum_case_under_own_enum() {
2242 let sub = Type::single(Atomic::TLiteralEnumCase {
2243 enum_fqcn: Name::new("RoundingMode"),
2244 case_name: Name::new("Unnecessary"),
2245 });
2246 let sup = Type::single(Atomic::TNamedObject {
2247 fqcn: Name::new("RoundingMode"),
2248 type_params: empty_type_params(),
2249 });
2250 assert!(sub.is_subtype_structural(&sup));
2251 }
2252
2253 #[test]
2254 fn enum_case_not_subtype_of_unrelated_enum() {
2255 let sub = Type::single(Atomic::TLiteralEnumCase {
2256 enum_fqcn: Name::new("RoundingMode"),
2257 case_name: Name::new("Unnecessary"),
2258 });
2259 let sup = Type::single(Atomic::TNamedObject {
2260 fqcn: Name::new("Suit"),
2261 type_params: empty_type_params(),
2262 });
2263 assert!(!sub.is_subtype_structural(&sup));
2264 }
2265
2266 #[test]
2267 fn subtype_enum_case_under_bare_object() {
2268 let sub = Type::single(Atomic::TLiteralEnumCase {
2269 enum_fqcn: Name::new("RoundingMode"),
2270 case_name: Name::new("Unnecessary"),
2271 });
2272 let sup = Type::single(Atomic::TObject);
2273 assert!(sub.is_subtype_structural(&sup));
2274 }
2275
2276 #[test]
2277 fn template_substitution() {
2278 let mut bindings = FxHashMap::default();
2279 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2280
2281 let tmpl = Type::single(Atomic::TTemplateParam {
2282 name: Name::new("T"),
2283 as_type: Box::new(Type::mixed()),
2284 defining_entity: Name::new("MyClass"),
2285 });
2286
2287 let resolved = tmpl.substitute_templates(&bindings);
2288 assert_eq!(resolved.types.len(), 1);
2289 assert!(matches!(resolved.types[0], Atomic::TString));
2290 }
2291
2292 #[test]
2293 fn intersection_is_object() {
2294 let parts = vec![
2295 Type::single(Atomic::TNamedObject {
2296 fqcn: Name::new("Iterator"),
2297 type_params: empty_type_params(),
2298 }),
2299 Type::single(Atomic::TNamedObject {
2300 fqcn: Name::new("Countable"),
2301 type_params: empty_type_params(),
2302 }),
2303 ];
2304 let atomic = Atomic::TIntersection {
2305 parts: vec_to_type_params(parts),
2306 };
2307 assert!(atomic.is_object());
2308 assert!(!atomic.can_be_falsy());
2309 assert!(atomic.can_be_truthy());
2310 }
2311
2312 #[test]
2313 fn intersection_display_two_parts() {
2314 let parts = vec![
2315 Type::single(Atomic::TNamedObject {
2316 fqcn: Name::new("Iterator"),
2317 type_params: empty_type_params(),
2318 }),
2319 Type::single(Atomic::TNamedObject {
2320 fqcn: Name::new("Countable"),
2321 type_params: empty_type_params(),
2322 }),
2323 ];
2324 let u = Type::single(Atomic::TIntersection {
2325 parts: vec_to_type_params(parts),
2326 });
2327 assert_eq!(format!("{u}"), "Iterator&Countable");
2328 }
2329
2330 #[test]
2331 fn intersection_display_three_parts() {
2332 let parts = vec![
2333 Type::single(Atomic::TNamedObject {
2334 fqcn: Name::new("A"),
2335 type_params: empty_type_params(),
2336 }),
2337 Type::single(Atomic::TNamedObject {
2338 fqcn: Name::new("B"),
2339 type_params: empty_type_params(),
2340 }),
2341 Type::single(Atomic::TNamedObject {
2342 fqcn: Name::new("C"),
2343 type_params: empty_type_params(),
2344 }),
2345 ];
2346 let u = Type::single(Atomic::TIntersection {
2347 parts: vec_to_type_params(parts),
2348 });
2349 assert_eq!(format!("{u}"), "A&B&C");
2350 }
2351
2352 #[test]
2353 fn intersection_in_nullable_union_display() {
2354 let intersection = Atomic::TIntersection {
2355 parts: vec_to_type_params(vec![
2356 Type::single(Atomic::TNamedObject {
2357 fqcn: Name::new("Iterator"),
2358 type_params: empty_type_params(),
2359 }),
2360 Type::single(Atomic::TNamedObject {
2361 fqcn: Name::new("Countable"),
2362 type_params: empty_type_params(),
2363 }),
2364 ]),
2365 };
2366 let mut u = Type::single(intersection);
2367 u.add_type(Atomic::TNull);
2368 assert!(u.is_nullable());
2369 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2370 }
2371
2372 fn t_param(name: &str) -> Type {
2375 Type::single(Atomic::TTemplateParam {
2376 name: Name::new(name),
2377 as_type: Box::new(Type::mixed()),
2378 defining_entity: Name::new("Fn"),
2379 })
2380 }
2381
2382 fn bindings_t_string() -> FxHashMap<Name, Type> {
2383 let mut b = FxHashMap::default();
2384 b.insert(Name::new("T"), Type::single(Atomic::TString));
2385 b
2386 }
2387
2388 #[test]
2389 fn substitute_non_empty_array_key_and_value() {
2390 let ty = Type::single(Atomic::TNonEmptyArray {
2391 key: Box::new(t_param("T")),
2392 value: Box::new(t_param("T")),
2393 });
2394 let result = ty.substitute_templates(&bindings_t_string());
2395 assert_eq!(result.types.len(), 1);
2396 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2397 panic!("expected TNonEmptyArray");
2398 };
2399 assert!(matches!(key.types[0], Atomic::TString));
2400 assert!(matches!(value.types[0], Atomic::TString));
2401 }
2402
2403 #[test]
2404 fn substitute_non_empty_list_value() {
2405 let ty = Type::single(Atomic::TNonEmptyList {
2406 value: Box::new(t_param("T")),
2407 });
2408 let result = ty.substitute_templates(&bindings_t_string());
2409 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2410 panic!("expected TNonEmptyList");
2411 };
2412 assert!(matches!(value.types[0], Atomic::TString));
2413 }
2414
2415 #[test]
2416 fn substitute_keyed_array_property_types() {
2417 use crate::atomic::{ArrayKey, KeyedProperty};
2418 use indexmap::IndexMap;
2419 let mut props = IndexMap::new();
2420 props.insert(
2421 ArrayKey::String(Arc::from("name")),
2422 KeyedProperty {
2423 ty: t_param("T"),
2424 optional: false,
2425 },
2426 );
2427 props.insert(
2428 ArrayKey::String(Arc::from("tag")),
2429 KeyedProperty {
2430 ty: t_param("T"),
2431 optional: true,
2432 },
2433 );
2434 let ty = Type::single(Atomic::TKeyedArray {
2435 properties: Box::new(props),
2436 is_open: true,
2437 is_list: false,
2438 });
2439 let result = ty.substitute_templates(&bindings_t_string());
2440 let Atomic::TKeyedArray {
2441 properties,
2442 is_open,
2443 is_list,
2444 } = &result.types[0]
2445 else {
2446 panic!("expected TKeyedArray");
2447 };
2448 assert!(is_open);
2449 assert!(!is_list);
2450 assert!(matches!(
2451 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2452 Atomic::TString
2453 ));
2454 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2455 assert!(matches!(
2456 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2457 Atomic::TString
2458 ));
2459 }
2460
2461 #[test]
2462 fn substitute_callable_params_and_return() {
2463 use crate::atomic::FnParam;
2464 let ty = Type::single(Atomic::TCallable {
2465 params: Some(Box::new([FnParam {
2466 name: Name::new("x"),
2467 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2468 out_ty: None,
2469 default: None,
2470 is_variadic: false,
2471 is_byref: false,
2472 is_optional: false,
2473 }])),
2474 return_type: Some(Box::new(t_param("T"))),
2475 });
2476 let result = ty.substitute_templates(&bindings_t_string());
2477 let Atomic::TCallable {
2478 params,
2479 return_type,
2480 } = &result.types[0]
2481 else {
2482 panic!("expected TCallable");
2483 };
2484 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2485 let param_union = param_ty.to_union();
2486 assert!(matches!(param_union.types[0], Atomic::TString));
2487 let ret = return_type.as_ref().unwrap();
2488 assert!(matches!(ret.types[0], Atomic::TString));
2489 }
2490
2491 #[test]
2492 fn substitute_callable_bare_no_panic() {
2493 let ty = Type::single(Atomic::TCallable {
2495 params: None,
2496 return_type: None,
2497 });
2498 let result = ty.substitute_templates(&bindings_t_string());
2499 assert!(matches!(
2500 result.types[0],
2501 Atomic::TCallable {
2502 params: None,
2503 return_type: None
2504 }
2505 ));
2506 }
2507
2508 #[test]
2509 fn substitute_closure_params_return_and_this() {
2510 use crate::atomic::FnParam;
2511 let ty = Type::single(Atomic::TClosure {
2512 data: Box::new(crate::atomic::ClosureData {
2513 params: Box::new([FnParam {
2514 name: Name::new("a"),
2515 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2516 out_ty: None,
2517 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2518 is_variadic: true,
2519 is_byref: true,
2520 is_optional: true,
2521 }]),
2522 return_type: t_param("T"),
2523 this_type: Some(t_param("T")),
2524 }),
2525 });
2526 let result = ty.substitute_templates(&bindings_t_string());
2527 let Atomic::TClosure { data } = &result.types[0] else {
2528 panic!("expected TClosure");
2529 };
2530 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2531 let p = ¶ms[0];
2532 let ty_union = p.ty.as_ref().unwrap().to_union();
2533 let default_union = p.default.as_ref().unwrap().to_union();
2534 assert!(matches!(ty_union.types[0], Atomic::TString));
2535 assert!(matches!(default_union.types[0], Atomic::TString));
2536 assert!(p.is_variadic);
2538 assert!(p.is_byref);
2539 assert!(p.is_optional);
2540 assert!(matches!(return_type.types[0], Atomic::TString));
2541 assert!(matches!(
2542 this_type.as_ref().unwrap().types[0],
2543 Atomic::TString
2544 ));
2545 }
2546
2547 #[test]
2548 fn substitute_conditional_all_branches() {
2549 let ty = Type::single(conditional(
2550 None,
2551 t_param("T"),
2552 t_param("T"),
2553 Type::single(Atomic::TInt),
2554 ));
2555 let result = ty.substitute_templates(&bindings_t_string());
2556 let Atomic::TConditional { data } = &result.types[0] else {
2557 panic!("expected TConditional");
2558 };
2559 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2560 assert!(matches!(subject.types[0], Atomic::TString));
2561 assert!(matches!(if_true.types[0], Atomic::TString));
2562 assert!(matches!(if_false.types[0], Atomic::TInt));
2563 }
2564
2565 #[test]
2566 fn resolve_conditional_is_null_non_null_arg() {
2567 let ty = Type::single(conditional(
2568 Some(Name::new("x")),
2569 Type::single(Atomic::TNull),
2570 Type::single(Atomic::TInt),
2571 Type::single(Atomic::TString),
2572 ));
2573 let result = ty.resolve_conditional_returns(|name| {
2574 if name == "x" {
2575 Some(Type::single(Atomic::TString)) } else {
2577 None
2578 }
2579 });
2580 assert!(result.types.len() == 1);
2581 assert!(matches!(result.types[0], Atomic::TString));
2582 }
2583
2584 #[test]
2585 fn resolve_conditional_is_null_null_arg() {
2586 let ty = Type::single(conditional(
2587 Some(Name::new("x")),
2588 Type::single(Atomic::TNull),
2589 Type::single(Atomic::TInt),
2590 Type::single(Atomic::TString),
2591 ));
2592 let result = ty.resolve_conditional_returns(|name| {
2593 if name == "x" {
2594 Some(Type::single(Atomic::TNull)) } else {
2596 None
2597 }
2598 });
2599 assert!(result.types.len() == 1);
2600 assert!(matches!(result.types[0], Atomic::TInt));
2601 }
2602
2603 #[test]
2604 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2605 let mut nullable_str = Type::single(Atomic::TString);
2606 nullable_str.add_type(Atomic::TNull);
2607 let ty = Type::single(conditional(
2608 Some(Name::new("x")),
2609 Type::single(Atomic::TNull),
2610 Type::single(Atomic::TInt),
2611 Type::single(Atomic::TString),
2612 ));
2613 let result = ty.resolve_conditional_returns(|name| {
2614 if name == "x" {
2615 Some(nullable_str.clone())
2616 } else {
2617 None
2618 }
2619 });
2620 assert_eq!(result.types.len(), 2);
2622 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2623 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2624 }
2625
2626 #[test]
2627 fn resolve_conditional_nested_widens_inner_branch() {
2628 let inner = Type::single(conditional(
2631 Some(Name::new("x")),
2632 Type::single(Atomic::TString),
2633 Type::single(Atomic::TString),
2634 Type::single(Atomic::TFloat),
2635 ));
2636 let ty = Type::single(conditional(
2637 Some(Name::new("x")),
2638 Type::single(Atomic::TNull),
2639 Type::single(Atomic::TInt),
2640 inner,
2641 ));
2642 let result = ty.resolve_conditional_returns(|_| None);
2644 assert!(
2645 result
2646 .types
2647 .iter()
2648 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2649 "no TConditional should survive: {:?}",
2650 result.types
2651 );
2652 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2653 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2654 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2655 }
2656
2657 #[test]
2658 fn resolve_conditional_nested_resolves_inner_branch() {
2659 let inner = Type::single(conditional(
2663 Some(Name::new("x")),
2664 Type::single(Atomic::TString),
2665 Type::single(Atomic::TString),
2666 Type::single(Atomic::TFloat),
2667 ));
2668 let ty = Type::single(conditional(
2669 Some(Name::new("x")),
2670 Type::single(Atomic::TNull),
2671 Type::single(Atomic::TInt),
2672 inner,
2673 ));
2674 let result = ty.resolve_conditional_returns(|name| {
2676 if name == "x" {
2677 Some(Type::single(Atomic::TString))
2678 } else {
2679 None
2680 }
2681 });
2682 assert!(
2683 result
2684 .types
2685 .iter()
2686 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2687 "no TConditional should survive: {:?}",
2688 result.types
2689 );
2690 assert_eq!(result.types.len(), 1);
2691 assert!(matches!(result.types[0], Atomic::TString));
2692 }
2693
2694 #[test]
2695 fn substitute_intersection_parts() {
2696 let ty = Type::single(Atomic::TIntersection {
2697 parts: vec_to_type_params(vec![
2698 Type::single(Atomic::TNamedObject {
2699 fqcn: Name::new("Countable"),
2700 type_params: empty_type_params(),
2701 }),
2702 t_param("T"),
2703 ]),
2704 });
2705 let result = ty.substitute_templates(&bindings_t_string());
2706 let Atomic::TIntersection { parts } = &result.types[0] else {
2707 panic!("expected TIntersection");
2708 };
2709 assert_eq!(parts.len(), 2);
2710 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2711 assert!(matches!(parts[1].types[0], Atomic::TString));
2712 }
2713
2714 #[test]
2715 fn substitute_no_template_params_identity() {
2716 let ty = Type::single(Atomic::TInt);
2717 let result = ty.substitute_templates(&bindings_t_string());
2718 assert!(matches!(result.types[0], Atomic::TInt));
2719 }
2720}