mago-analyzer 1.47.5

A PHP static analyzer that can detect type errors in PHP code, and provide suggestions for fixing them.
Documentation
use std::collections::BTreeMap;
use std::sync::Arc;

use mago_codex::ttype::atomic::TAtomic;
use mago_codex::ttype::atomic::array::TArray;
use mago_codex::ttype::atomic::array::key::ArrayKey;
use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
use mago_codex::ttype::get_arraykey;
use mago_codex::ttype::get_mixed;
use mago_codex::ttype::union::TUnion;

use crate::plugin::context::InvocationInfo;
use crate::plugin::context::ProviderContext;
use crate::plugin::provider::Provider;
use crate::plugin::provider::ProviderMeta;
use crate::plugin::provider::function::FunctionReturnTypeProvider;
use crate::plugin::provider::function::FunctionTarget;
use crate::visibility::is_visible_from_scope;

static META: ProviderMeta = ProviderMeta::new(
    "php::object::get_object_vars",
    "get_object_vars",
    "Returns the object's properties visible from the calling scope as a keyed array",
);

#[derive(Default)]
pub struct GetObjectVarsProvider;

impl Provider for GetObjectVarsProvider {
    fn meta() -> &'static ProviderMeta {
        &META
    }
}

impl FunctionReturnTypeProvider for GetObjectVarsProvider {
    fn targets() -> FunctionTarget {
        FunctionTarget::Exact(b"get_object_vars")
    }

    fn get_return_type(
        &self,
        context: &ProviderContext<'_, '_, '_>,
        invocation: &InvocationInfo<'_, '_, '_>,
    ) -> Option<TUnion> {
        let argument = invocation.get_argument(0, &[b"object"])?;
        let argument_type = context.get_expression_type(argument)?;
        let object = argument_type.get_single_named_object()?;

        let class_metadata = context.get_class_like(object.name)?;
        if class_metadata.kind.is_enum() {
            return None;
        }

        let current_class = context.current_class_name();

        let mut known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
        for (property_name, declaring_class) in class_metadata.appearing_property_ids.iter() {
            let Some(property) = context.codebase().get_property(declaring_class.as_bytes(), property_name.as_bytes())
            else {
                continue;
            };

            if property.flags.is_static() || property.flags.is_virtual_property() {
                continue;
            }

            if !is_visible_from_scope(
                context.codebase(),
                property.read_visibility,
                declaring_class.as_bytes(),
                current_class,
            ) {
                continue;
            }

            let Some(name) = property_name.as_bytes().strip_prefix(b"$") else {
                continue;
            };

            let property_type =
                property.type_metadata.as_ref().map(|metadata| metadata.type_union.clone()).unwrap_or_else(get_mixed);

            known_items.insert(ArrayKey::String(mago_word::word(name)), (false, property_type));
        }

        if known_items.is_empty() {
            return None;
        }

        let mut keyed_array = TKeyedArray::new();
        keyed_array.known_items = Some(known_items);
        keyed_array.non_empty = true;

        if !class_metadata.flags.is_final() || !class_metadata.flags.is_readonly() {
            keyed_array.parameters = Some((Arc::new(get_arraykey()), Arc::new(get_mixed())));
        }

        Some(TUnion::from_atomic(TAtomic::Array(TArray::Keyed(keyed_array))))
    }
}