php-ast 0.16.0

PHP Abstract Syntax Tree (AST) node definitions
Documentation
use serde::Serialize;

use crate::Span;

use super::{
    ArenaVec, Attribute, ClassDecl, Comment, EnumDecl, Expr, FunctionDecl, Ident, InterfaceDecl,
    Name, TraitDecl,
};

fn is_false(b: &bool) -> bool {
    !b
}

#[derive(Debug, Serialize)]
pub struct Stmt<'arena, 'src> {
    pub kind: StmtKind<'arena, 'src>,
    pub span: Span,
    /// The immediately preceding `/** */` doc-block, if any.
    ///
    /// Only `/** */` (doc-block) comments are attached here; `//`, `#`, and
    /// `/* */` comments remain in [`ParseResult::comments`].  When present,
    /// this comment is **removed** from `ParseResult::comments` — the two
    /// collections are disjoint.  A doc-block that has no following statement
    /// before the enclosing `}` or EOF is not attached and stays in
    /// `ParseResult::comments`.
    ///
    /// For declaration statements (`function`, `class`, `interface`, …) the
    /// doc-block is attached to the *inner* declaration node (e.g.
    /// [`FunctionDecl::doc_comment`]) and this field will always be `None`.
    ///
    /// Stored as a pointer into the arena rather than inline so that the
    /// `None` case (the vast majority of statements) costs only 8 bytes
    /// instead of the 32 bytes an inline `Option<Comment>` would require.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub doc_comment: Option<&'arena Comment<'src>>,
}

impl<'arena, 'src> Stmt<'arena, 'src> {
    /// The leading `/** */` doc-block for this statement, regardless of where
    /// it is stored.
    ///
    /// For non-declaration statements (`foreach`, `if`, assignments, …) the
    /// comment lives on [`Stmt::doc_comment`] and is returned directly.
    ///
    /// For declaration statements the comment is stored on the inner
    /// declaration node — this method checks each variant so callers do not
    /// need to match on [`StmtKind`]:
    ///
    /// | `StmtKind` variant | source field |
    /// |--------------------|--------------|
    /// | `Function`         | [`FunctionDecl::doc_comment`] |
    /// | `Class`            | [`ClassDecl::doc_comment`] |
    /// | `Interface`        | [`InterfaceDecl::doc_comment`] |
    /// | `Trait`            | [`TraitDecl::doc_comment`] |
    /// | `Enum`             | [`EnumDecl::doc_comment`] |
    /// | `Const`            | first [`ConstItem::doc_comment`] |
    ///
    /// Returns `None` when no doc-block precedes the statement.
    pub fn leading_doc_comment(&self) -> Option<&Comment<'src>> {
        if let Some(doc) = self.doc_comment {
            return Some(doc);
        }
        match &self.kind {
            StmtKind::Function(f) => f.doc_comment.as_ref(),
            StmtKind::Class(c) => c.doc_comment.as_ref(),
            StmtKind::Interface(i) => i.doc_comment.as_ref(),
            StmtKind::Trait(t) => t.doc_comment.as_ref(),
            StmtKind::Enum(e) => e.doc_comment.as_ref(),
            StmtKind::Const(items) => items.first().and_then(|i| i.doc_comment.as_ref()),
            _ => None,
        }
    }
}

