mago-analyzer 1.24.0

A PHP static analyzer that can detect type errors in PHP code, and provide suggestions for fixing them.
Documentation
use mago_atom::atom;
use mago_codex::scanner::inference::get_literal_constant_type;
use mago_codex::scanner::inference::get_platform_constant_type;
use mago_codex::ttype::expander;
use mago_codex::ttype::expander::TypeExpansionOptions;
use mago_codex::ttype::get_mixed;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_syntax::ast::ConstantAccess;

use crate::analyzable::Analyzable;
use crate::artifacts::AnalysisArtifacts;
use crate::code::IssueCode;
use crate::context::Context;
use crate::context::block::BlockContext;
use crate::error::AnalysisError;

impl<'arena> Analyzable<'_, 'arena> for ConstantAccess<'arena> {
    fn analyze<'ctx>(
        &self,
        context: &mut Context<'ctx, 'arena>,
        block_context: &mut BlockContext<'ctx>,
        artifacts: &mut AnalysisArtifacts,
    ) -> Result<(), AnalysisError> {
        let name = context.resolved_names.get(self);
        let unqualified_name = self.name.value();

        let constant_metadata =
            context.codebase.get_constant(name).or_else(|| context.codebase.get_constant(unqualified_name));

        let Some(constant_metadata) = constant_metadata else {
            if let Some(literal_type) = get_literal_constant_type(name) {
                artifacts.set_expression_type(self, literal_type);
                return Ok(());
            }

            let is_known = block_context.known_constants.contains(&atom(name));

            if is_known {
                artifacts.set_expression_type(self, get_mixed());
            } else {
                context.collector.report_with_code(
                    IssueCode::NonExistentConstant,
                    Issue::error(format!(
                        "Undefined constant: `{name}`."
                    ))
                    .with_annotation(
                        Annotation::primary(self.span())
                            .with_message(format!("Constant `{name}` is not defined."))
                    )
                    .with_note(
                        "The constant might be misspelled, not defined, or not imported."
                    )
                    .with_help(
                        format!(
                            "Define the constant `{name}` using `define()` or `const`, or check for typos and ensure it's available in this scope."
                        )
                    ),
                );
            }

            return Ok(());
        };

        if constant_metadata.flags.is_deprecated() {
            context.collector.report_with_code(
                IssueCode::DeprecatedConstant,
                Issue::warning(format!("Using deprecated constant: `{name}`."))
                    .with_annotation(Annotation::primary(self.span()).with_message("This constant is deprecated."))
                    .with_note("Consider using an alternative constant or variable.")
                    .with_help("Check `{name}` documentation for alternatives or updates."),
            );
        }

        crate::utils::experimental::check_experimental_constant(
            context,
            block_context,
            &constant_metadata.flags,
            name,
            self.span(),
        );

        let mut constant_type = if let Some(t) = get_platform_constant_type(name) {
            t
        } else {
            match &constant_metadata.type_metadata {
                Some(t) => t.type_union.clone(),
                None => match &constant_metadata.inferred_type {
                    Some(t) => t.clone(),
                    _ => get_mixed(),
                },
            }
        };

        expander::expand_union(context.codebase, &mut constant_type, &TypeExpansionOptions::default());

        artifacts.set_expression_type(self, constant_type);

        Ok(())
    }
}