Skip to main content

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

1//! `array_flip()` return type provider.
2//!
3//! Preserves the shape of an input array by swapping each key with its value: for a
4//! keyed array with known literal values, the result is a keyed array whose known items
5//! are the flipped pairs. Generic `array<K, V>` becomes `array<V, K>` when `V` is an
6//! `array-key` subtype.
7
8use std::collections::BTreeMap;
9use std::sync::Arc;
10
11use mago_codex::ttype::atomic::TAtomic;
12use mago_codex::ttype::atomic::array::TArray;
13use mago_codex::ttype::atomic::array::key::ArrayKey;
14use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
15use mago_codex::ttype::atomic::array::list::TList;
16use mago_codex::ttype::get_non_negative_int;
17use mago_codex::ttype::union::TUnion;
18use mago_codex::ttype::wrap_atomic;
19
20use crate::plugin::context::InvocationInfo;
21use crate::plugin::context::ProviderContext;
22use crate::plugin::provider::Provider;
23use crate::plugin::provider::ProviderMeta;
24use crate::plugin::provider::function::FunctionReturnTypeProvider;
25use crate::plugin::provider::function::FunctionTarget;
26
27static META: ProviderMeta = ProviderMeta::new(
28    "php::array::array_flip",
29    "array_flip",
30    "Returns an array with keys and values swapped, preserving known shapes when possible",
31);
32
33/// Provider for the `array_flip()` function.
34#[derive(Default)]
35pub struct ArrayFlipProvider;
36
37impl Provider for ArrayFlipProvider {
38    fn meta() -> &'static ProviderMeta {
39        &META
40    }
41}
42
43impl FunctionReturnTypeProvider for ArrayFlipProvider {
44    fn targets() -> FunctionTarget {
45        FunctionTarget::Exact(b"array_flip")
46    }
47
48    fn get_return_type(
49        &self,
50        context: &ProviderContext<'_, '_, '_>,
51        invocation: &InvocationInfo<'_, '_, '_>,
52    ) -> Option<TUnion> {
53        let array_argument = invocation.get_argument(0, &[b"array"])?;
54        let array_type = context.get_expression_type(array_argument)?;
55
56        if !array_type.is_single() {
57            return None;
58        }
59
60        let TAtomic::Array(array) = array_type.get_single() else {
61            return None;
62        };
63
64        match array {
65            TArray::Keyed(keyed) => flip_keyed_array(keyed),
66            TArray::List(list) => flip_list(list),
67        }
68    }
69}
70
71fn flip_keyed_array(keyed: &TKeyedArray) -> Option<TUnion> {
72    let mut new_known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
73
74    if let Some(items) = &keyed.known_items {
75        for (key, (optional, value_type)) in items {
76            let new_key = value_type.get_single_array_key()?;
77            new_known_items.insert(new_key, (*optional, key.to_union()));
78        }
79    }
80
81    let mut new_parameters: Option<(Arc<TUnion>, Arc<TUnion>)> = None;
82    if let Some((key_param, value_param)) = &keyed.parameters {
83        if !value_param.is_array_key() {
84            return None;
85        }
86
87        new_parameters = Some((Arc::clone(value_param), Arc::clone(key_param)));
88    }
89
90    let mut result = TKeyedArray::new();
91    if !new_known_items.is_empty() {
92        result = result.with_known_items(new_known_items);
93    }
94
95    if let Some((key_type, value_type)) = new_parameters {
96        result = result.with_parameters(key_type, value_type);
97    }
98
99    result.non_empty = keyed.non_empty;
100
101    Some(wrap_atomic(TAtomic::Array(TArray::Keyed(result))))
102}
103
104fn flip_list(list: &TList) -> Option<TUnion> {
105    let mut new_known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
106
107    if let Some(elements) = &list.known_elements {
108        for (idx, (optional, value_type)) in elements {
109            let new_key = value_type.get_single_array_key()?;
110            new_known_items.insert(new_key, (*optional, ArrayKey::Integer(*idx as i64).to_union()));
111        }
112    }
113
114    let mut new_parameters: Option<(Arc<TUnion>, Arc<TUnion>)> = None;
115    if !list.element_type.is_never() {
116        if !list.element_type.is_array_key() {
117            return None;
118        }
119
120        new_parameters = Some((Arc::clone(&list.element_type), Arc::new(get_non_negative_int())));
121    }
122
123    let mut result = TKeyedArray::new();
124    if !new_known_items.is_empty() {
125        result = result.with_known_items(new_known_items);
126    }
127
128    if let Some((key_type, value_type)) = new_parameters {
129        result = result.with_parameters(key_type, value_type);
130    }
131
132    result.non_empty = list.non_empty;
133
134    Some(wrap_atomic(TAtomic::Array(TArray::Keyed(result))))
135}