/// A brace-delimited statement block. Used both as a standalone block
/// statement ([`StmtKind::Block`]) and as the body of constructs that are
/// *always* braced (functions, methods, closures, `try`/`catch`/`finally`,
/// braced namespaces, property-hook blocks) — those positions hold a
/// `&Block` so the "it's a block" invariant is enforced by the type.
#[derive(Debug, Serialize)]
#[serde(transparent)]
pub struct Block<'arena, 'src> {
    pub stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
    /// Span covering `{`..`}` (or the keyword-delimited region for the
    /// alternative-syntax blocks reachable only via [`StmtKind::Block`]).
    #[serde(skip)]
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub enum StmtKind<'arena, 'src> {
    /// Expression statement (e.g. `foo();`)
    Expression(&'arena Expr<'arena, 'src>),

    /// Echo statement: `echo expr1, expr2;`
    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),

    /// Return statement: `return expr;`
    Return(Option<&'arena Expr<'arena, 'src>>),

    /// Block statement: `{ stmts }`
    Block(&'arena Block<'arena, 'src>),

    /// If statement
    If(&'arena IfStmt<'arena, 'src>),

    /// While loop
    While(&'arena WhileStmt<'arena, 'src>),

    /// For loop
    For(&'arena ForStmt<'arena, 'src>),

    /// Foreach loop
    Foreach(&'arena ForeachStmt<'arena, 'src>),

    /// Do-while loop
    DoWhile(&'arena DoWhileStmt<'arena, 'src>),

    /// Function declaration
    Function(&'arena FunctionDecl<'arena, 'src>),

    /// Break statement
    Break(Option<&'arena Expr<'arena, 'src>>),

    /// Continue statement
    Continue(Option<&'arena Expr<'arena, 'src>>),

    /// Switch statement
    Switch(&'arena SwitchStmt<'arena, 'src>),

    /// Goto statement
    Goto(Ident<'src>),

    /// Label statement
    Label(&'arena str),

    /// Declare statement
    Declare(&'arena DeclareStmt<'arena, 'src>),

    /// Unset statement
    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),

    /// Throw statement (also can be expression in PHP 8)
    Throw(&'arena Expr<'arena, 'src>),

    /// Try/catch/finally
    TryCatch(&'arena TryCatchStmt<'arena, 'src>),

    /// Global declaration
    Global(ArenaVec<'arena, Expr<'arena, 'src>>),

    /// Class declaration
    Class(&'arena ClassDecl<'arena, 'src>),

    /// Interface declaration
    Interface(&'arena InterfaceDecl<'arena, 'src>),

    /// Trait declaration
    Trait(&'arena TraitDecl<'arena, 'src>),

    /// Enum declaration
    Enum(&'arena EnumDecl<'arena, 'src>),

    /// Namespace declaration
    Namespace(&'arena NamespaceDecl<'arena, 'src>),

    /// Use declaration
    Use(&'arena UseDecl<'arena, 'src>),

    /// Top-level constant: `const FOO = expr;`
    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),

    /// Static variable declaration: `static $x = 1;`
    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),

    /// __halt_compiler(); with remaining data
    HaltCompiler(&'src str),

    /// Nop (empty statement `;`)
    Nop,

    /// Inline HTML
    InlineHtml(&'src str),

    /// Error placeholder — parser always produces a tree
    Error,
}

#[derive(Debug, Serialize)]
pub struct IfStmt<'arena, 'src> {
    pub condition: Expr<'arena, 'src>,
    pub then_branch: &'arena Stmt<'arena, 'src>,
    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
    /// Start byte offset of the `else` keyword; `None` when there is no else branch.
    #[serde(skip)]
    pub else_kw_start: Option<u32>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct ElseIfBranch<'arena, 'src> {
    pub condition: Expr<'arena, 'src>,
    pub body: Stmt<'arena, 'src>,
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub struct WhileStmt<'arena, 'src> {
    pub condition: Expr<'arena, 'src>,
    pub body: &'arena Stmt<'arena, 'src>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct ForStmt<'arena, 'src> {
    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
    pub body: &'arena Stmt<'arena, 'src>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct ForeachStmt<'arena, 'src> {
    pub expr: Expr<'arena, 'src>,
    pub key: Option<Expr<'arena, 'src>>,
    pub value: Expr<'arena, 'src>,
    pub body: &'arena Stmt<'arena, 'src>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct DoWhileStmt<'arena, 'src> {
    pub body: &'arena Stmt<'arena, 'src>,
    pub condition: Expr<'arena, 'src>,
}

#[derive(Debug, Serialize)]
pub struct SwitchBody<'arena, 'src> {
    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
    #[serde(skip)]
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub struct SwitchStmt<'arena, 'src> {
    pub expr: Expr<'arena, 'src>,
    #[serde(flatten)]
    pub body: SwitchBody<'arena, 'src>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct SwitchCase<'arena, 'src> {
    pub value: Option<Expr<'arena, 'src>>,
    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub struct TryCatchStmt<'arena, 'src> {
    pub body: &'arena Block<'arena, 'src>,
    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
    pub finally: Option<&'arena Block<'arena, 'src>>,
    /// Start byte offset of the `finally` keyword; `None` when there is no finally clause.
    #[serde(skip)]
    pub finally_kw_start: Option<u32>,
}

#[derive(Debug, Serialize)]
pub struct CatchClause<'arena, 'src> {
    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
    pub var: Option<&'src str>,
    pub body: &'arena Block<'arena, 'src>,
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub struct NamespaceDecl<'arena, 'src> {
    pub name: Option<Name<'arena, 'src>>,
    pub body: NamespaceBody<'arena, 'src>,
}

#[derive(Debug, Serialize)]
pub enum NamespaceBody<'arena, 'src> {
    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
    Braced(&'arena Block<'arena, 'src>),
    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
    Simple,
}

#[derive(Debug, Serialize)]
pub struct DeclareStmt<'arena, 'src> {
    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
    pub body: Option<&'arena Stmt<'arena, 'src>>,
    #[serde(default, skip_serializing_if = "is_false")]
    pub uses_alternative: bool,
}

#[derive(Debug, Serialize)]
pub struct UseDecl<'arena, 'src> {
    pub kind: UseKind,
    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum UseKind {
    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
    Normal,
    /// `use function Foo\bar` — imports a function.
    Function,
    /// `use const Foo\BAR` — imports a constant.
    Const,
}

#[derive(Debug, Serialize)]
pub struct UseItem<'arena, 'src> {
    pub name: Name<'arena, 'src>,
    pub alias: Option<&'src str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<UseKind>,
    pub span: Span,
}

#[derive(Debug, Serialize)]
pub struct ConstItem<'arena, 'src> {
    pub name: Ident<'src>,
    pub value: Expr<'arena, 'src>,
    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
    pub span: Span,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub doc_comment: Option<Comment<'src>>,
}

#[derive(Debug, Serialize)]
pub struct StaticVar<'arena, 'src> {
    pub name: Ident<'src>,
    pub default: Option<Expr<'arena, 'src>>,
    pub span: Span,
}