1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::Atomic;
7use crate::symbol::Name;
8
9pub fn empty_type_params() -> Arc<[Type]> {
13 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
14 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
15}
16
17pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
20 if v.is_empty() {
21 empty_type_params()
22 } else {
23 Arc::from(v)
24 }
25}
26
27pub type AtomicVec = SmallVec<[Atomic; 2]>;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum CloneValidity {
33 Cloneable,
35 Invalid,
37 PossiblyInvalid,
39 Unknown,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub struct Type {
49 pub types: AtomicVec,
50 pub possibly_undefined: bool,
52 pub from_docblock: bool,
54 pub falsy_stripped: bool,
62 pub possibly_absent_offset: bool,
73}
74
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
104impl Type {
105 pub fn empty() -> Self {
108 Self {
109 types: SmallVec::new(),
110 possibly_undefined: false,
111 from_docblock: false,
112 falsy_stripped: false,
113 possibly_absent_offset: false,
114 }
115 }
116
117 pub fn single(atomic: Atomic) -> Self {
118 let mut types = SmallVec::new();
119 types.push(atomic);
120 Self {
121 types,
122 possibly_undefined: false,
123 from_docblock: false,
124 falsy_stripped: false,
125 possibly_absent_offset: false,
126 }
127 }
128
129 pub fn mixed() -> Self {
130 Self::single(Atomic::TMixed)
131 }
132
133 pub fn void() -> Self {
134 Self::single(Atomic::TVoid)
135 }
136
137 pub fn never() -> Self {
138 Self::single(Atomic::TNever)
139 }
140
141 pub fn null() -> Self {
142 Self::single(Atomic::TNull)
143 }
144
145 pub fn bool() -> Self {
146 Self::single(Atomic::TBool)
147 }
148
149 pub fn int() -> Self {
150 Self::single(Atomic::TInt)
151 }
152
153 pub fn float() -> Self {
154 Self::single(Atomic::TFloat)
155 }
156
157 pub fn string() -> Self {
158 Self::single(Atomic::TString)
159 }
160
161 pub fn array_key() -> Self {
164 let mut u = Self::single(Atomic::TInt);
165 u.add_type(Atomic::TString);
166 u
167 }
168
169 pub fn nullable(atomic: Atomic) -> Self {
171 if matches!(atomic, Atomic::TMixed) {
173 return Self::mixed();
174 }
175 let mut types = SmallVec::new();
176 types.push(atomic);
177 types.push(Atomic::TNull);
178 Self {
179 types,
180 possibly_undefined: false,
181 from_docblock: false,
182 falsy_stripped: false,
183 possibly_absent_offset: false,
184 }
185 }
186
187 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
189 let mut u = Self::empty();
190 for a in atomics {
191 u.add_type(a);
192 }
193 u
194 }
195
196 pub fn is_empty(&self) -> bool {
199 self.types.is_empty()
200 }
201
202 pub fn is_single(&self) -> bool {
203 self.types.len() == 1
204 }
205
206 pub fn is_nullable(&self) -> bool {
207 self.types.iter().any(|t| matches!(t, Atomic::TNull))
208 }
209
210 pub fn is_array_key(&self) -> bool {
215 self.types.len() == 2
216 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
217 && self.types.iter().any(|t| matches!(t, Atomic::TString))
218 }
219
220 pub fn is_mixed(&self) -> bool {
221 self.types.iter().any(|t| match t {
222 Atomic::TMixed => true,
223 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
224 _ => false,
225 })
226 }
227
228 pub fn is_mixed_not_template(&self) -> bool {
233 self.is_mixed()
234 && !self
235 .types
236 .iter()
237 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
238 }
239
240 pub fn is_never(&self) -> bool {
241 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
242 }
243
244 pub fn clone_validity(&self) -> CloneValidity {
247 if self.types.is_empty() {
248 return CloneValidity::Unknown;
249 }
250 let mut has_non_object = false;
251 let mut has_other = false; for t in &self.types {
253 match t {
254 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
255 CloneValidity::Invalid => has_non_object = true,
256 CloneValidity::PossiblyInvalid => {
257 has_non_object = true;
258 has_other = true;
259 }
260 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
261 },
262 other if other.is_definitely_non_object() => has_non_object = true,
263 _ => has_other = true,
264 }
265 }
266 match (has_non_object, has_other) {
267 (true, false) => CloneValidity::Invalid,
268 (true, true) => CloneValidity::PossiblyInvalid,
269 _ => CloneValidity::Cloneable,
270 }
271 }
272
273 pub fn is_void(&self) -> bool {
274 self.is_single() && matches!(self.types[0], Atomic::TVoid)
275 }
276
277 pub fn can_be_falsy(&self) -> bool {
278 self.types.iter().any(|t| t.can_be_falsy())
279 }
280
281 pub fn can_be_truthy(&self) -> bool {
282 self.types.iter().any(|t| t.can_be_truthy())
283 }
284
285 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
286 self.types.iter().any(f)
287 }
288
289 pub fn has_named_object(&self, fqcn: &str) -> bool {
290 self.types.iter().any(|t| match t {
291 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
292 _ => false,
293 })
294 }
295
296 pub fn add_type(&mut self, atomic: Atomic) {
301 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
303 return;
304 }
305
306 if matches!(atomic, Atomic::TMixed) {
308 self.types.clear();
309 self.types.push(Atomic::TMixed);
310 return;
311 }
312
313 let atomic = if let Atomic::TConditional { data } = &atomic {
316 let (if_true, if_false) = (&data.if_true, &data.if_false);
317 let mut simplified_true = Type::empty();
318 for t in &if_true.types {
319 simplified_true.add_type(t.clone());
320 }
321 let mut simplified_false = Type::empty();
322 for t in &if_false.types {
323 simplified_false.add_type(t.clone());
324 }
325 if simplified_true == simplified_false {
326 for t in simplified_true.types {
327 self.add_type(t);
328 }
329 return;
330 }
331 atomic
332 } else {
333 atomic
334 };
335
336 if self.types.contains(&atomic) {
338 return;
339 }
340
341 if let Some(new_bounds) = int_bounds(&atomic) {
344 if self
345 .types
346 .iter()
347 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
348 {
349 return;
350 }
351 self.types
352 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
353 }
354 if let Atomic::TLiteralString(_) = &atomic {
356 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
357 return;
358 }
359 }
360 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
362 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
363 {
364 return;
365 }
366 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
369 {
370 self.types.retain(|t| !matches!(t, Atomic::TFalse));
371 self.types.push(Atomic::TBool);
372 return;
373 }
374 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
375 {
376 self.types.retain(|t| !matches!(t, Atomic::TTrue));
377 self.types.push(Atomic::TBool);
378 return;
379 }
380 if matches!(atomic, Atomic::TString) {
382 self.types
383 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
384 }
385 if matches!(atomic, Atomic::TBool) {
387 self.types
388 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
389 }
390
391 if matches!(atomic, Atomic::TNever) {
393 if !self.types.is_empty() {
394 return;
395 }
396 } else {
397 self.types.retain(|t| !matches!(t, Atomic::TNever));
398 }
399
400 if let Atomic::TKeyedArray {
406 properties,
407 is_open,
408 ..
409 } = &atomic
410 {
411 if properties.is_empty() && !is_open {
412 for existing in &self.types {
413 match existing {
414 Atomic::TArray { .. }
415 | Atomic::TNonEmptyArray { .. }
416 | Atomic::TList { .. }
417 | Atomic::TNonEmptyList { .. } => {
418 return; }
420 _ => {}
421 }
422 }
423 }
424 }
425
426 let is_generic_array_or_list = matches!(
429 &atomic,
430 Atomic::TArray { .. }
431 | Atomic::TNonEmptyArray { .. }
432 | Atomic::TList { .. }
433 | Atomic::TNonEmptyList { .. }
434 );
435 if is_generic_array_or_list {
436 self.types.retain(|t| {
437 if let Atomic::TKeyedArray {
438 properties,
439 is_open,
440 ..
441 } = t
442 {
443 !properties.is_empty() || *is_open
444 } else {
445 true
446 }
447 });
448 }
449
450 self.types.push(atomic);
451 }
452
453 pub fn remove_null(&self) -> Type {
457 self.filter(|t| !matches!(t, Atomic::TNull))
458 }
459
460 pub fn remove_false(&self) -> Type {
463 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
464 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
465 result.add_type(Atomic::TTrue);
466 }
467 result
468 }
469
470 pub fn remove_true(&self) -> Type {
473 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
474 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
475 result.add_type(Atomic::TFalse);
476 }
477 result
478 }
479
480 pub fn core_type(&self) -> Type {
482 self.remove_null().remove_false()
483 }
484
485 pub fn narrow_to_truthy(&self) -> Type {
487 if self.is_mixed_not_template() {
488 return Type::mixed();
489 }
490 let mut result = Type::empty();
491 result.from_docblock = self.from_docblock;
492 for t in &self.types {
493 match t {
494 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
498 Atomic::TLiteralInt(0)
500 | Atomic::TLiteralFloat(0, 0)
501 | Atomic::TNull
502 | Atomic::TFalse => {}
503 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
504 Atomic::TBool => result.add_type(Atomic::TTrue),
506 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
508 key: key.clone(),
509 value: value.clone(),
510 }),
511 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
512 value: value.clone(),
513 }),
514 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
518 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
523 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
524 let atom = if max.is_none() {
525 Atomic::TPositiveInt
526 } else {
527 Atomic::TIntRange {
528 min: Some(1),
529 max: *max,
530 }
531 };
532 result.add_type(atom);
533 }
534 Atomic::TIntRange { min, max: Some(0) } => {
536 let atom = match min {
537 None => Atomic::TNegativeInt,
538 Some(n) if *n <= -1 => Atomic::TIntRange {
539 min: *min,
540 max: Some(-1),
541 },
542 _ => continue, };
544 result.add_type(atom);
545 }
546 t if !t.can_be_truthy() => {}
548 _ => result.add_type(t.clone()),
549 }
550 }
551 result
552 }
553
554 pub fn narrow_to_falsy(&self) -> Type {
556 if self.is_mixed_not_template() {
557 return Type::from_vec(vec![
558 Atomic::TNull,
559 Atomic::TFalse,
560 Atomic::TLiteralInt(0),
561 Atomic::TLiteralString("".into()),
562 ]);
563 }
564 let mut result = Type::empty();
565 result.from_docblock = self.from_docblock;
566 for t in &self.types {
567 match t {
568 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
573 Atomic::TBool => result.add_type(Atomic::TFalse),
575 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
577 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
579 Atomic::TString => {
581 result.add_type(Atomic::TLiteralString("".into()));
582 result.add_type(Atomic::TLiteralString("0".into()));
583 }
584 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
586 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
588 Atomic::TIntRange {
590 min: Some(0),
591 max: Some(_) | None,
592 } => result.add_type(Atomic::TLiteralInt(0)),
593 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
595 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
597 }
598 }
599 result
600 }
601
602 pub fn narrow_instanceof(&self, class: &str) -> Type {
608 let narrowed_ty = Atomic::TNamedObject {
609 fqcn: class.into(),
610 type_params: empty_type_params(),
611 };
612 let has_object = self.types.iter().any(|t| {
614 matches!(
615 t,
616 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
618 });
619 if has_object || self.is_empty() {
620 Type::single(narrowed_ty)
621 } else {
622 Type::single(narrowed_ty)
625 }
626 }
627
628 pub fn narrow_to_string(&self) -> Type {
632 self.filter_replacing(
633 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
634 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
635 Atomic::TString,
636 )
637 }
638
639 pub fn narrow_to_int(&self) -> Type {
641 self.filter_replacing(
642 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
643 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
644 Atomic::TInt,
645 )
646 }
647
648 pub fn narrow_to_float(&self) -> Type {
650 self.filter_replacing(
651 |t| {
652 matches!(
653 t,
654 Atomic::TFloat
655 | Atomic::TIntegralFloat
656 | Atomic::TLiteralFloat(..)
657 | Atomic::TTemplateParam { .. }
658 )
659 },
660 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
661 Atomic::TFloat,
662 )
663 }
664
665 pub fn narrow_to_bool(&self) -> Type {
667 self.filter_replacing(
668 |t| {
669 matches!(
670 t,
671 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
672 )
673 },
674 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
675 Atomic::TBool,
676 )
677 }
678
679 pub fn narrow_to_null(&self) -> Type {
681 self.filter_replacing(
682 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
683 |t| matches!(t, Atomic::TMixed),
684 Atomic::TNull,
685 )
686 }
687
688 pub fn narrow_to_array(&self) -> Type {
690 self.filter_replacing(
691 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
692 |t| matches!(t, Atomic::TMixed),
693 Atomic::TArray {
694 key: Box::new(Type::mixed()),
695 value: Box::new(Type::mixed()),
696 },
697 )
698 }
699
700 pub fn widen_nested_non_empty(&self) -> Type {
703 fn relax_all(ty: &Type) -> Type {
704 map_atoms(ty, |a| match a {
705 Atomic::TNonEmptyList { value } => Atomic::TList {
706 value: Box::new(relax_all(value)),
707 },
708 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
709 key: key.clone(),
710 value: Box::new(relax_all(value)),
711 },
712 other => map_values(other, relax_all),
713 })
714 }
715 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
716 match atomic {
717 Atomic::TArray { key, value } => Atomic::TArray {
718 key: key.clone(),
719 value: Box::new(f(value)),
720 },
721 Atomic::TList { value } => Atomic::TList {
722 value: Box::new(f(value)),
723 },
724 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
725 key: key.clone(),
726 value: Box::new(f(value)),
727 },
728 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
729 value: Box::new(f(value)),
730 },
731 Atomic::TKeyedArray {
732 properties,
733 is_open,
734 is_list,
735 } => Atomic::TKeyedArray {
736 properties: Box::new(
737 properties
738 .iter()
739 .map(|(k, p)| {
740 (
741 k.clone(),
742 crate::atomic::KeyedProperty {
743 ty: f(&p.ty),
744 optional: p.optional,
745 },
746 )
747 })
748 .collect(),
749 ),
750 is_open: *is_open,
751 is_list: *is_list,
752 },
753 other => other.clone(),
754 }
755 }
756 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
757 let mut result = Type::empty();
758 result.possibly_undefined = ty.possibly_undefined;
759 result.from_docblock = ty.from_docblock;
760 for atomic in &ty.types {
761 result.add_type(f(atomic));
762 }
763 result
764 }
765 map_atoms(self, |a| map_values(a, relax_all))
766 }
767
768 pub fn narrow_to_non_empty_collection(&self) -> Type {
771 let mut out = Type::empty();
772 out.from_docblock = self.from_docblock;
773 for t in &self.types {
774 match t {
775 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
776 key: key.clone(),
777 value: value.clone(),
778 }),
779 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
780 value: value.clone(),
781 }),
782 Atomic::TKeyedArray {
783 properties,
784 is_open: false,
785 ..
786 } if properties.is_empty() => {}
787 _ => out.add_type(t.clone()),
788 }
789 }
790 out
791 }
792
793 pub fn narrow_to_empty_collection(&self) -> Type {
803 let mut out = Type::empty();
804 out.from_docblock = self.from_docblock;
805 for t in &self.types {
806 match t {
807 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
808 Atomic::TArray { .. } | Atomic::TList { .. } => {
809 out.add_type(Atomic::TKeyedArray {
810 properties: Box::default(),
811 is_open: false,
812 is_list: true,
813 });
814 }
815 _ => out.add_type(t.clone()),
816 }
817 }
818 out
819 }
820
821 pub fn narrow_to_list(&self) -> Type {
833 let mut out = Type::empty();
834 out.from_docblock = self.from_docblock;
835 for t in &self.types {
836 match t {
837 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
838 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
844 out.add_type(Atomic::TList {
845 value: value.clone(),
846 });
847 }
848 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
849 out.add_type(Atomic::TNonEmptyList {
850 value: value.clone(),
851 });
852 }
853 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
854 Atomic::TMixed => out.add_type(Atomic::TList {
855 value: Box::new(Type::mixed()),
856 }),
857 _ => {}
858 }
859 }
860 if out.is_empty() {
861 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
862 } else {
863 out
864 }
865 }
866
867 pub fn narrow_to_object(&self) -> Type {
872 let mut out = Type::empty();
873 for t in &self.types {
874 if matches!(t, Atomic::TMixed) {
875 out.add_type(Atomic::TObject);
876 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
877 out.add_type(t.clone());
878 }
879 }
880 if out.types.is_empty() {
881 self.filter(|t| t.is_object())
882 } else {
883 out
884 }
885 }
886
887 pub fn narrow_to_callable(&self) -> Type {
894 let narrowed = self.filter(|t| {
895 t.is_callable()
896 || t.is_string()
897 || t.is_array()
898 || t.is_object()
899 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
900 });
901 let mut result = Type::empty();
908 result.possibly_undefined = narrowed.possibly_undefined;
909 result.from_docblock = narrowed.from_docblock;
910 for atomic in narrowed.types {
911 if matches!(atomic, Atomic::TObject) {
912 result.add_type(Atomic::TCallable {
913 params: None,
914 return_type: None,
915 });
916 } else {
917 result.add_type(atomic);
918 }
919 }
920 result
921 }
922
923 pub fn narrow_to_scalar(&self) -> Type {
925 self.filter_replacing(
926 |t| {
927 t.is_string()
928 || t.is_int()
929 || matches!(
930 t,
931 Atomic::TFloat
932 | Atomic::TIntegralFloat
933 | Atomic::TLiteralFloat(..)
934 | Atomic::TBool
935 | Atomic::TTrue
936 | Atomic::TFalse
937 | Atomic::TScalar
938 | Atomic::TNumeric
939 | Atomic::TNumericString
940 | Atomic::TTemplateParam { .. }
941 )
942 },
943 |t| matches!(t, Atomic::TMixed),
944 Atomic::TScalar,
945 )
946 }
947
948 pub fn narrow_to_iterable(&self) -> Type {
951 self.filter(|t| {
952 t.is_array()
953 || t.is_object()
954 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
955 })
956 }
957
958 pub fn narrow_to_countable(&self) -> Type {
961 self.filter(|t| {
962 t.is_array()
963 || t.is_object()
964 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
965 })
966 }
967
968 pub fn narrow_to_resource(&self) -> Type {
972 self.filter(|t| matches!(t, Atomic::TMixed))
974 }
975
976 pub fn narrow_to_class_string(&self) -> Type {
981 let mut out = Type::empty();
982 out.from_docblock = self.from_docblock;
983 for t in &self.types {
984 match t {
985 Atomic::TClassString(_) => out.add_type(t.clone()),
986 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
987 out.add_type(Atomic::TClassString(None));
988 }
989 _ => {}
990 }
991 }
992 out
993 }
994
995 pub fn narrow_to_interface_string(&self) -> Type {
1000 let mut out = Type::empty();
1001 out.from_docblock = self.from_docblock;
1002 for t in &self.types {
1003 match t {
1004 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1005 Atomic::TClassString(name) => {
1009 out.add_type(Atomic::TInterfaceString(*name));
1010 }
1011 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1012 out.add_type(Atomic::TInterfaceString(None));
1013 }
1014 _ => {}
1015 }
1016 }
1017 out
1018 }
1019
1020 pub fn merge(a: &Type, b: &Type) -> Type {
1025 if b.types.is_empty() {
1027 let mut result = a.clone();
1028 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1029 return result;
1030 }
1031 if a.types.is_empty() {
1033 let mut result = b.clone();
1034 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1035 return result;
1036 }
1037 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1039 let mut result = a.clone();
1040 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1041 return result;
1042 }
1043 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1045 return Type {
1046 types: smallvec::smallvec![Atomic::TMixed],
1047 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1048 from_docblock: a.from_docblock || b.from_docblock,
1049 falsy_stripped: false,
1050 possibly_absent_offset: false,
1051 };
1052 }
1053 let mut result = a.clone();
1054 result.merge_with(b);
1055 result
1056 }
1057
1058 pub fn merge_with(&mut self, other: &Type) {
1060 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1061 self.possibly_undefined |= other.possibly_undefined;
1062 return;
1063 }
1064 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1065 self.types.clear();
1066 self.types.push(Atomic::TMixed);
1067 self.possibly_undefined |= other.possibly_undefined;
1068 return;
1069 }
1070 for atomic in &other.types {
1071 self.add_type(atomic.clone());
1072 }
1073 self.possibly_undefined |= other.possibly_undefined;
1074 }
1075
1076 pub fn intersect_with(&self, other: &Type) -> Type {
1080 if self.is_mixed() {
1081 return other.clone();
1082 }
1083 if other.is_mixed() {
1084 return self.clone();
1085 }
1086 let mut result = Type::empty();
1094 for a in &self.types {
1095 for b in &other.types {
1096 if a == b {
1097 result.add_type(a.clone());
1098 } else if atomic_subtype(b, a) {
1099 result.add_type(b.clone());
1100 } else if atomic_subtype(a, b) {
1101 result.add_type(a.clone());
1102 }
1103 }
1104 }
1105 if result.is_empty() {
1106 Type::never()
1107 } else {
1108 result
1109 }
1110 }
1111
1112 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1116 if bindings.is_empty() {
1117 return self.clone();
1118 }
1119 if !self.types.iter().any(atomic_may_contain_templates) {
1122 return self.clone();
1123 }
1124 let mut result = Type::empty();
1125 result.possibly_undefined = self.possibly_undefined;
1126 result.from_docblock = self.from_docblock;
1127 for atomic in &self.types {
1128 match atomic {
1129 Atomic::TTemplateParam { name, .. } => {
1130 if let Some(resolved) = bindings.get(name) {
1131 for t in &resolved.types {
1132 result.add_type(t.clone());
1133 }
1134 } else {
1135 result.add_type(atomic.clone());
1136 }
1137 }
1138 Atomic::TArray { key, value } => {
1139 result.add_type(Atomic::TArray {
1140 key: Box::new(key.substitute_templates(bindings)),
1141 value: Box::new(value.substitute_templates(bindings)),
1142 });
1143 }
1144 Atomic::TList { value } => {
1145 result.add_type(Atomic::TList {
1146 value: Box::new(value.substitute_templates(bindings)),
1147 });
1148 }
1149 Atomic::TNonEmptyArray { key, value } => {
1150 result.add_type(Atomic::TNonEmptyArray {
1151 key: Box::new(key.substitute_templates(bindings)),
1152 value: Box::new(value.substitute_templates(bindings)),
1153 });
1154 }
1155 Atomic::TNonEmptyList { value } => {
1156 result.add_type(Atomic::TNonEmptyList {
1157 value: Box::new(value.substitute_templates(bindings)),
1158 });
1159 }
1160 Atomic::TKeyedArray {
1161 properties,
1162 is_open,
1163 is_list,
1164 } => {
1165 use crate::atomic::KeyedProperty;
1166 let new_props = properties
1167 .iter()
1168 .map(|(k, prop)| {
1169 (
1170 k.clone(),
1171 KeyedProperty {
1172 ty: prop.ty.substitute_templates(bindings),
1173 optional: prop.optional,
1174 },
1175 )
1176 })
1177 .collect();
1178 result.add_type(Atomic::TKeyedArray {
1179 properties: Box::new(new_props),
1180 is_open: *is_open,
1181 is_list: *is_list,
1182 });
1183 }
1184 Atomic::TCallable {
1185 params,
1186 return_type,
1187 } => {
1188 result.add_type(Atomic::TCallable {
1189 params: params.as_ref().map(|ps| {
1190 ps.iter()
1191 .map(|p| substitute_in_fn_param(p, bindings))
1192 .collect()
1193 }),
1194 return_type: return_type
1195 .as_ref()
1196 .map(|r| Box::new(r.substitute_templates(bindings))),
1197 });
1198 }
1199 Atomic::TClosure { data } => {
1200 result.add_type(Atomic::TClosure {
1201 data: Box::new(crate::atomic::ClosureData {
1202 params: data
1203 .params
1204 .iter()
1205 .map(|p| substitute_in_fn_param(p, bindings))
1206 .collect(),
1207 return_type: data.return_type.substitute_templates(bindings),
1208 this_type: data
1209 .this_type
1210 .as_ref()
1211 .map(|t| t.substitute_templates(bindings)),
1212 }),
1213 });
1214 }
1215 Atomic::TConditional { data } => {
1216 let param_name = &data.param_name;
1217 let new_subject = data.subject.substitute_templates(bindings);
1218 let new_if_true = data.if_true.substitute_templates(bindings);
1219 let new_if_false = data.if_false.substitute_templates(bindings);
1220
1221 let resolved = if let Some(name) = param_name {
1225 if let Some(bound) = bindings.get(name) {
1226 if new_subject.types.len() == 1 {
1227 resolve_conditional_branch(
1228 &new_subject.types[0],
1229 bound,
1230 &new_if_true,
1231 &new_if_false,
1232 )
1233 } else {
1234 None
1235 }
1236 } else {
1237 None
1238 }
1239 } else {
1240 None
1241 };
1242
1243 if let Some(branch) = resolved {
1244 for t in branch.types {
1245 result.add_type(t);
1246 }
1247 } else {
1248 result.add_type(Atomic::TConditional {
1249 data: Box::new(crate::atomic::ConditionalData {
1250 param_name: *param_name,
1251 subject: new_subject,
1252 if_true: new_if_true,
1253 if_false: new_if_false,
1254 }),
1255 });
1256 }
1257 }
1258 Atomic::TKeyOf { target } => {
1259 let new_target = target.substitute_templates(bindings);
1260 if let Some(resolved) = eval_key_of_type(&new_target) {
1261 result.merge_with(&resolved);
1262 } else {
1263 result.add_type(Atomic::TKeyOf {
1264 target: Box::new(new_target),
1265 });
1266 }
1267 }
1268 Atomic::TValueOf { target } => {
1269 let new_target = target.substitute_templates(bindings);
1270 if let Some(resolved) = eval_value_of_type(&new_target) {
1271 result.merge_with(&resolved);
1272 } else {
1273 result.add_type(Atomic::TValueOf {
1274 target: Box::new(new_target),
1275 });
1276 }
1277 }
1278 Atomic::TIntersection { parts } => {
1279 result.add_type(Atomic::TIntersection {
1280 parts: vec_to_type_params(
1281 parts
1282 .iter()
1283 .map(|p| p.substitute_templates(bindings))
1284 .collect(),
1285 ),
1286 });
1287 }
1288 Atomic::TNamedObject { fqcn, type_params } => {
1289 if type_params.is_empty() && !fqcn.contains('\\') {
1291 if let Some(resolved) = bindings.get(fqcn) {
1292 for t in &resolved.types {
1293 result.add_type(t.clone());
1294 }
1295 continue;
1296 }
1297 }
1298 let new_params: Vec<Type> = type_params
1299 .iter()
1300 .map(|p| p.substitute_templates(bindings))
1301 .collect();
1302 result.add_type(Atomic::TNamedObject {
1303 fqcn: *fqcn,
1304 type_params: vec_to_type_params(new_params),
1305 });
1306 }
1307 Atomic::TClassString(Some(param_name)) => {
1309 if let Some(resolved) = bindings.get(param_name) {
1310 for r_atomic in &resolved.types {
1311 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1312 Some(*fqcn)
1313 } else {
1314 None
1315 };
1316 result.add_type(Atomic::TClassString(cls_name));
1317 }
1318 } else {
1319 result.add_type(atomic.clone());
1320 }
1321 }
1322 Atomic::TInterfaceString(Some(param_name)) => {
1324 if let Some(resolved) = bindings.get(param_name) {
1325 for r_atomic in &resolved.types {
1326 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1327 Some(*fqcn)
1328 } else {
1329 None
1330 };
1331 result.add_type(Atomic::TInterfaceString(iface_name));
1332 }
1333 } else {
1334 result.add_type(atomic.clone());
1335 }
1336 }
1337 _ => {
1338 result.add_type(atomic.clone());
1339 }
1340 }
1341 }
1342 result
1343 }
1344
1345 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1351 where
1352 F: Fn(&str) -> Option<Type>,
1353 {
1354 self.resolve_conditional_inner(&lookup)
1355 }
1356
1357 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1358 where
1359 F: Fn(&str) -> Option<Type>,
1360 {
1361 let mut result = Type::empty();
1362 for atomic in self.types {
1363 match atomic {
1364 Atomic::TConditional { ref data } => {
1365 let (param_name, subject, if_true, if_false) = (
1366 &data.param_name,
1367 &data.subject,
1368 &data.if_true,
1369 &data.if_false,
1370 );
1371 let resolved = if subject.types.len() == 1 {
1372 if let Some(name) = param_name {
1373 if let Some(arg_ty) = lookup(name.as_ref()) {
1374 resolve_conditional_branch(
1375 &subject.types[0],
1376 &arg_ty,
1377 if_true,
1378 if_false,
1379 )
1380 } else {
1381 None
1382 }
1383 } else {
1384 None
1385 }
1386 } else {
1387 None
1388 };
1389
1390 if let Some(branch) = resolved {
1391 for t in branch.resolve_conditional_inner(lookup).types {
1393 result.add_type(t);
1394 }
1395 } else {
1396 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1399 result.add_type(t);
1400 }
1401 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1402 result.add_type(t);
1403 }
1404 }
1405 }
1406 other => result.add_type(other),
1407 }
1408 }
1409 result
1410 }
1411
1412 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1422 if other.is_mixed() {
1423 return true;
1424 }
1425 if self.is_never() {
1426 return true; }
1428 self.types
1429 .iter()
1430 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1431 }
1432
1433 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1436 self.is_subtype_structural(other)
1437 || self.types.iter().all(|a| match a {
1438 Atomic::TTemplateParam { as_type, .. } => {
1439 as_type.is_subtype_by_template_bounds(other)
1440 }
1441 _ => other.accepts_atomic_structural(a),
1442 })
1443 }
1444
1445 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1449 if self.is_mixed() {
1450 return true;
1451 }
1452 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1453 }
1454
1455 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1458 let mut result = Type::empty();
1459 result.possibly_undefined = self.possibly_undefined;
1460 result.from_docblock = self.from_docblock;
1461 for atomic in &self.types {
1462 if f(atomic) {
1463 result.types.push(atomic.clone());
1464 }
1465 }
1466 result
1467 }
1468
1469 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1474 &self,
1475 keep: K,
1476 placeholder: P,
1477 replacement: Atomic,
1478 ) -> Type {
1479 let mut result = Type::empty();
1480 result.possibly_undefined = self.possibly_undefined;
1481 result.from_docblock = self.from_docblock;
1482 for atomic in &self.types {
1483 if keep(atomic) {
1484 result.add_type(atomic.clone());
1485 } else if placeholder(atomic) {
1486 result.add_type(replacement.clone());
1487 }
1488 }
1489 result
1490 }
1491
1492 pub fn possibly_undefined(mut self) -> Self {
1494 self.possibly_undefined = true;
1495 self
1496 }
1497
1498 pub fn from_docblock(mut self) -> Self {
1500 self.from_docblock = true;
1501 self
1502 }
1503
1504 pub fn falsy_stripped(mut self) -> Self {
1507 self.falsy_stripped = true;
1508 self
1509 }
1510
1511 pub fn possibly_absent_offset(mut self) -> Self {
1515 self.possibly_absent_offset = true;
1516 self
1517 }
1518}
1519
1520#[derive(Clone, PartialEq, Eq, Debug)]
1534enum ValueClass {
1535 Int,
1536 Float,
1537 True,
1538 False,
1539 String,
1540 Array,
1541 List,
1543 Object,
1544 Null,
1545 Top,
1548 LitInt(i64),
1551 LitFloat(i64, i64),
1553 LitString(Arc<str>),
1555}
1556
1557impl ValueClass {
1558 fn includes(&self, b: &Self) -> bool {
1563 self == b
1564 || matches!(
1565 (self, b),
1566 (Self::Top, _)
1567 | (Self::Int, Self::LitInt(_))
1568 | (Self::Float, Self::LitFloat(..))
1569 | (Self::String, Self::LitString(_))
1570 | (Self::Array, Self::List)
1571 )
1572 }
1573}
1574
1575fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1581 use ValueClass::*;
1582 match a {
1583 Atomic::TInt
1586 | Atomic::TIntRange { .. }
1587 | Atomic::TPositiveInt
1588 | Atomic::TNegativeInt
1589 | Atomic::TNonNegativeInt => vec![Int],
1590 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1591 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1593 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1594 vec![LitFloat(*int_bits, *frac_bits)]
1595 }
1596 Atomic::TBool => vec![True, False],
1598 Atomic::TTrue => vec![True],
1599 Atomic::TFalse => vec![False],
1600 Atomic::TString
1602 | Atomic::TNonEmptyString
1603 | Atomic::TNumericString
1604 | Atomic::TClassString(_)
1605 | Atomic::TInterfaceString(_)
1606 | Atomic::TEnumString
1607 | Atomic::TTraitString
1608 | Atomic::TCallableString => vec![String],
1609 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1610 Atomic::TArray { .. }
1612 | Atomic::TNonEmptyArray { .. }
1613 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1614 Atomic::TList { .. }
1615 | Atomic::TNonEmptyList { .. }
1616 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1617 Atomic::TObject
1619 | Atomic::TNamedObject { .. }
1620 | Atomic::TStaticObject { .. }
1621 | Atomic::TSelf { .. }
1622 | Atomic::TParent { .. }
1623 | Atomic::TClosure { .. }
1624 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1625 Atomic::TNull => vec![Null],
1628 Atomic::TVoid => vec![Top],
1629 Atomic::TMixed => vec![Top],
1631 Atomic::TScalar => vec![Int, Float, True, False, String],
1632 Atomic::TNumeric => vec![Int, Float],
1635 Atomic::TCallable { .. }
1637 | Atomic::TNever
1638 | Atomic::TTemplateParam { .. }
1639 | Atomic::TKeyOf { .. }
1640 | Atomic::TValueOf { .. }
1641 | Atomic::TConditional { .. }
1642 | Atomic::TIntersection { .. } => Vec::new(),
1643 }
1644}
1645
1646fn subject_is_decidable(subject: &Atomic) -> bool {
1654 matches!(
1655 subject,
1656 Atomic::TNull
1657 | Atomic::TTrue
1658 | Atomic::TFalse
1659 | Atomic::TBool
1660 | Atomic::TString
1661 | Atomic::TInt
1662 | Atomic::TFloat
1663 | Atomic::TArray { .. }
1664 | Atomic::TList { .. }
1665 | Atomic::TObject
1666 | Atomic::TMixed
1667 | Atomic::TScalar
1668 )
1669}
1670
1671fn resolve_conditional_branch(
1693 subject: &Atomic,
1694 arg_ty: &Type,
1695 if_true: &Type,
1696 if_false: &Type,
1697) -> Option<Type> {
1698 if !subject_is_decidable(subject) {
1699 return None;
1700 }
1701 if arg_ty.types.is_empty() {
1702 return None;
1703 }
1704 let subject_classes = value_classes(subject);
1705 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1706 if arg_classes.is_empty() {
1707 return None;
1709 }
1710 let all_match = arg_classes
1711 .iter()
1712 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1713 let any_overlap = arg_classes.iter().any(|c| {
1714 subject_classes
1715 .iter()
1716 .any(|s| s.includes(c) || c.includes(s))
1717 });
1718 if all_match {
1719 Some(if_true.clone())
1720 } else if !any_overlap {
1721 Some(if_false.clone())
1722 } else {
1723 None
1724 }
1725}
1726
1727fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1735 match atomic {
1736 Atomic::TNamedObject { fqcn, type_params } => {
1740 !type_params.is_empty() || !fqcn.contains('\\')
1741 }
1742 Atomic::TTemplateParam { .. }
1743 | Atomic::TKeyOf { .. }
1744 | Atomic::TValueOf { .. }
1745 | Atomic::TArray { .. }
1746 | Atomic::TList { .. }
1747 | Atomic::TNonEmptyArray { .. }
1748 | Atomic::TNonEmptyList { .. }
1749 | Atomic::TKeyedArray { .. }
1750 | Atomic::TCallable { .. }
1751 | Atomic::TClosure { .. }
1752 | Atomic::TConditional { .. }
1753 | Atomic::TIntersection { .. }
1754 | Atomic::TClassString(Some(_))
1755 | Atomic::TInterfaceString(Some(_)) => true,
1756 _ => false,
1757 }
1758}
1759
1760pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1761 let mut result = Type::empty();
1762 for atomic in &t.types {
1763 match atomic {
1764 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1765 for k in &key.types {
1766 result.add_type(k.clone());
1767 }
1768 }
1769 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1770 result.add_type(Atomic::TInt);
1771 }
1772 Atomic::TKeyedArray { properties, .. } => {
1773 for key in properties.keys() {
1774 match key {
1775 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1776 crate::atomic::ArrayKey::String(s) => {
1777 result.add_type(Atomic::TLiteralString(s.clone()))
1778 }
1779 }
1780 }
1781 }
1782 _ => return None,
1783 }
1784 }
1785 (!result.types.is_empty()).then_some(result)
1786}
1787
1788pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1789 let mut result = Type::empty();
1790 for atomic in &t.types {
1791 match atomic {
1792 Atomic::TArray { value, .. }
1793 | Atomic::TNonEmptyArray { value, .. }
1794 | Atomic::TList { value }
1795 | Atomic::TNonEmptyList { value } => {
1796 result.merge_with(value);
1797 }
1798 Atomic::TKeyedArray { properties, .. } => {
1799 for prop in properties.values() {
1800 result.merge_with(&prop.ty);
1801 }
1802 }
1803 _ => return None,
1804 }
1805 }
1806 (!result.types.is_empty()).then_some(result)
1807}
1808
1809fn substitute_in_fn_param(
1810 p: &crate::atomic::FnParam,
1811 bindings: &FxHashMap<Name, Type>,
1812) -> crate::atomic::FnParam {
1813 crate::atomic::FnParam {
1814 name: p.name,
1815 ty: p.ty.as_ref().map(|t| {
1816 let u = t.to_union();
1817 let substituted = u.substitute_templates(bindings);
1818 crate::compact::SimpleType::from_union(substituted)
1819 }),
1820 out_ty: p.out_ty.as_ref().map(|t| {
1821 let u = t.to_union();
1822 let substituted = u.substitute_templates(bindings);
1823 crate::compact::SimpleType::from_union(substituted)
1824 }),
1825 default: p.default.as_ref().map(|d| {
1826 let u = d.to_union();
1827 let substituted = u.substitute_templates(bindings);
1828 crate::compact::SimpleType::from_union(substituted)
1829 }),
1830 is_variadic: p.is_variadic,
1831 is_byref: p.is_byref,
1832 is_optional: p.is_optional,
1833 }
1834}
1835
1836pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1842 if sub == sup {
1843 return true;
1844 }
1845 match (sub, sup) {
1846 (Atomic::TNever, _) => true,
1848 (_, Atomic::TMixed) => true,
1850 (Atomic::TMixed, _) => true,
1851 (_, Atomic::TTemplateParam { as_type, .. }) => {
1856 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1857 }
1858
1859 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1861 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1862 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1863 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1864 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1865 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1866 (Atomic::TPositiveInt, Atomic::TInt) => true,
1867 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1868 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1869 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1870 (Atomic::TNegativeInt, Atomic::TInt) => true,
1871 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1872 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1873 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1874 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1875 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1876 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1877 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1878 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1879 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1881 max.is_none() && min.is_none_or(|m| m <= 1)
1882 }
1883 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1885 min.is_none() && max.is_none_or(|m| m >= -1)
1886 }
1887 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1889 max.is_none() && min.is_none_or(|m| m <= 0)
1890 }
1891 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1893 sub_min.is_some_and(|lo| lo >= 1)
1894 }
1895 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1896 sub_min.is_some_and(|lo| lo >= 0)
1897 }
1898 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1899 sub_max.is_some_and(|hi| hi <= -1)
1900 }
1901 (
1903 Atomic::TIntRange {
1904 min: sub_min,
1905 max: sub_max,
1906 },
1907 Atomic::TIntRange {
1908 min: sup_min,
1909 max: sup_max,
1910 },
1911 ) => {
1912 let lower_ok = match (sub_min, sup_min) {
1913 (_, None) => true,
1914 (None, Some(_)) => false,
1915 (Some(sl), Some(su)) => sl >= su,
1916 };
1917 let upper_ok = match (sub_max, sup_max) {
1918 (None, None) | (Some(_), None) => true,
1919 (None, Some(_)) => false,
1920 (Some(sl), Some(su)) => sl <= su,
1921 };
1922 lower_ok && upper_ok
1923 }
1924
1925 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1926 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1927 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1928
1929 (Atomic::TLiteralString(s), Atomic::TString) => {
1930 let _ = s;
1931 true
1932 }
1933 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1934 let _ = s;
1935 true
1936 }
1937 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1938 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1939 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1942 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1945 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1946 (Atomic::TNonEmptyString, Atomic::TString) => true,
1947 (Atomic::TCallableString, Atomic::TString) => true,
1948 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1950 (Atomic::TNumericString, Atomic::TString) => true,
1951 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1955 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1956 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1957 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1958 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1959 (Atomic::TClassString(_), Atomic::TString) => true,
1960 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1961 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1966 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
1967 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1968 (Atomic::TEnumString, Atomic::TString) => true,
1969 (Atomic::TTraitString, Atomic::TString) => true,
1970
1971 (Atomic::TTrue, Atomic::TBool) => true,
1972 (Atomic::TFalse, Atomic::TBool) => true,
1973
1974 (Atomic::TInt, Atomic::TNumeric) => true,
1975 (Atomic::TFloat, Atomic::TNumeric) => true,
1976 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1977 (Atomic::TNumericString, Atomic::TNumeric) => true,
1978
1979 (Atomic::TInt, Atomic::TScalar) => true,
1980 (Atomic::TFloat, Atomic::TScalar) => true,
1981 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1982 (Atomic::TString, Atomic::TScalar) => true,
1983 (Atomic::TBool, Atomic::TScalar) => true,
1984 (Atomic::TNumeric, Atomic::TScalar) => true,
1985 (Atomic::TTrue, Atomic::TScalar) => true,
1986 (Atomic::TFalse, Atomic::TScalar) => true,
1987 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1991 (Atomic::TNumericString, Atomic::TScalar) => true,
1992 (Atomic::TCallableString, Atomic::TScalar) => true,
1993 (Atomic::TClassString(_), Atomic::TScalar) => true,
1994 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1995 (Atomic::TEnumString, Atomic::TScalar) => true,
1996 (Atomic::TTraitString, Atomic::TScalar) => true,
1997
1998 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2000 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2001 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2002 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2004 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2006 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2007 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2009 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2010 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2013 enum_fqcn == fqcn
2014 }
2015 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2016 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2017 enum_fqcn == fqcn
2018 }
2019 (
2023 Atomic::TNamedObject {
2024 fqcn: sub_fqcn,
2025 type_params: sub_params,
2026 },
2027 Atomic::TNamedObject {
2028 fqcn: sup_fqcn,
2029 type_params: sup_params,
2030 },
2031 ) => {
2032 sub_fqcn == sup_fqcn
2033 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2034 }
2035
2036 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2038
2039 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2041 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2042 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2043 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2044 (Atomic::TInt, Atomic::TFloat) => true,
2045 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2046
2047 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2049 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2050 }
2051
2052 (Atomic::TString, Atomic::TCallable { .. }) => true,
2054 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2055 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2056 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2057 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2058 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2059
2060 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2062 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2064 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2074 fn has_nominal_type(t: &Type) -> bool {
2075 t.types.iter().any(|a| {
2076 matches!(
2077 a,
2078 Atomic::TNamedObject { .. }
2079 | Atomic::TSelf { .. }
2080 | Atomic::TStaticObject { .. }
2081 | Atomic::TTemplateParam { .. }
2082 | Atomic::TClosure { .. }
2083 | Atomic::TCallable { .. }
2084 )
2085 })
2086 }
2087 let sub_required = sub
2088 .params
2089 .iter()
2090 .filter(|p| !p.is_optional && !p.is_variadic)
2091 .count();
2092 if sub_required > sup.params.len() {
2093 false
2094 } else {
2095 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2096 let Some(sub_param) = sub.params.get(i) else {
2097 return true;
2098 };
2099 if sub_param.is_optional || sub_param.is_variadic {
2100 return true;
2101 }
2102 let (Some(sub_ty), Some(sup_ty)) =
2103 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2104 else {
2105 return true;
2106 };
2107 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2108 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2109 return true;
2110 }
2111 sup_u.is_subtype_structural(&sub_u)
2114 });
2115 params_ok
2116 && (sub.return_type.is_mixed()
2117 || sup.return_type.is_mixed()
2118 || sup.return_type.is_void()
2122 || has_nominal_type(&sub.return_type)
2123 || has_nominal_type(&sup.return_type)
2124 || sub.return_type.is_subtype_structural(&sup.return_type))
2125 }
2126 }
2127 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2129 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2131 fqcn.as_ref().eq_ignore_ascii_case("closure")
2132 }
2133 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2134 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2136 fqcn.as_ref().eq_ignore_ascii_case("closure")
2137 }
2138 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2140 fqcn.as_ref().eq_ignore_ascii_case("closure")
2141 }
2142
2143 (
2151 Atomic::TIntersection { parts: sub_parts },
2152 Atomic::TIntersection { parts: sup_parts },
2153 ) => sup_parts.iter().all(|sup_part| {
2154 sub_parts
2155 .iter()
2156 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2157 }),
2158
2159 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2161 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2162 }
2163 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2164 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2165 }
2166 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2167 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2168 }
2169 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2170 value.is_subtype_structural(lv)
2171 }
2172 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2173 v1.is_subtype_structural(v2)
2174 }
2175 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2177 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2178 && av.is_subtype_structural(lv)
2179 }
2180 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2181 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2182 && av.is_subtype_structural(lv)
2183 }
2184 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2185 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2186 && av.is_subtype_structural(lv)
2187 }
2188 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2189 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2190 && av.is_subtype_structural(lv)
2191 }
2192 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2194 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2195 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2196 }
2197
2198 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2200 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2201 }
2202
2203 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2212 properties.iter().all(|(prop_key, prop)| {
2213 let key_atomic = match prop_key {
2214 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2215 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2216 };
2217 if !Type::single(key_atomic).is_subtype_structural(key) {
2218 return false; }
2220 let has_named_obj = prop.ty.types.iter().any(|a| {
2222 matches!(
2223 a,
2224 Atomic::TNamedObject { .. }
2225 | Atomic::TSelf { .. }
2226 | Atomic::TStaticObject { .. }
2227 | Atomic::TClosure { .. }
2228 | Atomic::TTemplateParam { .. }
2229 )
2230 });
2231 has_named_obj || prop.ty.is_subtype_structural(value)
2232 })
2233 }
2234 (
2235 Atomic::TKeyedArray {
2236 properties,
2237 is_open,
2238 ..
2239 },
2240 Atomic::TNonEmptyArray { key, value },
2241 ) => {
2242 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2243 && properties.iter().all(|(prop_key, prop)| {
2244 let key_atomic = match prop_key {
2245 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2246 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2247 };
2248 if !Type::single(key_atomic).is_subtype_structural(key) {
2249 return false;
2250 }
2251 let has_named_obj = prop.ty.types.iter().any(|a| {
2252 matches!(
2253 a,
2254 Atomic::TNamedObject { .. }
2255 | Atomic::TSelf { .. }
2256 | Atomic::TStaticObject { .. }
2257 | Atomic::TClosure { .. }
2258 | Atomic::TTemplateParam { .. }
2259 )
2260 });
2261 has_named_obj || prop.ty.is_subtype_structural(value)
2262 })
2263 }
2264
2265 (
2267 Atomic::TKeyedArray {
2268 properties,
2269 is_list,
2270 ..
2271 },
2272 Atomic::TList { value: lv },
2273 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2274 (
2275 Atomic::TKeyedArray {
2276 properties,
2277 is_list,
2278 ..
2279 },
2280 Atomic::TNonEmptyList { value: lv },
2281 ) => {
2282 *is_list
2283 && !properties.is_empty()
2284 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2285 }
2286
2287 (
2292 Atomic::TKeyedArray {
2293 properties: sub_props,
2294 is_open: sub_open,
2295 ..
2296 },
2297 Atomic::TKeyedArray {
2298 properties: sup_props,
2299 is_open: sup_open,
2300 ..
2301 },
2302 ) => {
2303 let keys_satisfied = sup_props
2304 .iter()
2305 .all(|(key, sup_prop)| match sub_props.get(key) {
2306 Some(sub_prop) => {
2307 if !sup_prop.optional && sub_prop.optional {
2311 return false;
2312 }
2313 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2314 matches!(
2315 a,
2316 Atomic::TNamedObject { .. }
2317 | Atomic::TSelf { .. }
2318 | Atomic::TStaticObject { .. }
2319 | Atomic::TClosure { .. }
2320 | Atomic::TTemplateParam { .. }
2321 )
2322 });
2323 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2324 }
2325 None => sup_prop.optional || *sub_open,
2326 });
2327 let no_undeclared_extras =
2328 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2329 keys_satisfied && no_undeclared_extras
2330 }
2331 (
2336 Atomic::TKeyedArray {
2337 properties,
2338 is_open,
2339 ..
2340 },
2341 Atomic::TIntersection { parts },
2342 ) => parts.iter().all(|part| {
2343 part.types.iter().any(|part_atomic| match part_atomic {
2344 Atomic::TKeyedArray {
2345 properties: sup_props,
2346 is_open: sup_open,
2347 ..
2348 } => {
2349 let keys_satisfied =
2350 sup_props
2351 .iter()
2352 .all(|(key, sup_prop)| match properties.get(key) {
2353 Some(sub_prop) => {
2354 if !sup_prop.optional && sub_prop.optional {
2355 return false;
2356 }
2357 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2358 matches!(
2359 a,
2360 Atomic::TNamedObject { .. }
2361 | Atomic::TSelf { .. }
2362 | Atomic::TStaticObject { .. }
2363 | Atomic::TClosure { .. }
2364 | Atomic::TTemplateParam { .. }
2365 )
2366 });
2367 has_named_obj
2368 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2369 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2370 }
2371 None => *is_open || sup_prop.optional,
2372 });
2373 let has_array_part = parts.iter().any(|part| {
2374 part.types
2375 .iter()
2376 .any(|t| matches!(t, Atomic::TArray { .. }))
2377 });
2378 let has_keyed_array_part = parts.iter().any(|part| {
2379 part.types
2380 .iter()
2381 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2382 });
2383 let keys_allowed_by_some_part = if has_array_part {
2384 true
2385 } else if has_keyed_array_part {
2386 properties.keys().all(|k| {
2387 parts.iter().any(|part| {
2388 part.types.iter().any(|t| {
2389 if let Atomic::TKeyedArray {
2390 properties: part_props,
2391 ..
2392 } = t
2393 {
2394 part_props.contains_key(k)
2395 } else {
2396 false
2397 }
2398 })
2399 })
2400 })
2401 } else {
2402 true
2403 };
2404 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2405 keys_satisfied && no_undeclared_extras
2406 }
2407 Atomic::TArray { key, value } => 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 Atomic::TNamedObject { .. }
2428 | Atomic::TSelf { .. }
2429 | Atomic::TStaticObject { .. }
2430 | Atomic::TClosure { .. }
2431 | Atomic::TTemplateParam { .. } => true,
2432 _ => false,
2433 })
2434 }),
2435
2436 _ => false,
2437 }
2438}
2439
2440fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2442 fn array_like(t: &Atomic) -> bool {
2443 match t {
2444 Atomic::TArray { .. }
2445 | Atomic::TNonEmptyArray { .. }
2446 | Atomic::TList { .. }
2447 | Atomic::TNonEmptyList { .. }
2448 | Atomic::TKeyedArray { .. } => true,
2449 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2450 _ => false,
2451 }
2452 }
2453 matches!(
2454 sub.types.as_slice(),
2455 [Atomic::TArray { key, value }]
2456 if value.is_mixed()
2457 && (key.is_mixed()
2458 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2459 ) && sup.types.iter().all(array_like)
2460}
2461
2462fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2468 if sub.len() != sup.len() {
2469 return false;
2470 }
2471 sub.iter()
2472 .zip(sup.iter())
2473 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2474}
2475
2476fn is_empty_array_literal(t: &Type) -> bool {
2479 !t.types.is_empty()
2480 && t.types.iter().all(
2481 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2482 )
2483}
2484
2485fn is_array_like(t: &Type) -> bool {
2487 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2488}
2489
2490pub struct UnionBuilder {
2497 ty: Type,
2498 strings: std::collections::HashSet<std::sync::Arc<str>>,
2499 ints: std::collections::HashSet<i64>,
2500 literals_independent: bool,
2502}
2503
2504impl Default for UnionBuilder {
2505 fn default() -> Self {
2506 Self::new()
2507 }
2508}
2509
2510impl UnionBuilder {
2511 pub fn new() -> Self {
2512 Self {
2513 ty: Type::empty(),
2514 strings: Default::default(),
2515 ints: Default::default(),
2516 literals_independent: true,
2517 }
2518 }
2519
2520 pub fn is_empty(&self) -> bool {
2521 self.ty.is_empty()
2522 }
2523
2524 pub fn merge(&mut self, other: &Type) {
2525 for a in &other.types {
2526 self.add(a.clone());
2527 }
2528 self.ty.possibly_undefined |= other.possibly_undefined;
2529 }
2530
2531 pub fn add(&mut self, atomic: Atomic) {
2532 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2534 if self.literals_independent && !lone_never {
2535 match &atomic {
2536 Atomic::TLiteralString(s) => {
2537 if self.strings.insert(s.clone()) {
2538 self.ty.types.push(atomic);
2539 }
2540 return;
2541 }
2542 Atomic::TLiteralInt(n) => {
2543 if self.ints.insert(*n) {
2544 self.ty.types.push(atomic);
2545 }
2546 return;
2547 }
2548 _ => {}
2549 }
2550 }
2551 let disturbs_literals = matches!(
2552 atomic,
2553 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2554 ) || int_bounds(&atomic).is_some();
2555 self.ty.add_type(atomic);
2556 if disturbs_literals || lone_never {
2557 self.resync();
2558 }
2559 }
2560
2561 fn resync(&mut self) {
2562 self.strings.clear();
2563 self.ints.clear();
2564 self.literals_independent = true;
2565 for t in &self.ty.types {
2566 match t {
2567 Atomic::TLiteralString(s) => {
2568 self.strings.insert(s.clone());
2569 }
2570 Atomic::TLiteralInt(n) => {
2571 self.ints.insert(*n);
2572 }
2573 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2574 other if int_bounds(other).is_some() => self.literals_independent = false,
2575 _ => {}
2576 }
2577 }
2578 }
2579
2580 pub fn build(self) -> Type {
2581 self.ty
2582 }
2583}
2584
2585#[cfg(test)]
2586mod tests {
2587 use std::sync::Arc;
2588
2589 use super::*;
2590
2591 fn conditional(
2592 param_name: Option<Name>,
2593 subject: Type,
2594 if_true: Type,
2595 if_false: Type,
2596 ) -> Atomic {
2597 Atomic::TConditional {
2598 data: Box::new(crate::atomic::ConditionalData {
2599 param_name,
2600 subject,
2601 if_true,
2602 if_false,
2603 }),
2604 }
2605 }
2606
2607 #[test]
2608 fn single_is_single() {
2609 let u = Type::single(Atomic::TString);
2610 assert!(u.is_single());
2611 assert!(!u.is_nullable());
2612 }
2613
2614 #[test]
2615 fn nullable_has_null() {
2616 let u = Type::nullable(Atomic::TString);
2617 assert!(u.is_nullable());
2618 assert_eq!(u.types.len(), 2);
2619 }
2620
2621 #[test]
2622 fn add_type_deduplicates() {
2623 let mut u = Type::single(Atomic::TString);
2624 u.add_type(Atomic::TString);
2625 assert_eq!(u.types.len(), 1);
2626 }
2627
2628 #[test]
2629 fn array_key_is_int_string() {
2630 let k = Type::array_key();
2631 assert!(k.is_array_key());
2632 assert_eq!(k.types.len(), 2);
2633 }
2634
2635 #[test]
2636 fn is_array_key_false_for_plain_int() {
2637 assert!(!Type::int().is_array_key());
2638 }
2639
2640 #[test]
2641 fn is_array_key_false_for_mixed() {
2642 assert!(!Type::mixed().is_array_key());
2643 }
2644
2645 #[test]
2646 fn is_array_key_false_for_int_string_null() {
2647 let mut u = Type::array_key();
2648 u.add_type(Atomic::TNull);
2649 assert!(!u.is_array_key());
2650 }
2651
2652 #[test]
2653 fn add_type_literal_subsumed_by_base() {
2654 let mut u = Type::single(Atomic::TInt);
2655 u.add_type(Atomic::TLiteralInt(42));
2656 assert_eq!(u.types.len(), 1);
2657 assert!(matches!(u.types[0], Atomic::TInt));
2658 }
2659
2660 #[test]
2661 fn true_then_false_merges_to_bool() {
2662 let mut u = Type::single(Atomic::TTrue);
2663 u.add_type(Atomic::TFalse);
2664 assert_eq!(u.types.len(), 1);
2665 assert!(matches!(u.types[0], Atomic::TBool));
2666 }
2667
2668 #[test]
2669 fn false_then_true_merges_to_bool() {
2670 let mut u = Type::single(Atomic::TFalse);
2671 u.add_type(Atomic::TTrue);
2672 assert_eq!(u.types.len(), 1);
2673 assert!(matches!(u.types[0], Atomic::TBool));
2674 }
2675
2676 #[test]
2677 fn true_alone_stays_true() {
2678 let u = Type::single(Atomic::TTrue);
2679 assert_eq!(u.types.len(), 1);
2680 assert!(matches!(u.types[0], Atomic::TTrue));
2681 }
2682
2683 #[test]
2684 fn true_false_merge_preserves_other_union_members() {
2685 let mut u = Type::single(Atomic::TTrue);
2686 u.add_type(Atomic::TNull);
2687 u.add_type(Atomic::TFalse);
2688 assert_eq!(u.types.len(), 2);
2689 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2690 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2691 }
2692
2693 #[test]
2694 fn add_type_base_widens_literals() {
2695 let mut u = Type::single(Atomic::TLiteralInt(1));
2696 u.add_type(Atomic::TLiteralInt(2));
2697 u.add_type(Atomic::TInt);
2698 assert_eq!(u.types.len(), 1);
2699 assert!(matches!(u.types[0], Atomic::TInt));
2700 }
2701
2702 #[test]
2703 fn mixed_subsumes_everything() {
2704 let mut u = Type::single(Atomic::TString);
2705 u.add_type(Atomic::TMixed);
2706 assert_eq!(u.types.len(), 1);
2707 assert!(u.is_mixed());
2708 }
2709
2710 #[test]
2711 fn remove_null() {
2712 let u = Type::nullable(Atomic::TString);
2713 let narrowed = u.remove_null();
2714 assert!(!narrowed.is_nullable());
2715 assert_eq!(narrowed.types.len(), 1);
2716 }
2717
2718 #[test]
2719 fn narrow_to_truthy_removes_null_false() {
2720 let mut u = Type::empty();
2721 u.add_type(Atomic::TString);
2722 u.add_type(Atomic::TNull);
2723 u.add_type(Atomic::TFalse);
2724 let truthy = u.narrow_to_truthy();
2725 assert!(!truthy.is_nullable());
2726 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2727 }
2728
2729 #[test]
2730 fn merge_combines_types() {
2731 let a = Type::single(Atomic::TString);
2732 let b = Type::single(Atomic::TInt);
2733 let merged = Type::merge(&a, &b);
2734 assert_eq!(merged.types.len(), 2);
2735 }
2736
2737 #[test]
2738 fn intersect_keeps_narrower_side_not_self() {
2739 let int_ty = Type::single(Atomic::TInt);
2743 let mut literals = Type::empty();
2744 literals.add_type(Atomic::TLiteralInt(1));
2745 literals.add_type(Atomic::TLiteralInt(2));
2746
2747 let narrowed = int_ty.intersect_with(&literals);
2748 assert_eq!(narrowed.types.len(), 2);
2749 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2750 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2751 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2752 }
2753
2754 #[test]
2755 fn subtype_literal_int_under_int() {
2756 let sub = Type::single(Atomic::TLiteralInt(5));
2757 let sup = Type::single(Atomic::TInt);
2758 assert!(sub.is_subtype_structural(&sup));
2759 }
2760
2761 #[test]
2762 fn subtype_never_is_bottom() {
2763 let never = Type::never();
2764 let string = Type::single(Atomic::TString);
2765 assert!(never.is_subtype_structural(&string));
2766 }
2767
2768 #[test]
2769 fn subtype_everything_under_mixed() {
2770 let string = Type::single(Atomic::TString);
2771 let mixed = Type::mixed();
2772 assert!(string.is_subtype_structural(&mixed));
2773 }
2774
2775 #[test]
2776 fn subtype_enum_case_under_own_enum() {
2777 let sub = Type::single(Atomic::TLiteralEnumCase {
2778 enum_fqcn: Name::new("RoundingMode"),
2779 case_name: Name::new("Unnecessary"),
2780 });
2781 let sup = Type::single(Atomic::TNamedObject {
2782 fqcn: Name::new("RoundingMode"),
2783 type_params: empty_type_params(),
2784 });
2785 assert!(sub.is_subtype_structural(&sup));
2786 }
2787
2788 #[test]
2789 fn enum_case_not_subtype_of_unrelated_enum() {
2790 let sub = Type::single(Atomic::TLiteralEnumCase {
2791 enum_fqcn: Name::new("RoundingMode"),
2792 case_name: Name::new("Unnecessary"),
2793 });
2794 let sup = Type::single(Atomic::TNamedObject {
2795 fqcn: Name::new("Suit"),
2796 type_params: empty_type_params(),
2797 });
2798 assert!(!sub.is_subtype_structural(&sup));
2799 }
2800
2801 #[test]
2802 fn subtype_enum_case_under_own_self_and_static() {
2803 let case = |enum_fqcn: &str| {
2804 Type::single(Atomic::TLiteralEnumCase {
2805 enum_fqcn: Name::new(enum_fqcn),
2806 case_name: Name::new("A"),
2807 })
2808 };
2809 let own = Type::single(Atomic::TSelf {
2810 fqcn: Name::new("Suit"),
2811 });
2812 let own_static = Type::single(Atomic::TStaticObject {
2813 fqcn: Name::new("Suit"),
2814 });
2815 assert!(case("Suit").is_subtype_structural(&own));
2816 assert!(case("Suit").is_subtype_structural(&own_static));
2817 assert!(!case("Other").is_subtype_structural(&own));
2818 }
2819
2820 #[test]
2821 fn subtype_enum_case_under_bare_object() {
2822 let sub = Type::single(Atomic::TLiteralEnumCase {
2823 enum_fqcn: Name::new("RoundingMode"),
2824 case_name: Name::new("Unnecessary"),
2825 });
2826 let sup = Type::single(Atomic::TObject);
2827 assert!(sub.is_subtype_structural(&sup));
2828 }
2829
2830 #[test]
2831 fn template_substitution() {
2832 let mut bindings = FxHashMap::default();
2833 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2834
2835 let tmpl = Type::single(Atomic::TTemplateParam {
2836 name: Name::new("T"),
2837 as_type: Box::new(Type::mixed()),
2838 defining_entity: Name::new("MyClass"),
2839 });
2840
2841 let resolved = tmpl.substitute_templates(&bindings);
2842 assert_eq!(resolved.types.len(), 1);
2843 assert!(matches!(resolved.types[0], Atomic::TString));
2844 }
2845
2846 #[test]
2847 fn intersection_is_object() {
2848 let parts = vec![
2849 Type::single(Atomic::TNamedObject {
2850 fqcn: Name::new("Iterator"),
2851 type_params: empty_type_params(),
2852 }),
2853 Type::single(Atomic::TNamedObject {
2854 fqcn: Name::new("Countable"),
2855 type_params: empty_type_params(),
2856 }),
2857 ];
2858 let atomic = Atomic::TIntersection {
2859 parts: vec_to_type_params(parts),
2860 };
2861 assert!(atomic.is_object());
2862 assert!(!atomic.can_be_falsy());
2863 assert!(atomic.can_be_truthy());
2864 }
2865
2866 #[test]
2867 fn intersection_display_two_parts() {
2868 let parts = vec![
2869 Type::single(Atomic::TNamedObject {
2870 fqcn: Name::new("Iterator"),
2871 type_params: empty_type_params(),
2872 }),
2873 Type::single(Atomic::TNamedObject {
2874 fqcn: Name::new("Countable"),
2875 type_params: empty_type_params(),
2876 }),
2877 ];
2878 let u = Type::single(Atomic::TIntersection {
2879 parts: vec_to_type_params(parts),
2880 });
2881 assert_eq!(format!("{u}"), "Iterator&Countable");
2882 }
2883
2884 #[test]
2885 fn intersection_display_three_parts() {
2886 let parts = vec![
2887 Type::single(Atomic::TNamedObject {
2888 fqcn: Name::new("A"),
2889 type_params: empty_type_params(),
2890 }),
2891 Type::single(Atomic::TNamedObject {
2892 fqcn: Name::new("B"),
2893 type_params: empty_type_params(),
2894 }),
2895 Type::single(Atomic::TNamedObject {
2896 fqcn: Name::new("C"),
2897 type_params: empty_type_params(),
2898 }),
2899 ];
2900 let u = Type::single(Atomic::TIntersection {
2901 parts: vec_to_type_params(parts),
2902 });
2903 assert_eq!(format!("{u}"), "A&B&C");
2904 }
2905
2906 #[test]
2907 fn intersection_in_nullable_union_display() {
2908 let intersection = Atomic::TIntersection {
2909 parts: vec_to_type_params(vec![
2910 Type::single(Atomic::TNamedObject {
2911 fqcn: Name::new("Iterator"),
2912 type_params: empty_type_params(),
2913 }),
2914 Type::single(Atomic::TNamedObject {
2915 fqcn: Name::new("Countable"),
2916 type_params: empty_type_params(),
2917 }),
2918 ]),
2919 };
2920 let mut u = Type::single(intersection);
2921 u.add_type(Atomic::TNull);
2922 assert!(u.is_nullable());
2923 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2924 }
2925
2926 fn t_param(name: &str) -> Type {
2929 Type::single(Atomic::TTemplateParam {
2930 name: Name::new(name),
2931 as_type: Box::new(Type::mixed()),
2932 defining_entity: Name::new("Fn"),
2933 })
2934 }
2935
2936 fn bindings_t_string() -> FxHashMap<Name, Type> {
2937 let mut b = FxHashMap::default();
2938 b.insert(Name::new("T"), Type::single(Atomic::TString));
2939 b
2940 }
2941
2942 #[test]
2943 fn substitute_non_empty_array_key_and_value() {
2944 let ty = Type::single(Atomic::TNonEmptyArray {
2945 key: Box::new(t_param("T")),
2946 value: Box::new(t_param("T")),
2947 });
2948 let result = ty.substitute_templates(&bindings_t_string());
2949 assert_eq!(result.types.len(), 1);
2950 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2951 panic!("expected TNonEmptyArray");
2952 };
2953 assert!(matches!(key.types[0], Atomic::TString));
2954 assert!(matches!(value.types[0], Atomic::TString));
2955 }
2956
2957 #[test]
2958 fn substitute_non_empty_list_value() {
2959 let ty = Type::single(Atomic::TNonEmptyList {
2960 value: Box::new(t_param("T")),
2961 });
2962 let result = ty.substitute_templates(&bindings_t_string());
2963 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2964 panic!("expected TNonEmptyList");
2965 };
2966 assert!(matches!(value.types[0], Atomic::TString));
2967 }
2968
2969 #[test]
2970 fn substitute_keyed_array_property_types() {
2971 use crate::atomic::{ArrayKey, KeyedProperty};
2972 use indexmap::IndexMap;
2973 let mut props = IndexMap::new();
2974 props.insert(
2975 ArrayKey::String(Arc::from("name")),
2976 KeyedProperty {
2977 ty: t_param("T"),
2978 optional: false,
2979 },
2980 );
2981 props.insert(
2982 ArrayKey::String(Arc::from("tag")),
2983 KeyedProperty {
2984 ty: t_param("T"),
2985 optional: true,
2986 },
2987 );
2988 let ty = Type::single(Atomic::TKeyedArray {
2989 properties: Box::new(props),
2990 is_open: true,
2991 is_list: false,
2992 });
2993 let result = ty.substitute_templates(&bindings_t_string());
2994 let Atomic::TKeyedArray {
2995 properties,
2996 is_open,
2997 is_list,
2998 } = &result.types[0]
2999 else {
3000 panic!("expected TKeyedArray");
3001 };
3002 assert!(is_open);
3003 assert!(!is_list);
3004 assert!(matches!(
3005 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3006 Atomic::TString
3007 ));
3008 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3009 assert!(matches!(
3010 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3011 Atomic::TString
3012 ));
3013 }
3014
3015 #[test]
3016 fn substitute_callable_params_and_return() {
3017 use crate::atomic::FnParam;
3018 let ty = Type::single(Atomic::TCallable {
3019 params: Some(Box::new([FnParam {
3020 name: Name::new("x"),
3021 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3022 out_ty: None,
3023 default: None,
3024 is_variadic: false,
3025 is_byref: false,
3026 is_optional: false,
3027 }])),
3028 return_type: Some(Box::new(t_param("T"))),
3029 });
3030 let result = ty.substitute_templates(&bindings_t_string());
3031 let Atomic::TCallable {
3032 params,
3033 return_type,
3034 } = &result.types[0]
3035 else {
3036 panic!("expected TCallable");
3037 };
3038 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3039 let param_union = param_ty.to_union();
3040 assert!(matches!(param_union.types[0], Atomic::TString));
3041 let ret = return_type.as_ref().unwrap();
3042 assert!(matches!(ret.types[0], Atomic::TString));
3043 }
3044
3045 #[test]
3046 fn substitute_callable_bare_no_panic() {
3047 let ty = Type::single(Atomic::TCallable {
3049 params: None,
3050 return_type: None,
3051 });
3052 let result = ty.substitute_templates(&bindings_t_string());
3053 assert!(matches!(
3054 result.types[0],
3055 Atomic::TCallable {
3056 params: None,
3057 return_type: None
3058 }
3059 ));
3060 }
3061
3062 #[test]
3063 fn substitute_closure_params_return_and_this() {
3064 use crate::atomic::FnParam;
3065 let ty = Type::single(Atomic::TClosure {
3066 data: Box::new(crate::atomic::ClosureData {
3067 params: Box::new([FnParam {
3068 name: Name::new("a"),
3069 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3070 out_ty: None,
3071 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3072 is_variadic: true,
3073 is_byref: true,
3074 is_optional: true,
3075 }]),
3076 return_type: t_param("T"),
3077 this_type: Some(t_param("T")),
3078 }),
3079 });
3080 let result = ty.substitute_templates(&bindings_t_string());
3081 let Atomic::TClosure { data } = &result.types[0] else {
3082 panic!("expected TClosure");
3083 };
3084 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3085 let p = ¶ms[0];
3086 let ty_union = p.ty.as_ref().unwrap().to_union();
3087 let default_union = p.default.as_ref().unwrap().to_union();
3088 assert!(matches!(ty_union.types[0], Atomic::TString));
3089 assert!(matches!(default_union.types[0], Atomic::TString));
3090 assert!(p.is_variadic);
3092 assert!(p.is_byref);
3093 assert!(p.is_optional);
3094 assert!(matches!(return_type.types[0], Atomic::TString));
3095 assert!(matches!(
3096 this_type.as_ref().unwrap().types[0],
3097 Atomic::TString
3098 ));
3099 }
3100
3101 #[test]
3102 fn substitute_conditional_all_branches() {
3103 let ty = Type::single(conditional(
3104 None,
3105 t_param("T"),
3106 t_param("T"),
3107 Type::single(Atomic::TInt),
3108 ));
3109 let result = ty.substitute_templates(&bindings_t_string());
3110 let Atomic::TConditional { data } = &result.types[0] else {
3111 panic!("expected TConditional");
3112 };
3113 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3114 assert!(matches!(subject.types[0], Atomic::TString));
3115 assert!(matches!(if_true.types[0], Atomic::TString));
3116 assert!(matches!(if_false.types[0], Atomic::TInt));
3117 }
3118
3119 #[test]
3120 fn resolve_conditional_is_null_non_null_arg() {
3121 let ty = Type::single(conditional(
3122 Some(Name::new("x")),
3123 Type::single(Atomic::TNull),
3124 Type::single(Atomic::TInt),
3125 Type::single(Atomic::TString),
3126 ));
3127 let result = ty.resolve_conditional_returns(|name| {
3128 if name == "x" {
3129 Some(Type::single(Atomic::TString)) } else {
3131 None
3132 }
3133 });
3134 assert!(result.types.len() == 1);
3135 assert!(matches!(result.types[0], Atomic::TString));
3136 }
3137
3138 #[test]
3139 fn resolve_conditional_is_null_null_arg() {
3140 let ty = Type::single(conditional(
3141 Some(Name::new("x")),
3142 Type::single(Atomic::TNull),
3143 Type::single(Atomic::TInt),
3144 Type::single(Atomic::TString),
3145 ));
3146 let result = ty.resolve_conditional_returns(|name| {
3147 if name == "x" {
3148 Some(Type::single(Atomic::TNull)) } else {
3150 None
3151 }
3152 });
3153 assert!(result.types.len() == 1);
3154 assert!(matches!(result.types[0], Atomic::TInt));
3155 }
3156
3157 #[test]
3158 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3159 let mut nullable_str = Type::single(Atomic::TString);
3160 nullable_str.add_type(Atomic::TNull);
3161 let ty = Type::single(conditional(
3162 Some(Name::new("x")),
3163 Type::single(Atomic::TNull),
3164 Type::single(Atomic::TInt),
3165 Type::single(Atomic::TString),
3166 ));
3167 let result = ty.resolve_conditional_returns(|name| {
3168 if name == "x" {
3169 Some(nullable_str.clone())
3170 } else {
3171 None
3172 }
3173 });
3174 assert_eq!(result.types.len(), 2);
3176 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3177 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3178 }
3179
3180 #[test]
3181 fn resolve_conditional_nested_widens_inner_branch() {
3182 let inner = Type::single(conditional(
3185 Some(Name::new("x")),
3186 Type::single(Atomic::TString),
3187 Type::single(Atomic::TString),
3188 Type::single(Atomic::TFloat),
3189 ));
3190 let ty = Type::single(conditional(
3191 Some(Name::new("x")),
3192 Type::single(Atomic::TNull),
3193 Type::single(Atomic::TInt),
3194 inner,
3195 ));
3196 let result = ty.resolve_conditional_returns(|_| None);
3198 assert!(
3199 result
3200 .types
3201 .iter()
3202 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3203 "no TConditional should survive: {:?}",
3204 result.types
3205 );
3206 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3207 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3208 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3209 }
3210
3211 #[test]
3212 fn resolve_conditional_nested_resolves_inner_branch() {
3213 let inner = Type::single(conditional(
3217 Some(Name::new("x")),
3218 Type::single(Atomic::TString),
3219 Type::single(Atomic::TString),
3220 Type::single(Atomic::TFloat),
3221 ));
3222 let ty = Type::single(conditional(
3223 Some(Name::new("x")),
3224 Type::single(Atomic::TNull),
3225 Type::single(Atomic::TInt),
3226 inner,
3227 ));
3228 let result = ty.resolve_conditional_returns(|name| {
3230 if name == "x" {
3231 Some(Type::single(Atomic::TString))
3232 } else {
3233 None
3234 }
3235 });
3236 assert!(
3237 result
3238 .types
3239 .iter()
3240 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3241 "no TConditional should survive: {:?}",
3242 result.types
3243 );
3244 assert_eq!(result.types.len(), 1);
3245 assert!(matches!(result.types[0], Atomic::TString));
3246 }
3247
3248 #[test]
3249 fn resolve_conditional_true_subject_bool_arg_widens() {
3250 let ty = Type::single(conditional(
3254 Some(Name::new("x")),
3255 Type::single(Atomic::TTrue),
3256 Type::single(Atomic::TInt),
3257 Type::single(Atomic::TString),
3258 ));
3259 let result = ty.resolve_conditional_returns(|name| {
3260 if name == "x" {
3261 Some(Type::single(Atomic::TBool))
3262 } else {
3263 None
3264 }
3265 });
3266 assert_eq!(result.types.len(), 2);
3267 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3268 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3269 }
3270
3271 #[test]
3272 fn resolve_conditional_true_subject_mixed_arg_widens() {
3273 let ty = Type::single(conditional(
3276 Some(Name::new("x")),
3277 Type::single(Atomic::TTrue),
3278 Type::single(Atomic::TInt),
3279 Type::single(Atomic::TString),
3280 ));
3281 let result = ty.resolve_conditional_returns(|name| {
3282 if name == "x" {
3283 Some(Type::mixed())
3284 } else {
3285 None
3286 }
3287 });
3288 assert_eq!(result.types.len(), 2);
3289 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3290 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3291 }
3292
3293 #[test]
3294 fn resolve_conditional_true_subject_literal_args() {
3295 let ty = Type::single(conditional(
3298 Some(Name::new("x")),
3299 Type::single(Atomic::TTrue),
3300 Type::single(Atomic::TInt),
3301 Type::single(Atomic::TString),
3302 ));
3303 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3304 if name == "x" {
3305 Some(Type::single(Atomic::TFalse))
3306 } else {
3307 None
3308 }
3309 });
3310 assert_eq!(false_arg.types.len(), 1);
3311 assert!(matches!(false_arg.types[0], Atomic::TString));
3312
3313 let true_arg = ty.resolve_conditional_returns(|name| {
3314 if name == "x" {
3315 Some(Type::single(Atomic::TTrue))
3316 } else {
3317 None
3318 }
3319 });
3320 assert_eq!(true_arg.types.len(), 1);
3321 assert!(matches!(true_arg.types[0], Atomic::TInt));
3322 }
3323
3324 #[test]
3325 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3326 let ty = Type::single(conditional(
3330 Some(Name::new("x")),
3331 Type::single(Atomic::TBool),
3332 Type::single(Atomic::TInt),
3333 Type::single(Atomic::TString),
3334 ));
3335 let result = ty.resolve_conditional_returns(|name| {
3336 if name == "x" {
3337 Some(Type::single(Atomic::TScalar))
3338 } else {
3339 None
3340 }
3341 });
3342 assert_eq!(result.types.len(), 2);
3343 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3344 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3345 }
3346
3347 #[test]
3348 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3349 let ty = Type::single(conditional(
3353 Some(Name::new("x")),
3354 Type::single(Atomic::TString),
3355 Type::single(Atomic::TInt),
3356 Type::single(Atomic::TBool),
3357 ));
3358 let result = ty.resolve_conditional_returns(|name| {
3359 if name == "x" {
3360 Some(Type::single(Atomic::TBool))
3361 } else {
3362 None
3363 }
3364 });
3365 assert_eq!(result.types.len(), 1);
3366 assert!(matches!(result.types[0], Atomic::TBool));
3367 }
3368
3369 #[test]
3370 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3371 let ty = Type::single(conditional(
3374 Some(Name::new("x")),
3375 Type::single(Atomic::TList {
3376 value: Box::new(Type::mixed()),
3377 }),
3378 Type::single(Atomic::TInt),
3379 Type::single(Atomic::TString),
3380 ));
3381 let result = ty.resolve_conditional_returns(|name| {
3382 if name == "x" {
3383 Some(Type::single(Atomic::TArray {
3384 key: Box::new(Type::mixed()),
3385 value: Box::new(Type::mixed()),
3386 }))
3387 } else {
3388 None
3389 }
3390 });
3391 assert_eq!(result.types.len(), 2);
3392 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3393 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3394 }
3395
3396 #[test]
3397 fn resolve_conditional_float_subject_int_literal_false_branch() {
3398 let ty = resolve_conditional_branch(
3401 &Atomic::TFloat,
3402 &Type::single(Atomic::TLiteralInt(42)),
3403 &Type::single(Atomic::TInt),
3404 &Type::single(Atomic::TString),
3405 );
3406 assert!(matches!(
3407 ty.as_ref(),
3408 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3409 ));
3410 }
3411
3412 #[test]
3413 fn resolve_conditional_int_subject_float_literal_false_branch() {
3414 let ty = resolve_conditional_branch(
3415 &Atomic::TInt,
3416 &Type::single(Atomic::TLiteralFloat(1, 0)),
3417 &Type::single(Atomic::TInt),
3418 &Type::single(Atomic::TString),
3419 );
3420 assert!(matches!(
3421 ty.as_ref(),
3422 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3423 ));
3424 }
3425
3426 #[test]
3427 fn resolve_conditional_float_subject_float_literal_then_branch() {
3428 let ty = resolve_conditional_branch(
3429 &Atomic::TFloat,
3430 &Type::single(Atomic::TLiteralFloat(1, 5)),
3431 &Type::single(Atomic::TInt),
3432 &Type::single(Atomic::TString),
3433 );
3434 assert!(matches!(
3435 ty.as_ref(),
3436 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3437 ));
3438 }
3439
3440 #[test]
3441 fn resolve_conditional_int_subject_int_literal_then_branch() {
3442 let ty = resolve_conditional_branch(
3443 &Atomic::TInt,
3444 &Type::single(Atomic::TLiteralInt(7)),
3445 &Type::single(Atomic::TInt),
3446 &Type::single(Atomic::TString),
3447 );
3448 assert!(matches!(
3449 ty.as_ref(),
3450 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3451 ));
3452 }
3453
3454 #[test]
3455 fn resolve_conditional_int_subject_float_arg_false_branch() {
3456 let ty = resolve_conditional_branch(
3459 &Atomic::TInt,
3460 &Type::single(Atomic::TFloat),
3461 &Type::single(Atomic::TInt),
3462 &Type::single(Atomic::TString),
3463 );
3464 assert!(matches!(
3465 ty.as_ref(),
3466 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3467 ));
3468 }
3469
3470 #[test]
3471 fn resolve_conditional_array_subject_string_arg_false_branch() {
3472 let ty = resolve_conditional_branch(
3473 &Atomic::TArray {
3474 key: Box::new(Type::mixed()),
3475 value: Box::new(Type::mixed()),
3476 },
3477 &Type::single(Atomic::TString),
3478 &Type::single(Atomic::TInt),
3479 &Type::single(Atomic::TString),
3480 );
3481 assert!(matches!(
3482 ty.as_ref(),
3483 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3484 ));
3485 }
3486
3487 #[test]
3488 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3489 let ty = resolve_conditional_branch(
3490 &Atomic::TArray {
3491 key: Box::new(Type::mixed()),
3492 value: Box::new(Type::mixed()),
3493 },
3494 &Type::single(Atomic::TBool),
3495 &Type::single(Atomic::TInt),
3496 &Type::single(Atomic::TString),
3497 );
3498 assert!(matches!(
3499 ty.as_ref(),
3500 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3501 ));
3502 }
3503
3504 #[test]
3505 fn resolve_conditional_array_subject_mixed_arg_widens() {
3506 let ty = resolve_conditional_branch(
3510 &Atomic::TArray {
3511 key: Box::new(Type::mixed()),
3512 value: Box::new(Type::mixed()),
3513 },
3514 &Type::mixed(),
3515 &Type::single(Atomic::TInt),
3516 &Type::single(Atomic::TString),
3517 );
3518 assert!(ty.is_none());
3519 }
3520
3521 #[test]
3522 fn resolve_conditional_array_subject_list_arg_then_branch() {
3523 let ty = resolve_conditional_branch(
3526 &Atomic::TArray {
3527 key: Box::new(Type::mixed()),
3528 value: Box::new(Type::mixed()),
3529 },
3530 &Type::single(Atomic::TList {
3531 value: Box::new(Type::mixed()),
3532 }),
3533 &Type::single(Atomic::TInt),
3534 &Type::single(Atomic::TString),
3535 );
3536 assert!(matches!(
3537 ty.as_ref(),
3538 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3539 ));
3540 }
3541
3542 #[test]
3543 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3544 let ty = resolve_conditional_branch(
3546 &Atomic::TList {
3547 value: Box::new(Type::mixed()),
3548 },
3549 &Type::single(Atomic::TKeyedArray {
3550 properties: Box::default(),
3551 is_open: false,
3552 is_list: true,
3553 }),
3554 &Type::single(Atomic::TInt),
3555 &Type::single(Atomic::TString),
3556 );
3557 assert!(matches!(
3558 ty.as_ref(),
3559 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3560 ));
3561 }
3562
3563 #[test]
3564 fn resolve_conditional_list_subject_list_string_union_widens() {
3565 let ty = resolve_conditional_branch(
3569 &Atomic::TList {
3570 value: Box::new(Type::mixed()),
3571 },
3572 &Type::from_vec(vec![
3573 Atomic::TList {
3574 value: Box::new(Type::mixed()),
3575 },
3576 Atomic::TString,
3577 ]),
3578 &Type::single(Atomic::TInt),
3579 &Type::single(Atomic::TString),
3580 );
3581 assert!(ty.is_none());
3582 }
3583
3584 #[test]
3585 fn resolve_conditional_numeric_subject_widens() {
3586 let ty = resolve_conditional_branch(
3590 &Atomic::TNumeric,
3591 &Type::single(Atomic::TLiteralString("123".into())),
3592 &Type::single(Atomic::TInt),
3593 &Type::single(Atomic::TString),
3594 );
3595 assert!(ty.is_none());
3596 }
3597
3598 #[test]
3599 fn resolve_conditional_non_empty_string_subject_widens() {
3600 let ty = resolve_conditional_branch(
3603 &Atomic::TNonEmptyString,
3604 &Type::single(Atomic::TString),
3605 &Type::single(Atomic::TInt),
3606 &Type::single(Atomic::TString),
3607 );
3608 assert!(ty.is_none());
3609 }
3610
3611 #[test]
3612 fn resolve_conditional_template_arg_widens() {
3613 let ty = resolve_conditional_branch(
3616 &Atomic::TString,
3617 &t_param("T"),
3618 &Type::single(Atomic::TInt),
3619 &Type::single(Atomic::TString),
3620 );
3621 assert!(ty.is_none());
3622 }
3623
3624 #[test]
3625 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3626 let ty = resolve_conditional_branch(
3629 &Atomic::TMixed,
3630 &Type::single(Atomic::TBool),
3631 &Type::single(Atomic::TInt),
3632 &Type::single(Atomic::TString),
3633 );
3634 assert!(matches!(
3635 ty.as_ref(),
3636 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3637 ));
3638 }
3639
3640 #[test]
3641 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3642 let ty = resolve_conditional_branch(
3645 &Atomic::TScalar,
3646 &Type::single(Atomic::TNull),
3647 &Type::single(Atomic::TInt),
3648 &Type::single(Atomic::TString),
3649 );
3650 assert!(matches!(
3651 ty.as_ref(),
3652 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3653 ));
3654 }
3655
3656 #[test]
3657 fn resolve_conditional_object_subject_named_arg_then_branch() {
3658 let ty = resolve_conditional_branch(
3660 &Atomic::TObject,
3661 &Type::single(Atomic::TNamedObject {
3662 fqcn: Name::new("Foo"),
3663 type_params: empty_type_params(),
3664 }),
3665 &Type::single(Atomic::TInt),
3666 &Type::single(Atomic::TString),
3667 );
3668 assert!(matches!(
3669 ty.as_ref(),
3670 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3671 ));
3672 }
3673
3674 #[test]
3675 fn resolve_conditional_named_subject_other_class_widens() {
3676 let ty = resolve_conditional_branch(
3680 &Atomic::TNamedObject {
3681 fqcn: Name::new("Foo"),
3682 type_params: empty_type_params(),
3683 },
3684 &Type::single(Atomic::TNamedObject {
3685 fqcn: Name::new("Bar"),
3686 type_params: empty_type_params(),
3687 }),
3688 &Type::single(Atomic::TInt),
3689 &Type::single(Atomic::TString),
3690 );
3691 assert!(ty.is_none());
3692 }
3693
3694 #[test]
3695 fn resolve_conditional_enum_case_subject_widens() {
3696 let ty = resolve_conditional_branch(
3699 &Atomic::TLiteralEnumCase {
3700 enum_fqcn: Name::new("RoundingMode"),
3701 case_name: Name::new("Unnecessary"),
3702 },
3703 &Type::single(Atomic::TInt),
3704 &Type::single(Atomic::TInt),
3705 &Type::single(Atomic::TString),
3706 );
3707 assert!(ty.is_none());
3708 }
3709
3710 #[test]
3711 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3712 let ty = resolve_conditional_branch(
3714 &Atomic::TNull,
3715 &Type::single(Atomic::TBool),
3716 &Type::single(Atomic::TInt),
3717 &Type::single(Atomic::TString),
3718 );
3719 assert!(matches!(
3720 ty.as_ref(),
3721 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3722 ));
3723 }
3724
3725 #[test]
3726 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3727 let ty = resolve_conditional_branch(
3730 &Atomic::TBool,
3731 &Type::single(Atomic::TTrue),
3732 &Type::single(Atomic::TInt),
3733 &Type::single(Atomic::TString),
3734 );
3735 assert!(matches!(
3736 ty.as_ref(),
3737 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3738 ));
3739 }
3740
3741 #[test]
3742 fn substitute_intersection_parts() {
3743 let ty = Type::single(Atomic::TIntersection {
3744 parts: vec_to_type_params(vec![
3745 Type::single(Atomic::TNamedObject {
3746 fqcn: Name::new("Countable"),
3747 type_params: empty_type_params(),
3748 }),
3749 t_param("T"),
3750 ]),
3751 });
3752 let result = ty.substitute_templates(&bindings_t_string());
3753 let Atomic::TIntersection { parts } = &result.types[0] else {
3754 panic!("expected TIntersection");
3755 };
3756 assert_eq!(parts.len(), 2);
3757 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3758 assert!(matches!(parts[1].types[0], Atomic::TString));
3759 }
3760
3761 #[test]
3762 fn substitute_no_template_params_identity() {
3763 let ty = Type::single(Atomic::TInt);
3764 let result = ty.substitute_templates(&bindings_t_string());
3765 assert!(matches!(result.types[0], Atomic::TInt));
3766 }
3767}