1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::{ArrayKey, Atomic, KeyedProperties, KeyedProperty};
7use crate::symbol::Name;
8
9pub fn empty_type_params() -> Arc<[Type]> {
13 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
14 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
15}
16
17pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
20 if v.is_empty() {
21 empty_type_params()
22 } else {
23 Arc::from(v)
24 }
25}
26
27pub type AtomicVec = SmallVec<[Atomic; 2]>;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum CloneValidity {
33 Cloneable,
35 Invalid,
37 PossiblyInvalid,
39 Unknown,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub struct Type {
49 pub types: AtomicVec,
50 pub possibly_undefined: bool,
52 pub from_docblock: bool,
54 pub falsy_stripped: bool,
62 pub possibly_absent_offset: bool,
73}
74
75type IntBounds = (Option<i64>, Option<i64>);
76
77fn int_bounds(a: &Atomic) -> Option<IntBounds> {
79 match a {
80 Atomic::TInt => Some((None, None)),
81 Atomic::TLiteralInt(n) => Some((Some(*n), Some(*n))),
82 Atomic::TPositiveInt => Some((Some(1), None)),
83 Atomic::TNonNegativeInt => Some((Some(0), None)),
84 Atomic::TNegativeInt => Some((None, Some(-1))),
85 Atomic::TIntRange { min, max } => Some((*min, *max)),
86 _ => None,
87 }
88}
89
90fn bounds_contain(outer: IntBounds, inner: IntBounds) -> bool {
91 let lo_ok = match (outer.0, inner.0) {
92 (None, _) => true,
93 (Some(o), Some(i)) => o <= i,
94 (Some(_), None) => false,
95 };
96 let hi_ok = match (outer.1, inner.1) {
97 (None, _) => true,
98 (Some(o), Some(i)) => o >= i,
99 (Some(_), None) => false,
100 };
101 lo_ok && hi_ok
102}
103
104const MAX_KEYED_SHAPES: usize = 8;
111
112fn combine_keyed_arrays(a: &Atomic, b: &Atomic) -> Option<Atomic> {
117 let (
118 Atomic::TKeyedArray {
119 properties: pa,
120 is_open: oa,
121 is_list: la,
122 },
123 Atomic::TKeyedArray {
124 properties: pb,
125 is_open: ob,
126 is_list: lb,
127 },
128 ) = (a, b)
129 else {
130 return None;
131 };
132 let non_empty = |p: &KeyedProperties| p.values().any(|v| !v.optional);
133 if oa != ob || la != lb || non_empty(pa) != non_empty(pb) {
134 return None;
135 }
136 let mut properties =
137 KeyedProperties::with_capacity_and_hasher(pa.len().max(pb.len()), Default::default());
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: Arc::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 if self.types.contains(&atomic) {
411 return;
412 }
413 self.add_absent_type(atomic);
414 }
415
416 fn add_absent_type(&mut self, atomic: Atomic) {
418 let mut atomic = atomic;
421 if matches!(atomic, Atomic::TKeyedArray { .. })
422 && self
423 .types
424 .iter()
425 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
426 .count()
427 >= MAX_KEYED_SHAPES
428 {
429 if let Some((idx, combined)) = self
430 .types
431 .iter()
432 .enumerate()
433 .find_map(|(i, t)| combine_keyed_arrays(t, &atomic).map(|c| (i, c)))
434 {
435 self.types.remove(idx);
436 if self.types.contains(&combined) {
437 return;
438 }
439 atomic = combined;
440 }
441 }
442
443 if let Some(new_bounds) = int_bounds(&atomic) {
446 if self
447 .types
448 .iter()
449 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
450 {
451 return;
452 }
453 self.types
454 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
455 }
456 if let Atomic::TLiteralString(_) = &atomic {
458 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
459 return;
460 }
461 }
462 if matches!(atomic, Atomic::TNonEmptyString)
464 && self.types.iter().any(|t| matches!(t, Atomic::TString))
465 {
466 return;
467 }
468 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
470 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
471 {
472 return;
473 }
474 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
477 {
478 self.types.retain(|t| !matches!(t, Atomic::TFalse));
479 self.types.push(Atomic::TBool);
480 return;
481 }
482 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
483 {
484 self.types.retain(|t| !matches!(t, Atomic::TTrue));
485 self.types.push(Atomic::TBool);
486 return;
487 }
488 if matches!(atomic, Atomic::TString) {
490 self.types
491 .retain(|t| !matches!(t, Atomic::TLiteralString(_) | Atomic::TNonEmptyString));
492 }
493 if matches!(atomic, Atomic::TBool) {
495 self.types
496 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
497 }
498
499 if matches!(atomic, Atomic::TNever) {
501 if !self.types.is_empty() {
502 return;
503 }
504 } else {
505 self.types.retain(|t| !matches!(t, Atomic::TNever));
506 }
507
508 if let Atomic::TKeyedArray {
514 properties,
515 is_open,
516 ..
517 } = &atomic
518 {
519 if properties.is_empty() && !is_open {
520 for existing in &self.types {
521 match existing {
522 Atomic::TArray { .. }
523 | Atomic::TNonEmptyArray { .. }
524 | Atomic::TList { .. }
525 | Atomic::TNonEmptyList { .. } => {
526 return; }
528 _ => {}
529 }
530 }
531 }
532 }
533
534 let is_generic_array_or_list = matches!(
537 &atomic,
538 Atomic::TArray { .. }
539 | Atomic::TNonEmptyArray { .. }
540 | Atomic::TList { .. }
541 | Atomic::TNonEmptyList { .. }
542 );
543 if is_generic_array_or_list {
544 self.types.retain(|t| {
545 if let Atomic::TKeyedArray {
546 properties,
547 is_open,
548 ..
549 } = t
550 {
551 !properties.is_empty() || *is_open
552 } else {
553 true
554 }
555 });
556 }
557
558 self.types.push(atomic);
559 }
560
561 pub fn remove_null(&self) -> Type {
565 self.filter(|t| !matches!(t, Atomic::TNull))
566 }
567
568 pub fn remove_false(&self) -> Type {
571 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
572 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
573 result.add_type(Atomic::TTrue);
574 }
575 result
576 }
577
578 pub fn remove_true(&self) -> Type {
581 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
582 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
583 result.add_type(Atomic::TFalse);
584 }
585 result
586 }
587
588 pub fn core_type(&self) -> Type {
590 self.remove_null().remove_false()
591 }
592
593 pub fn narrow_to_truthy(&self) -> Type {
595 if self.is_mixed_not_template() {
596 return Type::mixed();
597 }
598 let mut result = Type::empty();
599 result.from_docblock = self.from_docblock;
600 for t in &self.types {
601 match t {
602 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
606 Atomic::TLiteralInt(0)
608 | Atomic::TLiteralFloat(0, 0)
609 | Atomic::TNull
610 | Atomic::TFalse => {}
611 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
612 Atomic::TBool => result.add_type(Atomic::TTrue),
614 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
616 key: key.clone(),
617 value: value.clone(),
618 }),
619 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
620 value: value.clone(),
621 }),
622 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
626 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
631 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
632 let atom = if max.is_none() {
633 Atomic::TPositiveInt
634 } else {
635 Atomic::TIntRange {
636 min: Some(1),
637 max: *max,
638 }
639 };
640 result.add_type(atom);
641 }
642 Atomic::TIntRange { min, max: Some(0) } => {
644 let atom = match min {
645 None => Atomic::TNegativeInt,
646 Some(n) if *n <= -1 => Atomic::TIntRange {
647 min: *min,
648 max: Some(-1),
649 },
650 _ => continue, };
652 result.add_type(atom);
653 }
654 t if !t.can_be_truthy() => {}
656 _ => result.add_type(t.clone()),
657 }
658 }
659 result
660 }
661
662 pub fn narrow_to_falsy(&self) -> Type {
664 if self.is_mixed_not_template() {
665 return Type::from_vec(vec![
666 Atomic::TNull,
667 Atomic::TFalse,
668 Atomic::TLiteralInt(0),
669 Atomic::TLiteralString("".into()),
670 ]);
671 }
672 let mut result = Type::empty();
673 result.from_docblock = self.from_docblock;
674 for t in &self.types {
675 match t {
676 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
681 Atomic::TBool => result.add_type(Atomic::TFalse),
683 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
685 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
687 Atomic::TString => {
689 result.add_type(Atomic::TLiteralString("".into()));
690 result.add_type(Atomic::TLiteralString("0".into()));
691 }
692 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
694 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
696 Atomic::TIntRange {
698 min: Some(0),
699 max: Some(_) | None,
700 } => result.add_type(Atomic::TLiteralInt(0)),
701 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
703 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
705 }
706 }
707 result
708 }
709
710 pub fn narrow_instanceof(&self, class: &str) -> Type {
716 let narrowed_ty = Atomic::TNamedObject {
717 fqcn: class.into(),
718 type_params: empty_type_params(),
719 };
720 let has_object = self.types.iter().any(|t| {
722 matches!(
723 t,
724 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
726 });
727 if has_object || self.is_empty() {
728 Type::single(narrowed_ty)
729 } else {
730 Type::single(narrowed_ty)
733 }
734 }
735
736 pub fn narrow_to_string(&self) -> Type {
740 self.filter_replacing(
741 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
742 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
743 Atomic::TString,
744 )
745 .with_callable_form(self, Atomic::TCallableString)
746 }
747
748 pub fn narrow_to_int(&self) -> Type {
750 self.filter_replacing(
751 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
752 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
753 Atomic::TInt,
754 )
755 }
756
757 pub fn narrow_to_float(&self) -> Type {
759 self.filter_replacing(
760 |t| {
761 matches!(
762 t,
763 Atomic::TFloat
764 | Atomic::TIntegralFloat
765 | Atomic::TLiteralFloat(..)
766 | Atomic::TTemplateParam { .. }
767 )
768 },
769 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
770 Atomic::TFloat,
771 )
772 }
773
774 pub fn narrow_to_bool(&self) -> Type {
776 self.filter_replacing(
777 |t| {
778 matches!(
779 t,
780 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
781 )
782 },
783 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
784 Atomic::TBool,
785 )
786 }
787
788 pub fn narrow_to_null(&self) -> Type {
790 self.filter_replacing(
791 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
792 |t| matches!(t, Atomic::TMixed),
793 Atomic::TNull,
794 )
795 }
796
797 pub fn narrow_to_array(&self) -> Type {
799 self.filter_replacing(
800 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
801 |t| matches!(t, Atomic::TMixed),
802 Atomic::TArray {
803 key: Box::new(Type::mixed()),
804 value: Box::new(Type::mixed()),
805 },
806 )
807 .with_callable_form(self, callable_array())
808 }
809
810 pub fn widen_nested_non_empty(&self) -> Type {
813 fn relax_all(ty: &Type) -> Type {
814 map_atoms(ty, |a| match a {
815 Atomic::TNonEmptyList { value } => Atomic::TList {
816 value: Box::new(relax_all(value)),
817 },
818 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
819 key: key.clone(),
820 value: Box::new(relax_all(value)),
821 },
822 other => map_values(other, relax_all),
823 })
824 }
825 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
826 match atomic {
827 Atomic::TArray { key, value } => Atomic::TArray {
828 key: key.clone(),
829 value: Box::new(f(value)),
830 },
831 Atomic::TList { value } => Atomic::TList {
832 value: Box::new(f(value)),
833 },
834 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
835 key: key.clone(),
836 value: Box::new(f(value)),
837 },
838 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
839 value: Box::new(f(value)),
840 },
841 Atomic::TKeyedArray {
842 properties,
843 is_open,
844 is_list,
845 } => Atomic::TKeyedArray {
846 properties: Arc::new(
847 properties
848 .iter()
849 .map(|(k, p)| {
850 (
851 k.clone(),
852 crate::atomic::KeyedProperty {
853 ty: f(&p.ty),
854 optional: p.optional,
855 },
856 )
857 })
858 .collect(),
859 ),
860 is_open: *is_open,
861 is_list: *is_list,
862 },
863 other => other.clone(),
864 }
865 }
866 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
867 let mut result = Type::empty();
868 result.possibly_undefined = ty.possibly_undefined;
869 result.from_docblock = ty.from_docblock;
870 for atomic in &ty.types {
871 result.add_type(f(atomic));
872 }
873 result
874 }
875 map_atoms(self, |a| map_values(a, relax_all))
876 }
877
878 pub fn narrow_to_non_empty_collection(&self) -> Type {
881 let mut out = Type::empty();
882 out.from_docblock = self.from_docblock;
883 for t in &self.types {
884 match t {
885 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
886 key: key.clone(),
887 value: value.clone(),
888 }),
889 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
890 value: value.clone(),
891 }),
892 Atomic::TKeyedArray {
893 properties,
894 is_open: false,
895 ..
896 } if properties.is_empty() => {}
897 _ => out.add_type(t.clone()),
898 }
899 }
900 out
901 }
902
903 pub fn narrow_to_empty_collection(&self) -> Type {
913 let mut out = Type::empty();
914 out.from_docblock = self.from_docblock;
915 for t in &self.types {
916 match t {
917 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
918 Atomic::TArray { .. } | Atomic::TList { .. } => {
919 out.add_type(Atomic::TKeyedArray {
920 properties: Arc::default(),
921 is_open: false,
922 is_list: true,
923 });
924 }
925 _ => out.add_type(t.clone()),
926 }
927 }
928 out
929 }
930
931 pub fn narrow_to_list(&self) -> Type {
943 let mut out = Type::empty();
944 out.from_docblock = self.from_docblock;
945 for t in &self.types {
946 match t {
947 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
948 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
954 out.add_type(Atomic::TList {
955 value: value.clone(),
956 });
957 }
958 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
959 out.add_type(Atomic::TNonEmptyList {
960 value: value.clone(),
961 });
962 }
963 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
964 Atomic::TMixed => out.add_type(Atomic::TList {
965 value: Box::new(Type::mixed()),
966 }),
967 _ => {}
968 }
969 }
970 if out.is_empty() {
971 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
972 } else {
973 out
974 }
975 }
976
977 pub fn narrow_to_object(&self) -> Type {
982 let mut out = Type::empty();
983 for t in &self.types {
984 if matches!(t, Atomic::TMixed) {
985 out.add_type(Atomic::TObject);
986 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
987 out.add_type(t.clone());
988 }
989 }
990 if out.types.is_empty() {
991 self.filter(|t| t.is_object())
992 .with_callable_form(self, Atomic::TObject)
993 } else {
994 out.with_callable_form(self, Atomic::TObject)
995 }
996 }
997
998 fn with_callable_form(mut self, source: &Type, form: Atomic) -> Type {
1001 if source
1002 .types
1003 .iter()
1004 .any(|t| matches!(t, Atomic::TCallable { .. }))
1005 {
1006 self.add_type(form);
1007 }
1008 self
1009 }
1010
1011 pub fn narrow_to_not_callable(&self) -> Type {
1014 let callable_array = callable_array();
1015 self.filter(|t| !t.is_callable() && *t != callable_array)
1016 }
1017
1018 pub fn narrow_to_callable(&self) -> Type {
1025 let narrowed = self.filter(|t| {
1026 t.is_callable()
1027 || t.is_string()
1028 || t.is_array()
1029 || t.is_object()
1030 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1031 });
1032 let mut result = Type::empty();
1039 result.possibly_undefined = narrowed.possibly_undefined;
1040 result.from_docblock = narrowed.from_docblock;
1041 for atomic in narrowed.types {
1042 if matches!(atomic, Atomic::TObject) {
1043 result.add_type(Atomic::TCallable {
1044 params: None,
1045 return_type: None,
1046 is_pure: false,
1047 });
1048 } else {
1049 result.add_type(atomic);
1050 }
1051 }
1052 result
1053 }
1054
1055 pub fn narrow_to_scalar(&self) -> Type {
1057 self.filter_replacing(
1058 |t| {
1059 t.is_string()
1060 || t.is_int()
1061 || matches!(
1062 t,
1063 Atomic::TFloat
1064 | Atomic::TIntegralFloat
1065 | Atomic::TLiteralFloat(..)
1066 | Atomic::TBool
1067 | Atomic::TTrue
1068 | Atomic::TFalse
1069 | Atomic::TScalar
1070 | Atomic::TNumeric
1071 | Atomic::TNumericString
1072 | Atomic::TTemplateParam { .. }
1073 )
1074 },
1075 |t| matches!(t, Atomic::TMixed),
1076 Atomic::TScalar,
1077 )
1078 }
1079
1080 pub fn narrow_to_iterable(&self) -> Type {
1083 self.filter(|t| {
1084 t.is_array()
1085 || t.is_object()
1086 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1087 })
1088 }
1089
1090 pub fn narrow_to_countable(&self) -> Type {
1093 self.filter(|t| {
1094 t.is_array()
1095 || t.is_object()
1096 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1097 })
1098 }
1099
1100 pub fn narrow_to_resource(&self) -> Type {
1104 self.filter(|t| matches!(t, Atomic::TMixed))
1106 }
1107
1108 pub fn narrow_to_class_string(&self) -> Type {
1113 let mut out = Type::empty();
1114 out.from_docblock = self.from_docblock;
1115 for t in &self.types {
1116 match t {
1117 Atomic::TClassString(_) => out.add_type(t.clone()),
1118 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1119 out.add_type(Atomic::TClassString(None));
1120 }
1121 _ => {}
1122 }
1123 }
1124 out
1125 }
1126
1127 pub fn narrow_to_interface_string(&self) -> Type {
1132 let mut out = Type::empty();
1133 out.from_docblock = self.from_docblock;
1134 for t in &self.types {
1135 match t {
1136 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1137 Atomic::TClassString(name) => {
1141 out.add_type(Atomic::TInterfaceString(*name));
1142 }
1143 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1144 out.add_type(Atomic::TInterfaceString(None));
1145 }
1146 _ => {}
1147 }
1148 }
1149 out
1150 }
1151
1152 pub fn merge(a: &Type, b: &Type) -> Type {
1157 if b.types.is_empty() {
1159 let mut result = a.clone();
1160 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1161 return result;
1162 }
1163 if a.types.is_empty() {
1165 let mut result = b.clone();
1166 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1167 return result;
1168 }
1169 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1171 let mut result = a.clone();
1172 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1173 return result;
1174 }
1175 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1177 return Type {
1178 types: smallvec::smallvec![Atomic::TMixed],
1179 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1180 from_docblock: a.from_docblock || b.from_docblock,
1181 falsy_stripped: false,
1182 possibly_absent_offset: false,
1183 };
1184 }
1185 let mut result = a.clone();
1186 result.merge_with(b);
1187 result
1188 }
1189
1190 pub fn merge_with(&mut self, other: &Type) {
1192 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1193 self.possibly_undefined |= other.possibly_undefined;
1194 return;
1195 }
1196 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1197 self.types.clear();
1198 self.types.push(Atomic::TMixed);
1199 self.possibly_undefined |= other.possibly_undefined;
1200 return;
1201 }
1202 for atomic in &other.types {
1203 if matches!(atomic, Atomic::TConditional { .. }) {
1205 self.add_type(atomic.clone());
1206 } else if !self.types.contains(atomic)
1207 && !self.types.iter().any(|t| matches!(t, Atomic::TMixed))
1208 {
1209 self.add_absent_type(atomic.clone());
1210 }
1211 }
1212 self.possibly_undefined |= other.possibly_undefined;
1213 }
1214
1215 pub fn intersect_with(&self, other: &Type) -> Type {
1219 if self.is_mixed() {
1220 return other.clone();
1221 }
1222 if other.is_mixed() {
1223 return self.clone();
1224 }
1225 let mut result = Type::empty();
1233 for a in &self.types {
1234 for b in &other.types {
1235 if a == b {
1236 result.add_type(a.clone());
1237 } else if atomic_subtype(b, a) {
1238 result.add_type(b.clone());
1239 } else if atomic_subtype(a, b) {
1240 result.add_type(a.clone());
1241 }
1242 }
1243 }
1244 if result.is_empty() {
1245 Type::never()
1246 } else {
1247 result
1248 }
1249 }
1250
1251 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1255 if bindings.is_empty() {
1256 return self.clone();
1257 }
1258 if !self.types.iter().any(atomic_may_contain_templates) {
1261 return self.clone();
1262 }
1263 let mut result = Type::empty();
1264 result.possibly_undefined = self.possibly_undefined;
1265 result.from_docblock = self.from_docblock;
1266 for atomic in &self.types {
1267 match atomic {
1268 Atomic::TTemplateParam { name, .. } => {
1269 if let Some(resolved) = bindings.get(name) {
1270 for t in &resolved.types {
1271 result.add_type(t.clone());
1272 }
1273 } else {
1274 result.add_type(atomic.clone());
1275 }
1276 }
1277 Atomic::TArray { key, value } => {
1278 result.add_type(Atomic::TArray {
1279 key: Box::new(key.substitute_templates(bindings)),
1280 value: Box::new(value.substitute_templates(bindings)),
1281 });
1282 }
1283 Atomic::TList { value } => {
1284 result.add_type(Atomic::TList {
1285 value: Box::new(value.substitute_templates(bindings)),
1286 });
1287 }
1288 Atomic::TNonEmptyArray { key, value } => {
1289 result.add_type(Atomic::TNonEmptyArray {
1290 key: Box::new(key.substitute_templates(bindings)),
1291 value: Box::new(value.substitute_templates(bindings)),
1292 });
1293 }
1294 Atomic::TNonEmptyList { value } => {
1295 result.add_type(Atomic::TNonEmptyList {
1296 value: Box::new(value.substitute_templates(bindings)),
1297 });
1298 }
1299 Atomic::TKeyedArray {
1300 properties,
1301 is_open,
1302 is_list,
1303 } => {
1304 use crate::atomic::KeyedProperty;
1305 let new_props = properties
1306 .iter()
1307 .map(|(k, prop)| {
1308 (
1309 k.clone(),
1310 KeyedProperty {
1311 ty: prop.ty.substitute_templates(bindings),
1312 optional: prop.optional,
1313 },
1314 )
1315 })
1316 .collect();
1317 result.add_type(Atomic::TKeyedArray {
1318 properties: Arc::new(new_props),
1319 is_open: *is_open,
1320 is_list: *is_list,
1321 });
1322 }
1323 Atomic::TCallable {
1324 params,
1325 return_type,
1326 is_pure,
1327 } => {
1328 result.add_type(Atomic::TCallable {
1329 params: params.as_ref().map(|ps| {
1330 Box::new(
1331 ps.iter()
1332 .map(|p| substitute_in_fn_param(p, bindings))
1333 .collect(),
1334 )
1335 }),
1336 return_type: return_type
1337 .as_ref()
1338 .map(|r| Box::new(r.substitute_templates(bindings))),
1339 is_pure: *is_pure,
1340 });
1341 }
1342 Atomic::TClosure { data } => {
1343 result.add_type(Atomic::TClosure {
1344 data: Box::new(crate::atomic::ClosureData {
1345 params: data
1346 .params
1347 .iter()
1348 .map(|p| substitute_in_fn_param(p, bindings))
1349 .collect(),
1350 return_type: data.return_type.substitute_templates(bindings),
1351 this_type: data
1352 .this_type
1353 .as_ref()
1354 .map(|t| t.substitute_templates(bindings)),
1355 is_pure: data.is_pure,
1356 is_static: data.is_static,
1357 rejects_surplus_args: data.rejects_surplus_args,
1358 }),
1359 });
1360 }
1361 Atomic::TConditional { data } => {
1362 let param_name = &data.param_name;
1363 let new_subject = data.subject.substitute_templates(bindings);
1364 let new_if_true = data.if_true.substitute_templates(bindings);
1365 let new_if_false = data.if_false.substitute_templates(bindings);
1366
1367 let resolved = if let Some(name) = param_name {
1371 if let Some(bound) = bindings.get(name) {
1372 if new_subject.types.len() == 1 {
1373 resolve_conditional_branch(
1374 &new_subject.types[0],
1375 bound,
1376 &new_if_true,
1377 &new_if_false,
1378 )
1379 } else {
1380 None
1381 }
1382 } else {
1383 None
1384 }
1385 } else {
1386 None
1387 };
1388
1389 if let Some(branch) = resolved {
1390 for t in branch.types {
1391 result.add_type(t);
1392 }
1393 } else {
1394 result.add_type(Atomic::TConditional {
1395 data: Box::new(crate::atomic::ConditionalData {
1396 param_name: *param_name,
1397 subject: new_subject,
1398 if_true: new_if_true,
1399 if_false: new_if_false,
1400 }),
1401 });
1402 }
1403 }
1404 Atomic::TKeyOf { target } => {
1405 let new_target = target.substitute_templates(bindings);
1406 if let Some(resolved) = eval_key_of_type(&new_target) {
1407 result.merge_with(&resolved);
1408 } else {
1409 result.add_type(Atomic::TKeyOf {
1410 target: Box::new(new_target),
1411 });
1412 }
1413 }
1414 Atomic::TValueOf { target } => {
1415 let new_target = target.substitute_templates(bindings);
1416 if let Some(resolved) = eval_value_of_type(&new_target) {
1417 result.merge_with(&resolved);
1418 } else {
1419 result.add_type(Atomic::TValueOf {
1420 target: Box::new(new_target),
1421 });
1422 }
1423 }
1424 Atomic::TIntersection { parts } => {
1425 result.add_type(Atomic::TIntersection {
1426 parts: vec_to_type_params(
1427 parts
1428 .iter()
1429 .map(|p| p.substitute_templates(bindings))
1430 .collect(),
1431 ),
1432 });
1433 }
1434 Atomic::TNamedObject { fqcn, type_params } => {
1435 if type_params.is_empty() && !fqcn.contains('\\') {
1437 if let Some(resolved) = bindings.get(fqcn) {
1438 for t in &resolved.types {
1439 result.add_type(t.clone());
1440 }
1441 continue;
1442 }
1443 }
1444 let new_params: Vec<Type> = type_params
1445 .iter()
1446 .map(|p| p.substitute_templates(bindings))
1447 .collect();
1448 result.add_type(Atomic::TNamedObject {
1449 fqcn: *fqcn,
1450 type_params: vec_to_type_params(new_params),
1451 });
1452 }
1453 Atomic::TClassString(Some(param_name)) => {
1455 if let Some(resolved) = bindings.get(param_name) {
1456 for r_atomic in &resolved.types {
1457 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1458 Some(*fqcn)
1459 } else {
1460 None
1461 };
1462 result.add_type(Atomic::TClassString(cls_name));
1463 }
1464 } else {
1465 result.add_type(atomic.clone());
1466 }
1467 }
1468 Atomic::TInterfaceString(Some(param_name)) => {
1470 if let Some(resolved) = bindings.get(param_name) {
1471 for r_atomic in &resolved.types {
1472 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1473 Some(*fqcn)
1474 } else {
1475 None
1476 };
1477 result.add_type(Atomic::TInterfaceString(iface_name));
1478 }
1479 } else {
1480 result.add_type(atomic.clone());
1481 }
1482 }
1483 _ => {
1484 result.add_type(atomic.clone());
1485 }
1486 }
1487 }
1488 result
1489 }
1490
1491 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1497 where
1498 F: Fn(&str) -> Option<Type>,
1499 {
1500 self.resolve_conditional_inner(&lookup)
1501 }
1502
1503 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1504 where
1505 F: Fn(&str) -> Option<Type>,
1506 {
1507 let mut result = Type::empty();
1508 for atomic in self.types {
1509 match atomic {
1510 Atomic::TConditional { ref data } => {
1511 let (param_name, subject, if_true, if_false) = (
1512 &data.param_name,
1513 &data.subject,
1514 &data.if_true,
1515 &data.if_false,
1516 );
1517 let resolved = if subject.types.len() == 1 {
1518 if let Some(name) = param_name {
1519 if let Some(arg_ty) = lookup(name.as_ref()) {
1520 resolve_conditional_branch(
1521 &subject.types[0],
1522 &arg_ty,
1523 if_true,
1524 if_false,
1525 )
1526 } else {
1527 None
1528 }
1529 } else {
1530 None
1531 }
1532 } else {
1533 None
1534 };
1535
1536 if let Some(branch) = resolved {
1537 for t in branch.resolve_conditional_inner(lookup).types {
1539 result.add_type(t);
1540 }
1541 } else {
1542 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1545 result.add_type(t);
1546 }
1547 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1548 result.add_type(t);
1549 }
1550 }
1551 }
1552 other => result.add_type(other),
1553 }
1554 }
1555 result
1556 }
1557
1558 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1568 if other.is_mixed() {
1569 return true;
1570 }
1571 if self.is_never() {
1572 return true; }
1574 self.types.iter().all(|a| {
1575 let fits = |a: &Atomic| other.types.iter().any(|b| atomic_subtype(a, b));
1576 fits(a) || composite_parts(a).is_some_and(|parts| parts.iter().all(fits))
1577 })
1578 }
1579
1580 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1583 self.is_subtype_structural(other)
1584 || self.types.iter().all(|a| match a {
1585 Atomic::TTemplateParam { as_type, .. } => {
1586 as_type.is_subtype_by_template_bounds(other)
1587 }
1588 _ => other.accepts_atomic_structural(a),
1589 })
1590 }
1591
1592 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1596 if self.is_mixed() {
1597 return true;
1598 }
1599 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1600 }
1601
1602 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1605 let mut result = Type::empty();
1606 result.possibly_undefined = self.possibly_undefined;
1607 result.from_docblock = self.from_docblock;
1608 for atomic in &self.types {
1609 if f(atomic) {
1610 result.types.push(atomic.clone());
1611 }
1612 }
1613 result
1614 }
1615
1616 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1621 &self,
1622 keep: K,
1623 placeholder: P,
1624 replacement: Atomic,
1625 ) -> Type {
1626 let mut result = Type::empty();
1627 result.possibly_undefined = self.possibly_undefined;
1628 result.from_docblock = self.from_docblock;
1629 for atomic in &self.types {
1630 if keep(atomic) {
1631 result.add_type(atomic.clone());
1632 } else if placeholder(atomic) {
1633 result.add_type(replacement.clone());
1634 }
1635 }
1636 result
1637 }
1638
1639 pub fn possibly_undefined(mut self) -> Self {
1641 self.possibly_undefined = true;
1642 self
1643 }
1644
1645 pub fn from_docblock(mut self) -> Self {
1647 self.from_docblock = true;
1648 self
1649 }
1650
1651 pub fn falsy_stripped(mut self) -> Self {
1654 self.falsy_stripped = true;
1655 self
1656 }
1657
1658 pub fn possibly_absent_offset(mut self) -> Self {
1662 self.possibly_absent_offset = true;
1663 self
1664 }
1665}
1666
1667#[derive(Clone, PartialEq, Eq, Debug)]
1681enum ValueClass {
1682 Int,
1683 Float,
1684 True,
1685 False,
1686 String,
1687 Array,
1688 List,
1690 Object,
1691 Null,
1692 Top,
1695 LitInt(i64),
1698 LitFloat(i64, i64),
1700 LitString(Arc<str>),
1702}
1703
1704impl ValueClass {
1705 fn includes(&self, b: &Self) -> bool {
1710 self == b
1711 || matches!(
1712 (self, b),
1713 (Self::Top, _)
1714 | (Self::Int, Self::LitInt(_))
1715 | (Self::Float, Self::LitFloat(..))
1716 | (Self::String, Self::LitString(_))
1717 | (Self::Array, Self::List)
1718 )
1719 }
1720}
1721
1722fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1728 use ValueClass::*;
1729 match a {
1730 Atomic::TInt
1733 | Atomic::TIntRange { .. }
1734 | Atomic::TPositiveInt
1735 | Atomic::TNegativeInt
1736 | Atomic::TNonNegativeInt => vec![Int],
1737 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1738 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1740 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1741 vec![LitFloat(*int_bits, *frac_bits)]
1742 }
1743 Atomic::TBool => vec![True, False],
1745 Atomic::TTrue => vec![True],
1746 Atomic::TFalse => vec![False],
1747 Atomic::TString
1749 | Atomic::TNonEmptyString
1750 | Atomic::TNumericString
1751 | Atomic::TClassString(_)
1752 | Atomic::TInterfaceString(_)
1753 | Atomic::TEnumString
1754 | Atomic::TTraitString
1755 | Atomic::TCallableString => vec![String],
1756 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1757 Atomic::TArray { .. }
1759 | Atomic::TNonEmptyArray { .. }
1760 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1761 Atomic::TList { .. }
1762 | Atomic::TNonEmptyList { .. }
1763 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1764 Atomic::TObject
1766 | Atomic::TNamedObject { .. }
1767 | Atomic::TStaticObject { .. }
1768 | Atomic::TSelf { .. }
1769 | Atomic::TParent { .. }
1770 | Atomic::TClosure { .. }
1771 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1772 Atomic::TNull => vec![Null],
1775 Atomic::TVoid => vec![Top],
1776 Atomic::TMixed => vec![Top],
1778 Atomic::TScalar => vec![Int, Float, True, False, String],
1779 Atomic::TNumeric => vec![Int, Float],
1782 Atomic::TCallable { .. }
1784 | Atomic::TNever
1785 | Atomic::TTemplateParam { .. }
1786 | Atomic::TKeyOf { .. }
1787 | Atomic::TValueOf { .. }
1788 | Atomic::TConditional { .. }
1789 | Atomic::TIntersection { .. } => Vec::new(),
1790 }
1791}
1792
1793fn subject_is_decidable(subject: &Atomic) -> bool {
1801 matches!(
1802 subject,
1803 Atomic::TNull
1804 | Atomic::TTrue
1805 | Atomic::TFalse
1806 | Atomic::TBool
1807 | Atomic::TString
1808 | Atomic::TInt
1809 | Atomic::TFloat
1810 | Atomic::TLiteralString(_)
1811 | Atomic::TLiteralInt(_)
1812 | Atomic::TLiteralFloat(..)
1813 | Atomic::TArray { .. }
1814 | Atomic::TList { .. }
1815 | Atomic::TObject
1816 | Atomic::TMixed
1817 | Atomic::TScalar
1818 )
1819}
1820
1821fn resolve_conditional_branch(
1843 subject: &Atomic,
1844 arg_ty: &Type,
1845 if_true: &Type,
1846 if_false: &Type,
1847) -> Option<Type> {
1848 if !subject_is_decidable(subject) {
1849 return None;
1850 }
1851 if arg_ty.types.is_empty() {
1852 return None;
1853 }
1854 let subject_classes = value_classes(subject);
1855 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1856 if arg_classes.is_empty() {
1857 return None;
1859 }
1860 let all_match = arg_classes
1861 .iter()
1862 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1863 let any_overlap = arg_classes.iter().any(|c| {
1864 subject_classes
1865 .iter()
1866 .any(|s| s.includes(c) || c.includes(s))
1867 });
1868 if all_match {
1869 Some(if_true.clone())
1870 } else if !any_overlap {
1871 Some(if_false.clone())
1872 } else {
1873 None
1874 }
1875}
1876
1877fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1885 match atomic {
1886 Atomic::TNamedObject { fqcn, type_params } => {
1890 !type_params.is_empty() || !fqcn.contains('\\')
1891 }
1892 Atomic::TTemplateParam { .. }
1893 | Atomic::TKeyOf { .. }
1894 | Atomic::TValueOf { .. }
1895 | Atomic::TArray { .. }
1896 | Atomic::TList { .. }
1897 | Atomic::TNonEmptyArray { .. }
1898 | Atomic::TNonEmptyList { .. }
1899 | Atomic::TKeyedArray { .. }
1900 | Atomic::TCallable { .. }
1901 | Atomic::TClosure { .. }
1902 | Atomic::TConditional { .. }
1903 | Atomic::TIntersection { .. }
1904 | Atomic::TClassString(Some(_))
1905 | Atomic::TInterfaceString(Some(_)) => true,
1906 _ => false,
1907 }
1908}
1909
1910pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1911 let mut result = Type::empty();
1912 for atomic in &t.types {
1913 match atomic {
1914 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1915 for k in &key.types {
1916 result.add_type(k.clone());
1917 }
1918 }
1919 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1920 result.add_type(Atomic::TInt);
1921 }
1922 Atomic::TKeyedArray { properties, .. } => {
1923 for key in properties.keys() {
1924 match key {
1925 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1926 crate::atomic::ArrayKey::String(s) => {
1927 result.add_type(Atomic::TLiteralString(s.clone()))
1928 }
1929 }
1930 }
1931 }
1932 _ => return None,
1933 }
1934 }
1935 (!result.types.is_empty()).then_some(result)
1936}
1937
1938pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1939 let mut result = Type::empty();
1940 for atomic in &t.types {
1941 match atomic {
1942 Atomic::TArray { value, .. }
1943 | Atomic::TNonEmptyArray { value, .. }
1944 | Atomic::TList { value }
1945 | Atomic::TNonEmptyList { value } => {
1946 result.merge_with(value);
1947 }
1948 Atomic::TKeyedArray { properties, .. } => {
1949 for prop in properties.values() {
1950 result.merge_with(&prop.ty);
1951 }
1952 }
1953 _ => return None,
1954 }
1955 }
1956 (!result.types.is_empty()).then_some(result)
1957}
1958
1959fn substitute_in_fn_param(
1960 p: &crate::atomic::FnParam,
1961 bindings: &FxHashMap<Name, Type>,
1962) -> crate::atomic::FnParam {
1963 crate::atomic::FnParam {
1964 name: p.name,
1965 ty: p.ty.as_ref().map(|t| {
1966 let u = t.to_union();
1967 let substituted = u.substitute_templates(bindings);
1968 crate::compact::SimpleType::from_union(substituted)
1969 }),
1970 out_ty: p.out_ty.as_ref().map(|t| {
1971 let u = t.to_union();
1972 let substituted = u.substitute_templates(bindings);
1973 crate::compact::SimpleType::from_union(substituted)
1974 }),
1975 default: p.default.as_ref().map(|d| {
1976 let u = d.to_union();
1977 let substituted = u.substitute_templates(bindings);
1978 crate::compact::SimpleType::from_union(substituted)
1979 }),
1980 is_variadic: p.is_variadic,
1981 is_byref: p.is_byref,
1982 is_optional: p.is_optional,
1983 }
1984}
1985
1986fn composite_parts(atomic: &Atomic) -> Option<&'static [Atomic]> {
1993 match atomic {
1994 Atomic::TScalar => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TString, Atomic::TBool]),
1995 Atomic::TNumeric => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TNumericString]),
1996 _ => None,
1997 }
1998}
1999
2000pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
2002 if sub == sup {
2003 return true;
2004 }
2005 match (sub, sup) {
2006 (Atomic::TNever, _) => true,
2008 (_, Atomic::TMixed) => true,
2010 (Atomic::TMixed, _) => true,
2011 (_, Atomic::TTemplateParam { as_type, .. }) => {
2016 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
2017 }
2018
2019 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
2021 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
2022 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
2023 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
2024 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
2025 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
2026 (Atomic::TPositiveInt, Atomic::TInt) => true,
2027 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
2028 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
2029 (Atomic::TPositiveInt, Atomic::TScalar) => true,
2030 (Atomic::TNegativeInt, Atomic::TInt) => true,
2031 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
2032 (Atomic::TNegativeInt, Atomic::TScalar) => true,
2033 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
2034 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
2035 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
2036 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
2037 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
2038 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
2039 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
2041 max.is_none() && min.is_none_or(|m| m <= 1)
2042 }
2043 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
2045 min.is_none() && max.is_none_or(|m| m >= -1)
2046 }
2047 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
2049 max.is_none() && min.is_none_or(|m| m <= 0)
2050 }
2051 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
2053 sub_min.is_some_and(|lo| lo >= 1)
2054 }
2055 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
2056 sub_min.is_some_and(|lo| lo >= 0)
2057 }
2058 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
2059 sub_max.is_some_and(|hi| hi <= -1)
2060 }
2061 (
2063 Atomic::TIntRange {
2064 min: sub_min,
2065 max: sub_max,
2066 },
2067 Atomic::TIntRange {
2068 min: sup_min,
2069 max: sup_max,
2070 },
2071 ) => {
2072 let lower_ok = match (sub_min, sup_min) {
2073 (_, None) => true,
2074 (None, Some(_)) => false,
2075 (Some(sl), Some(su)) => sl >= su,
2076 };
2077 let upper_ok = match (sub_max, sup_max) {
2078 (None, None) | (Some(_), None) => true,
2079 (None, Some(_)) => false,
2080 (Some(sl), Some(su)) => sl <= su,
2081 };
2082 lower_ok && upper_ok
2083 }
2084
2085 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
2086 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
2087 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
2088
2089 (Atomic::TLiteralString(s), Atomic::TString) => {
2090 let _ = s;
2091 true
2092 }
2093 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
2094 let _ = s;
2095 true
2096 }
2097 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
2098 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
2099 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
2102 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
2105 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
2106 (Atomic::TNonEmptyString, Atomic::TString) => true,
2107 (Atomic::TCallableString, Atomic::TString) => true,
2108 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
2110 (Atomic::TNumericString, Atomic::TString) => true,
2111 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
2115 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
2116 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
2117 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
2118 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
2119 (Atomic::TClassString(_), Atomic::TString) => true,
2120 (Atomic::TInterfaceString(_), Atomic::TString) => true,
2121 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
2126 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
2127 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
2128 (Atomic::TEnumString, Atomic::TString) => true,
2129 (Atomic::TTraitString, Atomic::TString) => true,
2130
2131 (Atomic::TTrue, Atomic::TBool) => true,
2132 (Atomic::TFalse, Atomic::TBool) => true,
2133
2134 (Atomic::TInt, Atomic::TNumeric) => true,
2135 (Atomic::TFloat, Atomic::TNumeric) => true,
2136 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
2137 (Atomic::TNumericString, Atomic::TNumeric) => true,
2138
2139 (Atomic::TInt, Atomic::TScalar) => true,
2140 (Atomic::TFloat, Atomic::TScalar) => true,
2141 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
2142 (Atomic::TString, Atomic::TScalar) => true,
2143 (Atomic::TBool, Atomic::TScalar) => true,
2144 (Atomic::TNumeric, Atomic::TScalar) => true,
2145 (Atomic::TTrue, Atomic::TScalar) => true,
2146 (Atomic::TFalse, Atomic::TScalar) => true,
2147 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
2151 (Atomic::TNumericString, Atomic::TScalar) => true,
2152 (Atomic::TCallableString, Atomic::TScalar) => true,
2153 (Atomic::TClassString(_), Atomic::TScalar) => true,
2154 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
2155 (Atomic::TEnumString, Atomic::TScalar) => true,
2156 (Atomic::TTraitString, Atomic::TScalar) => true,
2157
2158 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2160 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2161 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2162 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2164 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2166 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2167 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2169 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2170 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2173 enum_fqcn == fqcn
2174 }
2175 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2176 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2177 enum_fqcn == fqcn
2178 }
2179 (
2183 Atomic::TNamedObject {
2184 fqcn: sub_fqcn,
2185 type_params: sub_params,
2186 },
2187 Atomic::TNamedObject {
2188 fqcn: sup_fqcn,
2189 type_params: sup_params,
2190 },
2191 ) => {
2192 sub_fqcn == sup_fqcn
2193 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2194 }
2195
2196 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2198
2199 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2201 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2202 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2203 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2204 (Atomic::TInt, Atomic::TFloat) => true,
2205 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2206
2207 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2209 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2210 }
2211
2212 (Atomic::TString, Atomic::TCallable { .. }) => true,
2214 (Atomic::TCallableString, Atomic::TCallable { .. }) => true,
2215 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2216 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2217 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2218 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2219 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2220
2221 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2223 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2225 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2235 fn has_nominal_type(t: &Type) -> bool {
2236 t.types.iter().any(|a| {
2237 matches!(
2238 a,
2239 Atomic::TNamedObject { .. }
2240 | Atomic::TSelf { .. }
2241 | Atomic::TStaticObject { .. }
2242 | Atomic::TTemplateParam { .. }
2243 | Atomic::TClosure { .. }
2244 | Atomic::TCallable { .. }
2245 )
2246 })
2247 }
2248 let sub_required = sub
2249 .params
2250 .iter()
2251 .filter(|p| !p.is_optional && !p.is_variadic)
2252 .count();
2253 if sub_required > sup.params.len() {
2254 false
2255 } else {
2256 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2257 let Some(sub_param) = sub.params.get(i) else {
2258 return true;
2259 };
2260 if sub_param.is_optional || sub_param.is_variadic {
2261 return true;
2262 }
2263 let (Some(sub_ty), Some(sup_ty)) =
2264 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2265 else {
2266 return true;
2267 };
2268 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2269 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2270 return true;
2271 }
2272 sup_u.is_subtype_structural(&sub_u)
2275 });
2276 params_ok
2277 && (sub.return_type.is_mixed()
2278 || sup.return_type.is_mixed()
2279 || sup.return_type.is_void()
2283 || has_nominal_type(&sub.return_type)
2284 || has_nominal_type(&sup.return_type)
2285 || sub.return_type.is_subtype_structural(&sup.return_type))
2286 }
2287 }
2288 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2290 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2292 fqcn.as_ref().eq_ignore_ascii_case("closure")
2293 }
2294 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2295 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2297 fqcn.as_ref().eq_ignore_ascii_case("closure")
2298 }
2299 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2301 fqcn.as_ref().eq_ignore_ascii_case("closure")
2302 }
2303
2304 (
2312 Atomic::TIntersection { parts: sub_parts },
2313 Atomic::TIntersection { parts: sup_parts },
2314 ) => sup_parts.iter().all(|sup_part| {
2315 sub_parts
2316 .iter()
2317 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2318 }),
2319 (Atomic::TIntersection { parts }, Atomic::TObject) => parts
2320 .iter()
2321 .any(|part| part.is_subtype_structural(&Type::single(Atomic::TObject))),
2322
2323 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2325 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2326 }
2327 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2328 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2329 }
2330 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2331 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2332 }
2333 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2334 value.is_subtype_structural(lv)
2335 }
2336 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2337 v1.is_subtype_structural(v2)
2338 }
2339 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2341 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2342 && av.is_subtype_structural(lv)
2343 }
2344 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2345 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2346 && av.is_subtype_structural(lv)
2347 }
2348 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2349 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2350 && av.is_subtype_structural(lv)
2351 }
2352 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2353 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2354 && av.is_subtype_structural(lv)
2355 }
2356 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2358 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2359 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2360 }
2361
2362 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2364 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2365 }
2366
2367 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2376 properties.iter().all(|(prop_key, prop)| {
2377 let key_atomic = match prop_key {
2378 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2379 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2380 };
2381 if !Type::single(key_atomic).is_subtype_structural(key) {
2382 return false; }
2384 let has_named_obj = prop.ty.types.iter().any(|a| {
2386 matches!(
2387 a,
2388 Atomic::TNamedObject { .. }
2389 | Atomic::TSelf { .. }
2390 | Atomic::TStaticObject { .. }
2391 | Atomic::TClosure { .. }
2392 | Atomic::TTemplateParam { .. }
2393 )
2394 });
2395 has_named_obj || prop.ty.is_subtype_structural(value)
2396 })
2397 }
2398 (
2399 Atomic::TKeyedArray {
2400 properties,
2401 is_open,
2402 ..
2403 },
2404 Atomic::TNonEmptyArray { key, value },
2405 ) => {
2406 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2407 && properties.iter().all(|(prop_key, prop)| {
2408 let key_atomic = match prop_key {
2409 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2410 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2411 };
2412 if !Type::single(key_atomic).is_subtype_structural(key) {
2413 return false;
2414 }
2415 let has_named_obj = prop.ty.types.iter().any(|a| {
2416 matches!(
2417 a,
2418 Atomic::TNamedObject { .. }
2419 | Atomic::TSelf { .. }
2420 | Atomic::TStaticObject { .. }
2421 | Atomic::TClosure { .. }
2422 | Atomic::TTemplateParam { .. }
2423 )
2424 });
2425 has_named_obj || prop.ty.is_subtype_structural(value)
2426 })
2427 }
2428
2429 (
2431 Atomic::TKeyedArray {
2432 properties,
2433 is_list,
2434 ..
2435 },
2436 Atomic::TList { value: lv },
2437 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2438 (
2439 Atomic::TKeyedArray {
2440 properties,
2441 is_list,
2442 ..
2443 },
2444 Atomic::TNonEmptyList { value: lv },
2445 ) => {
2446 *is_list
2447 && !properties.is_empty()
2448 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2449 }
2450
2451 (
2456 Atomic::TKeyedArray {
2457 properties: sub_props,
2458 is_open: sub_open,
2459 ..
2460 },
2461 Atomic::TKeyedArray {
2462 properties: sup_props,
2463 is_open: sup_open,
2464 ..
2465 },
2466 ) => {
2467 let keys_satisfied = sup_props
2468 .iter()
2469 .all(|(key, sup_prop)| match sub_props.get(key) {
2470 Some(sub_prop) => {
2471 if !sup_prop.optional && sub_prop.optional {
2475 return false;
2476 }
2477 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2478 matches!(
2479 a,
2480 Atomic::TNamedObject { .. }
2481 | Atomic::TSelf { .. }
2482 | Atomic::TStaticObject { .. }
2483 | Atomic::TClosure { .. }
2484 | Atomic::TTemplateParam { .. }
2485 )
2486 });
2487 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2488 }
2489 None => sup_prop.optional || *sub_open,
2490 });
2491 let no_undeclared_extras =
2492 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2493 keys_satisfied && no_undeclared_extras
2494 }
2495 (
2500 Atomic::TKeyedArray {
2501 properties,
2502 is_open,
2503 ..
2504 },
2505 Atomic::TIntersection { parts },
2506 ) => parts.iter().all(|part| {
2507 part.types.iter().any(|part_atomic| match part_atomic {
2508 Atomic::TKeyedArray {
2509 properties: sup_props,
2510 is_open: sup_open,
2511 ..
2512 } => {
2513 let keys_satisfied =
2514 sup_props
2515 .iter()
2516 .all(|(key, sup_prop)| match properties.get(key) {
2517 Some(sub_prop) => {
2518 if !sup_prop.optional && sub_prop.optional {
2519 return false;
2520 }
2521 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2522 matches!(
2523 a,
2524 Atomic::TNamedObject { .. }
2525 | Atomic::TSelf { .. }
2526 | Atomic::TStaticObject { .. }
2527 | Atomic::TClosure { .. }
2528 | Atomic::TTemplateParam { .. }
2529 )
2530 });
2531 has_named_obj
2532 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2533 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2534 }
2535 None => *is_open || sup_prop.optional,
2536 });
2537 let has_array_part = parts.iter().any(|part| {
2538 part.types
2539 .iter()
2540 .any(|t| matches!(t, Atomic::TArray { .. }))
2541 });
2542 let has_keyed_array_part = parts.iter().any(|part| {
2543 part.types
2544 .iter()
2545 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2546 });
2547 let keys_allowed_by_some_part = if has_array_part {
2548 true
2549 } else if has_keyed_array_part {
2550 properties.keys().all(|k| {
2551 parts.iter().any(|part| {
2552 part.types.iter().any(|t| {
2553 if let Atomic::TKeyedArray {
2554 properties: part_props,
2555 ..
2556 } = t
2557 {
2558 part_props.contains_key(k)
2559 } else {
2560 false
2561 }
2562 })
2563 })
2564 })
2565 } else {
2566 true
2567 };
2568 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2569 keys_satisfied && no_undeclared_extras
2570 }
2571 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2572 let key_atomic = match prop_key {
2573 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2574 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2575 };
2576 if !Type::single(key_atomic).is_subtype_structural(key) {
2577 return false;
2578 }
2579 let has_named_obj = prop.ty.types.iter().any(|a| {
2580 matches!(
2581 a,
2582 Atomic::TNamedObject { .. }
2583 | Atomic::TSelf { .. }
2584 | Atomic::TStaticObject { .. }
2585 | Atomic::TClosure { .. }
2586 | Atomic::TTemplateParam { .. }
2587 )
2588 });
2589 has_named_obj || prop.ty.is_subtype_structural(value)
2590 }),
2591 Atomic::TNamedObject { .. }
2592 | Atomic::TSelf { .. }
2593 | Atomic::TStaticObject { .. }
2594 | Atomic::TClosure { .. }
2595 | Atomic::TTemplateParam { .. } => true,
2596 _ => false,
2597 })
2598 }),
2599
2600 _ => false,
2601 }
2602}
2603
2604fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2606 fn array_like(t: &Atomic) -> bool {
2607 match t {
2608 Atomic::TArray { .. }
2609 | Atomic::TNonEmptyArray { .. }
2610 | Atomic::TList { .. }
2611 | Atomic::TNonEmptyList { .. }
2612 | Atomic::TKeyedArray { .. } => true,
2613 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2614 _ => false,
2615 }
2616 }
2617 matches!(
2618 sub.types.as_slice(),
2619 [Atomic::TArray { key, value }]
2620 if value.is_mixed()
2621 && (key.is_mixed()
2622 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2623 ) && sup.types.iter().all(array_like)
2624}
2625
2626fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2632 if sub.len() != sup.len() {
2633 return false;
2634 }
2635 sub.iter()
2636 .zip(sup.iter())
2637 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2638}
2639
2640fn is_empty_array_literal(t: &Type) -> bool {
2643 !t.types.is_empty()
2644 && t.types.iter().all(
2645 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2646 )
2647}
2648
2649fn is_array_like(t: &Type) -> bool {
2651 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2652}
2653
2654pub struct UnionBuilder {
2661 ty: Type,
2662 strings: std::collections::HashSet<std::sync::Arc<str>>,
2663 ints: std::collections::HashSet<i64>,
2664 literals_independent: bool,
2666}
2667
2668impl Default for UnionBuilder {
2669 fn default() -> Self {
2670 Self::new()
2671 }
2672}
2673
2674impl UnionBuilder {
2675 pub fn new() -> Self {
2676 Self {
2677 ty: Type::empty(),
2678 strings: Default::default(),
2679 ints: Default::default(),
2680 literals_independent: true,
2681 }
2682 }
2683
2684 pub fn is_empty(&self) -> bool {
2685 self.ty.is_empty()
2686 }
2687
2688 pub fn merge(&mut self, other: &Type) {
2689 for a in &other.types {
2690 self.add(a.clone());
2691 }
2692 self.ty.possibly_undefined |= other.possibly_undefined;
2693 }
2694
2695 pub fn add(&mut self, atomic: Atomic) {
2696 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2698 if self.literals_independent && !lone_never {
2699 match &atomic {
2700 Atomic::TLiteralString(s) => {
2701 if self.strings.insert(s.clone()) {
2702 self.ty.types.push(atomic);
2703 }
2704 return;
2705 }
2706 Atomic::TLiteralInt(n) => {
2707 if self.ints.insert(*n) {
2708 self.ty.types.push(atomic);
2709 }
2710 return;
2711 }
2712 _ => {}
2713 }
2714 }
2715 let disturbs_literals = matches!(
2716 atomic,
2717 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2718 ) || int_bounds(&atomic).is_some();
2719 self.ty.add_type(atomic);
2720 if disturbs_literals || lone_never {
2721 self.resync();
2722 }
2723 }
2724
2725 fn resync(&mut self) {
2726 self.strings.clear();
2727 self.ints.clear();
2728 self.literals_independent = true;
2729 for t in &self.ty.types {
2730 match t {
2731 Atomic::TLiteralString(s) => {
2732 self.strings.insert(s.clone());
2733 }
2734 Atomic::TLiteralInt(n) => {
2735 self.ints.insert(*n);
2736 }
2737 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2738 other if int_bounds(other).is_some() => self.literals_independent = false,
2739 _ => {}
2740 }
2741 }
2742 }
2743
2744 pub fn build(self) -> Type {
2745 self.ty
2746 }
2747}
2748
2749fn callable_array() -> Atomic {
2751 let mut properties = KeyedProperties::with_capacity_and_hasher(2, Default::default());
2752 properties.insert(
2753 ArrayKey::Int(0),
2754 KeyedProperty {
2755 ty: Type::from_vec(vec![Atomic::TClassString(None), Atomic::TObject]),
2756 optional: false,
2757 },
2758 );
2759 properties.insert(
2760 ArrayKey::Int(1),
2761 KeyedProperty {
2762 ty: Type::single(Atomic::TNonEmptyString),
2763 optional: false,
2764 },
2765 );
2766 Atomic::TKeyedArray {
2767 properties: Arc::new(properties),
2768 is_open: false,
2769 is_list: true,
2770 }
2771}
2772
2773#[cfg(test)]
2774mod tests {
2775 use std::sync::Arc;
2776
2777 use super::*;
2778
2779 fn conditional(
2780 param_name: Option<Name>,
2781 subject: Type,
2782 if_true: Type,
2783 if_false: Type,
2784 ) -> Atomic {
2785 Atomic::TConditional {
2786 data: Box::new(crate::atomic::ConditionalData {
2787 param_name,
2788 subject,
2789 if_true,
2790 if_false,
2791 }),
2792 }
2793 }
2794
2795 #[test]
2796 fn single_is_single() {
2797 let u = Type::single(Atomic::TString);
2798 assert!(u.is_single());
2799 assert!(!u.is_nullable());
2800 }
2801
2802 #[test]
2803 fn nullable_has_null() {
2804 let u = Type::nullable(Atomic::TString);
2805 assert!(u.is_nullable());
2806 assert_eq!(u.types.len(), 2);
2807 }
2808
2809 #[test]
2810 fn add_type_deduplicates() {
2811 let mut u = Type::single(Atomic::TString);
2812 u.add_type(Atomic::TString);
2813 assert_eq!(u.types.len(), 1);
2814 }
2815
2816 #[test]
2817 fn array_key_is_int_string() {
2818 let k = Type::array_key();
2819 assert!(k.is_array_key());
2820 assert_eq!(k.types.len(), 2);
2821 }
2822
2823 #[test]
2824 fn is_array_key_false_for_plain_int() {
2825 assert!(!Type::int().is_array_key());
2826 }
2827
2828 #[test]
2829 fn is_array_key_false_for_mixed() {
2830 assert!(!Type::mixed().is_array_key());
2831 }
2832
2833 #[test]
2834 fn is_array_key_false_for_int_string_null() {
2835 let mut u = Type::array_key();
2836 u.add_type(Atomic::TNull);
2837 assert!(!u.is_array_key());
2838 }
2839
2840 #[test]
2841 fn add_type_literal_subsumed_by_base() {
2842 let mut u = Type::single(Atomic::TInt);
2843 u.add_type(Atomic::TLiteralInt(42));
2844 assert_eq!(u.types.len(), 1);
2845 assert!(matches!(u.types[0], Atomic::TInt));
2846 }
2847
2848 #[test]
2849 fn true_then_false_merges_to_bool() {
2850 let mut u = Type::single(Atomic::TTrue);
2851 u.add_type(Atomic::TFalse);
2852 assert_eq!(u.types.len(), 1);
2853 assert!(matches!(u.types[0], Atomic::TBool));
2854 }
2855
2856 #[test]
2857 fn false_then_true_merges_to_bool() {
2858 let mut u = Type::single(Atomic::TFalse);
2859 u.add_type(Atomic::TTrue);
2860 assert_eq!(u.types.len(), 1);
2861 assert!(matches!(u.types[0], Atomic::TBool));
2862 }
2863
2864 #[test]
2865 fn true_alone_stays_true() {
2866 let u = Type::single(Atomic::TTrue);
2867 assert_eq!(u.types.len(), 1);
2868 assert!(matches!(u.types[0], Atomic::TTrue));
2869 }
2870
2871 #[test]
2872 fn true_false_merge_preserves_other_union_members() {
2873 let mut u = Type::single(Atomic::TTrue);
2874 u.add_type(Atomic::TNull);
2875 u.add_type(Atomic::TFalse);
2876 assert_eq!(u.types.len(), 2);
2877 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2878 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2879 }
2880
2881 #[test]
2882 fn add_type_base_widens_literals() {
2883 let mut u = Type::single(Atomic::TLiteralInt(1));
2884 u.add_type(Atomic::TLiteralInt(2));
2885 u.add_type(Atomic::TInt);
2886 assert_eq!(u.types.len(), 1);
2887 assert!(matches!(u.types[0], Atomic::TInt));
2888 }
2889
2890 #[test]
2891 fn mixed_subsumes_everything() {
2892 let mut u = Type::single(Atomic::TString);
2893 u.add_type(Atomic::TMixed);
2894 assert_eq!(u.types.len(), 1);
2895 assert!(u.is_mixed());
2896 }
2897
2898 #[test]
2899 fn remove_null() {
2900 let u = Type::nullable(Atomic::TString);
2901 let narrowed = u.remove_null();
2902 assert!(!narrowed.is_nullable());
2903 assert_eq!(narrowed.types.len(), 1);
2904 }
2905
2906 #[test]
2907 fn narrow_to_truthy_removes_null_false() {
2908 let mut u = Type::empty();
2909 u.add_type(Atomic::TString);
2910 u.add_type(Atomic::TNull);
2911 u.add_type(Atomic::TFalse);
2912 let truthy = u.narrow_to_truthy();
2913 assert!(!truthy.is_nullable());
2914 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2915 }
2916
2917 #[test]
2918 fn merge_combines_types() {
2919 let a = Type::single(Atomic::TString);
2920 let b = Type::single(Atomic::TInt);
2921 let merged = Type::merge(&a, &b);
2922 assert_eq!(merged.types.len(), 2);
2923 }
2924
2925 #[test]
2926 fn intersect_keeps_narrower_side_not_self() {
2927 let int_ty = Type::single(Atomic::TInt);
2931 let mut literals = Type::empty();
2932 literals.add_type(Atomic::TLiteralInt(1));
2933 literals.add_type(Atomic::TLiteralInt(2));
2934
2935 let narrowed = int_ty.intersect_with(&literals);
2936 assert_eq!(narrowed.types.len(), 2);
2937 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2938 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2939 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2940 }
2941
2942 #[test]
2943 fn subtype_literal_int_under_int() {
2944 let sub = Type::single(Atomic::TLiteralInt(5));
2945 let sup = Type::single(Atomic::TInt);
2946 assert!(sub.is_subtype_structural(&sup));
2947 }
2948
2949 #[test]
2950 fn subtype_never_is_bottom() {
2951 let never = Type::never();
2952 let string = Type::single(Atomic::TString);
2953 assert!(never.is_subtype_structural(&string));
2954 }
2955
2956 #[test]
2957 fn subtype_everything_under_mixed() {
2958 let string = Type::single(Atomic::TString);
2959 let mixed = Type::mixed();
2960 assert!(string.is_subtype_structural(&mixed));
2961 }
2962
2963 #[test]
2964 fn subtype_enum_case_under_own_enum() {
2965 let sub = Type::single(Atomic::TLiteralEnumCase {
2966 enum_fqcn: Name::new("RoundingMode"),
2967 case_name: Name::new("Unnecessary"),
2968 });
2969 let sup = Type::single(Atomic::TNamedObject {
2970 fqcn: Name::new("RoundingMode"),
2971 type_params: empty_type_params(),
2972 });
2973 assert!(sub.is_subtype_structural(&sup));
2974 }
2975
2976 #[test]
2977 fn enum_case_not_subtype_of_unrelated_enum() {
2978 let sub = Type::single(Atomic::TLiteralEnumCase {
2979 enum_fqcn: Name::new("RoundingMode"),
2980 case_name: Name::new("Unnecessary"),
2981 });
2982 let sup = Type::single(Atomic::TNamedObject {
2983 fqcn: Name::new("Suit"),
2984 type_params: empty_type_params(),
2985 });
2986 assert!(!sub.is_subtype_structural(&sup));
2987 }
2988
2989 #[test]
2990 fn subtype_enum_case_under_own_self_and_static() {
2991 let case = |enum_fqcn: &str| {
2992 Type::single(Atomic::TLiteralEnumCase {
2993 enum_fqcn: Name::new(enum_fqcn),
2994 case_name: Name::new("A"),
2995 })
2996 };
2997 let own = Type::single(Atomic::TSelf {
2998 fqcn: Name::new("Suit"),
2999 });
3000 let own_static = Type::single(Atomic::TStaticObject {
3001 fqcn: Name::new("Suit"),
3002 });
3003 assert!(case("Suit").is_subtype_structural(&own));
3004 assert!(case("Suit").is_subtype_structural(&own_static));
3005 assert!(!case("Other").is_subtype_structural(&own));
3006 }
3007
3008 #[test]
3009 fn subtype_enum_case_under_bare_object() {
3010 let sub = Type::single(Atomic::TLiteralEnumCase {
3011 enum_fqcn: Name::new("RoundingMode"),
3012 case_name: Name::new("Unnecessary"),
3013 });
3014 let sup = Type::single(Atomic::TObject);
3015 assert!(sub.is_subtype_structural(&sup));
3016 }
3017
3018 #[test]
3019 fn template_substitution() {
3020 let mut bindings = FxHashMap::default();
3021 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
3022
3023 let tmpl = Type::single(Atomic::TTemplateParam {
3024 name: Name::new("T"),
3025 as_type: Box::new(Type::mixed()),
3026 defining_entity: Name::new("MyClass"),
3027 });
3028
3029 let resolved = tmpl.substitute_templates(&bindings);
3030 assert_eq!(resolved.types.len(), 1);
3031 assert!(matches!(resolved.types[0], Atomic::TString));
3032 }
3033
3034 #[test]
3035 fn intersection_is_object() {
3036 let parts = vec![
3037 Type::single(Atomic::TNamedObject {
3038 fqcn: Name::new("Iterator"),
3039 type_params: empty_type_params(),
3040 }),
3041 Type::single(Atomic::TNamedObject {
3042 fqcn: Name::new("Countable"),
3043 type_params: empty_type_params(),
3044 }),
3045 ];
3046 let atomic = Atomic::TIntersection {
3047 parts: vec_to_type_params(parts),
3048 };
3049 assert!(atomic.is_object());
3050 assert!(!atomic.can_be_falsy());
3051 assert!(atomic.can_be_truthy());
3052 }
3053
3054 #[test]
3055 fn intersection_display_two_parts() {
3056 let parts = vec![
3057 Type::single(Atomic::TNamedObject {
3058 fqcn: Name::new("Iterator"),
3059 type_params: empty_type_params(),
3060 }),
3061 Type::single(Atomic::TNamedObject {
3062 fqcn: Name::new("Countable"),
3063 type_params: empty_type_params(),
3064 }),
3065 ];
3066 let u = Type::single(Atomic::TIntersection {
3067 parts: vec_to_type_params(parts),
3068 });
3069 assert_eq!(format!("{u}"), "Iterator&Countable");
3070 }
3071
3072 #[test]
3073 fn intersection_display_three_parts() {
3074 let parts = vec![
3075 Type::single(Atomic::TNamedObject {
3076 fqcn: Name::new("A"),
3077 type_params: empty_type_params(),
3078 }),
3079 Type::single(Atomic::TNamedObject {
3080 fqcn: Name::new("B"),
3081 type_params: empty_type_params(),
3082 }),
3083 Type::single(Atomic::TNamedObject {
3084 fqcn: Name::new("C"),
3085 type_params: empty_type_params(),
3086 }),
3087 ];
3088 let u = Type::single(Atomic::TIntersection {
3089 parts: vec_to_type_params(parts),
3090 });
3091 assert_eq!(format!("{u}"), "A&B&C");
3092 }
3093
3094 #[test]
3095 fn intersection_in_nullable_union_display() {
3096 let intersection = Atomic::TIntersection {
3097 parts: vec_to_type_params(vec![
3098 Type::single(Atomic::TNamedObject {
3099 fqcn: Name::new("Iterator"),
3100 type_params: empty_type_params(),
3101 }),
3102 Type::single(Atomic::TNamedObject {
3103 fqcn: Name::new("Countable"),
3104 type_params: empty_type_params(),
3105 }),
3106 ]),
3107 };
3108 let mut u = Type::single(intersection);
3109 u.add_type(Atomic::TNull);
3110 assert!(u.is_nullable());
3111 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
3112 }
3113
3114 fn t_param(name: &str) -> Type {
3117 Type::single(Atomic::TTemplateParam {
3118 name: Name::new(name),
3119 as_type: Box::new(Type::mixed()),
3120 defining_entity: Name::new("Fn"),
3121 })
3122 }
3123
3124 fn bindings_t_string() -> FxHashMap<Name, Type> {
3125 let mut b = FxHashMap::default();
3126 b.insert(Name::new("T"), Type::single(Atomic::TString));
3127 b
3128 }
3129
3130 #[test]
3131 fn substitute_non_empty_array_key_and_value() {
3132 let ty = Type::single(Atomic::TNonEmptyArray {
3133 key: Box::new(t_param("T")),
3134 value: Box::new(t_param("T")),
3135 });
3136 let result = ty.substitute_templates(&bindings_t_string());
3137 assert_eq!(result.types.len(), 1);
3138 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
3139 panic!("expected TNonEmptyArray");
3140 };
3141 assert!(matches!(key.types[0], Atomic::TString));
3142 assert!(matches!(value.types[0], Atomic::TString));
3143 }
3144
3145 #[test]
3146 fn substitute_non_empty_list_value() {
3147 let ty = Type::single(Atomic::TNonEmptyList {
3148 value: Box::new(t_param("T")),
3149 });
3150 let result = ty.substitute_templates(&bindings_t_string());
3151 let Atomic::TNonEmptyList { value } = &result.types[0] else {
3152 panic!("expected TNonEmptyList");
3153 };
3154 assert!(matches!(value.types[0], Atomic::TString));
3155 }
3156
3157 #[test]
3158 fn substitute_keyed_array_property_types() {
3159 use crate::atomic::{ArrayKey, KeyedProperty};
3160 let mut props = KeyedProperties::default();
3161 props.insert(
3162 ArrayKey::String(Arc::from("name")),
3163 KeyedProperty {
3164 ty: t_param("T"),
3165 optional: false,
3166 },
3167 );
3168 props.insert(
3169 ArrayKey::String(Arc::from("tag")),
3170 KeyedProperty {
3171 ty: t_param("T"),
3172 optional: true,
3173 },
3174 );
3175 let ty = Type::single(Atomic::TKeyedArray {
3176 properties: Arc::new(props),
3177 is_open: true,
3178 is_list: false,
3179 });
3180 let result = ty.substitute_templates(&bindings_t_string());
3181 let Atomic::TKeyedArray {
3182 properties,
3183 is_open,
3184 is_list,
3185 } = &result.types[0]
3186 else {
3187 panic!("expected TKeyedArray");
3188 };
3189 assert!(is_open);
3190 assert!(!is_list);
3191 assert!(matches!(
3192 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3193 Atomic::TString
3194 ));
3195 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3196 assert!(matches!(
3197 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3198 Atomic::TString
3199 ));
3200 }
3201
3202 #[test]
3203 fn substitute_callable_params_and_return() {
3204 use crate::atomic::FnParam;
3205 let ty = Type::single(Atomic::TCallable {
3206 params: Some(Box::new(vec![FnParam {
3207 name: Name::new("x"),
3208 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3209 out_ty: None,
3210 default: None,
3211 is_variadic: false,
3212 is_byref: false,
3213 is_optional: false,
3214 }])),
3215 return_type: Some(Box::new(t_param("T"))),
3216 is_pure: false,
3217 });
3218 let result = ty.substitute_templates(&bindings_t_string());
3219 let Atomic::TCallable {
3220 params,
3221 return_type,
3222 ..
3223 } = &result.types[0]
3224 else {
3225 panic!("expected TCallable");
3226 };
3227 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3228 let param_union = param_ty.to_union();
3229 assert!(matches!(param_union.types[0], Atomic::TString));
3230 let ret = return_type.as_ref().unwrap();
3231 assert!(matches!(ret.types[0], Atomic::TString));
3232 }
3233
3234 #[test]
3235 fn substitute_callable_bare_no_panic() {
3236 let ty = Type::single(Atomic::TCallable {
3238 params: None,
3239 return_type: None,
3240 is_pure: false,
3241 });
3242 let result = ty.substitute_templates(&bindings_t_string());
3243 assert!(matches!(
3244 result.types[0],
3245 Atomic::TCallable {
3246 params: None,
3247 return_type: None,
3248 ..
3249 }
3250 ));
3251 }
3252
3253 #[test]
3254 fn substitute_closure_params_return_and_this() {
3255 use crate::atomic::FnParam;
3256 let ty = Type::single(Atomic::TClosure {
3257 data: Box::new(crate::atomic::ClosureData {
3258 params: Box::new([FnParam {
3259 name: Name::new("a"),
3260 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3261 out_ty: None,
3262 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3263 is_variadic: true,
3264 is_byref: true,
3265 is_optional: true,
3266 }]),
3267 return_type: t_param("T"),
3268 this_type: Some(t_param("T")),
3269 is_pure: false,
3270 is_static: false,
3271 rejects_surplus_args: false,
3272 }),
3273 });
3274 let result = ty.substitute_templates(&bindings_t_string());
3275 let Atomic::TClosure { data } = &result.types[0] else {
3276 panic!("expected TClosure");
3277 };
3278 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3279 let p = ¶ms[0];
3280 let ty_union = p.ty.as_ref().unwrap().to_union();
3281 let default_union = p.default.as_ref().unwrap().to_union();
3282 assert!(matches!(ty_union.types[0], Atomic::TString));
3283 assert!(matches!(default_union.types[0], Atomic::TString));
3284 assert!(p.is_variadic);
3286 assert!(p.is_byref);
3287 assert!(p.is_optional);
3288 assert!(matches!(return_type.types[0], Atomic::TString));
3289 assert!(matches!(
3290 this_type.as_ref().unwrap().types[0],
3291 Atomic::TString
3292 ));
3293 }
3294
3295 #[test]
3296 fn substitute_conditional_all_branches() {
3297 let ty = Type::single(conditional(
3298 None,
3299 t_param("T"),
3300 t_param("T"),
3301 Type::single(Atomic::TInt),
3302 ));
3303 let result = ty.substitute_templates(&bindings_t_string());
3304 let Atomic::TConditional { data } = &result.types[0] else {
3305 panic!("expected TConditional");
3306 };
3307 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3308 assert!(matches!(subject.types[0], Atomic::TString));
3309 assert!(matches!(if_true.types[0], Atomic::TString));
3310 assert!(matches!(if_false.types[0], Atomic::TInt));
3311 }
3312
3313 #[test]
3314 fn resolve_conditional_is_null_non_null_arg() {
3315 let ty = Type::single(conditional(
3316 Some(Name::new("x")),
3317 Type::single(Atomic::TNull),
3318 Type::single(Atomic::TInt),
3319 Type::single(Atomic::TString),
3320 ));
3321 let result = ty.resolve_conditional_returns(|name| {
3322 if name == "x" {
3323 Some(Type::single(Atomic::TString)) } else {
3325 None
3326 }
3327 });
3328 assert!(result.types.len() == 1);
3329 assert!(matches!(result.types[0], Atomic::TString));
3330 }
3331
3332 #[test]
3333 fn resolve_conditional_is_null_null_arg() {
3334 let ty = Type::single(conditional(
3335 Some(Name::new("x")),
3336 Type::single(Atomic::TNull),
3337 Type::single(Atomic::TInt),
3338 Type::single(Atomic::TString),
3339 ));
3340 let result = ty.resolve_conditional_returns(|name| {
3341 if name == "x" {
3342 Some(Type::single(Atomic::TNull)) } else {
3344 None
3345 }
3346 });
3347 assert!(result.types.len() == 1);
3348 assert!(matches!(result.types[0], Atomic::TInt));
3349 }
3350
3351 #[test]
3352 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3353 let mut nullable_str = Type::single(Atomic::TString);
3354 nullable_str.add_type(Atomic::TNull);
3355 let ty = Type::single(conditional(
3356 Some(Name::new("x")),
3357 Type::single(Atomic::TNull),
3358 Type::single(Atomic::TInt),
3359 Type::single(Atomic::TString),
3360 ));
3361 let result = ty.resolve_conditional_returns(|name| {
3362 if name == "x" {
3363 Some(nullable_str.clone())
3364 } else {
3365 None
3366 }
3367 });
3368 assert_eq!(result.types.len(), 2);
3370 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3371 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3372 }
3373
3374 #[test]
3375 fn resolve_conditional_nested_widens_inner_branch() {
3376 let inner = Type::single(conditional(
3379 Some(Name::new("x")),
3380 Type::single(Atomic::TString),
3381 Type::single(Atomic::TString),
3382 Type::single(Atomic::TFloat),
3383 ));
3384 let ty = Type::single(conditional(
3385 Some(Name::new("x")),
3386 Type::single(Atomic::TNull),
3387 Type::single(Atomic::TInt),
3388 inner,
3389 ));
3390 let result = ty.resolve_conditional_returns(|_| None);
3392 assert!(
3393 result
3394 .types
3395 .iter()
3396 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3397 "no TConditional should survive: {:?}",
3398 result.types
3399 );
3400 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3401 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3402 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3403 }
3404
3405 #[test]
3406 fn resolve_conditional_nested_resolves_inner_branch() {
3407 let inner = Type::single(conditional(
3411 Some(Name::new("x")),
3412 Type::single(Atomic::TString),
3413 Type::single(Atomic::TString),
3414 Type::single(Atomic::TFloat),
3415 ));
3416 let ty = Type::single(conditional(
3417 Some(Name::new("x")),
3418 Type::single(Atomic::TNull),
3419 Type::single(Atomic::TInt),
3420 inner,
3421 ));
3422 let result = ty.resolve_conditional_returns(|name| {
3424 if name == "x" {
3425 Some(Type::single(Atomic::TString))
3426 } else {
3427 None
3428 }
3429 });
3430 assert!(
3431 result
3432 .types
3433 .iter()
3434 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3435 "no TConditional should survive: {:?}",
3436 result.types
3437 );
3438 assert_eq!(result.types.len(), 1);
3439 assert!(matches!(result.types[0], Atomic::TString));
3440 }
3441
3442 #[test]
3443 fn resolve_conditional_true_subject_bool_arg_widens() {
3444 let ty = Type::single(conditional(
3448 Some(Name::new("x")),
3449 Type::single(Atomic::TTrue),
3450 Type::single(Atomic::TInt),
3451 Type::single(Atomic::TString),
3452 ));
3453 let result = ty.resolve_conditional_returns(|name| {
3454 if name == "x" {
3455 Some(Type::single(Atomic::TBool))
3456 } else {
3457 None
3458 }
3459 });
3460 assert_eq!(result.types.len(), 2);
3461 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3462 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3463 }
3464
3465 #[test]
3466 fn resolve_conditional_true_subject_mixed_arg_widens() {
3467 let ty = Type::single(conditional(
3470 Some(Name::new("x")),
3471 Type::single(Atomic::TTrue),
3472 Type::single(Atomic::TInt),
3473 Type::single(Atomic::TString),
3474 ));
3475 let result = ty.resolve_conditional_returns(|name| {
3476 if name == "x" {
3477 Some(Type::mixed())
3478 } else {
3479 None
3480 }
3481 });
3482 assert_eq!(result.types.len(), 2);
3483 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3484 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3485 }
3486
3487 #[test]
3488 fn resolve_conditional_true_subject_literal_args() {
3489 let ty = Type::single(conditional(
3492 Some(Name::new("x")),
3493 Type::single(Atomic::TTrue),
3494 Type::single(Atomic::TInt),
3495 Type::single(Atomic::TString),
3496 ));
3497 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3498 if name == "x" {
3499 Some(Type::single(Atomic::TFalse))
3500 } else {
3501 None
3502 }
3503 });
3504 assert_eq!(false_arg.types.len(), 1);
3505 assert!(matches!(false_arg.types[0], Atomic::TString));
3506
3507 let true_arg = ty.resolve_conditional_returns(|name| {
3508 if name == "x" {
3509 Some(Type::single(Atomic::TTrue))
3510 } else {
3511 None
3512 }
3513 });
3514 assert_eq!(true_arg.types.len(), 1);
3515 assert!(matches!(true_arg.types[0], Atomic::TInt));
3516 }
3517
3518 #[test]
3519 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3520 let ty = Type::single(conditional(
3524 Some(Name::new("x")),
3525 Type::single(Atomic::TBool),
3526 Type::single(Atomic::TInt),
3527 Type::single(Atomic::TString),
3528 ));
3529 let result = ty.resolve_conditional_returns(|name| {
3530 if name == "x" {
3531 Some(Type::single(Atomic::TScalar))
3532 } else {
3533 None
3534 }
3535 });
3536 assert_eq!(result.types.len(), 2);
3537 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3538 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3539 }
3540
3541 #[test]
3542 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3543 let ty = Type::single(conditional(
3547 Some(Name::new("x")),
3548 Type::single(Atomic::TString),
3549 Type::single(Atomic::TInt),
3550 Type::single(Atomic::TBool),
3551 ));
3552 let result = ty.resolve_conditional_returns(|name| {
3553 if name == "x" {
3554 Some(Type::single(Atomic::TBool))
3555 } else {
3556 None
3557 }
3558 });
3559 assert_eq!(result.types.len(), 1);
3560 assert!(matches!(result.types[0], Atomic::TBool));
3561 }
3562
3563 #[test]
3564 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3565 let ty = Type::single(conditional(
3568 Some(Name::new("x")),
3569 Type::single(Atomic::TList {
3570 value: Box::new(Type::mixed()),
3571 }),
3572 Type::single(Atomic::TInt),
3573 Type::single(Atomic::TString),
3574 ));
3575 let result = ty.resolve_conditional_returns(|name| {
3576 if name == "x" {
3577 Some(Type::single(Atomic::TArray {
3578 key: Box::new(Type::mixed()),
3579 value: Box::new(Type::mixed()),
3580 }))
3581 } else {
3582 None
3583 }
3584 });
3585 assert_eq!(result.types.len(), 2);
3586 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3587 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3588 }
3589
3590 #[test]
3591 fn resolve_conditional_float_subject_int_literal_false_branch() {
3592 let ty = resolve_conditional_branch(
3595 &Atomic::TFloat,
3596 &Type::single(Atomic::TLiteralInt(42)),
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_int_subject_float_literal_false_branch() {
3608 let ty = resolve_conditional_branch(
3609 &Atomic::TInt,
3610 &Type::single(Atomic::TLiteralFloat(1, 0)),
3611 &Type::single(Atomic::TInt),
3612 &Type::single(Atomic::TString),
3613 );
3614 assert!(matches!(
3615 ty.as_ref(),
3616 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3617 ));
3618 }
3619
3620 #[test]
3621 fn resolve_conditional_float_subject_float_literal_then_branch() {
3622 let ty = resolve_conditional_branch(
3623 &Atomic::TFloat,
3624 &Type::single(Atomic::TLiteralFloat(1, 5)),
3625 &Type::single(Atomic::TInt),
3626 &Type::single(Atomic::TString),
3627 );
3628 assert!(matches!(
3629 ty.as_ref(),
3630 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3631 ));
3632 }
3633
3634 #[test]
3635 fn resolve_conditional_int_subject_int_literal_then_branch() {
3636 let ty = resolve_conditional_branch(
3637 &Atomic::TInt,
3638 &Type::single(Atomic::TLiteralInt(7)),
3639 &Type::single(Atomic::TInt),
3640 &Type::single(Atomic::TString),
3641 );
3642 assert!(matches!(
3643 ty.as_ref(),
3644 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3645 ));
3646 }
3647
3648 #[test]
3649 fn resolve_conditional_int_subject_float_arg_false_branch() {
3650 let ty = resolve_conditional_branch(
3653 &Atomic::TInt,
3654 &Type::single(Atomic::TFloat),
3655 &Type::single(Atomic::TInt),
3656 &Type::single(Atomic::TString),
3657 );
3658 assert!(matches!(
3659 ty.as_ref(),
3660 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3661 ));
3662 }
3663
3664 #[test]
3665 fn resolve_conditional_array_subject_string_arg_false_branch() {
3666 let ty = resolve_conditional_branch(
3667 &Atomic::TArray {
3668 key: Box::new(Type::mixed()),
3669 value: Box::new(Type::mixed()),
3670 },
3671 &Type::single(Atomic::TString),
3672 &Type::single(Atomic::TInt),
3673 &Type::single(Atomic::TString),
3674 );
3675 assert!(matches!(
3676 ty.as_ref(),
3677 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3678 ));
3679 }
3680
3681 #[test]
3682 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3683 let ty = resolve_conditional_branch(
3684 &Atomic::TArray {
3685 key: Box::new(Type::mixed()),
3686 value: Box::new(Type::mixed()),
3687 },
3688 &Type::single(Atomic::TBool),
3689 &Type::single(Atomic::TInt),
3690 &Type::single(Atomic::TString),
3691 );
3692 assert!(matches!(
3693 ty.as_ref(),
3694 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3695 ));
3696 }
3697
3698 #[test]
3699 fn resolve_conditional_array_subject_mixed_arg_widens() {
3700 let ty = resolve_conditional_branch(
3704 &Atomic::TArray {
3705 key: Box::new(Type::mixed()),
3706 value: Box::new(Type::mixed()),
3707 },
3708 &Type::mixed(),
3709 &Type::single(Atomic::TInt),
3710 &Type::single(Atomic::TString),
3711 );
3712 assert!(ty.is_none());
3713 }
3714
3715 #[test]
3716 fn resolve_conditional_array_subject_list_arg_then_branch() {
3717 let ty = resolve_conditional_branch(
3720 &Atomic::TArray {
3721 key: Box::new(Type::mixed()),
3722 value: Box::new(Type::mixed()),
3723 },
3724 &Type::single(Atomic::TList {
3725 value: Box::new(Type::mixed()),
3726 }),
3727 &Type::single(Atomic::TInt),
3728 &Type::single(Atomic::TString),
3729 );
3730 assert!(matches!(
3731 ty.as_ref(),
3732 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3733 ));
3734 }
3735
3736 #[test]
3737 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3738 let ty = resolve_conditional_branch(
3740 &Atomic::TList {
3741 value: Box::new(Type::mixed()),
3742 },
3743 &Type::single(Atomic::TKeyedArray {
3744 properties: Arc::default(),
3745 is_open: false,
3746 is_list: true,
3747 }),
3748 &Type::single(Atomic::TInt),
3749 &Type::single(Atomic::TString),
3750 );
3751 assert!(matches!(
3752 ty.as_ref(),
3753 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3754 ));
3755 }
3756
3757 #[test]
3758 fn resolve_conditional_list_subject_list_string_union_widens() {
3759 let ty = resolve_conditional_branch(
3763 &Atomic::TList {
3764 value: Box::new(Type::mixed()),
3765 },
3766 &Type::from_vec(vec![
3767 Atomic::TList {
3768 value: Box::new(Type::mixed()),
3769 },
3770 Atomic::TString,
3771 ]),
3772 &Type::single(Atomic::TInt),
3773 &Type::single(Atomic::TString),
3774 );
3775 assert!(ty.is_none());
3776 }
3777
3778 #[test]
3779 fn resolve_conditional_numeric_subject_widens() {
3780 let ty = resolve_conditional_branch(
3784 &Atomic::TNumeric,
3785 &Type::single(Atomic::TLiteralString("123".into())),
3786 &Type::single(Atomic::TInt),
3787 &Type::single(Atomic::TString),
3788 );
3789 assert!(ty.is_none());
3790 }
3791
3792 #[test]
3793 fn resolve_conditional_non_empty_string_subject_widens() {
3794 let ty = resolve_conditional_branch(
3797 &Atomic::TNonEmptyString,
3798 &Type::single(Atomic::TString),
3799 &Type::single(Atomic::TInt),
3800 &Type::single(Atomic::TString),
3801 );
3802 assert!(ty.is_none());
3803 }
3804
3805 #[test]
3806 fn resolve_conditional_template_arg_widens() {
3807 let ty = resolve_conditional_branch(
3810 &Atomic::TString,
3811 &t_param("T"),
3812 &Type::single(Atomic::TInt),
3813 &Type::single(Atomic::TString),
3814 );
3815 assert!(ty.is_none());
3816 }
3817
3818 #[test]
3819 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3820 let ty = resolve_conditional_branch(
3823 &Atomic::TMixed,
3824 &Type::single(Atomic::TBool),
3825 &Type::single(Atomic::TInt),
3826 &Type::single(Atomic::TString),
3827 );
3828 assert!(matches!(
3829 ty.as_ref(),
3830 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3831 ));
3832 }
3833
3834 #[test]
3835 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3836 let ty = resolve_conditional_branch(
3839 &Atomic::TScalar,
3840 &Type::single(Atomic::TNull),
3841 &Type::single(Atomic::TInt),
3842 &Type::single(Atomic::TString),
3843 );
3844 assert!(matches!(
3845 ty.as_ref(),
3846 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3847 ));
3848 }
3849
3850 #[test]
3851 fn resolve_conditional_object_subject_named_arg_then_branch() {
3852 let ty = resolve_conditional_branch(
3854 &Atomic::TObject,
3855 &Type::single(Atomic::TNamedObject {
3856 fqcn: Name::new("Foo"),
3857 type_params: empty_type_params(),
3858 }),
3859 &Type::single(Atomic::TInt),
3860 &Type::single(Atomic::TString),
3861 );
3862 assert!(matches!(
3863 ty.as_ref(),
3864 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3865 ));
3866 }
3867
3868 #[test]
3869 fn resolve_conditional_named_subject_other_class_widens() {
3870 let ty = resolve_conditional_branch(
3874 &Atomic::TNamedObject {
3875 fqcn: Name::new("Foo"),
3876 type_params: empty_type_params(),
3877 },
3878 &Type::single(Atomic::TNamedObject {
3879 fqcn: Name::new("Bar"),
3880 type_params: empty_type_params(),
3881 }),
3882 &Type::single(Atomic::TInt),
3883 &Type::single(Atomic::TString),
3884 );
3885 assert!(ty.is_none());
3886 }
3887
3888 #[test]
3889 fn resolve_conditional_enum_case_subject_widens() {
3890 let ty = resolve_conditional_branch(
3893 &Atomic::TLiteralEnumCase {
3894 enum_fqcn: Name::new("RoundingMode"),
3895 case_name: Name::new("Unnecessary"),
3896 },
3897 &Type::single(Atomic::TInt),
3898 &Type::single(Atomic::TInt),
3899 &Type::single(Atomic::TString),
3900 );
3901 assert!(ty.is_none());
3902 }
3903
3904 #[test]
3905 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3906 let ty = resolve_conditional_branch(
3908 &Atomic::TNull,
3909 &Type::single(Atomic::TBool),
3910 &Type::single(Atomic::TInt),
3911 &Type::single(Atomic::TString),
3912 );
3913 assert!(matches!(
3914 ty.as_ref(),
3915 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3916 ));
3917 }
3918
3919 #[test]
3920 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3921 let ty = resolve_conditional_branch(
3924 &Atomic::TBool,
3925 &Type::single(Atomic::TTrue),
3926 &Type::single(Atomic::TInt),
3927 &Type::single(Atomic::TString),
3928 );
3929 assert!(matches!(
3930 ty.as_ref(),
3931 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3932 ));
3933 }
3934
3935 #[test]
3936 fn substitute_intersection_parts() {
3937 let ty = Type::single(Atomic::TIntersection {
3938 parts: vec_to_type_params(vec![
3939 Type::single(Atomic::TNamedObject {
3940 fqcn: Name::new("Countable"),
3941 type_params: empty_type_params(),
3942 }),
3943 t_param("T"),
3944 ]),
3945 });
3946 let result = ty.substitute_templates(&bindings_t_string());
3947 let Atomic::TIntersection { parts } = &result.types[0] else {
3948 panic!("expected TIntersection");
3949 };
3950 assert_eq!(parts.len(), 2);
3951 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3952 assert!(matches!(parts[1].types[0], Atomic::TString));
3953 }
3954
3955 fn shape(props: &[(&str, Atomic, bool)], is_open: bool) -> Atomic {
3956 Atomic::TKeyedArray {
3957 properties: Arc::new(
3958 props
3959 .iter()
3960 .map(|(k, ty, optional)| {
3961 (
3962 ArrayKey::String(Arc::from(*k)),
3963 KeyedProperty {
3964 ty: Type::single(ty.clone()),
3965 optional: *optional,
3966 },
3967 )
3968 })
3969 .collect(),
3970 ),
3971 is_open,
3972 is_list: false,
3973 }
3974 }
3975
3976 fn shape_with_bits(bits: u32, is_open: bool) -> Atomic {
3978 let names: Vec<String> = (0..5).map(|i| format!("k{i}")).collect();
3979 let mut props = vec![("a", Atomic::TInt, false)];
3980 props.extend(
3981 (0..5)
3982 .filter(|i| bits & (1 << i) != 0)
3983 .map(|i| (names[i].as_str(), Atomic::TString, false)),
3984 );
3985 shape(&props, is_open)
3986 }
3987
3988 fn keyed_count(ty: &Type) -> usize {
3989 ty.types
3990 .iter()
3991 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
3992 .count()
3993 }
3994
3995 #[test]
3996 fn add_type_keeps_few_shapes_apart() {
3997 let mut ty = Type::empty();
3998 for bits in 0..MAX_KEYED_SHAPES as u32 {
3999 ty.add_type(shape_with_bits(bits, false));
4000 }
4001 assert_eq!(keyed_count(&ty), MAX_KEYED_SHAPES);
4002 }
4003
4004 #[test]
4005 fn add_type_folds_shapes_past_the_limit() {
4006 let mut ty = Type::empty();
4007 for bits in 0..32 {
4008 ty.add_type(shape_with_bits(bits, false));
4009 }
4010 assert!(keyed_count(&ty) <= MAX_KEYED_SHAPES);
4011 for t in &ty.types {
4013 let Atomic::TKeyedArray { properties, .. } = t else {
4014 continue;
4015 };
4016 assert!(!properties[&ArrayKey::String(Arc::from("a"))].optional);
4017 }
4018 for i in 0..5 {
4019 let key = ArrayKey::String(Arc::from(format!("k{i}").as_str()));
4020 assert!(ty.types.iter().any(|t| matches!(
4021 t,
4022 Atomic::TKeyedArray { properties, .. } if properties.contains_key(&key)
4023 )));
4024 }
4025 }
4026
4027 #[test]
4028 fn combine_keeps_emptiness_and_open_shape_soundness() {
4029 assert!(combine_keyed_arrays(
4031 &shape(&[], false),
4032 &shape(&[("a", Atomic::TInt, false)], false)
4033 )
4034 .is_none());
4035 assert!(combine_keyed_arrays(
4037 &shape(&[("a", Atomic::TInt, false)], true),
4038 &shape(&[("a", Atomic::TInt, false)], false)
4039 )
4040 .is_none());
4041 assert_eq!(
4043 combine_keyed_arrays(
4044 &shape(&[("a", Atomic::TInt, false)], true),
4045 &shape(
4046 &[("a", Atomic::TInt, false), ("k", Atomic::TString, false)],
4047 true
4048 )
4049 ),
4050 Some(shape(&[("a", Atomic::TInt, false)], true))
4051 );
4052 }
4053
4054 #[test]
4055 fn substitute_no_template_params_identity() {
4056 let ty = Type::single(Atomic::TInt);
4057 let result = ty.substitute_templates(&bindings_t_string());
4058 assert!(matches!(result.types[0], Atomic::TInt));
4059 }
4060}