Skip to main content

mago_analyzer/plugin/libraries/stdlib/object/
get_object_vars.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use mago_codex::ttype::atomic::TAtomic;
5use mago_codex::ttype::atomic::array::TArray;
6use mago_codex::ttype::atomic::array::key::ArrayKey;
7use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
8use mago_codex::ttype::get_arraykey;
9use mago_codex::ttype::get_mixed;
10use mago_codex::ttype::union::TUnion;
11
12use crate::plugin::context::InvocationInfo;
13use crate::plugin::context::ProviderContext;
14use crate::plugin::provider::Provider;
15use crate::plugin::provider::ProviderMeta;
16use crate::plugin::provider::function::FunctionReturnTypeProvider;
17use crate::plugin::provider::function::FunctionTarget;
18use crate::visibility::is_visible_from_scope;
19
20static META: ProviderMeta = ProviderMeta::new(
21    "php::object::get_object_vars",
22    "get_object_vars",
23    "Returns the object's properties visible from the calling scope as a keyed array",
24);
25
26#[derive(Default)]
27pub struct GetObjectVarsProvider;
28
29impl Provider for GetObjectVarsProvider {
30    fn meta() -> &'static ProviderMeta {
31        &META
32    }
33}
34
35impl FunctionReturnTypeProvider for GetObjectVarsProvider {
36    fn targets() -> FunctionTarget {
37        FunctionTarget::Exact(b"get_object_vars")
38    }
39
40    fn get_return_type(
41        &self,
42        context: &ProviderContext<'_, '_, '_>,
43        invocation: &InvocationInfo<'_, '_, '_>,
44    ) -> Option<TUnion> {
45        let argument = invocation.get_argument(0, &[b"object"])?;
46        let argument_type = context.get_expression_type(argument)?;
47        let object = argument_type.get_single_named_object()?;
48
49        let class_metadata = context.get_class_like(object.name)?;
50        if class_metadata.kind.is_enum() {
51            return None;
52        }
53
54        let current_class = context.current_class_name();
55
56        let mut known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
57        for (property_name, declaring_class) in class_metadata.appearing_property_ids.iter() {
58            let Some(property) = context.codebase().get_property(declaring_class.as_bytes(), property_name.as_bytes())
59            else {
60                continue;
61            };
62
63            if property.flags.is_static() || property.flags.is_virtual_property() {
64                continue;
65            }
66
67            if !is_visible_from_scope(
68                context.codebase(),
69                property.read_visibility,
70                declaring_class.as_bytes(),
71                current_class,
72            ) {
73                continue;
74            }
75
76            let Some(name) = property_name.as_bytes().strip_prefix(b"$") else {
77                continue;
78            };
79
80            let property_type =
81                property.type_metadata.as_ref().map(|metadata| metadata.type_union.clone()).unwrap_or_else(get_mixed);
82
83            known_items.insert(ArrayKey::String(mago_word::word(name)), (false, property_type));
84        }
85
86        if known_items.is_empty() {
87            return None;
88        }
89
90        let mut keyed_array = TKeyedArray::new();
91        keyed_array.known_items = Some(known_items);
92        keyed_array.non_empty = true;
93
94        if !class_metadata.flags.is_final() || !class_metadata.flags.is_readonly() {
95            keyed_array.parameters = Some((Arc::new(get_arraykey()), Arc::new(get_mixed())));
96        }
97
98        Some(TUnion::from_atomic(TAtomic::Array(TArray::Keyed(keyed_array))))
99    }
100}