mago_analyzer/plugin/libraries/stdlib/spl/
iterator_to_array.rs1use std::rc::Rc;
4
5use mago_codex::ttype::add_optional_union_type;
6use mago_codex::ttype::atomic::TAtomic;
7use mago_codex::ttype::comparator::ComparisonResult;
8use mago_codex::ttype::comparator::union_comparator::is_contained_by;
9use mago_codex::ttype::get_arraykey;
10use mago_codex::ttype::get_iterable_parameters;
11use mago_codex::ttype::get_keyed_array;
12use mago_codex::ttype::get_list;
13use mago_codex::ttype::get_mixed;
14use mago_codex::ttype::get_mixed_iterable;
15use mago_codex::ttype::union::TUnion;
16
17use crate::plugin::context::InvocationInfo;
18use crate::plugin::context::ProviderContext;
19use crate::plugin::provider::Provider;
20use crate::plugin::provider::ProviderMeta;
21use crate::plugin::provider::function::FunctionReturnTypeProvider;
22use crate::plugin::provider::function::FunctionTarget;
23
24static META: ProviderMeta = ProviderMeta::new(
25 "php::spl::iterator_to_array",
26 "iterator_to_array",
27 "Returns array with inferred key/value types from iterator",
28);
29
30#[derive(Default)]
34pub struct IteratorToArrayProvider;
35
36impl Provider for IteratorToArrayProvider {
37 fn meta() -> &'static ProviderMeta {
38 &META
39 }
40}
41
42impl FunctionReturnTypeProvider for IteratorToArrayProvider {
43 fn targets() -> FunctionTarget {
44 FunctionTarget::Exact("iterator_to_array")
45 }
46
47 fn get_return_type(
48 &self,
49 context: &ProviderContext<'_, '_, '_>,
50 invocation: &InvocationInfo<'_, '_, '_>,
51 ) -> Option<TUnion> {
52 let preserve_keys = match invocation.get_argument(1, &["preserve_keys"]) {
53 Some(argument) => context.get_expression_type(argument).and_then(|argument_type| {
54 if argument_type.is_always_truthy() {
55 Some(true)
56 } else if argument_type.is_always_falsy() {
57 Some(false)
58 } else {
59 None
60 }
61 }),
62 None => Some(true),
63 };
64
65 let iterator_argument = invocation
66 .get_argument(0, &["iterator"])
67 .and_then(|arg| context.get_rc_expression_type(arg))
68 .cloned()
69 .unwrap_or_else(|| Rc::new(get_mixed_iterable()));
70
71 let codebase = context.codebase();
72
73 let mut key_type: Option<TUnion> = None;
74 let mut value_type: Option<TUnion> = None;
75
76 let mut iterator_atomics: Vec<&TAtomic> = iterator_argument.types.iter().collect();
77 while let Some(iterator_atomic) = iterator_atomics.pop() {
78 if let TAtomic::GenericParameter(parameter) = iterator_atomic {
79 iterator_atomics.extend(parameter.constraint.types.iter());
80 }
81
82 let Some((k, v)) = get_iterable_parameters(iterator_atomic, codebase) else {
83 continue;
84 };
85
86 key_type = Some(add_optional_union_type(k, key_type.as_ref(), codebase));
87 value_type = Some(add_optional_union_type(v, value_type.as_ref(), codebase));
88 }
89
90 let mut iterator_key_type = key_type.unwrap_or_else(get_arraykey);
91 let iterator_value_type = value_type.unwrap_or_else(get_mixed);
92
93 let Some(preserve_keys) = preserve_keys else {
94 return Some(get_keyed_array(get_arraykey(), iterator_value_type));
95 };
96
97 if !preserve_keys {
98 return Some(get_list(iterator_value_type));
99 }
100
101 if !is_contained_by(
102 codebase,
103 &iterator_key_type,
104 &get_arraykey(),
105 false,
106 false,
107 false,
108 &mut ComparisonResult::default(),
109 ) {
110 iterator_key_type = get_arraykey();
111 }
112
113 Some(get_keyed_array(iterator_key_type, iterator_value_type))
114 }
115}