Skip to main content

mago_analyzer/plugin/libraries/stdlib/array/
array_is_list.rs

1//! `array_is_list()` return type provider.
2//!
3//! A generic `array<Tk, Tv>` with `Tk of array-key` may be instantiated as a
4//! list, but its conditional stub return type cannot preserve that dependency.
5//! Keep the return as `bool` in that case; the assertion reconciler still
6//! narrows the true branch to `list<Tv>`.
7
8use mago_codex::ttype::atomic::TAtomic;
9use mago_codex::ttype::atomic::array::TArray;
10use mago_codex::ttype::get_bool;
11use mago_codex::ttype::union::TUnion;
12
13use crate::plugin::context::InvocationInfo;
14use crate::plugin::context::ProviderContext;
15use crate::plugin::provider::Provider;
16use crate::plugin::provider::ProviderMeta;
17use crate::plugin::provider::function::FunctionReturnTypeProvider;
18use crate::plugin::provider::function::FunctionTarget;
19
20static META: ProviderMeta = ProviderMeta::new(
21    "php::array::array_is_list",
22    "array_is_list",
23    "Keeps generic array key/list checks indeterminate until narrowed",
24);
25
26#[derive(Default)]
27pub struct ArrayIsListProvider;
28
29impl Provider for ArrayIsListProvider {
30    fn meta() -> &'static ProviderMeta {
31        &META
32    }
33}
34
35impl FunctionReturnTypeProvider for ArrayIsListProvider {
36    fn targets() -> FunctionTarget {
37        FunctionTarget::Exact(b"array_is_list")
38    }
39
40    fn get_return_type(
41        &self,
42        context: &ProviderContext<'_, '_, '_>,
43        invocation: &InvocationInfo<'_, '_, '_>,
44    ) -> Option<TUnion> {
45        let array = invocation.get_argument(0, &[b"array"])?;
46        let array_type = context.get_expression_type(array)?;
47
48        array_type.types.iter().any(generic_keyed_array_can_be_list).then_some(get_bool())
49    }
50}
51
52fn generic_keyed_array_can_be_list(atomic: &TAtomic) -> bool {
53    let TAtomic::Array(TArray::Keyed(keyed)) = atomic else {
54        return false;
55    };
56
57    let Some((key_type, _)) = keyed.parameters.as_ref() else {
58        return false;
59    };
60
61    key_type.types.iter().any(
62        |key_atomic| matches!(key_atomic, TAtomic::GenericParameter(parameter) if parameter.constraint.is_array_key()),
63    )
64}