mago-semantics 1.20.1

PHP Semantics Checker.
Documentation
use mago_php_version::feature::Feature;
use mago_reporting::Annotation;
use mago_reporting::Issue;
use mago_span::HasSpan;
use mago_syntax::ast::Hint;
use mago_syntax::ast::Try;

use crate::internal::context::Context;

#[inline]
pub fn check_try<'arena>(r#try: &Try<'arena>, context: &mut Context<'_, '_, 'arena>) {
    for catch in &r#try.catch_clauses {
        if catch.variable.is_none() && !context.version.is_supported(Feature::CatchOptionalVariable) {
            context.report(
                Issue::error("Cannot use optional variable in `catch` clause.")
                    .with_annotation(Annotation::primary(catch.span()).with_message("`catch` clause without variable."))
                    .with_note("Before PHP 8.0, `catch` clauses must have a variable to store the exception.")
                    .with_help("Add a variable to the `catch` clause to store the exception, or upgrade to PHP 8.0.")
                    .with_link("https://www.php.net/manual/en/language.exceptions.php"),
            );
        }

        let union_supported = context.version.is_supported(Feature::CatchUnionType);

        check_try_catch_hint(&catch.hint, union_supported, context);
    }

    if !r#try.catch_clauses.is_empty() || r#try.finally_clause.is_some() {
        return;
    }

    context.report(
        Issue::error("Cannot use `try` without a `catch` or `finally` clause.")
            .with_annotation(
                Annotation::primary(r#try.span()).with_message("`try` statement without `catch` or `finally`."),
            )
            .with_note("Each `try` block must have at least one corresponding `catch` or `finally` clause.")
            .with_help("Add either a `catch` or `finally` clause to the `try` block.")
            .with_link("https://www.php.net/manual/en/language.exceptions.php"),
    );
}

#[inline]
fn check_try_catch_hint(hint: &Hint, union_supported: bool, context: &mut Context) {
    match hint {
        Hint::Identifier(_) => {}
        Hint::Union(union) => {
            if !union_supported {
                context.report(
                    Issue::error("Cannot use union type hint in `catch` clause.")
                        .with_annotation(
                            Annotation::primary(union.span()).with_message("Union type hint in `catch` clause."),
                        )
                        .with_note("Before PHP 7.1, `catch` clauses must have a single type hint.")
                        .with_help("Use a single type hint in the `catch` clause, or upgrade to PHP 7.1.")
                        .with_link("https://www.php.net/manual/en/language.exceptions.php"),
                );
            }

            check_try_catch_hint(union.left, union_supported, context);
            check_try_catch_hint(union.right, union_supported, context);
        }
        _ => {
            if union_supported {
                context.report(
                    Issue::error("Invalid type hint in `catch` clause.")
                        .with_annotation(
                            Annotation::primary(hint.span()).with_message("Invalid type hint in `catch` clause."),
                        )
                        .with_note("Only identifiers and union types are allowed in `catch` clauses.")
                        .with_help("Use an identifier or union type in the `catch` clause.")
                        .with_link("https://www.php.net/manual/en/language.exceptions.php"),
                );
            } else {
                context.report(
                    Issue::error("Invalid type hint in `catch` clause.")
                        .with_annotation(
                            Annotation::primary(hint.span()).with_message("Invalid type hint in `catch` clause."),
                        )
                        .with_note("Only identifiers are allowed in `catch` clauses.")
                        .with_help("Use an identifier in the `catch` clause.")
                        .with_link("https://www.php.net/manual/en/language.exceptions.php"),
                );
            }
        }
    }
}