Skip to main content

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

1//! `array_all()` assertion provider.
2//!
3//! Lifts a callback predicate's `@assert-if-true` onto the `$array` argument:
4//! when `array_all($array, fn ($v) => is_int($v))` returns true, every value
5//! of `$array` matches the callback's `if_true_assertion`, so the array can
6//! be narrowed to `array<K, NarrowedV>`. Hosts like `assert(array_all(...))`
7//! and `if (array_all(...))` consume this through the existing if-true
8//! assertion pipeline.
9
10use std::sync::Arc;
11
12use mago_codex::assertion::Assertion;
13use mago_codex::ttype::atomic::TAtomic;
14use mago_codex::ttype::atomic::array::TArray;
15use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
16use mago_codex::ttype::get_array_parameters;
17use mago_word::word;
18
19use crate::plugin::context::InvocationInfo;
20use crate::plugin::context::ProviderContext;
21use crate::plugin::libraries::stdlib::array::array_filter::apply_assertion_to_narrow_type;
22use crate::plugin::provider::Provider;
23use crate::plugin::provider::ProviderMeta;
24use crate::plugin::provider::assertion::FunctionAssertionProvider;
25use crate::plugin::provider::assertion::InvocationAssertions;
26use crate::plugin::provider::function::FunctionTarget;
27
28static META: ProviderMeta =
29    ProviderMeta::new("php::array::array_all", "array_all", "Lifts a callback's if-true assertion onto the array");
30
31#[derive(Default)]
32pub struct ArrayAllAssertionProvider;
33
34impl Provider for ArrayAllAssertionProvider {
35    fn meta() -> &'static ProviderMeta {
36        &META
37    }
38}
39
40impl FunctionAssertionProvider for ArrayAllAssertionProvider {
41    fn targets() -> FunctionTarget {
42        FunctionTarget::Exact(b"array_all")
43    }
44
45    fn get_assertions(
46        &self,
47        context: &ProviderContext<'_, '_, '_>,
48        invocation: &InvocationInfo<'_, '_, '_>,
49    ) -> Option<InvocationAssertions> {
50        let array_argument = invocation.get_argument(0, &[b"array"])?;
51        let array_type = context.get_expression_type(array_argument)?;
52
53        let callback_argument = invocation.get_argument(1, &[b"callback"])?;
54        let callback_metadata = context.get_callable_metadata(callback_argument)?;
55        if callback_metadata.if_true_assertions.is_empty() {
56            return None;
57        }
58
59        let first_param = callback_metadata.parameters.first()?;
60        let param_name = first_param.get_name().0;
61        let callback_assertions = callback_metadata.if_true_assertions.get(&param_name)?;
62
63        let codebase = context.codebase();
64        let mut narrowed_atomics: Vec<TAtomic> = Vec::new();
65        for variant in array_type.types.as_ref().iter() {
66            let TAtomic::Array(array) = variant else {
67                return None;
68            };
69
70            let (key_type, value_type) = get_array_parameters(array, codebase);
71            let mut narrowed_value = value_type;
72            for assertion in callback_assertions {
73                narrowed_value = apply_assertion_to_narrow_type(narrowed_value, assertion, codebase);
74            }
75
76            // Preserve known-items shape when present so a `$array{0: int}`
77            // input doesn't lose its known entry on narrowing.
78            let mut keyed = if let TArray::Keyed(keyed) = array { keyed.clone() } else { TKeyedArray::new() };
79
80            keyed = keyed.with_parameters(Arc::new(key_type), Arc::new(narrowed_value));
81            narrowed_atomics.push(TAtomic::Array(TArray::Keyed(keyed)));
82        }
83
84        // `Conjunction<Assertion>` carries an AND of constraints; only emit
85        // one `IsType` here, so back out if the input union has more than one
86        // array variant we'd need to OR.
87        let [narrowed_atomic] = narrowed_atomics.try_into().ok()?;
88
89        let assertion = Assertion::IsType(narrowed_atomic);
90
91        let mut result = InvocationAssertions::new();
92        result.add_if_true(word("$array"), vec![assertion]);
93        Some(result)
94    }
95}