mago-codex 1.47.4

PHP type system representation, comparison logic, and codebase metadata for static analysis.
Documentation
use mago_allocator::Arena;
use mago_names::scope::NamespaceScope;
use mago_phpdoc_syntax::cst::TagValue;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_syntax::cst::ClassLikeConstant;
use mago_syntax::cst::ModifierSequenceExt;
use mago_word::Word;
use mago_word::word;

use crate::issue::ScanningIssueKind;
use crate::metadata::class_like::ClassLikeMetadata;
use crate::metadata::class_like_constant::ClassLikeConstantMetadata;
use crate::metadata::flags::MetadataFlags;
use crate::scanner::Context;
use crate::scanner::attribute::scan_attribute_lists;
use crate::scanner::docblock::apply_common_metadata_flag;
use crate::scanner::docblock::find_most_trusted_tag;
use crate::scanner::docblock::parse_docblock;
use crate::scanner::inference::infer;
use crate::scanner::ttype::get_type_metadata_from_hint;
use crate::scanner::ttype::get_type_metadata_from_type;
use crate::scanner::ttype::merge_type_preserving_nullability;
use crate::scanner::typing_error_issue;
use crate::scanner::version_claim::evaluate_version_attributes;
use crate::ttype::atomic::TAtomic;
use crate::ttype::atomic::reference::TReference;
use crate::ttype::atomic::reference::TReferenceMemberSelector;
use crate::ttype::resolution::TypeResolutionContext;
use crate::visibility::Visibility;

use super::super::ttype::union::TUnion;

#[inline]
pub fn scan_class_like_constants<'arena, A>(
    class_like_metadata: &mut ClassLikeMetadata,
    constant: &'arena ClassLikeConstant<'arena>,
    classname: Option<Word>,
    type_context: &TypeResolutionContext,
    context: &mut Context<'_, 'arena, A>,
    scope: &NamespaceScope,
) -> Vec<ClassLikeConstantMetadata>
where
    A: Arena,
{
    let verdict = evaluate_version_attributes(&constant.attribute_lists, context, context.php_version);

    let attributes = scan_attribute_lists(&constant.attribute_lists, context, scope, classname);
    let visibility =
        constant.modifiers.get_first_visibility().and_then(|m| Visibility::try_from(m).ok()).unwrap_or_default();
    let is_final = constant.modifiers.contains_final();
    let type_declaration =
        constant.hint.as_ref().map(|h| get_type_metadata_from_hint(h, Some(class_like_metadata.name), context));

    let mut flags = if is_final { MetadataFlags::FINAL } else { MetadataFlags::empty() };
    flags |= MetadataFlags::origin_flags(context.file.file_type);

    let document = parse_docblock(context, constant);

    if let Some(document) = document.as_ref() {
        for parse_error in document.errors {
            class_like_metadata.issues.push(
                Issue::error("Failed to parse constant docblock comment.")
                    .with_code(ScanningIssueKind::MalformedDocblockComment)
                    .with_annotation(Annotation::primary(parse_error.span()).with_message(parse_error.to_string()))
                    .with_note(parse_error.note())
                    .with_help(parse_error.help()),
            );
        }
    }

    constant
        .items
        .iter()
        .map(|item| {
            let mut meta = ClassLikeConstantMetadata::new(word(item.name.value), item.span(), visibility, flags);
            meta.version_constraint = verdict.constraint.clone();
            if let Some(type_declaration) = type_declaration.clone() {
                meta.set_type_declaration(type_declaration);
            }

            meta.attributes.clone_from(&attributes);
            meta.inferred_type = infer(context, scope, item.value, classname).map(TUnion::get_single_owned);

            if let Some(TAtomic::Reference(TReference::Member {
                class_like_name,
                member_selector: TReferenceMemberSelector::Identifier(member_name),
            })) = meta.inferred_type.as_ref()
                && classname.is_some_and(|c| c.as_bytes().eq_ignore_ascii_case(class_like_name.as_bytes()))
                && member_name.as_bytes() == item.name.value
            {
                meta.inferred_type = Some(TAtomic::Never);
            }

            if let Some(document) = document.as_ref() {
                for tag in document.tags() {
                    if apply_common_metadata_flag(&mut meta.flags, &tag.value) {
                        continue;
                    }

                    match &tag.value {
                        TagValue::Final(_) => {
                            meta.flags |= MetadataFlags::FINAL;
                        }
                        _ => {}
                    }
                }

                let var = find_most_trusted_tag(document, |tag| match &tag.value {
                    TagValue::Var(var) => Some(*var),
                    _ => None,
                });

                if let Some(var) = &var {
                    match get_type_metadata_from_type(var.r#type, classname, type_context, scope) {
                        Ok(type_metadata) => {
                            let real_type = meta.type_declaration.as_ref();
                            let type_metadata = merge_type_preserving_nullability(type_metadata, real_type);

                            meta.type_metadata = Some(type_metadata);
                        }
                        Err(typing_error) => class_like_metadata.issues.push(typing_error_issue(
                            "Could not resolve the type for the @var tag.",
                            ScanningIssueKind::InvalidVarTag,
                            &typing_error,
                        )),
                    }
                }
            }

            meta
        })
        .collect()
}