1use std::sync::Arc;
2use std::sync::LazyLock;
3
4use foldhash::HashSet;
5
6use mago_atom::Atom;
7use mago_atom::atom;
8
9static ATOM_FALSE: LazyLock<Atom> = LazyLock::new(|| atom("false"));
10static ATOM_TRUE: LazyLock<Atom> = LazyLock::new(|| atom("true"));
11static ATOM_BOOL: LazyLock<Atom> = LazyLock::new(|| atom("bool"));
12static ATOM_VOID: LazyLock<Atom> = LazyLock::new(|| atom("void"));
13static ATOM_NULL: LazyLock<Atom> = LazyLock::new(|| atom("null"));
14static ATOM_STRING: LazyLock<Atom> = LazyLock::new(|| atom("string"));
15static ATOM_FLOAT: LazyLock<Atom> = LazyLock::new(|| atom("float"));
16static ATOM_INT: LazyLock<Atom> = LazyLock::new(|| atom("int"));
17static ATOM_MIXED: LazyLock<Atom> = LazyLock::new(|| atom("mixed"));
18static ATOM_SCALAR: LazyLock<Atom> = LazyLock::new(|| atom("scalar"));
19static ATOM_ARRAY_KEY: LazyLock<Atom> = LazyLock::new(|| atom("array-key"));
20static ATOM_NUMERIC: LazyLock<Atom> = LazyLock::new(|| atom("numeric"));
21static ATOM_NEVER: LazyLock<Atom> = LazyLock::new(|| atom("never"));
22
23use crate::metadata::CodebaseMetadata;
24use crate::symbol::SymbolKind;
25use crate::ttype::TType;
26use crate::ttype::atomic::TAtomic;
27use crate::ttype::atomic::array::TArray;
28use crate::ttype::atomic::array::key::ArrayKey;
29use crate::ttype::atomic::array::keyed::TKeyedArray;
30use crate::ttype::atomic::array::list::TList;
31use crate::ttype::atomic::mixed::TMixed;
32use crate::ttype::atomic::mixed::truthiness::TMixedTruthiness;
33use crate::ttype::atomic::object::TObject;
34use crate::ttype::atomic::object::named::TNamedObject;
35use crate::ttype::atomic::resource::TResource;
36use crate::ttype::atomic::scalar::TScalar;
37use crate::ttype::atomic::scalar::float::TFloat;
38use crate::ttype::atomic::scalar::int::TInteger;
39use crate::ttype::atomic::scalar::string::TString;
40use crate::ttype::atomic::scalar::string::TStringLiteral;
41use crate::ttype::combination::CombinationFlags;
42use crate::ttype::combination::TypeCombination;
43use crate::ttype::combine_union_types;
44use crate::ttype::comparator::ComparisonResult;
45use crate::ttype::comparator::array_comparator::is_array_contained_by_array;
46use crate::ttype::comparator::object_comparator;
47use crate::ttype::template::variance::Variance;
48use crate::ttype::union::TUnion;
49use crate::utils::str_is_numeric;
50
51pub const DEFAULT_ARRAY_COMBINATION_THRESHOLD: u16 = 128;
58
59pub const DEFAULT_STRING_COMBINATION_THRESHOLD: u16 = 128;
65
66pub const DEFAULT_INTEGER_COMBINATION_THRESHOLD: u16 = 128;
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct CombinerOptions {
76 pub overwrite_empty_array: bool,
78 pub array_combination_threshold: u16,
80 pub string_combination_threshold: u16,
82 pub integer_combination_threshold: u16,
84}
85
86impl Default for CombinerOptions {
87 fn default() -> Self {
88 Self {
89 overwrite_empty_array: false,
90 array_combination_threshold: DEFAULT_ARRAY_COMBINATION_THRESHOLD,
91 string_combination_threshold: DEFAULT_STRING_COMBINATION_THRESHOLD,
92 integer_combination_threshold: DEFAULT_INTEGER_COMBINATION_THRESHOLD,
93 }
94 }
95}
96
97impl CombinerOptions {
98 #[inline]
100 #[must_use]
101 pub fn with_overwrite_empty_array(mut self) -> Self {
102 self.overwrite_empty_array = true;
103 self
104 }
105
106 #[inline]
108 #[must_use]
109 pub fn with_array_combination_threshold(mut self, threshold: u16) -> Self {
110 self.array_combination_threshold = threshold;
111 self
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn with_string_combination_threshold(mut self, threshold: u16) -> Self {
118 self.string_combination_threshold = threshold;
119 self
120 }
121
122 #[inline]
124 #[must_use]
125 pub fn with_integer_combination_threshold(mut self, threshold: u16) -> Self {
126 self.integer_combination_threshold = threshold;
127 self
128 }
129}
130
131pub fn combine(types: Vec<TAtomic>, codebase: &CodebaseMetadata, options: CombinerOptions) -> Vec<TAtomic> {
132 if types.len() == 1 {
133 return types;
134 }
135
136 let mut combination = TypeCombination::new();
137
138 for atomic in types {
139 if let TAtomic::Derived(derived) = atomic {
140 combination.derived_types.insert(derived);
141 continue;
142 }
143
144 scrape_type_properties(atomic, &mut combination, codebase, options);
145 }
146
147 combination.integers.sort_unstable();
148 combination.integers.dedup();
149 combination.literal_floats.sort_unstable();
150 combination.literal_floats.dedup();
151
152 finalize_sealed_arrays(&mut combination.sealed_arrays, codebase);
153
154 let is_falsy_mixed = combination.flags.falsy_mixed().unwrap_or(false);
155 let is_truthy_mixed = combination.flags.truthy_mixed().unwrap_or(false);
156 let is_nonnull_mixed = combination.flags.nonnull_mixed().unwrap_or(false);
157
158 if is_falsy_mixed
159 || is_nonnull_mixed
160 || combination.flags.contains(CombinationFlags::GENERIC_MIXED)
161 || is_truthy_mixed
162 {
163 return vec![TAtomic::Mixed(TMixed::new().with_is_non_null(is_nonnull_mixed).with_truthiness(
164 if is_truthy_mixed && !is_falsy_mixed {
165 TMixedTruthiness::Truthy
166 } else if is_falsy_mixed && !is_truthy_mixed {
167 TMixedTruthiness::Falsy
168 } else {
169 TMixedTruthiness::Undetermined
170 },
171 ))];
172 } else if combination.flags.contains(CombinationFlags::HAS_MIXED) {
173 return vec![TAtomic::Mixed(TMixed::new())];
174 }
175
176 if combination.is_simple() {
177 if combination.value_types.contains_key(&*ATOM_FALSE) {
178 return vec![TAtomic::Scalar(TScalar::r#false())];
179 }
180
181 if combination.value_types.contains_key(&*ATOM_TRUE) {
182 return vec![TAtomic::Scalar(TScalar::r#true())];
183 }
184
185 return combination.value_types.into_values().collect();
186 }
187
188 if combination.value_types.remove(&*ATOM_VOID).is_some() && combination.value_types.contains_key(&*ATOM_NULL) {
189 combination.value_types.insert(*ATOM_NULL, TAtomic::Null);
190 }
191
192 if combination.value_types.contains_key(&*ATOM_FALSE) && combination.value_types.contains_key(&*ATOM_TRUE) {
193 combination.value_types.remove(&*ATOM_FALSE);
194 combination.value_types.remove(&*ATOM_TRUE);
195 combination.value_types.insert(*ATOM_BOOL, TAtomic::Scalar(TScalar::bool()));
196 }
197
198 let estimated_capacity = combination.derived_types.len()
199 + combination.integers.len().min(10)
200 + combination.literal_floats.len()
201 + combination.enum_names.len()
202 + combination.value_types.len()
203 + combination.sealed_arrays.len()
204 + 5;
205
206 let mut new_types = Vec::with_capacity(estimated_capacity);
207 for derived_type in combination.derived_types {
208 new_types.push(TAtomic::Derived(derived_type));
209 }
210
211 if combination.flags.contains(CombinationFlags::RESOURCE) {
212 new_types.push(TAtomic::Resource(TResource { closed: None }));
213 } else {
214 let open = combination.flags.contains(CombinationFlags::OPEN_RESOURCE);
215 let closed = combination.flags.contains(CombinationFlags::CLOSED_RESOURCE);
216 match (open, closed) {
217 (true, true) => {
218 new_types.push(TAtomic::Resource(TResource { closed: None }));
219 }
220 (true, false) => {
221 new_types.push(TAtomic::Resource(TResource { closed: Some(false) }));
222 }
223 (false, true) => {
224 new_types.push(TAtomic::Resource(TResource { closed: Some(true) }));
225 }
226 _ => {
227 }
229 }
230 }
231
232 let mut arrays = vec![];
233
234 if combination.flags.contains(CombinationFlags::HAS_KEYED_ARRAY) {
235 arrays.push(TArray::Keyed(TKeyedArray {
236 known_items: if combination.keyed_array_entries.is_empty() {
237 None
238 } else {
239 Some(combination.keyed_array_entries)
240 },
241 parameters: if let Some((k, v)) = combination.keyed_array_parameters {
242 Some((Arc::new(k), Arc::new(v)))
243 } else {
244 None
245 },
246 non_empty: combination.flags.contains(CombinationFlags::KEYED_ARRAY_ALWAYS_FILLED),
247 }));
248 }
249
250 if let Some(list_parameter) = combination.list_array_parameter {
251 arrays.push(TArray::List(TList {
252 known_elements: if combination.list_array_entries.is_empty() {
253 None
254 } else {
255 Some(combination.list_array_entries)
256 },
257 element_type: Arc::new(list_parameter),
258 non_empty: combination.flags.contains(CombinationFlags::LIST_ARRAY_ALWAYS_FILLED),
259 known_count: None,
260 }));
261 }
262
263 for array in combination.sealed_arrays {
264 arrays.push(array);
265 }
266
267 if arrays.is_empty() && combination.flags.contains(CombinationFlags::HAS_EMPTY_ARRAY) {
268 arrays.push(TArray::Keyed(TKeyedArray { known_items: None, parameters: None, non_empty: false }));
269 }
270
271 new_types.extend(arrays.into_iter().map(TAtomic::Array));
272
273 for (_, (generic_type, generic_type_parameters)) in combination.object_type_params {
274 let generic_object = TAtomic::Object(TObject::Named(
275 TNamedObject::new(generic_type)
276 .with_is_this(*combination.object_static.get(&generic_type).unwrap_or(&false))
277 .with_type_parameters(Some(generic_type_parameters)),
278 ));
279
280 new_types.push(generic_object);
281 }
282
283 new_types.extend(combination.literal_strings.into_iter().map(|s| TAtomic::Scalar(TScalar::literal_string(s))));
284
285 if combination.value_types.contains_key(&*ATOM_STRING)
286 && combination.value_types.contains_key(&*ATOM_FLOAT)
287 && combination.value_types.contains_key(&*ATOM_BOOL)
288 && combination.integers.iter().any(super::atomic::scalar::int::TInteger::is_unspecified)
289 {
290 combination.integers.clear();
291 combination.value_types.remove(&*ATOM_STRING);
292 combination.value_types.remove(&*ATOM_FLOAT);
293 combination.value_types.remove(&*ATOM_BOOL);
294
295 new_types.push(TAtomic::Scalar(TScalar::Generic));
296 }
297
298 new_types.extend(TInteger::combine(combination.integers));
299 new_types.extend(combination.literal_floats.into_iter().map(|f| TAtomic::Scalar(TScalar::literal_float(f.into()))));
300
301 for (enum_name, enum_case) in combination.enum_names {
302 if combination.value_types.contains_key(&enum_name) {
303 continue;
304 }
305
306 let enum_object = match enum_case {
307 Some(case) => TAtomic::Object(TObject::new_enum_case(enum_name, case)),
308 None => TAtomic::Object(TObject::new_enum(enum_name)),
309 };
310
311 combination.value_types.insert(enum_object.get_id(), enum_object);
312 }
313
314 let mut has_never = combination.value_types.contains_key(&*ATOM_NEVER);
315
316 let combination_value_type_count = combination.value_types.len();
317 let mixed_from_loop_isset = combination.flags.mixed_from_loop_isset().unwrap_or(false);
318
319 for (_, atomic) in combination.value_types {
320 let tc = usize::from(has_never);
321 if atomic.is_mixed()
322 && mixed_from_loop_isset
323 && (combination_value_type_count > (tc + 1) || new_types.len() > tc)
324 {
325 continue;
326 }
327
328 if (atomic.is_never() || atomic.is_templated_as_never())
329 && (combination_value_type_count > 1 || !new_types.is_empty())
330 {
331 has_never = true;
332 continue;
333 }
334
335 new_types.push(atomic);
336 }
337
338 if new_types.is_empty() {
339 if !has_never {
340 unreachable!("No types to return, but no 'never' type found in combination.");
341 }
342
343 return vec![TAtomic::Never];
344 }
345
346 new_types
347}
348
349fn finalize_sealed_arrays(arrays: &mut Vec<TArray>, codebase: &CodebaseMetadata) {
350 if arrays.len() <= 1 {
351 return;
352 }
353
354 arrays.sort_unstable_by_key(|a| match a {
355 TArray::List(list) => list.known_elements.as_ref().map_or(0, std::collections::BTreeMap::len),
356 TArray::Keyed(keyed) => keyed.known_items.as_ref().map_or(0, std::collections::BTreeMap::len),
357 });
358
359 let mut keep = vec![true; arrays.len()];
360
361 for i in 0..arrays.len() {
362 if !keep[i] {
363 continue;
364 }
365
366 for j in (i + 1)..arrays.len() {
367 if !keep[j] {
368 continue;
369 }
370
371 if is_array_contained_by_array(codebase, &arrays[i], &arrays[j], false, &mut ComparisonResult::new()) {
372 keep[i] = false;
373 break;
374 }
375
376 if is_array_contained_by_array(codebase, &arrays[j], &arrays[i], false, &mut ComparisonResult::new()) {
377 keep[j] = false;
378 }
379 }
380 }
381
382 let mut write = 0;
383 for (read, item) in keep.iter().enumerate().take(arrays.len()) {
384 if *item {
385 if write != read {
386 arrays.swap(write, read);
387 }
388
389 write += 1;
390 }
391 }
392
393 arrays.truncate(write);
394}
395
396fn scrape_type_properties(
397 atomic: TAtomic,
398 combination: &mut TypeCombination,
399 codebase: &CodebaseMetadata,
400 options: CombinerOptions,
401) {
402 if combination.flags.contains(CombinationFlags::GENERIC_MIXED) {
403 return;
404 }
405
406 if let TAtomic::Mixed(mixed) = atomic {
407 if mixed.is_isset_from_loop() {
408 if combination.flags.contains(CombinationFlags::GENERIC_MIXED) {
409 return; }
411
412 if combination.flags.mixed_from_loop_isset().is_none() {
413 combination.flags.set_mixed_from_loop_isset(Some(true));
414 }
415
416 combination.value_types.insert(*ATOM_MIXED, atomic);
417
418 return;
419 }
420
421 combination.flags.insert(CombinationFlags::HAS_MIXED);
422
423 if mixed.is_vanilla() {
424 combination.flags.set_falsy_mixed(Some(false));
425 combination.flags.set_truthy_mixed(Some(false));
426 combination.flags.set_mixed_from_loop_isset(Some(false));
427 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
428
429 return;
430 }
431
432 if mixed.is_truthy() {
433 if combination.flags.contains(CombinationFlags::GENERIC_MIXED) {
434 return;
435 }
436
437 combination.flags.set_mixed_from_loop_isset(Some(false));
438
439 if combination.flags.falsy_mixed().unwrap_or(false) {
440 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
441 combination.flags.set_falsy_mixed(Some(false));
442 return;
443 }
444
445 if combination.flags.truthy_mixed().is_some() {
446 return;
447 }
448
449 for existing_value_type in combination.value_types.values() {
450 if !existing_value_type.is_truthy() {
451 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
452 return;
453 }
454 }
455
456 combination.flags.set_truthy_mixed(Some(true));
457 } else {
458 combination.flags.set_truthy_mixed(Some(false));
459 }
460
461 if mixed.is_falsy() {
462 if combination.flags.contains(CombinationFlags::GENERIC_MIXED) {
463 return;
464 }
465
466 combination.flags.set_mixed_from_loop_isset(Some(false));
467
468 if combination.flags.truthy_mixed().unwrap_or(false) {
469 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
470 combination.flags.set_truthy_mixed(Some(false));
471 return;
472 }
473
474 if combination.flags.falsy_mixed().is_some() {
475 return;
476 }
477
478 for existing_value_type in combination.value_types.values() {
479 if !existing_value_type.is_falsy() {
480 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
481 return;
482 }
483 }
484
485 combination.flags.set_falsy_mixed(Some(true));
486 } else {
487 combination.flags.set_falsy_mixed(Some(false));
488 }
489
490 if mixed.is_non_null() {
491 if combination.flags.contains(CombinationFlags::GENERIC_MIXED) {
492 return;
493 }
494
495 combination.flags.set_mixed_from_loop_isset(Some(false));
496
497 if combination.value_types.contains_key(&*ATOM_NULL) {
498 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
499 return;
500 }
501
502 if combination.flags.falsy_mixed().unwrap_or(false) {
503 combination.flags.set_falsy_mixed(Some(false));
504 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
505 return;
506 }
507
508 if combination.flags.nonnull_mixed().is_some() {
509 return;
510 }
511
512 combination.flags.set_mixed_from_loop_isset(Some(false));
513 combination.flags.set_nonnull_mixed(Some(true));
514 } else {
515 combination.flags.set_nonnull_mixed(Some(false));
516 }
517
518 return;
519 }
520
521 if combination.flags.falsy_mixed().unwrap_or(false) {
522 if !atomic.is_falsy() {
523 combination.flags.set_falsy_mixed(Some(false));
524 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
525 }
526
527 return;
528 }
529
530 if combination.flags.truthy_mixed().unwrap_or(false) {
531 if !atomic.is_truthy() {
532 combination.flags.set_truthy_mixed(Some(false));
533 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
534 }
535
536 return;
537 }
538
539 if combination.flags.nonnull_mixed().unwrap_or(false) {
540 if let TAtomic::Null = atomic {
541 combination.flags.set_nonnull_mixed(Some(false));
542 combination.flags.insert(CombinationFlags::GENERIC_MIXED);
543 }
544
545 return;
546 }
547
548 if combination.flags.contains(CombinationFlags::HAS_MIXED) {
549 return;
550 }
551
552 if matches!(&atomic, TAtomic::Scalar(TScalar::Bool(bool)) if !bool.is_general())
553 && combination.value_types.contains_key(&*ATOM_BOOL)
554 {
555 return;
556 }
557
558 if let TAtomic::Resource(TResource { closed }) = atomic {
559 match closed {
560 Some(closed) => {
561 if closed {
562 combination.flags.insert(CombinationFlags::CLOSED_RESOURCE);
563 } else {
564 combination.flags.insert(CombinationFlags::OPEN_RESOURCE);
565 }
566 }
567 None => {
568 combination.flags.insert(CombinationFlags::RESOURCE);
569 }
570 }
571
572 return;
573 }
574
575 if matches!(&atomic, TAtomic::Scalar(TScalar::Bool(bool)) if bool.is_general()) {
576 combination.value_types.remove(&*ATOM_FALSE);
577 combination.value_types.remove(&*ATOM_TRUE);
578 }
579
580 if let TAtomic::Array(array) = atomic {
581 if options.overwrite_empty_array && array.is_empty() {
582 combination.flags.insert(CombinationFlags::HAS_EMPTY_ARRAY);
583
584 return;
585 }
586
587 if !array.is_empty()
591 && array.is_sealed()
592 && combination.list_array_parameter.is_some()
593 && !combination.flags.contains(CombinationFlags::HAS_KEYED_ARRAY)
594 && combination.sealed_arrays.len() < options.array_combination_threshold as usize
595 {
596 combination.sealed_arrays.push(array);
597 return;
598 }
599
600 for array in std::iter::once(array).chain(combination.sealed_arrays.drain(..)) {
601 match array {
602 TArray::List(TList { element_type, known_elements, non_empty, known_count }) => {
603 if non_empty {
604 if let Some(ref mut existing_counts) = combination.list_array_counts {
605 if let Some(known_count) = known_count {
606 existing_counts.insert(known_count);
607 } else {
608 combination.list_array_counts = None;
609 }
610 }
611
612 combination.flags.insert(CombinationFlags::LIST_ARRAY_SOMETIMES_FILLED);
613 } else {
614 combination.flags.remove(CombinationFlags::LIST_ARRAY_ALWAYS_FILLED);
615 }
616
617 if let Some(known_elements) = known_elements {
618 let mut has_defined_keys = false;
619
620 for (candidate_element_index, (candidate_optional, candidate_element_type)) in known_elements {
621 let existing_entry = combination.list_array_entries.get(&candidate_element_index);
622
623 let new_entry = if let Some((existing_optional, existing_type)) = existing_entry {
624 (
625 *existing_optional || candidate_optional,
626 combine_union_types(existing_type, &candidate_element_type, codebase, options),
627 )
628 } else {
629 (
630 candidate_optional,
631 if let Some(ref mut existing_value_parameter) = combination.list_array_parameter {
632 if !existing_value_parameter.is_never() {
633 *existing_value_parameter = combine_union_types(
634 existing_value_parameter,
635 &candidate_element_type,
636 codebase,
637 options,
638 );
639
640 continue;
641 }
642
643 candidate_element_type
644 } else {
645 candidate_element_type
646 },
647 )
648 };
649
650 combination.list_array_entries.insert(candidate_element_index, new_entry);
651
652 if !candidate_optional {
653 has_defined_keys = true;
654 }
655 }
656
657 if !has_defined_keys {
658 combination.flags.remove(CombinationFlags::LIST_ARRAY_ALWAYS_FILLED);
659 }
660 } else if !options.overwrite_empty_array {
661 if element_type.is_never() {
662 for (pu, _) in combination.list_array_entries.values_mut() {
663 *pu = true;
664 }
665 } else {
666 for (_, entry_type) in combination.list_array_entries.values() {
667 if let Some(ref mut existing_value_param) = combination.list_array_parameter {
668 *existing_value_param =
669 combine_union_types(existing_value_param, entry_type, codebase, options);
670 }
671 }
672
673 combination.list_array_entries.clear();
674 }
675 }
676
677 combination.list_array_parameter = if let Some(ref existing_type) = combination.list_array_parameter
678 {
679 Some(combine_union_types(existing_type, &element_type, codebase, options))
680 } else {
681 Some((*element_type).clone())
682 };
683 }
684 TArray::Keyed(TKeyedArray { parameters, known_items, non_empty, .. }) => {
685 let had_previous_keyed_array = combination.flags.contains(CombinationFlags::HAS_KEYED_ARRAY);
686 combination.flags.insert(CombinationFlags::HAS_KEYED_ARRAY);
687
688 if non_empty {
689 combination.flags.insert(CombinationFlags::KEYED_ARRAY_SOMETIMES_FILLED);
690 } else {
691 combination.flags.remove(CombinationFlags::KEYED_ARRAY_ALWAYS_FILLED);
692 }
693
694 if let Some(known_items) = known_items {
695 let has_existing_entries =
696 !combination.keyed_array_entries.is_empty() || had_previous_keyed_array;
697 let mut possibly_undefined_entries =
698 combination.keyed_array_entries.keys().copied().collect::<HashSet<_>>();
699
700 let mut has_defined_keys = false;
701
702 for (candidate_item_name, (cu, candidate_item_type)) in known_items {
703 if let Some((eu, existing_type)) =
704 combination.keyed_array_entries.get_mut(&candidate_item_name)
705 {
706 if cu {
707 *eu = true;
708 }
709 if &candidate_item_type != existing_type {
710 *existing_type =
711 combine_union_types(existing_type, &candidate_item_type, codebase, options);
712 }
713 } else {
714 let new_item_value_type =
715 if let Some((ref mut existing_key_param, ref mut existing_value_param)) =
716 combination.keyed_array_parameters
717 {
718 adjust_keyed_array_parameters(
719 existing_value_param,
720 &candidate_item_type,
721 codebase,
722 options,
723 &candidate_item_name,
724 existing_key_param,
725 );
726
727 continue;
728 } else {
729 let new_type = candidate_item_type.clone();
730 (has_existing_entries || cu, new_type)
731 };
732
733 combination.keyed_array_entries.insert(candidate_item_name, new_item_value_type);
734 }
735
736 possibly_undefined_entries.remove(&candidate_item_name);
737
738 if !cu {
739 has_defined_keys = true;
740 }
741 }
742
743 if !has_defined_keys {
744 combination.flags.remove(CombinationFlags::KEYED_ARRAY_ALWAYS_FILLED);
745 }
746
747 for possibly_undefined_type_key in possibly_undefined_entries {
748 let possibly_undefined_type =
749 combination.keyed_array_entries.get_mut(&possibly_undefined_type_key);
750 if let Some((pu, _)) = possibly_undefined_type {
751 *pu = true;
752 }
753 }
754 } else if !options.overwrite_empty_array {
755 if match ¶meters {
756 Some((_, value_param)) => value_param.is_never(),
757 None => true,
758 } {
759 for (tu, _) in combination.keyed_array_entries.values_mut() {
760 *tu = true;
761 }
762 } else {
763 for (key, (_, entry_type)) in &combination.keyed_array_entries {
764 if let Some((ref mut existing_key_param, ref mut existing_value_param)) =
765 combination.keyed_array_parameters
766 {
767 adjust_keyed_array_parameters(
768 existing_value_param,
769 entry_type,
770 codebase,
771 options,
772 key,
773 existing_key_param,
774 );
775 }
776 }
777
778 combination.keyed_array_entries.clear();
779 }
780 }
781
782 combination.keyed_array_parameters = match (&combination.keyed_array_parameters, parameters) {
783 (None, None) => None,
784 (Some(existing_types), None) => Some(existing_types.clone()),
785 (None, Some(params)) => Some(((*params.0).clone(), (*params.1).clone())),
786 (Some(existing_types), Some(params)) => Some((
787 combine_union_types(&existing_types.0, ¶ms.0, codebase, options),
788 combine_union_types(&existing_types.1, ¶ms.1, codebase, options),
789 )),
790 };
791 }
792 }
793 }
794
795 return;
796 }
797
798 if let TAtomic::Object(TObject::Any) = atomic {
801 combination.flags.insert(CombinationFlags::HAS_OBJECT_TOP_TYPE);
802 combination.value_types.retain(|_, t| !matches!(t, TAtomic::Object(TObject::Named(_))));
803 combination.value_types.insert(atomic.get_id(), atomic);
804
805 return;
806 }
807
808 if let TAtomic::Object(TObject::Named(named_object)) = &atomic {
809 if let Some(object_static) = combination.object_static.get(named_object.get_name_ref()) {
810 if *object_static && !named_object.is_this() {
811 combination.object_static.insert(named_object.get_name(), false);
812 }
813 } else {
814 combination.object_static.insert(named_object.get_name(), named_object.is_this());
815 }
816 }
817
818 if let TAtomic::Object(TObject::Named(named_object)) = &atomic {
819 let fq_class_name = named_object.get_name();
820 if let Some(type_parameters) = named_object.get_type_parameters() {
821 let object_type_key = get_combiner_key(fq_class_name, type_parameters, codebase);
822
823 if let Some((_, existing_type_params)) = combination.object_type_params.get(&object_type_key) {
824 let mut new_type_parameters = Vec::with_capacity(type_parameters.len());
825 for (i, type_param) in type_parameters.iter().enumerate() {
826 if let Some(existing_type_param) = existing_type_params.get(i) {
827 new_type_parameters.push(combine_union_types(
828 existing_type_param,
829 type_param,
830 codebase,
831 options,
832 ));
833 }
834 }
835
836 combination.object_type_params.insert(object_type_key, (fq_class_name, new_type_parameters));
837 } else {
838 combination.object_type_params.insert(object_type_key, (fq_class_name, type_parameters.to_vec()));
839 }
840
841 return;
842 }
843 }
844
845 if let TAtomic::Object(TObject::Enum(enum_object)) = atomic {
846 combination.enum_names.insert((enum_object.get_name(), enum_object.get_case()));
847
848 return;
849 }
850
851 if let TAtomic::Object(TObject::Named(named_object)) = &atomic {
852 let fq_class_name = named_object.get_name();
853 let intersection_types = named_object.get_intersection_types();
854
855 if combination.flags.contains(CombinationFlags::HAS_OBJECT_TOP_TYPE)
856 || combination.value_types.contains_key(&atomic.get_id())
857 {
858 return;
859 }
860
861 let Some(symbol_type) = codebase.symbols.get_kind(&fq_class_name) else {
862 combination.value_types.insert(atomic.get_id(), atomic);
863 return;
864 };
865
866 if !matches!(symbol_type, SymbolKind::Class | SymbolKind::Enum | SymbolKind::Interface) {
867 combination.value_types.insert(atomic.get_id(), atomic);
868 return;
869 }
870
871 let is_class = matches!(symbol_type, SymbolKind::Class);
872 let is_interface = matches!(symbol_type, SymbolKind::Interface);
873
874 let mut types_to_remove: Vec<Atom> = Vec::new();
875
876 for (key, existing_type) in &combination.value_types {
877 if let TAtomic::Object(TObject::Named(existing_object)) = &existing_type {
878 let existing_name = existing_object.get_name();
879
880 if intersection_types.is_some() || existing_object.has_intersection_types() {
881 if object_comparator::is_shallowly_contained_by(
882 codebase,
883 existing_type,
884 &atomic,
885 false,
886 &mut ComparisonResult::new(),
887 ) {
888 types_to_remove.push(existing_name);
889 continue;
890 }
891
892 if object_comparator::is_shallowly_contained_by(
893 codebase,
894 &atomic,
895 existing_type,
896 false,
897 &mut ComparisonResult::new(),
898 ) {
899 return;
900 }
901
902 continue;
903 }
904
905 let Some(existing_symbol_kind) = codebase.symbols.get_kind(existing_object.get_name_ref()) else {
906 continue;
907 };
908
909 if matches!(existing_symbol_kind, SymbolKind::Class) {
910 if codebase.is_instance_of(&existing_name, &fq_class_name) {
912 types_to_remove.push(*key);
913 continue;
914 }
915
916 if is_class {
917 if codebase.class_extends(&fq_class_name, &existing_name) {
919 return;
920 }
921 } else if is_interface {
922 if codebase.class_implements(&fq_class_name, &existing_name) {
924 return;
925 }
926 }
927 } else if matches!(existing_symbol_kind, SymbolKind::Interface) {
928 if codebase.class_implements(&existing_name, &fq_class_name) {
929 types_to_remove.push(existing_name);
930 continue;
931 }
932
933 if (is_class || is_interface) && codebase.class_implements(&fq_class_name, &existing_name) {
934 return;
935 }
936 }
937 }
938 }
939
940 combination.value_types.insert(atomic.get_id(), atomic);
941
942 for type_key in types_to_remove {
943 combination.value_types.remove(&type_key);
944 }
945
946 return;
947 }
948
949 if let TAtomic::Scalar(TScalar::Generic) = atomic {
950 combination.literal_strings.clear();
951 combination.integers.clear();
952 combination.literal_floats.clear();
953 combination.value_types.retain(|k, _| {
954 k != "string"
955 && k != "bool"
956 && k != "false"
957 && k != "true"
958 && k != "float"
959 && k != "numeric"
960 && k != "array-key"
961 });
962
963 combination.value_types.insert(atomic.get_id(), atomic);
964 return;
965 }
966
967 if let TAtomic::Scalar(TScalar::ArrayKey) = atomic {
968 if combination.value_types.contains_key(&*ATOM_SCALAR) {
969 return;
970 }
971
972 combination.literal_strings.clear();
973 combination.integers.clear();
974 combination.value_types.retain(|k, _| k != &*ATOM_STRING && k != &*ATOM_INT);
975 combination.value_types.insert(atomic.get_id(), atomic);
976
977 return;
978 }
979
980 if let TAtomic::Scalar(TScalar::String(_) | TScalar::Integer(_)) = atomic
981 && (combination.value_types.contains_key(&*ATOM_SCALAR)
982 || combination.value_types.contains_key(&*ATOM_ARRAY_KEY))
983 {
984 return;
985 }
986
987 if let TAtomic::Scalar(TScalar::Float(_) | TScalar::Integer(_)) = atomic
988 && (combination.value_types.contains_key(&*ATOM_NUMERIC) || combination.value_types.contains_key(&*ATOM_SCALAR))
989 {
990 return;
991 }
992
993 if let TAtomic::Scalar(TScalar::String(mut string_scalar)) = atomic {
994 if let Some(existing_string_type) = combination.value_types.get_mut(&*ATOM_STRING) {
995 if let TAtomic::Scalar(TScalar::String(existing_string_type)) = existing_string_type {
996 if let Some(lit_atom) = string_scalar.get_known_literal_atom() {
997 let lit_value = lit_atom.as_str();
998 let is_incompatible = (existing_string_type.is_numeric && !str_is_numeric(lit_value))
999 || (existing_string_type.is_truthy && (lit_value.is_empty() || lit_value == "0"))
1000 || (existing_string_type.is_non_empty && lit_value.is_empty())
1001 || (existing_string_type.is_lowercase && lit_value.chars().any(char::is_uppercase));
1002
1003 if is_incompatible {
1004 if combination.literal_strings.len() >= options.string_combination_threshold as usize {
1006 *existing_string_type = combine_string_scalars(existing_string_type, string_scalar);
1008 } else {
1009 combination.literal_strings.insert(lit_atom);
1010 }
1011 } else {
1012 *existing_string_type = combine_string_scalars(existing_string_type, string_scalar);
1013 }
1014 } else {
1015 *existing_string_type = combine_string_scalars(existing_string_type, string_scalar);
1016 }
1017 }
1018 } else if let Some(atom) = string_scalar.get_known_literal_atom() {
1019 if combination.literal_strings.len() >= options.string_combination_threshold as usize {
1021 combination.literal_strings.clear();
1023 combination.value_types.insert(*ATOM_STRING, TAtomic::Scalar(TScalar::string()));
1024 } else {
1025 combination.literal_strings.insert(atom);
1026 }
1027 } else {
1028 let mut literals_to_keep = Vec::new();
1032
1033 if string_scalar.is_truthy
1034 || string_scalar.is_non_empty
1035 || string_scalar.is_numeric
1036 || string_scalar.is_lowercase
1037 {
1038 for value in &combination.literal_strings {
1039 if value.is_empty() {
1040 string_scalar.is_non_empty = false;
1041 string_scalar.is_truthy = false;
1042 string_scalar.is_numeric = false;
1043 break;
1044 } else if value == "0" {
1045 string_scalar.is_truthy = false;
1046 }
1047
1048 if string_scalar.is_numeric && !str_is_numeric(value) {
1050 literals_to_keep.push(*value);
1051 } else {
1052 string_scalar.is_numeric = string_scalar.is_numeric && str_is_numeric(value);
1053 }
1054
1055 string_scalar.is_lowercase = string_scalar.is_lowercase && value.chars().all(|c| !c.is_uppercase());
1056 }
1057 }
1058
1059 combination.value_types.insert(*ATOM_STRING, TAtomic::Scalar(TScalar::String(string_scalar)));
1060
1061 combination.literal_strings.clear();
1062 for lit in literals_to_keep {
1063 combination.literal_strings.insert(lit);
1064 }
1065 }
1066
1067 return;
1068 }
1069
1070 if let TAtomic::Scalar(TScalar::Integer(integer)) = &atomic {
1071 if combination.value_types.contains_key(&*ATOM_INT) {
1073 return;
1074 }
1075
1076 if integer.is_literal() && combination.integers.len() >= options.integer_combination_threshold as usize {
1078 combination.integers.clear();
1080 combination.value_types.insert(*ATOM_INT, TAtomic::Scalar(TScalar::int()));
1081 return;
1082 }
1083
1084 combination.integers.push(*integer);
1085
1086 return;
1087 }
1088
1089 if let TAtomic::Scalar(TScalar::Float(float_scalar)) = &atomic {
1090 if combination.value_types.contains_key(&*ATOM_FLOAT) {
1091 return;
1092 }
1093
1094 if let TFloat::Literal(literal_value) = float_scalar {
1095 if combination.literal_floats.len() >= options.string_combination_threshold as usize {
1097 combination.literal_floats.clear();
1099 combination.value_types.insert(*ATOM_FLOAT, TAtomic::Scalar(TScalar::float()));
1100 return;
1101 }
1102 combination.literal_floats.push(*literal_value);
1103 } else {
1104 combination.literal_floats.clear();
1105 combination.value_types.insert(*ATOM_FLOAT, atomic);
1106 }
1107
1108 return;
1109 }
1110
1111 combination.value_types.insert(atomic.get_id(), atomic);
1112}
1113
1114fn adjust_keyed_array_parameters(
1115 existing_value_param: &mut TUnion,
1116 entry_type: &TUnion,
1117 codebase: &CodebaseMetadata,
1118 options: CombinerOptions,
1119 key: &ArrayKey,
1120 existing_key_param: &mut TUnion,
1121) {
1122 *existing_value_param = combine_union_types(existing_value_param, entry_type, codebase, options);
1123 let new_key_type = key.to_union();
1124 *existing_key_param = combine_union_types(existing_key_param, &new_key_type, codebase, options);
1125}
1126
1127const COMBINER_KEY_STACK_BUF: usize = 256;
1128
1129fn get_combiner_key(name: Atom, type_params: &[TUnion], codebase: &CodebaseMetadata) -> Atom {
1130 let covariants = if let Some(class_like_metadata) = codebase.get_class_like(&name) {
1131 &class_like_metadata.template_variance
1132 } else {
1133 return name;
1134 };
1135
1136 let name_str = name.as_str();
1137 let mut estimated_len = name_str.len() + 2; for (i, tunion) in type_params.iter().enumerate() {
1139 if i > 0 {
1140 estimated_len += 2; }
1142
1143 if covariants.get(&i) == Some(&Variance::Covariant) {
1144 estimated_len += 1; } else {
1146 estimated_len += tunion.get_id().len();
1147 }
1148 }
1149
1150 if estimated_len <= COMBINER_KEY_STACK_BUF {
1151 let mut buffer = [0u8; COMBINER_KEY_STACK_BUF];
1152 let mut pos = 0;
1153
1154 buffer[pos..pos + name_str.len()].copy_from_slice(name_str.as_bytes());
1155 pos += name_str.len();
1156
1157 buffer[pos] = b'<';
1158 pos += 1;
1159
1160 for (i, tunion) in type_params.iter().enumerate() {
1161 if i > 0 {
1162 buffer[pos..pos + 2].copy_from_slice(b", ");
1163 pos += 2;
1164 }
1165 let param_str =
1166 if covariants.get(&i) == Some(&Variance::Covariant) { "*" } else { tunion.get_id().as_str() };
1167 buffer[pos..pos + param_str.len()].copy_from_slice(param_str.as_bytes());
1168 pos += param_str.len();
1169 }
1170
1171 buffer[pos] = b'>';
1172 pos += 1;
1173
1174 return atom(unsafe { std::str::from_utf8_unchecked(&buffer[..pos]) });
1176 }
1177
1178 let mut result = String::with_capacity(estimated_len);
1179 result.push_str(name_str);
1180 result.push('<');
1181 for (i, tunion) in type_params.iter().enumerate() {
1182 if i > 0 {
1183 result.push_str(", ");
1184 }
1185 if covariants.get(&i) == Some(&Variance::Covariant) {
1186 result.push('*');
1187 } else {
1188 result.push_str(tunion.get_id().as_str());
1189 }
1190 }
1191 result.push('>');
1192 atom(&result)
1193}
1194
1195fn combine_string_scalars(s1: &TString, s2: TString) -> TString {
1196 TString {
1197 literal: match (&s1.literal, s2.literal) {
1198 (Some(TStringLiteral::Value(v1)), Some(TStringLiteral::Value(v2))) => {
1199 if v1 == &v2 {
1200 Some(TStringLiteral::Value(v2))
1201 } else {
1202 Some(TStringLiteral::Unspecified)
1203 }
1204 }
1205 (Some(TStringLiteral::Unspecified), Some(_)) | (Some(_), Some(TStringLiteral::Unspecified)) => {
1206 Some(TStringLiteral::Unspecified)
1207 }
1208 _ => None,
1209 },
1210 is_numeric: s1.is_numeric && s2.is_numeric,
1211 is_truthy: s1.is_truthy && s2.is_truthy,
1212 is_non_empty: s1.is_non_empty && s2.is_non_empty,
1213 is_lowercase: s1.is_lowercase && s2.is_lowercase,
1214 }
1215}
1216
1217#[cfg(test)]
1218mod tests {
1219 use std::collections::BTreeMap;
1220
1221 use super::*;
1222
1223 use crate::ttype::atomic::TAtomic;
1224 use crate::ttype::atomic::array::list::TList;
1225 use crate::ttype::atomic::scalar::TScalar;
1226
1227 #[test]
1228 fn test_combine_scalars() {
1229 let types = vec![
1230 TAtomic::Scalar(TScalar::string()),
1231 TAtomic::Scalar(TScalar::int()),
1232 TAtomic::Scalar(TScalar::float()),
1233 TAtomic::Scalar(TScalar::bool()),
1234 ];
1235
1236 let combined =
1237 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1238
1239 assert_eq!(combined.len(), 1);
1240 assert!(matches!(combined[0], TAtomic::Scalar(TScalar::Generic)));
1241 }
1242
1243 #[test]
1244 fn test_combine_boolean_lists() {
1245 let types = vec![
1246 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1247 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::r#false())))),
1248 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::r#true())))),
1249 ])))),
1250 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1251 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::r#true())))),
1252 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::r#false())))),
1253 ])))),
1254 ];
1255
1256 let combined =
1257 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1258
1259 assert_eq!(combined.len(), 2);
1260 assert!(matches!(combined[0], TAtomic::Array(TArray::List(_))));
1261 assert!(matches!(combined[1], TAtomic::Array(TArray::List(_))));
1262 }
1263
1264 #[test]
1265 fn test_combine_integer_lists() {
1266 let types = vec![
1267 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1268 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(1)))))),
1269 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(2)))))),
1270 ])))),
1271 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1272 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(2)))))),
1273 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(1)))))),
1274 ])))),
1275 ];
1276
1277 let combined =
1278 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1279
1280 assert_eq!(combined.len(), 2);
1281 assert!(matches!(combined[0], TAtomic::Array(TArray::List(_))));
1282 assert!(matches!(combined[1], TAtomic::Array(TArray::List(_))));
1283 }
1284
1285 #[test]
1286 fn test_combine_string_lists() {
1287 let types = vec![
1288 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1289 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("a".into())))))),
1290 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("b".into())))))),
1291 ])))),
1292 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1293 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("b".into())))))),
1294 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("a".into())))))),
1295 ])))),
1296 ];
1297
1298 let combined =
1299 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1300
1301 assert_eq!(combined.len(), 2);
1302 assert!(matches!(combined[0], TAtomic::Array(TArray::List(_))));
1303 assert!(matches!(combined[1], TAtomic::Array(TArray::List(_))));
1304 }
1305
1306 #[test]
1307 fn test_combine_mixed_literal_lists() {
1308 let types = vec![
1309 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1310 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(1)))))),
1311 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("a".into())))))),
1312 ])))),
1313 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1314 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::String(TString::known_literal("b".into())))))),
1315 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(2)))))),
1316 ])))),
1317 ];
1318
1319 let combined =
1320 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1321
1322 assert_eq!(combined.len(), 2);
1323 assert!(matches!(combined[0], TAtomic::Array(TArray::List(_))));
1324 assert!(matches!(combined[1], TAtomic::Array(TArray::List(_))));
1325 }
1326
1327 #[test]
1328 fn test_combine_list_with_generic_list() {
1329 let types = vec![
1330 TAtomic::Array(TArray::List(TList::from_known_elements(BTreeMap::from_iter([
1331 (0, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(1)))))),
1332 (1, (false, TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::literal(2)))))),
1333 ])))),
1334 TAtomic::Array(TArray::List(TList::new(Arc::new(TUnion::from_atomic(TAtomic::Scalar(TScalar::int())))))), ];
1336
1337 let combined =
1338 combine(types, &CodebaseMetadata::default(), CombinerOptions::default().with_overwrite_empty_array());
1339
1340 assert_eq!(combined.len(), 1);
1342
1343 let TAtomic::Array(TArray::List(list_type)) = &combined[0] else {
1344 panic!("Expected a list type");
1345 };
1346
1347 let Some(known_elements) = &list_type.known_elements else {
1348 panic!("Expected known elements");
1349 };
1350
1351 assert!(!list_type.is_non_empty());
1352 assert!(list_type.known_count.is_none());
1353 assert!(list_type.element_type.is_int());
1354
1355 assert_eq!(known_elements.len(), 2);
1356 assert!(known_elements.contains_key(&0));
1357 assert!(known_elements.contains_key(&1));
1358
1359 let Some(first_element) = known_elements.get(&0) else {
1360 panic!("Expected first element");
1361 };
1362
1363 let Some(second_element) = known_elements.get(&1) else {
1364 panic!("Expected second element");
1365 };
1366
1367 assert!(first_element.1.is_int());
1368 assert!(second_element.1.is_int());
1369 }
1370}