1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::{ArrayKey, Atomic, KeyedProperty};
7use crate::symbol::Name;
8use indexmap::IndexMap;
9
10pub fn empty_type_params() -> Arc<[Type]> {
14 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
15 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
16}
17
18pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
21 if v.is_empty() {
22 empty_type_params()
23 } else {
24 Arc::from(v)
25 }
26}
27
28pub type AtomicVec = SmallVec<[Atomic; 2]>;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum CloneValidity {
34 Cloneable,
36 Invalid,
38 PossiblyInvalid,
40 Unknown,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub struct Type {
50 pub types: AtomicVec,
51 pub possibly_undefined: bool,
53 pub from_docblock: bool,
55 pub falsy_stripped: bool,
63 pub possibly_absent_offset: bool,
74}
75
76type IntBounds = (Option<i64>, Option<i64>);
77
78fn int_bounds(a: &Atomic) -> Option<IntBounds> {
80 match a {
81 Atomic::TInt => Some((None, None)),
82 Atomic::TLiteralInt(n) => Some((Some(*n), Some(*n))),
83 Atomic::TPositiveInt => Some((Some(1), None)),
84 Atomic::TNonNegativeInt => Some((Some(0), None)),
85 Atomic::TNegativeInt => Some((None, Some(-1))),
86 Atomic::TIntRange { min, max } => Some((*min, *max)),
87 _ => None,
88 }
89}
90
91fn bounds_contain(outer: IntBounds, inner: IntBounds) -> bool {
92 let lo_ok = match (outer.0, inner.0) {
93 (None, _) => true,
94 (Some(o), Some(i)) => o <= i,
95 (Some(_), None) => false,
96 };
97 let hi_ok = match (outer.1, inner.1) {
98 (None, _) => true,
99 (Some(o), Some(i)) => o >= i,
100 (Some(_), None) => false,
101 };
102 lo_ok && hi_ok
103}
104
105const MAX_KEYED_SHAPES: usize = 8;
112
113fn combine_keyed_arrays(a: &Atomic, b: &Atomic) -> Option<Atomic> {
118 let (
119 Atomic::TKeyedArray {
120 properties: pa,
121 is_open: oa,
122 is_list: la,
123 },
124 Atomic::TKeyedArray {
125 properties: pb,
126 is_open: ob,
127 is_list: lb,
128 },
129 ) = (a, b)
130 else {
131 return None;
132 };
133 let non_empty = |p: &IndexMap<ArrayKey, KeyedProperty>| p.values().any(|v| !v.optional);
134 if oa != ob || la != lb || non_empty(pa) != non_empty(pb) {
135 return None;
136 }
137 let mut properties = IndexMap::with_capacity(pa.len().max(pb.len()));
138 for (key, x) in pa.iter() {
139 let prop = match pb.get(key) {
140 Some(y) => {
141 let mut ty = x.ty.clone();
142 ty.merge_with(&y.ty);
143 KeyedProperty {
144 ty,
145 optional: x.optional || y.optional,
146 }
147 }
148 None if *oa => continue,
151 None => KeyedProperty {
152 ty: x.ty.clone(),
153 optional: true,
154 },
155 };
156 properties.insert(key.clone(), prop);
157 }
158 if !*oa {
159 for (key, y) in pb.iter() {
160 if !pa.contains_key(key) {
161 properties.insert(
162 key.clone(),
163 KeyedProperty {
164 ty: y.ty.clone(),
165 optional: true,
166 },
167 );
168 }
169 }
170 }
171 Some(Atomic::TKeyedArray {
172 properties: Box::new(properties),
173 is_open: *oa,
174 is_list: *la,
175 })
176}
177
178impl Type {
179 pub fn empty() -> Self {
182 Self {
183 types: SmallVec::new(),
184 possibly_undefined: false,
185 from_docblock: false,
186 falsy_stripped: false,
187 possibly_absent_offset: false,
188 }
189 }
190
191 pub fn single(atomic: Atomic) -> Self {
192 let mut types = SmallVec::new();
193 types.push(atomic);
194 Self {
195 types,
196 possibly_undefined: false,
197 from_docblock: false,
198 falsy_stripped: false,
199 possibly_absent_offset: false,
200 }
201 }
202
203 pub fn mixed() -> Self {
204 Self::single(Atomic::TMixed)
205 }
206
207 pub fn void() -> Self {
208 Self::single(Atomic::TVoid)
209 }
210
211 pub fn never() -> Self {
212 Self::single(Atomic::TNever)
213 }
214
215 pub fn null() -> Self {
216 Self::single(Atomic::TNull)
217 }
218
219 pub fn bool() -> Self {
220 Self::single(Atomic::TBool)
221 }
222
223 pub fn int() -> Self {
224 Self::single(Atomic::TInt)
225 }
226
227 pub fn float() -> Self {
228 Self::single(Atomic::TFloat)
229 }
230
231 pub fn string() -> Self {
232 Self::single(Atomic::TString)
233 }
234
235 pub fn array_key() -> Self {
238 let mut u = Self::single(Atomic::TInt);
239 u.add_type(Atomic::TString);
240 u
241 }
242
243 pub fn nullable(atomic: Atomic) -> Self {
245 if matches!(atomic, Atomic::TMixed) {
247 return Self::mixed();
248 }
249 let mut types = SmallVec::new();
250 types.push(atomic);
251 types.push(Atomic::TNull);
252 Self {
253 types,
254 possibly_undefined: false,
255 from_docblock: false,
256 falsy_stripped: false,
257 possibly_absent_offset: false,
258 }
259 }
260
261 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
263 let mut u = Self::empty();
264 for a in atomics {
265 u.add_type(a);
266 }
267 u
268 }
269
270 pub fn is_empty(&self) -> bool {
273 self.types.is_empty()
274 }
275
276 pub fn is_single(&self) -> bool {
277 self.types.len() == 1
278 }
279
280 pub fn is_nullable(&self) -> bool {
281 self.types.iter().any(|t| matches!(t, Atomic::TNull))
282 }
283
284 pub fn is_array_key(&self) -> bool {
289 self.types.len() == 2
290 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
291 && self.types.iter().any(|t| matches!(t, Atomic::TString))
292 }
293
294 pub fn is_mixed(&self) -> bool {
295 self.types.iter().any(|t| match t {
296 Atomic::TMixed => true,
297 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
298 _ => false,
299 })
300 }
301
302 pub fn is_mixed_not_template(&self) -> bool {
307 self.is_mixed()
308 && !self
309 .types
310 .iter()
311 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
312 }
313
314 pub fn is_never(&self) -> bool {
315 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
316 }
317
318 pub fn clone_validity(&self) -> CloneValidity {
321 if self.types.is_empty() {
322 return CloneValidity::Unknown;
323 }
324 let mut has_non_object = false;
325 let mut has_other = false; for t in &self.types {
327 match t {
328 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
329 CloneValidity::Invalid => has_non_object = true,
330 CloneValidity::PossiblyInvalid => {
331 has_non_object = true;
332 has_other = true;
333 }
334 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
335 },
336 other if other.is_definitely_non_object() => has_non_object = true,
337 _ => has_other = true,
338 }
339 }
340 match (has_non_object, has_other) {
341 (true, false) => CloneValidity::Invalid,
342 (true, true) => CloneValidity::PossiblyInvalid,
343 _ => CloneValidity::Cloneable,
344 }
345 }
346
347 pub fn is_void(&self) -> bool {
348 self.is_single() && matches!(self.types[0], Atomic::TVoid)
349 }
350
351 pub fn can_be_falsy(&self) -> bool {
352 self.types.iter().any(|t| t.can_be_falsy())
353 }
354
355 pub fn can_be_truthy(&self) -> bool {
356 self.types.iter().any(|t| t.can_be_truthy())
357 }
358
359 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
360 self.types.iter().any(f)
361 }
362
363 pub fn has_named_object(&self, fqcn: &str) -> bool {
364 self.types.iter().any(|t| match t {
365 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
366 _ => false,
367 })
368 }
369
370 pub fn add_type(&mut self, atomic: Atomic) {
375 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
377 return;
378 }
379
380 if matches!(atomic, Atomic::TMixed) {
382 self.types.clear();
383 self.types.push(Atomic::TMixed);
384 return;
385 }
386
387 let atomic = if let Atomic::TConditional { data } = &atomic {
390 let (if_true, if_false) = (&data.if_true, &data.if_false);
391 let mut simplified_true = Type::empty();
392 for t in &if_true.types {
393 simplified_true.add_type(t.clone());
394 }
395 let mut simplified_false = Type::empty();
396 for t in &if_false.types {
397 simplified_false.add_type(t.clone());
398 }
399 if simplified_true == simplified_false {
400 for t in simplified_true.types {
401 self.add_type(t);
402 }
403 return;
404 }
405 atomic
406 } else {
407 atomic
408 };
409
410 let mut atomic = atomic;
413 if matches!(atomic, Atomic::TKeyedArray { .. })
414 && !self.types.contains(&atomic)
415 && self
416 .types
417 .iter()
418 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
419 .count()
420 >= MAX_KEYED_SHAPES
421 {
422 if let Some((idx, combined)) = self
423 .types
424 .iter()
425 .enumerate()
426 .find_map(|(i, t)| combine_keyed_arrays(t, &atomic).map(|c| (i, c)))
427 {
428 self.types.remove(idx);
429 atomic = combined;
430 }
431 }
432
433 if self.types.contains(&atomic) {
435 return;
436 }
437
438 if let Some(new_bounds) = int_bounds(&atomic) {
441 if self
442 .types
443 .iter()
444 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
445 {
446 return;
447 }
448 self.types
449 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
450 }
451 if let Atomic::TLiteralString(_) = &atomic {
453 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
454 return;
455 }
456 }
457 if matches!(atomic, Atomic::TNonEmptyString)
459 && self.types.iter().any(|t| matches!(t, Atomic::TString))
460 {
461 return;
462 }
463 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
465 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
466 {
467 return;
468 }
469 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
472 {
473 self.types.retain(|t| !matches!(t, Atomic::TFalse));
474 self.types.push(Atomic::TBool);
475 return;
476 }
477 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
478 {
479 self.types.retain(|t| !matches!(t, Atomic::TTrue));
480 self.types.push(Atomic::TBool);
481 return;
482 }
483 if matches!(atomic, Atomic::TString) {
485 self.types
486 .retain(|t| !matches!(t, Atomic::TLiteralString(_) | Atomic::TNonEmptyString));
487 }
488 if matches!(atomic, Atomic::TBool) {
490 self.types
491 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
492 }
493
494 if matches!(atomic, Atomic::TNever) {
496 if !self.types.is_empty() {
497 return;
498 }
499 } else {
500 self.types.retain(|t| !matches!(t, Atomic::TNever));
501 }
502
503 if let Atomic::TKeyedArray {
509 properties,
510 is_open,
511 ..
512 } = &atomic
513 {
514 if properties.is_empty() && !is_open {
515 for existing in &self.types {
516 match existing {
517 Atomic::TArray { .. }
518 | Atomic::TNonEmptyArray { .. }
519 | Atomic::TList { .. }
520 | Atomic::TNonEmptyList { .. } => {
521 return; }
523 _ => {}
524 }
525 }
526 }
527 }
528
529 let is_generic_array_or_list = matches!(
532 &atomic,
533 Atomic::TArray { .. }
534 | Atomic::TNonEmptyArray { .. }
535 | Atomic::TList { .. }
536 | Atomic::TNonEmptyList { .. }
537 );
538 if is_generic_array_or_list {
539 self.types.retain(|t| {
540 if let Atomic::TKeyedArray {
541 properties,
542 is_open,
543 ..
544 } = t
545 {
546 !properties.is_empty() || *is_open
547 } else {
548 true
549 }
550 });
551 }
552
553 self.types.push(atomic);
554 }
555
556 pub fn remove_null(&self) -> Type {
560 self.filter(|t| !matches!(t, Atomic::TNull))
561 }
562
563 pub fn remove_false(&self) -> Type {
566 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
567 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
568 result.add_type(Atomic::TTrue);
569 }
570 result
571 }
572
573 pub fn remove_true(&self) -> Type {
576 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
577 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
578 result.add_type(Atomic::TFalse);
579 }
580 result
581 }
582
583 pub fn core_type(&self) -> Type {
585 self.remove_null().remove_false()
586 }
587
588 pub fn narrow_to_truthy(&self) -> Type {
590 if self.is_mixed_not_template() {
591 return Type::mixed();
592 }
593 let mut result = Type::empty();
594 result.from_docblock = self.from_docblock;
595 for t in &self.types {
596 match t {
597 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
601 Atomic::TLiteralInt(0)
603 | Atomic::TLiteralFloat(0, 0)
604 | Atomic::TNull
605 | Atomic::TFalse => {}
606 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
607 Atomic::TBool => result.add_type(Atomic::TTrue),
609 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
611 key: key.clone(),
612 value: value.clone(),
613 }),
614 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
615 value: value.clone(),
616 }),
617 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
621 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
626 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
627 let atom = if max.is_none() {
628 Atomic::TPositiveInt
629 } else {
630 Atomic::TIntRange {
631 min: Some(1),
632 max: *max,
633 }
634 };
635 result.add_type(atom);
636 }
637 Atomic::TIntRange { min, max: Some(0) } => {
639 let atom = match min {
640 None => Atomic::TNegativeInt,
641 Some(n) if *n <= -1 => Atomic::TIntRange {
642 min: *min,
643 max: Some(-1),
644 },
645 _ => continue, };
647 result.add_type(atom);
648 }
649 t if !t.can_be_truthy() => {}
651 _ => result.add_type(t.clone()),
652 }
653 }
654 result
655 }
656
657 pub fn narrow_to_falsy(&self) -> Type {
659 if self.is_mixed_not_template() {
660 return Type::from_vec(vec![
661 Atomic::TNull,
662 Atomic::TFalse,
663 Atomic::TLiteralInt(0),
664 Atomic::TLiteralString("".into()),
665 ]);
666 }
667 let mut result = Type::empty();
668 result.from_docblock = self.from_docblock;
669 for t in &self.types {
670 match t {
671 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
676 Atomic::TBool => result.add_type(Atomic::TFalse),
678 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
680 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
682 Atomic::TString => {
684 result.add_type(Atomic::TLiteralString("".into()));
685 result.add_type(Atomic::TLiteralString("0".into()));
686 }
687 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
689 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
691 Atomic::TIntRange {
693 min: Some(0),
694 max: Some(_) | None,
695 } => result.add_type(Atomic::TLiteralInt(0)),
696 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
698 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
700 }
701 }
702 result
703 }
704
705 pub fn narrow_instanceof(&self, class: &str) -> Type {
711 let narrowed_ty = Atomic::TNamedObject {
712 fqcn: class.into(),
713 type_params: empty_type_params(),
714 };
715 let has_object = self.types.iter().any(|t| {
717 matches!(
718 t,
719 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
721 });
722 if has_object || self.is_empty() {
723 Type::single(narrowed_ty)
724 } else {
725 Type::single(narrowed_ty)
728 }
729 }
730
731 pub fn narrow_to_string(&self) -> Type {
735 self.filter_replacing(
736 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
737 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
738 Atomic::TString,
739 )
740 }
741
742 pub fn narrow_to_int(&self) -> Type {
744 self.filter_replacing(
745 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
746 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
747 Atomic::TInt,
748 )
749 }
750
751 pub fn narrow_to_float(&self) -> Type {
753 self.filter_replacing(
754 |t| {
755 matches!(
756 t,
757 Atomic::TFloat
758 | Atomic::TIntegralFloat
759 | Atomic::TLiteralFloat(..)
760 | Atomic::TTemplateParam { .. }
761 )
762 },
763 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
764 Atomic::TFloat,
765 )
766 }
767
768 pub fn narrow_to_bool(&self) -> Type {
770 self.filter_replacing(
771 |t| {
772 matches!(
773 t,
774 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
775 )
776 },
777 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
778 Atomic::TBool,
779 )
780 }
781
782 pub fn narrow_to_null(&self) -> Type {
784 self.filter_replacing(
785 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
786 |t| matches!(t, Atomic::TMixed),
787 Atomic::TNull,
788 )
789 }
790
791 pub fn narrow_to_array(&self) -> Type {
793 self.filter_replacing(
794 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
795 |t| matches!(t, Atomic::TMixed),
796 Atomic::TArray {
797 key: Box::new(Type::mixed()),
798 value: Box::new(Type::mixed()),
799 },
800 )
801 }
802
803 pub fn widen_nested_non_empty(&self) -> Type {
806 fn relax_all(ty: &Type) -> Type {
807 map_atoms(ty, |a| match a {
808 Atomic::TNonEmptyList { value } => Atomic::TList {
809 value: Box::new(relax_all(value)),
810 },
811 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
812 key: key.clone(),
813 value: Box::new(relax_all(value)),
814 },
815 other => map_values(other, relax_all),
816 })
817 }
818 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
819 match atomic {
820 Atomic::TArray { key, value } => Atomic::TArray {
821 key: key.clone(),
822 value: Box::new(f(value)),
823 },
824 Atomic::TList { value } => Atomic::TList {
825 value: Box::new(f(value)),
826 },
827 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
828 key: key.clone(),
829 value: Box::new(f(value)),
830 },
831 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
832 value: Box::new(f(value)),
833 },
834 Atomic::TKeyedArray {
835 properties,
836 is_open,
837 is_list,
838 } => Atomic::TKeyedArray {
839 properties: Box::new(
840 properties
841 .iter()
842 .map(|(k, p)| {
843 (
844 k.clone(),
845 crate::atomic::KeyedProperty {
846 ty: f(&p.ty),
847 optional: p.optional,
848 },
849 )
850 })
851 .collect(),
852 ),
853 is_open: *is_open,
854 is_list: *is_list,
855 },
856 other => other.clone(),
857 }
858 }
859 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
860 let mut result = Type::empty();
861 result.possibly_undefined = ty.possibly_undefined;
862 result.from_docblock = ty.from_docblock;
863 for atomic in &ty.types {
864 result.add_type(f(atomic));
865 }
866 result
867 }
868 map_atoms(self, |a| map_values(a, relax_all))
869 }
870
871 pub fn narrow_to_non_empty_collection(&self) -> Type {
874 let mut out = Type::empty();
875 out.from_docblock = self.from_docblock;
876 for t in &self.types {
877 match t {
878 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
879 key: key.clone(),
880 value: value.clone(),
881 }),
882 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
883 value: value.clone(),
884 }),
885 Atomic::TKeyedArray {
886 properties,
887 is_open: false,
888 ..
889 } if properties.is_empty() => {}
890 _ => out.add_type(t.clone()),
891 }
892 }
893 out
894 }
895
896 pub fn narrow_to_empty_collection(&self) -> Type {
906 let mut out = Type::empty();
907 out.from_docblock = self.from_docblock;
908 for t in &self.types {
909 match t {
910 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
911 Atomic::TArray { .. } | Atomic::TList { .. } => {
912 out.add_type(Atomic::TKeyedArray {
913 properties: Box::default(),
914 is_open: false,
915 is_list: true,
916 });
917 }
918 _ => out.add_type(t.clone()),
919 }
920 }
921 out
922 }
923
924 pub fn narrow_to_list(&self) -> Type {
936 let mut out = Type::empty();
937 out.from_docblock = self.from_docblock;
938 for t in &self.types {
939 match t {
940 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
941 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
947 out.add_type(Atomic::TList {
948 value: value.clone(),
949 });
950 }
951 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
952 out.add_type(Atomic::TNonEmptyList {
953 value: value.clone(),
954 });
955 }
956 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
957 Atomic::TMixed => out.add_type(Atomic::TList {
958 value: Box::new(Type::mixed()),
959 }),
960 _ => {}
961 }
962 }
963 if out.is_empty() {
964 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
965 } else {
966 out
967 }
968 }
969
970 pub fn narrow_to_object(&self) -> Type {
975 let mut out = Type::empty();
976 for t in &self.types {
977 if matches!(t, Atomic::TMixed) {
978 out.add_type(Atomic::TObject);
979 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
980 out.add_type(t.clone());
981 }
982 }
983 if out.types.is_empty() {
984 self.filter(|t| t.is_object())
985 } else {
986 out
987 }
988 }
989
990 pub fn narrow_to_callable(&self) -> Type {
997 let narrowed = self.filter(|t| {
998 t.is_callable()
999 || t.is_string()
1000 || t.is_array()
1001 || t.is_object()
1002 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1003 });
1004 let mut result = Type::empty();
1011 result.possibly_undefined = narrowed.possibly_undefined;
1012 result.from_docblock = narrowed.from_docblock;
1013 for atomic in narrowed.types {
1014 if matches!(atomic, Atomic::TObject) {
1015 result.add_type(Atomic::TCallable {
1016 params: None,
1017 return_type: None,
1018 is_pure: false,
1019 });
1020 } else {
1021 result.add_type(atomic);
1022 }
1023 }
1024 result
1025 }
1026
1027 pub fn narrow_to_scalar(&self) -> Type {
1029 self.filter_replacing(
1030 |t| {
1031 t.is_string()
1032 || t.is_int()
1033 || matches!(
1034 t,
1035 Atomic::TFloat
1036 | Atomic::TIntegralFloat
1037 | Atomic::TLiteralFloat(..)
1038 | Atomic::TBool
1039 | Atomic::TTrue
1040 | Atomic::TFalse
1041 | Atomic::TScalar
1042 | Atomic::TNumeric
1043 | Atomic::TNumericString
1044 | Atomic::TTemplateParam { .. }
1045 )
1046 },
1047 |t| matches!(t, Atomic::TMixed),
1048 Atomic::TScalar,
1049 )
1050 }
1051
1052 pub fn narrow_to_iterable(&self) -> Type {
1055 self.filter(|t| {
1056 t.is_array()
1057 || t.is_object()
1058 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1059 })
1060 }
1061
1062 pub fn narrow_to_countable(&self) -> Type {
1065 self.filter(|t| {
1066 t.is_array()
1067 || t.is_object()
1068 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1069 })
1070 }
1071
1072 pub fn narrow_to_resource(&self) -> Type {
1076 self.filter(|t| matches!(t, Atomic::TMixed))
1078 }
1079
1080 pub fn narrow_to_class_string(&self) -> Type {
1085 let mut out = Type::empty();
1086 out.from_docblock = self.from_docblock;
1087 for t in &self.types {
1088 match t {
1089 Atomic::TClassString(_) => out.add_type(t.clone()),
1090 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1091 out.add_type(Atomic::TClassString(None));
1092 }
1093 _ => {}
1094 }
1095 }
1096 out
1097 }
1098
1099 pub fn narrow_to_interface_string(&self) -> Type {
1104 let mut out = Type::empty();
1105 out.from_docblock = self.from_docblock;
1106 for t in &self.types {
1107 match t {
1108 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1109 Atomic::TClassString(name) => {
1113 out.add_type(Atomic::TInterfaceString(*name));
1114 }
1115 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1116 out.add_type(Atomic::TInterfaceString(None));
1117 }
1118 _ => {}
1119 }
1120 }
1121 out
1122 }
1123
1124 pub fn merge(a: &Type, b: &Type) -> Type {
1129 if b.types.is_empty() {
1131 let mut result = a.clone();
1132 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1133 return result;
1134 }
1135 if a.types.is_empty() {
1137 let mut result = b.clone();
1138 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1139 return result;
1140 }
1141 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1143 let mut result = a.clone();
1144 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1145 return result;
1146 }
1147 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1149 return Type {
1150 types: smallvec::smallvec![Atomic::TMixed],
1151 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1152 from_docblock: a.from_docblock || b.from_docblock,
1153 falsy_stripped: false,
1154 possibly_absent_offset: false,
1155 };
1156 }
1157 let mut result = a.clone();
1158 result.merge_with(b);
1159 result
1160 }
1161
1162 pub fn merge_with(&mut self, other: &Type) {
1164 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1165 self.possibly_undefined |= other.possibly_undefined;
1166 return;
1167 }
1168 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1169 self.types.clear();
1170 self.types.push(Atomic::TMixed);
1171 self.possibly_undefined |= other.possibly_undefined;
1172 return;
1173 }
1174 for atomic in &other.types {
1175 self.add_type(atomic.clone());
1176 }
1177 self.possibly_undefined |= other.possibly_undefined;
1178 }
1179
1180 pub fn intersect_with(&self, other: &Type) -> Type {
1184 if self.is_mixed() {
1185 return other.clone();
1186 }
1187 if other.is_mixed() {
1188 return self.clone();
1189 }
1190 let mut result = Type::empty();
1198 for a in &self.types {
1199 for b in &other.types {
1200 if a == b {
1201 result.add_type(a.clone());
1202 } else if atomic_subtype(b, a) {
1203 result.add_type(b.clone());
1204 } else if atomic_subtype(a, b) {
1205 result.add_type(a.clone());
1206 }
1207 }
1208 }
1209 if result.is_empty() {
1210 Type::never()
1211 } else {
1212 result
1213 }
1214 }
1215
1216 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1220 if bindings.is_empty() {
1221 return self.clone();
1222 }
1223 if !self.types.iter().any(atomic_may_contain_templates) {
1226 return self.clone();
1227 }
1228 let mut result = Type::empty();
1229 result.possibly_undefined = self.possibly_undefined;
1230 result.from_docblock = self.from_docblock;
1231 for atomic in &self.types {
1232 match atomic {
1233 Atomic::TTemplateParam { name, .. } => {
1234 if let Some(resolved) = bindings.get(name) {
1235 for t in &resolved.types {
1236 result.add_type(t.clone());
1237 }
1238 } else {
1239 result.add_type(atomic.clone());
1240 }
1241 }
1242 Atomic::TArray { key, value } => {
1243 result.add_type(Atomic::TArray {
1244 key: Box::new(key.substitute_templates(bindings)),
1245 value: Box::new(value.substitute_templates(bindings)),
1246 });
1247 }
1248 Atomic::TList { value } => {
1249 result.add_type(Atomic::TList {
1250 value: Box::new(value.substitute_templates(bindings)),
1251 });
1252 }
1253 Atomic::TNonEmptyArray { key, value } => {
1254 result.add_type(Atomic::TNonEmptyArray {
1255 key: Box::new(key.substitute_templates(bindings)),
1256 value: Box::new(value.substitute_templates(bindings)),
1257 });
1258 }
1259 Atomic::TNonEmptyList { value } => {
1260 result.add_type(Atomic::TNonEmptyList {
1261 value: Box::new(value.substitute_templates(bindings)),
1262 });
1263 }
1264 Atomic::TKeyedArray {
1265 properties,
1266 is_open,
1267 is_list,
1268 } => {
1269 use crate::atomic::KeyedProperty;
1270 let new_props = properties
1271 .iter()
1272 .map(|(k, prop)| {
1273 (
1274 k.clone(),
1275 KeyedProperty {
1276 ty: prop.ty.substitute_templates(bindings),
1277 optional: prop.optional,
1278 },
1279 )
1280 })
1281 .collect();
1282 result.add_type(Atomic::TKeyedArray {
1283 properties: Box::new(new_props),
1284 is_open: *is_open,
1285 is_list: *is_list,
1286 });
1287 }
1288 Atomic::TCallable {
1289 params,
1290 return_type,
1291 is_pure,
1292 } => {
1293 result.add_type(Atomic::TCallable {
1294 params: params.as_ref().map(|ps| {
1295 Box::new(
1296 ps.iter()
1297 .map(|p| substitute_in_fn_param(p, bindings))
1298 .collect(),
1299 )
1300 }),
1301 return_type: return_type
1302 .as_ref()
1303 .map(|r| Box::new(r.substitute_templates(bindings))),
1304 is_pure: *is_pure,
1305 });
1306 }
1307 Atomic::TClosure { data } => {
1308 result.add_type(Atomic::TClosure {
1309 data: Box::new(crate::atomic::ClosureData {
1310 params: data
1311 .params
1312 .iter()
1313 .map(|p| substitute_in_fn_param(p, bindings))
1314 .collect(),
1315 return_type: data.return_type.substitute_templates(bindings),
1316 this_type: data
1317 .this_type
1318 .as_ref()
1319 .map(|t| t.substitute_templates(bindings)),
1320 is_pure: data.is_pure,
1321 is_static: data.is_static,
1322 }),
1323 });
1324 }
1325 Atomic::TConditional { data } => {
1326 let param_name = &data.param_name;
1327 let new_subject = data.subject.substitute_templates(bindings);
1328 let new_if_true = data.if_true.substitute_templates(bindings);
1329 let new_if_false = data.if_false.substitute_templates(bindings);
1330
1331 let resolved = if let Some(name) = param_name {
1335 if let Some(bound) = bindings.get(name) {
1336 if new_subject.types.len() == 1 {
1337 resolve_conditional_branch(
1338 &new_subject.types[0],
1339 bound,
1340 &new_if_true,
1341 &new_if_false,
1342 )
1343 } else {
1344 None
1345 }
1346 } else {
1347 None
1348 }
1349 } else {
1350 None
1351 };
1352
1353 if let Some(branch) = resolved {
1354 for t in branch.types {
1355 result.add_type(t);
1356 }
1357 } else {
1358 result.add_type(Atomic::TConditional {
1359 data: Box::new(crate::atomic::ConditionalData {
1360 param_name: *param_name,
1361 subject: new_subject,
1362 if_true: new_if_true,
1363 if_false: new_if_false,
1364 }),
1365 });
1366 }
1367 }
1368 Atomic::TKeyOf { target } => {
1369 let new_target = target.substitute_templates(bindings);
1370 if let Some(resolved) = eval_key_of_type(&new_target) {
1371 result.merge_with(&resolved);
1372 } else {
1373 result.add_type(Atomic::TKeyOf {
1374 target: Box::new(new_target),
1375 });
1376 }
1377 }
1378 Atomic::TValueOf { target } => {
1379 let new_target = target.substitute_templates(bindings);
1380 if let Some(resolved) = eval_value_of_type(&new_target) {
1381 result.merge_with(&resolved);
1382 } else {
1383 result.add_type(Atomic::TValueOf {
1384 target: Box::new(new_target),
1385 });
1386 }
1387 }
1388 Atomic::TIntersection { parts } => {
1389 result.add_type(Atomic::TIntersection {
1390 parts: vec_to_type_params(
1391 parts
1392 .iter()
1393 .map(|p| p.substitute_templates(bindings))
1394 .collect(),
1395 ),
1396 });
1397 }
1398 Atomic::TNamedObject { fqcn, type_params } => {
1399 if type_params.is_empty() && !fqcn.contains('\\') {
1401 if let Some(resolved) = bindings.get(fqcn) {
1402 for t in &resolved.types {
1403 result.add_type(t.clone());
1404 }
1405 continue;
1406 }
1407 }
1408 let new_params: Vec<Type> = type_params
1409 .iter()
1410 .map(|p| p.substitute_templates(bindings))
1411 .collect();
1412 result.add_type(Atomic::TNamedObject {
1413 fqcn: *fqcn,
1414 type_params: vec_to_type_params(new_params),
1415 });
1416 }
1417 Atomic::TClassString(Some(param_name)) => {
1419 if let Some(resolved) = bindings.get(param_name) {
1420 for r_atomic in &resolved.types {
1421 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1422 Some(*fqcn)
1423 } else {
1424 None
1425 };
1426 result.add_type(Atomic::TClassString(cls_name));
1427 }
1428 } else {
1429 result.add_type(atomic.clone());
1430 }
1431 }
1432 Atomic::TInterfaceString(Some(param_name)) => {
1434 if let Some(resolved) = bindings.get(param_name) {
1435 for r_atomic in &resolved.types {
1436 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1437 Some(*fqcn)
1438 } else {
1439 None
1440 };
1441 result.add_type(Atomic::TInterfaceString(iface_name));
1442 }
1443 } else {
1444 result.add_type(atomic.clone());
1445 }
1446 }
1447 _ => {
1448 result.add_type(atomic.clone());
1449 }
1450 }
1451 }
1452 result
1453 }
1454
1455 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1461 where
1462 F: Fn(&str) -> Option<Type>,
1463 {
1464 self.resolve_conditional_inner(&lookup)
1465 }
1466
1467 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1468 where
1469 F: Fn(&str) -> Option<Type>,
1470 {
1471 let mut result = Type::empty();
1472 for atomic in self.types {
1473 match atomic {
1474 Atomic::TConditional { ref data } => {
1475 let (param_name, subject, if_true, if_false) = (
1476 &data.param_name,
1477 &data.subject,
1478 &data.if_true,
1479 &data.if_false,
1480 );
1481 let resolved = if subject.types.len() == 1 {
1482 if let Some(name) = param_name {
1483 if let Some(arg_ty) = lookup(name.as_ref()) {
1484 resolve_conditional_branch(
1485 &subject.types[0],
1486 &arg_ty,
1487 if_true,
1488 if_false,
1489 )
1490 } else {
1491 None
1492 }
1493 } else {
1494 None
1495 }
1496 } else {
1497 None
1498 };
1499
1500 if let Some(branch) = resolved {
1501 for t in branch.resolve_conditional_inner(lookup).types {
1503 result.add_type(t);
1504 }
1505 } else {
1506 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1509 result.add_type(t);
1510 }
1511 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1512 result.add_type(t);
1513 }
1514 }
1515 }
1516 other => result.add_type(other),
1517 }
1518 }
1519 result
1520 }
1521
1522 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1532 if other.is_mixed() {
1533 return true;
1534 }
1535 if self.is_never() {
1536 return true; }
1538 self.types
1539 .iter()
1540 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1541 }
1542
1543 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1546 self.is_subtype_structural(other)
1547 || self.types.iter().all(|a| match a {
1548 Atomic::TTemplateParam { as_type, .. } => {
1549 as_type.is_subtype_by_template_bounds(other)
1550 }
1551 _ => other.accepts_atomic_structural(a),
1552 })
1553 }
1554
1555 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1559 if self.is_mixed() {
1560 return true;
1561 }
1562 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1563 }
1564
1565 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1568 let mut result = Type::empty();
1569 result.possibly_undefined = self.possibly_undefined;
1570 result.from_docblock = self.from_docblock;
1571 for atomic in &self.types {
1572 if f(atomic) {
1573 result.types.push(atomic.clone());
1574 }
1575 }
1576 result
1577 }
1578
1579 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1584 &self,
1585 keep: K,
1586 placeholder: P,
1587 replacement: Atomic,
1588 ) -> Type {
1589 let mut result = Type::empty();
1590 result.possibly_undefined = self.possibly_undefined;
1591 result.from_docblock = self.from_docblock;
1592 for atomic in &self.types {
1593 if keep(atomic) {
1594 result.add_type(atomic.clone());
1595 } else if placeholder(atomic) {
1596 result.add_type(replacement.clone());
1597 }
1598 }
1599 result
1600 }
1601
1602 pub fn possibly_undefined(mut self) -> Self {
1604 self.possibly_undefined = true;
1605 self
1606 }
1607
1608 pub fn from_docblock(mut self) -> Self {
1610 self.from_docblock = true;
1611 self
1612 }
1613
1614 pub fn falsy_stripped(mut self) -> Self {
1617 self.falsy_stripped = true;
1618 self
1619 }
1620
1621 pub fn possibly_absent_offset(mut self) -> Self {
1625 self.possibly_absent_offset = true;
1626 self
1627 }
1628}
1629
1630#[derive(Clone, PartialEq, Eq, Debug)]
1644enum ValueClass {
1645 Int,
1646 Float,
1647 True,
1648 False,
1649 String,
1650 Array,
1651 List,
1653 Object,
1654 Null,
1655 Top,
1658 LitInt(i64),
1661 LitFloat(i64, i64),
1663 LitString(Arc<str>),
1665}
1666
1667impl ValueClass {
1668 fn includes(&self, b: &Self) -> bool {
1673 self == b
1674 || matches!(
1675 (self, b),
1676 (Self::Top, _)
1677 | (Self::Int, Self::LitInt(_))
1678 | (Self::Float, Self::LitFloat(..))
1679 | (Self::String, Self::LitString(_))
1680 | (Self::Array, Self::List)
1681 )
1682 }
1683}
1684
1685fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1691 use ValueClass::*;
1692 match a {
1693 Atomic::TInt
1696 | Atomic::TIntRange { .. }
1697 | Atomic::TPositiveInt
1698 | Atomic::TNegativeInt
1699 | Atomic::TNonNegativeInt => vec![Int],
1700 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1701 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1703 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1704 vec![LitFloat(*int_bits, *frac_bits)]
1705 }
1706 Atomic::TBool => vec![True, False],
1708 Atomic::TTrue => vec![True],
1709 Atomic::TFalse => vec![False],
1710 Atomic::TString
1712 | Atomic::TNonEmptyString
1713 | Atomic::TNumericString
1714 | Atomic::TClassString(_)
1715 | Atomic::TInterfaceString(_)
1716 | Atomic::TEnumString
1717 | Atomic::TTraitString
1718 | Atomic::TCallableString => vec![String],
1719 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1720 Atomic::TArray { .. }
1722 | Atomic::TNonEmptyArray { .. }
1723 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1724 Atomic::TList { .. }
1725 | Atomic::TNonEmptyList { .. }
1726 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1727 Atomic::TObject
1729 | Atomic::TNamedObject { .. }
1730 | Atomic::TStaticObject { .. }
1731 | Atomic::TSelf { .. }
1732 | Atomic::TParent { .. }
1733 | Atomic::TClosure { .. }
1734 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1735 Atomic::TNull => vec![Null],
1738 Atomic::TVoid => vec![Top],
1739 Atomic::TMixed => vec![Top],
1741 Atomic::TScalar => vec![Int, Float, True, False, String],
1742 Atomic::TNumeric => vec![Int, Float],
1745 Atomic::TCallable { .. }
1747 | Atomic::TNever
1748 | Atomic::TTemplateParam { .. }
1749 | Atomic::TKeyOf { .. }
1750 | Atomic::TValueOf { .. }
1751 | Atomic::TConditional { .. }
1752 | Atomic::TIntersection { .. } => Vec::new(),
1753 }
1754}
1755
1756fn subject_is_decidable(subject: &Atomic) -> bool {
1764 matches!(
1765 subject,
1766 Atomic::TNull
1767 | Atomic::TTrue
1768 | Atomic::TFalse
1769 | Atomic::TBool
1770 | Atomic::TString
1771 | Atomic::TInt
1772 | Atomic::TFloat
1773 | Atomic::TLiteralString(_)
1774 | Atomic::TLiteralInt(_)
1775 | Atomic::TLiteralFloat(..)
1776 | Atomic::TArray { .. }
1777 | Atomic::TList { .. }
1778 | Atomic::TObject
1779 | Atomic::TMixed
1780 | Atomic::TScalar
1781 )
1782}
1783
1784fn resolve_conditional_branch(
1806 subject: &Atomic,
1807 arg_ty: &Type,
1808 if_true: &Type,
1809 if_false: &Type,
1810) -> Option<Type> {
1811 if !subject_is_decidable(subject) {
1812 return None;
1813 }
1814 if arg_ty.types.is_empty() {
1815 return None;
1816 }
1817 let subject_classes = value_classes(subject);
1818 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1819 if arg_classes.is_empty() {
1820 return None;
1822 }
1823 let all_match = arg_classes
1824 .iter()
1825 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1826 let any_overlap = arg_classes.iter().any(|c| {
1827 subject_classes
1828 .iter()
1829 .any(|s| s.includes(c) || c.includes(s))
1830 });
1831 if all_match {
1832 Some(if_true.clone())
1833 } else if !any_overlap {
1834 Some(if_false.clone())
1835 } else {
1836 None
1837 }
1838}
1839
1840fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1848 match atomic {
1849 Atomic::TNamedObject { fqcn, type_params } => {
1853 !type_params.is_empty() || !fqcn.contains('\\')
1854 }
1855 Atomic::TTemplateParam { .. }
1856 | Atomic::TKeyOf { .. }
1857 | Atomic::TValueOf { .. }
1858 | Atomic::TArray { .. }
1859 | Atomic::TList { .. }
1860 | Atomic::TNonEmptyArray { .. }
1861 | Atomic::TNonEmptyList { .. }
1862 | Atomic::TKeyedArray { .. }
1863 | Atomic::TCallable { .. }
1864 | Atomic::TClosure { .. }
1865 | Atomic::TConditional { .. }
1866 | Atomic::TIntersection { .. }
1867 | Atomic::TClassString(Some(_))
1868 | Atomic::TInterfaceString(Some(_)) => true,
1869 _ => false,
1870 }
1871}
1872
1873pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1874 let mut result = Type::empty();
1875 for atomic in &t.types {
1876 match atomic {
1877 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1878 for k in &key.types {
1879 result.add_type(k.clone());
1880 }
1881 }
1882 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1883 result.add_type(Atomic::TInt);
1884 }
1885 Atomic::TKeyedArray { properties, .. } => {
1886 for key in properties.keys() {
1887 match key {
1888 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1889 crate::atomic::ArrayKey::String(s) => {
1890 result.add_type(Atomic::TLiteralString(s.clone()))
1891 }
1892 }
1893 }
1894 }
1895 _ => return None,
1896 }
1897 }
1898 (!result.types.is_empty()).then_some(result)
1899}
1900
1901pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1902 let mut result = Type::empty();
1903 for atomic in &t.types {
1904 match atomic {
1905 Atomic::TArray { value, .. }
1906 | Atomic::TNonEmptyArray { value, .. }
1907 | Atomic::TList { value }
1908 | Atomic::TNonEmptyList { value } => {
1909 result.merge_with(value);
1910 }
1911 Atomic::TKeyedArray { properties, .. } => {
1912 for prop in properties.values() {
1913 result.merge_with(&prop.ty);
1914 }
1915 }
1916 _ => return None,
1917 }
1918 }
1919 (!result.types.is_empty()).then_some(result)
1920}
1921
1922fn substitute_in_fn_param(
1923 p: &crate::atomic::FnParam,
1924 bindings: &FxHashMap<Name, Type>,
1925) -> crate::atomic::FnParam {
1926 crate::atomic::FnParam {
1927 name: p.name,
1928 ty: p.ty.as_ref().map(|t| {
1929 let u = t.to_union();
1930 let substituted = u.substitute_templates(bindings);
1931 crate::compact::SimpleType::from_union(substituted)
1932 }),
1933 out_ty: p.out_ty.as_ref().map(|t| {
1934 let u = t.to_union();
1935 let substituted = u.substitute_templates(bindings);
1936 crate::compact::SimpleType::from_union(substituted)
1937 }),
1938 default: p.default.as_ref().map(|d| {
1939 let u = d.to_union();
1940 let substituted = u.substitute_templates(bindings);
1941 crate::compact::SimpleType::from_union(substituted)
1942 }),
1943 is_variadic: p.is_variadic,
1944 is_byref: p.is_byref,
1945 is_optional: p.is_optional,
1946 }
1947}
1948
1949pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1955 if sub == sup {
1956 return true;
1957 }
1958 match (sub, sup) {
1959 (Atomic::TNever, _) => true,
1961 (_, Atomic::TMixed) => true,
1963 (Atomic::TMixed, _) => true,
1964 (_, Atomic::TTemplateParam { as_type, .. }) => {
1969 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1970 }
1971
1972 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1974 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1975 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1976 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1977 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1978 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1979 (Atomic::TPositiveInt, Atomic::TInt) => true,
1980 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1981 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1982 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1983 (Atomic::TNegativeInt, Atomic::TInt) => true,
1984 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1985 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1986 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1987 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1988 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1989 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1990 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1991 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1992 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1994 max.is_none() && min.is_none_or(|m| m <= 1)
1995 }
1996 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1998 min.is_none() && max.is_none_or(|m| m >= -1)
1999 }
2000 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
2002 max.is_none() && min.is_none_or(|m| m <= 0)
2003 }
2004 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
2006 sub_min.is_some_and(|lo| lo >= 1)
2007 }
2008 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
2009 sub_min.is_some_and(|lo| lo >= 0)
2010 }
2011 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
2012 sub_max.is_some_and(|hi| hi <= -1)
2013 }
2014 (
2016 Atomic::TIntRange {
2017 min: sub_min,
2018 max: sub_max,
2019 },
2020 Atomic::TIntRange {
2021 min: sup_min,
2022 max: sup_max,
2023 },
2024 ) => {
2025 let lower_ok = match (sub_min, sup_min) {
2026 (_, None) => true,
2027 (None, Some(_)) => false,
2028 (Some(sl), Some(su)) => sl >= su,
2029 };
2030 let upper_ok = match (sub_max, sup_max) {
2031 (None, None) | (Some(_), None) => true,
2032 (None, Some(_)) => false,
2033 (Some(sl), Some(su)) => sl <= su,
2034 };
2035 lower_ok && upper_ok
2036 }
2037
2038 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
2039 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
2040 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
2041
2042 (Atomic::TLiteralString(s), Atomic::TString) => {
2043 let _ = s;
2044 true
2045 }
2046 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
2047 let _ = s;
2048 true
2049 }
2050 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
2051 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
2052 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
2055 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
2058 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
2059 (Atomic::TNonEmptyString, Atomic::TString) => true,
2060 (Atomic::TCallableString, Atomic::TString) => true,
2061 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
2063 (Atomic::TNumericString, Atomic::TString) => true,
2064 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
2068 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
2069 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
2070 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
2071 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
2072 (Atomic::TClassString(_), Atomic::TString) => true,
2073 (Atomic::TInterfaceString(_), Atomic::TString) => true,
2074 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
2079 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
2080 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
2081 (Atomic::TEnumString, Atomic::TString) => true,
2082 (Atomic::TTraitString, Atomic::TString) => true,
2083
2084 (Atomic::TTrue, Atomic::TBool) => true,
2085 (Atomic::TFalse, Atomic::TBool) => true,
2086
2087 (Atomic::TInt, Atomic::TNumeric) => true,
2088 (Atomic::TFloat, Atomic::TNumeric) => true,
2089 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
2090 (Atomic::TNumericString, Atomic::TNumeric) => true,
2091
2092 (Atomic::TInt, Atomic::TScalar) => true,
2093 (Atomic::TFloat, Atomic::TScalar) => true,
2094 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
2095 (Atomic::TString, Atomic::TScalar) => true,
2096 (Atomic::TBool, Atomic::TScalar) => true,
2097 (Atomic::TNumeric, Atomic::TScalar) => true,
2098 (Atomic::TTrue, Atomic::TScalar) => true,
2099 (Atomic::TFalse, Atomic::TScalar) => true,
2100 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
2104 (Atomic::TNumericString, Atomic::TScalar) => true,
2105 (Atomic::TCallableString, Atomic::TScalar) => true,
2106 (Atomic::TClassString(_), Atomic::TScalar) => true,
2107 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
2108 (Atomic::TEnumString, Atomic::TScalar) => true,
2109 (Atomic::TTraitString, Atomic::TScalar) => true,
2110
2111 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2113 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2114 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2115 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2117 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2119 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2120 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2122 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2123 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2126 enum_fqcn == fqcn
2127 }
2128 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2129 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2130 enum_fqcn == fqcn
2131 }
2132 (
2136 Atomic::TNamedObject {
2137 fqcn: sub_fqcn,
2138 type_params: sub_params,
2139 },
2140 Atomic::TNamedObject {
2141 fqcn: sup_fqcn,
2142 type_params: sup_params,
2143 },
2144 ) => {
2145 sub_fqcn == sup_fqcn
2146 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2147 }
2148
2149 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2151
2152 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2154 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2155 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2156 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2157 (Atomic::TInt, Atomic::TFloat) => true,
2158 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2159
2160 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2162 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2163 }
2164
2165 (Atomic::TString, Atomic::TCallable { .. }) => true,
2167 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2168 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2169 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2170 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2171 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2172
2173 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2175 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2177 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2187 fn has_nominal_type(t: &Type) -> bool {
2188 t.types.iter().any(|a| {
2189 matches!(
2190 a,
2191 Atomic::TNamedObject { .. }
2192 | Atomic::TSelf { .. }
2193 | Atomic::TStaticObject { .. }
2194 | Atomic::TTemplateParam { .. }
2195 | Atomic::TClosure { .. }
2196 | Atomic::TCallable { .. }
2197 )
2198 })
2199 }
2200 let sub_required = sub
2201 .params
2202 .iter()
2203 .filter(|p| !p.is_optional && !p.is_variadic)
2204 .count();
2205 if sub_required > sup.params.len() {
2206 false
2207 } else {
2208 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2209 let Some(sub_param) = sub.params.get(i) else {
2210 return true;
2211 };
2212 if sub_param.is_optional || sub_param.is_variadic {
2213 return true;
2214 }
2215 let (Some(sub_ty), Some(sup_ty)) =
2216 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2217 else {
2218 return true;
2219 };
2220 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2221 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2222 return true;
2223 }
2224 sup_u.is_subtype_structural(&sub_u)
2227 });
2228 params_ok
2229 && (sub.return_type.is_mixed()
2230 || sup.return_type.is_mixed()
2231 || sup.return_type.is_void()
2235 || has_nominal_type(&sub.return_type)
2236 || has_nominal_type(&sup.return_type)
2237 || sub.return_type.is_subtype_structural(&sup.return_type))
2238 }
2239 }
2240 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2242 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2244 fqcn.as_ref().eq_ignore_ascii_case("closure")
2245 }
2246 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2247 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2249 fqcn.as_ref().eq_ignore_ascii_case("closure")
2250 }
2251 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2253 fqcn.as_ref().eq_ignore_ascii_case("closure")
2254 }
2255
2256 (
2264 Atomic::TIntersection { parts: sub_parts },
2265 Atomic::TIntersection { parts: sup_parts },
2266 ) => sup_parts.iter().all(|sup_part| {
2267 sub_parts
2268 .iter()
2269 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2270 }),
2271
2272 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2274 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2275 }
2276 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2277 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2278 }
2279 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2280 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2281 }
2282 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2283 value.is_subtype_structural(lv)
2284 }
2285 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2286 v1.is_subtype_structural(v2)
2287 }
2288 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2290 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2291 && av.is_subtype_structural(lv)
2292 }
2293 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2294 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2295 && av.is_subtype_structural(lv)
2296 }
2297 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2298 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2299 && av.is_subtype_structural(lv)
2300 }
2301 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2302 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2303 && av.is_subtype_structural(lv)
2304 }
2305 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2307 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2308 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2309 }
2310
2311 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2313 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2314 }
2315
2316 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2325 properties.iter().all(|(prop_key, prop)| {
2326 let key_atomic = match prop_key {
2327 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2328 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2329 };
2330 if !Type::single(key_atomic).is_subtype_structural(key) {
2331 return false; }
2333 let has_named_obj = prop.ty.types.iter().any(|a| {
2335 matches!(
2336 a,
2337 Atomic::TNamedObject { .. }
2338 | Atomic::TSelf { .. }
2339 | Atomic::TStaticObject { .. }
2340 | Atomic::TClosure { .. }
2341 | Atomic::TTemplateParam { .. }
2342 )
2343 });
2344 has_named_obj || prop.ty.is_subtype_structural(value)
2345 })
2346 }
2347 (
2348 Atomic::TKeyedArray {
2349 properties,
2350 is_open,
2351 ..
2352 },
2353 Atomic::TNonEmptyArray { key, value },
2354 ) => {
2355 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2356 && properties.iter().all(|(prop_key, prop)| {
2357 let key_atomic = match prop_key {
2358 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2359 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2360 };
2361 if !Type::single(key_atomic).is_subtype_structural(key) {
2362 return false;
2363 }
2364 let has_named_obj = prop.ty.types.iter().any(|a| {
2365 matches!(
2366 a,
2367 Atomic::TNamedObject { .. }
2368 | Atomic::TSelf { .. }
2369 | Atomic::TStaticObject { .. }
2370 | Atomic::TClosure { .. }
2371 | Atomic::TTemplateParam { .. }
2372 )
2373 });
2374 has_named_obj || prop.ty.is_subtype_structural(value)
2375 })
2376 }
2377
2378 (
2380 Atomic::TKeyedArray {
2381 properties,
2382 is_list,
2383 ..
2384 },
2385 Atomic::TList { value: lv },
2386 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2387 (
2388 Atomic::TKeyedArray {
2389 properties,
2390 is_list,
2391 ..
2392 },
2393 Atomic::TNonEmptyList { value: lv },
2394 ) => {
2395 *is_list
2396 && !properties.is_empty()
2397 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2398 }
2399
2400 (
2405 Atomic::TKeyedArray {
2406 properties: sub_props,
2407 is_open: sub_open,
2408 ..
2409 },
2410 Atomic::TKeyedArray {
2411 properties: sup_props,
2412 is_open: sup_open,
2413 ..
2414 },
2415 ) => {
2416 let keys_satisfied = sup_props
2417 .iter()
2418 .all(|(key, sup_prop)| match sub_props.get(key) {
2419 Some(sub_prop) => {
2420 if !sup_prop.optional && sub_prop.optional {
2424 return false;
2425 }
2426 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2427 matches!(
2428 a,
2429 Atomic::TNamedObject { .. }
2430 | Atomic::TSelf { .. }
2431 | Atomic::TStaticObject { .. }
2432 | Atomic::TClosure { .. }
2433 | Atomic::TTemplateParam { .. }
2434 )
2435 });
2436 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2437 }
2438 None => sup_prop.optional || *sub_open,
2439 });
2440 let no_undeclared_extras =
2441 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2442 keys_satisfied && no_undeclared_extras
2443 }
2444 (
2449 Atomic::TKeyedArray {
2450 properties,
2451 is_open,
2452 ..
2453 },
2454 Atomic::TIntersection { parts },
2455 ) => parts.iter().all(|part| {
2456 part.types.iter().any(|part_atomic| match part_atomic {
2457 Atomic::TKeyedArray {
2458 properties: sup_props,
2459 is_open: sup_open,
2460 ..
2461 } => {
2462 let keys_satisfied =
2463 sup_props
2464 .iter()
2465 .all(|(key, sup_prop)| match properties.get(key) {
2466 Some(sub_prop) => {
2467 if !sup_prop.optional && sub_prop.optional {
2468 return false;
2469 }
2470 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2471 matches!(
2472 a,
2473 Atomic::TNamedObject { .. }
2474 | Atomic::TSelf { .. }
2475 | Atomic::TStaticObject { .. }
2476 | Atomic::TClosure { .. }
2477 | Atomic::TTemplateParam { .. }
2478 )
2479 });
2480 has_named_obj
2481 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2482 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2483 }
2484 None => *is_open || sup_prop.optional,
2485 });
2486 let has_array_part = parts.iter().any(|part| {
2487 part.types
2488 .iter()
2489 .any(|t| matches!(t, Atomic::TArray { .. }))
2490 });
2491 let has_keyed_array_part = parts.iter().any(|part| {
2492 part.types
2493 .iter()
2494 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2495 });
2496 let keys_allowed_by_some_part = if has_array_part {
2497 true
2498 } else if has_keyed_array_part {
2499 properties.keys().all(|k| {
2500 parts.iter().any(|part| {
2501 part.types.iter().any(|t| {
2502 if let Atomic::TKeyedArray {
2503 properties: part_props,
2504 ..
2505 } = t
2506 {
2507 part_props.contains_key(k)
2508 } else {
2509 false
2510 }
2511 })
2512 })
2513 })
2514 } else {
2515 true
2516 };
2517 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2518 keys_satisfied && no_undeclared_extras
2519 }
2520 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2521 let key_atomic = match prop_key {
2522 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2523 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2524 };
2525 if !Type::single(key_atomic).is_subtype_structural(key) {
2526 return false;
2527 }
2528 let has_named_obj = prop.ty.types.iter().any(|a| {
2529 matches!(
2530 a,
2531 Atomic::TNamedObject { .. }
2532 | Atomic::TSelf { .. }
2533 | Atomic::TStaticObject { .. }
2534 | Atomic::TClosure { .. }
2535 | Atomic::TTemplateParam { .. }
2536 )
2537 });
2538 has_named_obj || prop.ty.is_subtype_structural(value)
2539 }),
2540 Atomic::TNamedObject { .. }
2541 | Atomic::TSelf { .. }
2542 | Atomic::TStaticObject { .. }
2543 | Atomic::TClosure { .. }
2544 | Atomic::TTemplateParam { .. } => true,
2545 _ => false,
2546 })
2547 }),
2548
2549 _ => false,
2550 }
2551}
2552
2553fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2555 fn array_like(t: &Atomic) -> bool {
2556 match t {
2557 Atomic::TArray { .. }
2558 | Atomic::TNonEmptyArray { .. }
2559 | Atomic::TList { .. }
2560 | Atomic::TNonEmptyList { .. }
2561 | Atomic::TKeyedArray { .. } => true,
2562 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2563 _ => false,
2564 }
2565 }
2566 matches!(
2567 sub.types.as_slice(),
2568 [Atomic::TArray { key, value }]
2569 if value.is_mixed()
2570 && (key.is_mixed()
2571 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2572 ) && sup.types.iter().all(array_like)
2573}
2574
2575fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2581 if sub.len() != sup.len() {
2582 return false;
2583 }
2584 sub.iter()
2585 .zip(sup.iter())
2586 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2587}
2588
2589fn is_empty_array_literal(t: &Type) -> bool {
2592 !t.types.is_empty()
2593 && t.types.iter().all(
2594 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2595 )
2596}
2597
2598fn is_array_like(t: &Type) -> bool {
2600 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2601}
2602
2603pub struct UnionBuilder {
2610 ty: Type,
2611 strings: std::collections::HashSet<std::sync::Arc<str>>,
2612 ints: std::collections::HashSet<i64>,
2613 literals_independent: bool,
2615}
2616
2617impl Default for UnionBuilder {
2618 fn default() -> Self {
2619 Self::new()
2620 }
2621}
2622
2623impl UnionBuilder {
2624 pub fn new() -> Self {
2625 Self {
2626 ty: Type::empty(),
2627 strings: Default::default(),
2628 ints: Default::default(),
2629 literals_independent: true,
2630 }
2631 }
2632
2633 pub fn is_empty(&self) -> bool {
2634 self.ty.is_empty()
2635 }
2636
2637 pub fn merge(&mut self, other: &Type) {
2638 for a in &other.types {
2639 self.add(a.clone());
2640 }
2641 self.ty.possibly_undefined |= other.possibly_undefined;
2642 }
2643
2644 pub fn add(&mut self, atomic: Atomic) {
2645 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2647 if self.literals_independent && !lone_never {
2648 match &atomic {
2649 Atomic::TLiteralString(s) => {
2650 if self.strings.insert(s.clone()) {
2651 self.ty.types.push(atomic);
2652 }
2653 return;
2654 }
2655 Atomic::TLiteralInt(n) => {
2656 if self.ints.insert(*n) {
2657 self.ty.types.push(atomic);
2658 }
2659 return;
2660 }
2661 _ => {}
2662 }
2663 }
2664 let disturbs_literals = matches!(
2665 atomic,
2666 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2667 ) || int_bounds(&atomic).is_some();
2668 self.ty.add_type(atomic);
2669 if disturbs_literals || lone_never {
2670 self.resync();
2671 }
2672 }
2673
2674 fn resync(&mut self) {
2675 self.strings.clear();
2676 self.ints.clear();
2677 self.literals_independent = true;
2678 for t in &self.ty.types {
2679 match t {
2680 Atomic::TLiteralString(s) => {
2681 self.strings.insert(s.clone());
2682 }
2683 Atomic::TLiteralInt(n) => {
2684 self.ints.insert(*n);
2685 }
2686 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2687 other if int_bounds(other).is_some() => self.literals_independent = false,
2688 _ => {}
2689 }
2690 }
2691 }
2692
2693 pub fn build(self) -> Type {
2694 self.ty
2695 }
2696}
2697
2698#[cfg(test)]
2699mod tests {
2700 use std::sync::Arc;
2701
2702 use super::*;
2703
2704 fn conditional(
2705 param_name: Option<Name>,
2706 subject: Type,
2707 if_true: Type,
2708 if_false: Type,
2709 ) -> Atomic {
2710 Atomic::TConditional {
2711 data: Box::new(crate::atomic::ConditionalData {
2712 param_name,
2713 subject,
2714 if_true,
2715 if_false,
2716 }),
2717 }
2718 }
2719
2720 #[test]
2721 fn single_is_single() {
2722 let u = Type::single(Atomic::TString);
2723 assert!(u.is_single());
2724 assert!(!u.is_nullable());
2725 }
2726
2727 #[test]
2728 fn nullable_has_null() {
2729 let u = Type::nullable(Atomic::TString);
2730 assert!(u.is_nullable());
2731 assert_eq!(u.types.len(), 2);
2732 }
2733
2734 #[test]
2735 fn add_type_deduplicates() {
2736 let mut u = Type::single(Atomic::TString);
2737 u.add_type(Atomic::TString);
2738 assert_eq!(u.types.len(), 1);
2739 }
2740
2741 #[test]
2742 fn array_key_is_int_string() {
2743 let k = Type::array_key();
2744 assert!(k.is_array_key());
2745 assert_eq!(k.types.len(), 2);
2746 }
2747
2748 #[test]
2749 fn is_array_key_false_for_plain_int() {
2750 assert!(!Type::int().is_array_key());
2751 }
2752
2753 #[test]
2754 fn is_array_key_false_for_mixed() {
2755 assert!(!Type::mixed().is_array_key());
2756 }
2757
2758 #[test]
2759 fn is_array_key_false_for_int_string_null() {
2760 let mut u = Type::array_key();
2761 u.add_type(Atomic::TNull);
2762 assert!(!u.is_array_key());
2763 }
2764
2765 #[test]
2766 fn add_type_literal_subsumed_by_base() {
2767 let mut u = Type::single(Atomic::TInt);
2768 u.add_type(Atomic::TLiteralInt(42));
2769 assert_eq!(u.types.len(), 1);
2770 assert!(matches!(u.types[0], Atomic::TInt));
2771 }
2772
2773 #[test]
2774 fn true_then_false_merges_to_bool() {
2775 let mut u = Type::single(Atomic::TTrue);
2776 u.add_type(Atomic::TFalse);
2777 assert_eq!(u.types.len(), 1);
2778 assert!(matches!(u.types[0], Atomic::TBool));
2779 }
2780
2781 #[test]
2782 fn false_then_true_merges_to_bool() {
2783 let mut u = Type::single(Atomic::TFalse);
2784 u.add_type(Atomic::TTrue);
2785 assert_eq!(u.types.len(), 1);
2786 assert!(matches!(u.types[0], Atomic::TBool));
2787 }
2788
2789 #[test]
2790 fn true_alone_stays_true() {
2791 let u = Type::single(Atomic::TTrue);
2792 assert_eq!(u.types.len(), 1);
2793 assert!(matches!(u.types[0], Atomic::TTrue));
2794 }
2795
2796 #[test]
2797 fn true_false_merge_preserves_other_union_members() {
2798 let mut u = Type::single(Atomic::TTrue);
2799 u.add_type(Atomic::TNull);
2800 u.add_type(Atomic::TFalse);
2801 assert_eq!(u.types.len(), 2);
2802 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2803 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2804 }
2805
2806 #[test]
2807 fn add_type_base_widens_literals() {
2808 let mut u = Type::single(Atomic::TLiteralInt(1));
2809 u.add_type(Atomic::TLiteralInt(2));
2810 u.add_type(Atomic::TInt);
2811 assert_eq!(u.types.len(), 1);
2812 assert!(matches!(u.types[0], Atomic::TInt));
2813 }
2814
2815 #[test]
2816 fn mixed_subsumes_everything() {
2817 let mut u = Type::single(Atomic::TString);
2818 u.add_type(Atomic::TMixed);
2819 assert_eq!(u.types.len(), 1);
2820 assert!(u.is_mixed());
2821 }
2822
2823 #[test]
2824 fn remove_null() {
2825 let u = Type::nullable(Atomic::TString);
2826 let narrowed = u.remove_null();
2827 assert!(!narrowed.is_nullable());
2828 assert_eq!(narrowed.types.len(), 1);
2829 }
2830
2831 #[test]
2832 fn narrow_to_truthy_removes_null_false() {
2833 let mut u = Type::empty();
2834 u.add_type(Atomic::TString);
2835 u.add_type(Atomic::TNull);
2836 u.add_type(Atomic::TFalse);
2837 let truthy = u.narrow_to_truthy();
2838 assert!(!truthy.is_nullable());
2839 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2840 }
2841
2842 #[test]
2843 fn merge_combines_types() {
2844 let a = Type::single(Atomic::TString);
2845 let b = Type::single(Atomic::TInt);
2846 let merged = Type::merge(&a, &b);
2847 assert_eq!(merged.types.len(), 2);
2848 }
2849
2850 #[test]
2851 fn intersect_keeps_narrower_side_not_self() {
2852 let int_ty = Type::single(Atomic::TInt);
2856 let mut literals = Type::empty();
2857 literals.add_type(Atomic::TLiteralInt(1));
2858 literals.add_type(Atomic::TLiteralInt(2));
2859
2860 let narrowed = int_ty.intersect_with(&literals);
2861 assert_eq!(narrowed.types.len(), 2);
2862 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2863 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2864 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2865 }
2866
2867 #[test]
2868 fn subtype_literal_int_under_int() {
2869 let sub = Type::single(Atomic::TLiteralInt(5));
2870 let sup = Type::single(Atomic::TInt);
2871 assert!(sub.is_subtype_structural(&sup));
2872 }
2873
2874 #[test]
2875 fn subtype_never_is_bottom() {
2876 let never = Type::never();
2877 let string = Type::single(Atomic::TString);
2878 assert!(never.is_subtype_structural(&string));
2879 }
2880
2881 #[test]
2882 fn subtype_everything_under_mixed() {
2883 let string = Type::single(Atomic::TString);
2884 let mixed = Type::mixed();
2885 assert!(string.is_subtype_structural(&mixed));
2886 }
2887
2888 #[test]
2889 fn subtype_enum_case_under_own_enum() {
2890 let sub = Type::single(Atomic::TLiteralEnumCase {
2891 enum_fqcn: Name::new("RoundingMode"),
2892 case_name: Name::new("Unnecessary"),
2893 });
2894 let sup = Type::single(Atomic::TNamedObject {
2895 fqcn: Name::new("RoundingMode"),
2896 type_params: empty_type_params(),
2897 });
2898 assert!(sub.is_subtype_structural(&sup));
2899 }
2900
2901 #[test]
2902 fn enum_case_not_subtype_of_unrelated_enum() {
2903 let sub = Type::single(Atomic::TLiteralEnumCase {
2904 enum_fqcn: Name::new("RoundingMode"),
2905 case_name: Name::new("Unnecessary"),
2906 });
2907 let sup = Type::single(Atomic::TNamedObject {
2908 fqcn: Name::new("Suit"),
2909 type_params: empty_type_params(),
2910 });
2911 assert!(!sub.is_subtype_structural(&sup));
2912 }
2913
2914 #[test]
2915 fn subtype_enum_case_under_own_self_and_static() {
2916 let case = |enum_fqcn: &str| {
2917 Type::single(Atomic::TLiteralEnumCase {
2918 enum_fqcn: Name::new(enum_fqcn),
2919 case_name: Name::new("A"),
2920 })
2921 };
2922 let own = Type::single(Atomic::TSelf {
2923 fqcn: Name::new("Suit"),
2924 });
2925 let own_static = Type::single(Atomic::TStaticObject {
2926 fqcn: Name::new("Suit"),
2927 });
2928 assert!(case("Suit").is_subtype_structural(&own));
2929 assert!(case("Suit").is_subtype_structural(&own_static));
2930 assert!(!case("Other").is_subtype_structural(&own));
2931 }
2932
2933 #[test]
2934 fn subtype_enum_case_under_bare_object() {
2935 let sub = Type::single(Atomic::TLiteralEnumCase {
2936 enum_fqcn: Name::new("RoundingMode"),
2937 case_name: Name::new("Unnecessary"),
2938 });
2939 let sup = Type::single(Atomic::TObject);
2940 assert!(sub.is_subtype_structural(&sup));
2941 }
2942
2943 #[test]
2944 fn template_substitution() {
2945 let mut bindings = FxHashMap::default();
2946 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2947
2948 let tmpl = Type::single(Atomic::TTemplateParam {
2949 name: Name::new("T"),
2950 as_type: Box::new(Type::mixed()),
2951 defining_entity: Name::new("MyClass"),
2952 });
2953
2954 let resolved = tmpl.substitute_templates(&bindings);
2955 assert_eq!(resolved.types.len(), 1);
2956 assert!(matches!(resolved.types[0], Atomic::TString));
2957 }
2958
2959 #[test]
2960 fn intersection_is_object() {
2961 let parts = vec![
2962 Type::single(Atomic::TNamedObject {
2963 fqcn: Name::new("Iterator"),
2964 type_params: empty_type_params(),
2965 }),
2966 Type::single(Atomic::TNamedObject {
2967 fqcn: Name::new("Countable"),
2968 type_params: empty_type_params(),
2969 }),
2970 ];
2971 let atomic = Atomic::TIntersection {
2972 parts: vec_to_type_params(parts),
2973 };
2974 assert!(atomic.is_object());
2975 assert!(!atomic.can_be_falsy());
2976 assert!(atomic.can_be_truthy());
2977 }
2978
2979 #[test]
2980 fn intersection_display_two_parts() {
2981 let parts = vec![
2982 Type::single(Atomic::TNamedObject {
2983 fqcn: Name::new("Iterator"),
2984 type_params: empty_type_params(),
2985 }),
2986 Type::single(Atomic::TNamedObject {
2987 fqcn: Name::new("Countable"),
2988 type_params: empty_type_params(),
2989 }),
2990 ];
2991 let u = Type::single(Atomic::TIntersection {
2992 parts: vec_to_type_params(parts),
2993 });
2994 assert_eq!(format!("{u}"), "Iterator&Countable");
2995 }
2996
2997 #[test]
2998 fn intersection_display_three_parts() {
2999 let parts = vec![
3000 Type::single(Atomic::TNamedObject {
3001 fqcn: Name::new("A"),
3002 type_params: empty_type_params(),
3003 }),
3004 Type::single(Atomic::TNamedObject {
3005 fqcn: Name::new("B"),
3006 type_params: empty_type_params(),
3007 }),
3008 Type::single(Atomic::TNamedObject {
3009 fqcn: Name::new("C"),
3010 type_params: empty_type_params(),
3011 }),
3012 ];
3013 let u = Type::single(Atomic::TIntersection {
3014 parts: vec_to_type_params(parts),
3015 });
3016 assert_eq!(format!("{u}"), "A&B&C");
3017 }
3018
3019 #[test]
3020 fn intersection_in_nullable_union_display() {
3021 let intersection = Atomic::TIntersection {
3022 parts: vec_to_type_params(vec![
3023 Type::single(Atomic::TNamedObject {
3024 fqcn: Name::new("Iterator"),
3025 type_params: empty_type_params(),
3026 }),
3027 Type::single(Atomic::TNamedObject {
3028 fqcn: Name::new("Countable"),
3029 type_params: empty_type_params(),
3030 }),
3031 ]),
3032 };
3033 let mut u = Type::single(intersection);
3034 u.add_type(Atomic::TNull);
3035 assert!(u.is_nullable());
3036 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
3037 }
3038
3039 fn t_param(name: &str) -> Type {
3042 Type::single(Atomic::TTemplateParam {
3043 name: Name::new(name),
3044 as_type: Box::new(Type::mixed()),
3045 defining_entity: Name::new("Fn"),
3046 })
3047 }
3048
3049 fn bindings_t_string() -> FxHashMap<Name, Type> {
3050 let mut b = FxHashMap::default();
3051 b.insert(Name::new("T"), Type::single(Atomic::TString));
3052 b
3053 }
3054
3055 #[test]
3056 fn substitute_non_empty_array_key_and_value() {
3057 let ty = Type::single(Atomic::TNonEmptyArray {
3058 key: Box::new(t_param("T")),
3059 value: Box::new(t_param("T")),
3060 });
3061 let result = ty.substitute_templates(&bindings_t_string());
3062 assert_eq!(result.types.len(), 1);
3063 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
3064 panic!("expected TNonEmptyArray");
3065 };
3066 assert!(matches!(key.types[0], Atomic::TString));
3067 assert!(matches!(value.types[0], Atomic::TString));
3068 }
3069
3070 #[test]
3071 fn substitute_non_empty_list_value() {
3072 let ty = Type::single(Atomic::TNonEmptyList {
3073 value: Box::new(t_param("T")),
3074 });
3075 let result = ty.substitute_templates(&bindings_t_string());
3076 let Atomic::TNonEmptyList { value } = &result.types[0] else {
3077 panic!("expected TNonEmptyList");
3078 };
3079 assert!(matches!(value.types[0], Atomic::TString));
3080 }
3081
3082 #[test]
3083 fn substitute_keyed_array_property_types() {
3084 use crate::atomic::{ArrayKey, KeyedProperty};
3085 use indexmap::IndexMap;
3086 let mut props = IndexMap::new();
3087 props.insert(
3088 ArrayKey::String(Arc::from("name")),
3089 KeyedProperty {
3090 ty: t_param("T"),
3091 optional: false,
3092 },
3093 );
3094 props.insert(
3095 ArrayKey::String(Arc::from("tag")),
3096 KeyedProperty {
3097 ty: t_param("T"),
3098 optional: true,
3099 },
3100 );
3101 let ty = Type::single(Atomic::TKeyedArray {
3102 properties: Box::new(props),
3103 is_open: true,
3104 is_list: false,
3105 });
3106 let result = ty.substitute_templates(&bindings_t_string());
3107 let Atomic::TKeyedArray {
3108 properties,
3109 is_open,
3110 is_list,
3111 } = &result.types[0]
3112 else {
3113 panic!("expected TKeyedArray");
3114 };
3115 assert!(is_open);
3116 assert!(!is_list);
3117 assert!(matches!(
3118 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3119 Atomic::TString
3120 ));
3121 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3122 assert!(matches!(
3123 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3124 Atomic::TString
3125 ));
3126 }
3127
3128 #[test]
3129 fn substitute_callable_params_and_return() {
3130 use crate::atomic::FnParam;
3131 let ty = Type::single(Atomic::TCallable {
3132 params: Some(Box::new(vec![FnParam {
3133 name: Name::new("x"),
3134 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3135 out_ty: None,
3136 default: None,
3137 is_variadic: false,
3138 is_byref: false,
3139 is_optional: false,
3140 }])),
3141 return_type: Some(Box::new(t_param("T"))),
3142 is_pure: false,
3143 });
3144 let result = ty.substitute_templates(&bindings_t_string());
3145 let Atomic::TCallable {
3146 params,
3147 return_type,
3148 ..
3149 } = &result.types[0]
3150 else {
3151 panic!("expected TCallable");
3152 };
3153 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3154 let param_union = param_ty.to_union();
3155 assert!(matches!(param_union.types[0], Atomic::TString));
3156 let ret = return_type.as_ref().unwrap();
3157 assert!(matches!(ret.types[0], Atomic::TString));
3158 }
3159
3160 #[test]
3161 fn substitute_callable_bare_no_panic() {
3162 let ty = Type::single(Atomic::TCallable {
3164 params: None,
3165 return_type: None,
3166 is_pure: false,
3167 });
3168 let result = ty.substitute_templates(&bindings_t_string());
3169 assert!(matches!(
3170 result.types[0],
3171 Atomic::TCallable {
3172 params: None,
3173 return_type: None,
3174 ..
3175 }
3176 ));
3177 }
3178
3179 #[test]
3180 fn substitute_closure_params_return_and_this() {
3181 use crate::atomic::FnParam;
3182 let ty = Type::single(Atomic::TClosure {
3183 data: Box::new(crate::atomic::ClosureData {
3184 params: Box::new([FnParam {
3185 name: Name::new("a"),
3186 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3187 out_ty: None,
3188 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3189 is_variadic: true,
3190 is_byref: true,
3191 is_optional: true,
3192 }]),
3193 return_type: t_param("T"),
3194 this_type: Some(t_param("T")),
3195 is_pure: false,
3196 is_static: false,
3197 }),
3198 });
3199 let result = ty.substitute_templates(&bindings_t_string());
3200 let Atomic::TClosure { data } = &result.types[0] else {
3201 panic!("expected TClosure");
3202 };
3203 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3204 let p = ¶ms[0];
3205 let ty_union = p.ty.as_ref().unwrap().to_union();
3206 let default_union = p.default.as_ref().unwrap().to_union();
3207 assert!(matches!(ty_union.types[0], Atomic::TString));
3208 assert!(matches!(default_union.types[0], Atomic::TString));
3209 assert!(p.is_variadic);
3211 assert!(p.is_byref);
3212 assert!(p.is_optional);
3213 assert!(matches!(return_type.types[0], Atomic::TString));
3214 assert!(matches!(
3215 this_type.as_ref().unwrap().types[0],
3216 Atomic::TString
3217 ));
3218 }
3219
3220 #[test]
3221 fn substitute_conditional_all_branches() {
3222 let ty = Type::single(conditional(
3223 None,
3224 t_param("T"),
3225 t_param("T"),
3226 Type::single(Atomic::TInt),
3227 ));
3228 let result = ty.substitute_templates(&bindings_t_string());
3229 let Atomic::TConditional { data } = &result.types[0] else {
3230 panic!("expected TConditional");
3231 };
3232 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3233 assert!(matches!(subject.types[0], Atomic::TString));
3234 assert!(matches!(if_true.types[0], Atomic::TString));
3235 assert!(matches!(if_false.types[0], Atomic::TInt));
3236 }
3237
3238 #[test]
3239 fn resolve_conditional_is_null_non_null_arg() {
3240 let ty = Type::single(conditional(
3241 Some(Name::new("x")),
3242 Type::single(Atomic::TNull),
3243 Type::single(Atomic::TInt),
3244 Type::single(Atomic::TString),
3245 ));
3246 let result = ty.resolve_conditional_returns(|name| {
3247 if name == "x" {
3248 Some(Type::single(Atomic::TString)) } else {
3250 None
3251 }
3252 });
3253 assert!(result.types.len() == 1);
3254 assert!(matches!(result.types[0], Atomic::TString));
3255 }
3256
3257 #[test]
3258 fn resolve_conditional_is_null_null_arg() {
3259 let ty = Type::single(conditional(
3260 Some(Name::new("x")),
3261 Type::single(Atomic::TNull),
3262 Type::single(Atomic::TInt),
3263 Type::single(Atomic::TString),
3264 ));
3265 let result = ty.resolve_conditional_returns(|name| {
3266 if name == "x" {
3267 Some(Type::single(Atomic::TNull)) } else {
3269 None
3270 }
3271 });
3272 assert!(result.types.len() == 1);
3273 assert!(matches!(result.types[0], Atomic::TInt));
3274 }
3275
3276 #[test]
3277 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3278 let mut nullable_str = Type::single(Atomic::TString);
3279 nullable_str.add_type(Atomic::TNull);
3280 let ty = Type::single(conditional(
3281 Some(Name::new("x")),
3282 Type::single(Atomic::TNull),
3283 Type::single(Atomic::TInt),
3284 Type::single(Atomic::TString),
3285 ));
3286 let result = ty.resolve_conditional_returns(|name| {
3287 if name == "x" {
3288 Some(nullable_str.clone())
3289 } else {
3290 None
3291 }
3292 });
3293 assert_eq!(result.types.len(), 2);
3295 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3296 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3297 }
3298
3299 #[test]
3300 fn resolve_conditional_nested_widens_inner_branch() {
3301 let inner = Type::single(conditional(
3304 Some(Name::new("x")),
3305 Type::single(Atomic::TString),
3306 Type::single(Atomic::TString),
3307 Type::single(Atomic::TFloat),
3308 ));
3309 let ty = Type::single(conditional(
3310 Some(Name::new("x")),
3311 Type::single(Atomic::TNull),
3312 Type::single(Atomic::TInt),
3313 inner,
3314 ));
3315 let result = ty.resolve_conditional_returns(|_| None);
3317 assert!(
3318 result
3319 .types
3320 .iter()
3321 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3322 "no TConditional should survive: {:?}",
3323 result.types
3324 );
3325 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3326 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3327 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3328 }
3329
3330 #[test]
3331 fn resolve_conditional_nested_resolves_inner_branch() {
3332 let inner = Type::single(conditional(
3336 Some(Name::new("x")),
3337 Type::single(Atomic::TString),
3338 Type::single(Atomic::TString),
3339 Type::single(Atomic::TFloat),
3340 ));
3341 let ty = Type::single(conditional(
3342 Some(Name::new("x")),
3343 Type::single(Atomic::TNull),
3344 Type::single(Atomic::TInt),
3345 inner,
3346 ));
3347 let result = ty.resolve_conditional_returns(|name| {
3349 if name == "x" {
3350 Some(Type::single(Atomic::TString))
3351 } else {
3352 None
3353 }
3354 });
3355 assert!(
3356 result
3357 .types
3358 .iter()
3359 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3360 "no TConditional should survive: {:?}",
3361 result.types
3362 );
3363 assert_eq!(result.types.len(), 1);
3364 assert!(matches!(result.types[0], Atomic::TString));
3365 }
3366
3367 #[test]
3368 fn resolve_conditional_true_subject_bool_arg_widens() {
3369 let ty = Type::single(conditional(
3373 Some(Name::new("x")),
3374 Type::single(Atomic::TTrue),
3375 Type::single(Atomic::TInt),
3376 Type::single(Atomic::TString),
3377 ));
3378 let result = ty.resolve_conditional_returns(|name| {
3379 if name == "x" {
3380 Some(Type::single(Atomic::TBool))
3381 } else {
3382 None
3383 }
3384 });
3385 assert_eq!(result.types.len(), 2);
3386 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3387 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3388 }
3389
3390 #[test]
3391 fn resolve_conditional_true_subject_mixed_arg_widens() {
3392 let ty = Type::single(conditional(
3395 Some(Name::new("x")),
3396 Type::single(Atomic::TTrue),
3397 Type::single(Atomic::TInt),
3398 Type::single(Atomic::TString),
3399 ));
3400 let result = ty.resolve_conditional_returns(|name| {
3401 if name == "x" {
3402 Some(Type::mixed())
3403 } else {
3404 None
3405 }
3406 });
3407 assert_eq!(result.types.len(), 2);
3408 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3409 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3410 }
3411
3412 #[test]
3413 fn resolve_conditional_true_subject_literal_args() {
3414 let ty = Type::single(conditional(
3417 Some(Name::new("x")),
3418 Type::single(Atomic::TTrue),
3419 Type::single(Atomic::TInt),
3420 Type::single(Atomic::TString),
3421 ));
3422 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3423 if name == "x" {
3424 Some(Type::single(Atomic::TFalse))
3425 } else {
3426 None
3427 }
3428 });
3429 assert_eq!(false_arg.types.len(), 1);
3430 assert!(matches!(false_arg.types[0], Atomic::TString));
3431
3432 let true_arg = ty.resolve_conditional_returns(|name| {
3433 if name == "x" {
3434 Some(Type::single(Atomic::TTrue))
3435 } else {
3436 None
3437 }
3438 });
3439 assert_eq!(true_arg.types.len(), 1);
3440 assert!(matches!(true_arg.types[0], Atomic::TInt));
3441 }
3442
3443 #[test]
3444 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3445 let ty = Type::single(conditional(
3449 Some(Name::new("x")),
3450 Type::single(Atomic::TBool),
3451 Type::single(Atomic::TInt),
3452 Type::single(Atomic::TString),
3453 ));
3454 let result = ty.resolve_conditional_returns(|name| {
3455 if name == "x" {
3456 Some(Type::single(Atomic::TScalar))
3457 } else {
3458 None
3459 }
3460 });
3461 assert_eq!(result.types.len(), 2);
3462 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3463 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3464 }
3465
3466 #[test]
3467 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3468 let ty = Type::single(conditional(
3472 Some(Name::new("x")),
3473 Type::single(Atomic::TString),
3474 Type::single(Atomic::TInt),
3475 Type::single(Atomic::TBool),
3476 ));
3477 let result = ty.resolve_conditional_returns(|name| {
3478 if name == "x" {
3479 Some(Type::single(Atomic::TBool))
3480 } else {
3481 None
3482 }
3483 });
3484 assert_eq!(result.types.len(), 1);
3485 assert!(matches!(result.types[0], Atomic::TBool));
3486 }
3487
3488 #[test]
3489 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3490 let ty = Type::single(conditional(
3493 Some(Name::new("x")),
3494 Type::single(Atomic::TList {
3495 value: Box::new(Type::mixed()),
3496 }),
3497 Type::single(Atomic::TInt),
3498 Type::single(Atomic::TString),
3499 ));
3500 let result = ty.resolve_conditional_returns(|name| {
3501 if name == "x" {
3502 Some(Type::single(Atomic::TArray {
3503 key: Box::new(Type::mixed()),
3504 value: Box::new(Type::mixed()),
3505 }))
3506 } else {
3507 None
3508 }
3509 });
3510 assert_eq!(result.types.len(), 2);
3511 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3512 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3513 }
3514
3515 #[test]
3516 fn resolve_conditional_float_subject_int_literal_false_branch() {
3517 let ty = resolve_conditional_branch(
3520 &Atomic::TFloat,
3521 &Type::single(Atomic::TLiteralInt(42)),
3522 &Type::single(Atomic::TInt),
3523 &Type::single(Atomic::TString),
3524 );
3525 assert!(matches!(
3526 ty.as_ref(),
3527 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3528 ));
3529 }
3530
3531 #[test]
3532 fn resolve_conditional_int_subject_float_literal_false_branch() {
3533 let ty = resolve_conditional_branch(
3534 &Atomic::TInt,
3535 &Type::single(Atomic::TLiteralFloat(1, 0)),
3536 &Type::single(Atomic::TInt),
3537 &Type::single(Atomic::TString),
3538 );
3539 assert!(matches!(
3540 ty.as_ref(),
3541 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3542 ));
3543 }
3544
3545 #[test]
3546 fn resolve_conditional_float_subject_float_literal_then_branch() {
3547 let ty = resolve_conditional_branch(
3548 &Atomic::TFloat,
3549 &Type::single(Atomic::TLiteralFloat(1, 5)),
3550 &Type::single(Atomic::TInt),
3551 &Type::single(Atomic::TString),
3552 );
3553 assert!(matches!(
3554 ty.as_ref(),
3555 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3556 ));
3557 }
3558
3559 #[test]
3560 fn resolve_conditional_int_subject_int_literal_then_branch() {
3561 let ty = resolve_conditional_branch(
3562 &Atomic::TInt,
3563 &Type::single(Atomic::TLiteralInt(7)),
3564 &Type::single(Atomic::TInt),
3565 &Type::single(Atomic::TString),
3566 );
3567 assert!(matches!(
3568 ty.as_ref(),
3569 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3570 ));
3571 }
3572
3573 #[test]
3574 fn resolve_conditional_int_subject_float_arg_false_branch() {
3575 let ty = resolve_conditional_branch(
3578 &Atomic::TInt,
3579 &Type::single(Atomic::TFloat),
3580 &Type::single(Atomic::TInt),
3581 &Type::single(Atomic::TString),
3582 );
3583 assert!(matches!(
3584 ty.as_ref(),
3585 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3586 ));
3587 }
3588
3589 #[test]
3590 fn resolve_conditional_array_subject_string_arg_false_branch() {
3591 let ty = resolve_conditional_branch(
3592 &Atomic::TArray {
3593 key: Box::new(Type::mixed()),
3594 value: Box::new(Type::mixed()),
3595 },
3596 &Type::single(Atomic::TString),
3597 &Type::single(Atomic::TInt),
3598 &Type::single(Atomic::TString),
3599 );
3600 assert!(matches!(
3601 ty.as_ref(),
3602 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3603 ));
3604 }
3605
3606 #[test]
3607 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3608 let ty = resolve_conditional_branch(
3609 &Atomic::TArray {
3610 key: Box::new(Type::mixed()),
3611 value: Box::new(Type::mixed()),
3612 },
3613 &Type::single(Atomic::TBool),
3614 &Type::single(Atomic::TInt),
3615 &Type::single(Atomic::TString),
3616 );
3617 assert!(matches!(
3618 ty.as_ref(),
3619 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3620 ));
3621 }
3622
3623 #[test]
3624 fn resolve_conditional_array_subject_mixed_arg_widens() {
3625 let ty = resolve_conditional_branch(
3629 &Atomic::TArray {
3630 key: Box::new(Type::mixed()),
3631 value: Box::new(Type::mixed()),
3632 },
3633 &Type::mixed(),
3634 &Type::single(Atomic::TInt),
3635 &Type::single(Atomic::TString),
3636 );
3637 assert!(ty.is_none());
3638 }
3639
3640 #[test]
3641 fn resolve_conditional_array_subject_list_arg_then_branch() {
3642 let ty = resolve_conditional_branch(
3645 &Atomic::TArray {
3646 key: Box::new(Type::mixed()),
3647 value: Box::new(Type::mixed()),
3648 },
3649 &Type::single(Atomic::TList {
3650 value: Box::new(Type::mixed()),
3651 }),
3652 &Type::single(Atomic::TInt),
3653 &Type::single(Atomic::TString),
3654 );
3655 assert!(matches!(
3656 ty.as_ref(),
3657 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3658 ));
3659 }
3660
3661 #[test]
3662 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3663 let ty = resolve_conditional_branch(
3665 &Atomic::TList {
3666 value: Box::new(Type::mixed()),
3667 },
3668 &Type::single(Atomic::TKeyedArray {
3669 properties: Box::default(),
3670 is_open: false,
3671 is_list: true,
3672 }),
3673 &Type::single(Atomic::TInt),
3674 &Type::single(Atomic::TString),
3675 );
3676 assert!(matches!(
3677 ty.as_ref(),
3678 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3679 ));
3680 }
3681
3682 #[test]
3683 fn resolve_conditional_list_subject_list_string_union_widens() {
3684 let ty = resolve_conditional_branch(
3688 &Atomic::TList {
3689 value: Box::new(Type::mixed()),
3690 },
3691 &Type::from_vec(vec![
3692 Atomic::TList {
3693 value: Box::new(Type::mixed()),
3694 },
3695 Atomic::TString,
3696 ]),
3697 &Type::single(Atomic::TInt),
3698 &Type::single(Atomic::TString),
3699 );
3700 assert!(ty.is_none());
3701 }
3702
3703 #[test]
3704 fn resolve_conditional_numeric_subject_widens() {
3705 let ty = resolve_conditional_branch(
3709 &Atomic::TNumeric,
3710 &Type::single(Atomic::TLiteralString("123".into())),
3711 &Type::single(Atomic::TInt),
3712 &Type::single(Atomic::TString),
3713 );
3714 assert!(ty.is_none());
3715 }
3716
3717 #[test]
3718 fn resolve_conditional_non_empty_string_subject_widens() {
3719 let ty = resolve_conditional_branch(
3722 &Atomic::TNonEmptyString,
3723 &Type::single(Atomic::TString),
3724 &Type::single(Atomic::TInt),
3725 &Type::single(Atomic::TString),
3726 );
3727 assert!(ty.is_none());
3728 }
3729
3730 #[test]
3731 fn resolve_conditional_template_arg_widens() {
3732 let ty = resolve_conditional_branch(
3735 &Atomic::TString,
3736 &t_param("T"),
3737 &Type::single(Atomic::TInt),
3738 &Type::single(Atomic::TString),
3739 );
3740 assert!(ty.is_none());
3741 }
3742
3743 #[test]
3744 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3745 let ty = resolve_conditional_branch(
3748 &Atomic::TMixed,
3749 &Type::single(Atomic::TBool),
3750 &Type::single(Atomic::TInt),
3751 &Type::single(Atomic::TString),
3752 );
3753 assert!(matches!(
3754 ty.as_ref(),
3755 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3756 ));
3757 }
3758
3759 #[test]
3760 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3761 let ty = resolve_conditional_branch(
3764 &Atomic::TScalar,
3765 &Type::single(Atomic::TNull),
3766 &Type::single(Atomic::TInt),
3767 &Type::single(Atomic::TString),
3768 );
3769 assert!(matches!(
3770 ty.as_ref(),
3771 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3772 ));
3773 }
3774
3775 #[test]
3776 fn resolve_conditional_object_subject_named_arg_then_branch() {
3777 let ty = resolve_conditional_branch(
3779 &Atomic::TObject,
3780 &Type::single(Atomic::TNamedObject {
3781 fqcn: Name::new("Foo"),
3782 type_params: empty_type_params(),
3783 }),
3784 &Type::single(Atomic::TInt),
3785 &Type::single(Atomic::TString),
3786 );
3787 assert!(matches!(
3788 ty.as_ref(),
3789 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3790 ));
3791 }
3792
3793 #[test]
3794 fn resolve_conditional_named_subject_other_class_widens() {
3795 let ty = resolve_conditional_branch(
3799 &Atomic::TNamedObject {
3800 fqcn: Name::new("Foo"),
3801 type_params: empty_type_params(),
3802 },
3803 &Type::single(Atomic::TNamedObject {
3804 fqcn: Name::new("Bar"),
3805 type_params: empty_type_params(),
3806 }),
3807 &Type::single(Atomic::TInt),
3808 &Type::single(Atomic::TString),
3809 );
3810 assert!(ty.is_none());
3811 }
3812
3813 #[test]
3814 fn resolve_conditional_enum_case_subject_widens() {
3815 let ty = resolve_conditional_branch(
3818 &Atomic::TLiteralEnumCase {
3819 enum_fqcn: Name::new("RoundingMode"),
3820 case_name: Name::new("Unnecessary"),
3821 },
3822 &Type::single(Atomic::TInt),
3823 &Type::single(Atomic::TInt),
3824 &Type::single(Atomic::TString),
3825 );
3826 assert!(ty.is_none());
3827 }
3828
3829 #[test]
3830 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3831 let ty = resolve_conditional_branch(
3833 &Atomic::TNull,
3834 &Type::single(Atomic::TBool),
3835 &Type::single(Atomic::TInt),
3836 &Type::single(Atomic::TString),
3837 );
3838 assert!(matches!(
3839 ty.as_ref(),
3840 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3841 ));
3842 }
3843
3844 #[test]
3845 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3846 let ty = resolve_conditional_branch(
3849 &Atomic::TBool,
3850 &Type::single(Atomic::TTrue),
3851 &Type::single(Atomic::TInt),
3852 &Type::single(Atomic::TString),
3853 );
3854 assert!(matches!(
3855 ty.as_ref(),
3856 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3857 ));
3858 }
3859
3860 #[test]
3861 fn substitute_intersection_parts() {
3862 let ty = Type::single(Atomic::TIntersection {
3863 parts: vec_to_type_params(vec![
3864 Type::single(Atomic::TNamedObject {
3865 fqcn: Name::new("Countable"),
3866 type_params: empty_type_params(),
3867 }),
3868 t_param("T"),
3869 ]),
3870 });
3871 let result = ty.substitute_templates(&bindings_t_string());
3872 let Atomic::TIntersection { parts } = &result.types[0] else {
3873 panic!("expected TIntersection");
3874 };
3875 assert_eq!(parts.len(), 2);
3876 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3877 assert!(matches!(parts[1].types[0], Atomic::TString));
3878 }
3879
3880 fn shape(props: &[(&str, Atomic, bool)], is_open: bool) -> Atomic {
3881 Atomic::TKeyedArray {
3882 properties: Box::new(
3883 props
3884 .iter()
3885 .map(|(k, ty, optional)| {
3886 (
3887 ArrayKey::String(Arc::from(*k)),
3888 KeyedProperty {
3889 ty: Type::single(ty.clone()),
3890 optional: *optional,
3891 },
3892 )
3893 })
3894 .collect(),
3895 ),
3896 is_open,
3897 is_list: false,
3898 }
3899 }
3900
3901 fn shape_with_bits(bits: u32, is_open: bool) -> Atomic {
3903 let names: Vec<String> = (0..5).map(|i| format!("k{i}")).collect();
3904 let mut props = vec![("a", Atomic::TInt, false)];
3905 props.extend(
3906 (0..5)
3907 .filter(|i| bits & (1 << i) != 0)
3908 .map(|i| (names[i].as_str(), Atomic::TString, false)),
3909 );
3910 shape(&props, is_open)
3911 }
3912
3913 fn keyed_count(ty: &Type) -> usize {
3914 ty.types
3915 .iter()
3916 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
3917 .count()
3918 }
3919
3920 #[test]
3921 fn add_type_keeps_few_shapes_apart() {
3922 let mut ty = Type::empty();
3923 for bits in 0..MAX_KEYED_SHAPES as u32 {
3924 ty.add_type(shape_with_bits(bits, false));
3925 }
3926 assert_eq!(keyed_count(&ty), MAX_KEYED_SHAPES);
3927 }
3928
3929 #[test]
3930 fn add_type_folds_shapes_past_the_limit() {
3931 let mut ty = Type::empty();
3932 for bits in 0..32 {
3933 ty.add_type(shape_with_bits(bits, false));
3934 }
3935 assert!(keyed_count(&ty) <= MAX_KEYED_SHAPES);
3936 for t in &ty.types {
3938 let Atomic::TKeyedArray { properties, .. } = t else {
3939 continue;
3940 };
3941 assert!(!properties[&ArrayKey::String(Arc::from("a"))].optional);
3942 }
3943 for i in 0..5 {
3944 let key = ArrayKey::String(Arc::from(format!("k{i}").as_str()));
3945 assert!(ty.types.iter().any(|t| matches!(
3946 t,
3947 Atomic::TKeyedArray { properties, .. } if properties.contains_key(&key)
3948 )));
3949 }
3950 }
3951
3952 #[test]
3953 fn combine_keeps_emptiness_and_open_shape_soundness() {
3954 assert!(combine_keyed_arrays(
3956 &shape(&[], false),
3957 &shape(&[("a", Atomic::TInt, false)], false)
3958 )
3959 .is_none());
3960 assert!(combine_keyed_arrays(
3962 &shape(&[("a", Atomic::TInt, false)], true),
3963 &shape(&[("a", Atomic::TInt, false)], false)
3964 )
3965 .is_none());
3966 assert_eq!(
3968 combine_keyed_arrays(
3969 &shape(&[("a", Atomic::TInt, false)], true),
3970 &shape(
3971 &[("a", Atomic::TInt, false), ("k", Atomic::TString, false)],
3972 true
3973 )
3974 ),
3975 Some(shape(&[("a", Atomic::TInt, false)], true))
3976 );
3977 }
3978
3979 #[test]
3980 fn substitute_no_template_params_identity() {
3981 let ty = Type::single(Atomic::TInt);
3982 let result = ty.substitute_templates(&bindings_t_string());
3983 assert!(matches!(result.types[0], Atomic::TInt));
3984 }
3985}