mago_analyzer/plugin/libraries/stdlib/array/
array_flip.rs1use 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#[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}