Skip to main content

mago_analyzer/plugin/libraries/stdlib/spl/
iterator_to_array.rs

1//! `iterator_to_array()` return type provider.
2
3use 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/// Provider for the `iterator_to_array()` function.
31///
32/// Returns an array type with key/value types inferred from the iterator.
33#[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}