Skip to main content

php_ast/ast/
stmts.rs

1use serde::Serialize;
2
3use crate::Span;
4
5use super::{
6    ArenaVec, Attribute, ClassDecl, Comment, EnumDecl, Expr, FunctionDecl, Ident, InterfaceDecl,
7    Name, TraitDecl,
8};
9
10fn is_false(b: &bool) -> bool {
11    !b
12}
13
14/// A statement node.
15#[derive(Debug, Serialize)]
16pub struct Stmt<'arena, 'src> {
17    /// Statement kind.
18    pub kind: StmtKind<'arena, 'src>,
19    /// Source range of this node.
20    pub span: Span,
21    /// The immediately preceding `/** */` doc-block, if any.
22    ///
23    /// Only `/** */` (doc-block) comments are attached here; `//`, `#`, and
24    /// `/* */` comments remain in `ParseResult::comments`.  When present,
25    /// this comment is **removed** from `ParseResult::comments` — the two
26    /// collections are disjoint.  A doc-block that has no following statement
27    /// before the enclosing `}` or EOF is not attached and stays in
28    /// `ParseResult::comments`.
29    ///
30    /// For declaration statements (`function`, `class`, `interface`, …) the
31    /// doc-block is attached to the *inner* declaration node (e.g.
32    /// [`FunctionDecl::doc_comment`]) and this field will always be `None`.
33    ///
34    /// Stored as a pointer into the arena rather than inline so that the
35    /// `None` case (the vast majority of statements) costs only 8 bytes
36    /// instead of the 32 bytes an inline `Option<Comment>` would require.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub doc_comment: Option<&'arena Comment<'src>>,
39}
40
41impl<'arena, 'src> Stmt<'arena, 'src> {
42    /// The leading `/** */` doc-block for this statement, regardless of where
43    /// it is stored.
44    ///
45    /// For non-declaration statements (`foreach`, `if`, assignments, …) the
46    /// comment lives on [`Stmt::doc_comment`] and is returned directly.
47    ///
48    /// For declaration statements the comment is stored on the inner
49    /// declaration node — this method checks each variant so callers do not
50    /// need to match on [`StmtKind`]:
51    ///
52    /// | `StmtKind` variant | source field |
53    /// |--------------------|--------------|
54    /// | `Function`         | [`FunctionDecl::doc_comment`] |
55    /// | `Class`            | [`ClassDecl::doc_comment`] |
56    /// | `Interface`        | [`InterfaceDecl::doc_comment`] |
57    /// | `Trait`            | [`TraitDecl::doc_comment`] |
58    /// | `Enum`             | [`EnumDecl::doc_comment`] |
59    /// | `Const`            | first [`ConstItem::doc_comment`] |
60    ///
61    /// Returns `None` when no doc-block precedes the statement.
62    pub fn leading_doc_comment(&self) -> Option<&Comment<'src>> {
63        if let Some(doc) = self.doc_comment {
64            return Some(doc);
65        }
66        match &self.kind {
67            StmtKind::Function(f) => f.doc_comment.as_ref(),
68            StmtKind::Class(c) => c.doc_comment.as_ref(),
69            StmtKind::Interface(i) => i.doc_comment.as_ref(),
70            StmtKind::Trait(t) => t.doc_comment.as_ref(),
71            StmtKind::Enum(e) => e.doc_comment.as_ref(),
72            StmtKind::Const(items) => items.first().and_then(|i| i.doc_comment.as_ref()),
73            _ => None,
74        }
75    }
76}
77
78/// A brace-delimited statement block. Used both as a standalone block
79/// statement ([`StmtKind::Block`]) and as the body of constructs that are
80/// *always* braced (functions, methods, closures, `try`/`catch`/`finally`,
81/// braced namespaces, property-hook blocks) — those positions hold a
82/// `&Block` so the "it's a block" invariant is enforced by the type.
83#[derive(Debug, Serialize)]
84#[serde(transparent)]
85pub struct Block<'arena, 'src> {
86    /// Statements in the block.
87    pub stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
88    /// Span covering `{`..`}` (or the keyword-delimited region for the
89    /// alternative-syntax blocks reachable only via [`StmtKind::Block`]).
90    #[serde(skip)]
91    pub span: Span,
92}
93
94/// The kinds of statement.
95#[derive(Debug, Serialize)]
96pub enum StmtKind<'arena, 'src> {
97    /// Expression statement (e.g. `foo();`)
98    Expression(&'arena Expr<'arena, 'src>),
99
100    /// Echo statement: `echo expr1, expr2;`
101    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),
102
103    /// Return statement: `return expr;`
104    Return(Option<&'arena Expr<'arena, 'src>>),
105
106    /// Block statement: `{ stmts }`
107    Block(&'arena Block<'arena, 'src>),
108
109    /// If statement
110    If(&'arena IfStmt<'arena, 'src>),
111
112    /// While loop
113    While(&'arena WhileStmt<'arena, 'src>),
114
115    /// For loop
116    For(&'arena ForStmt<'arena, 'src>),
117
118    /// Foreach loop
119    Foreach(&'arena ForeachStmt<'arena, 'src>),
120
121    /// Do-while loop
122    DoWhile(&'arena DoWhileStmt<'arena, 'src>),
123
124    /// Function declaration
125    Function(&'arena FunctionDecl<'arena, 'src>),
126
127    /// Break statement
128    Break(Option<&'arena Expr<'arena, 'src>>),
129
130    /// Continue statement
131    Continue(Option<&'arena Expr<'arena, 'src>>),
132
133    /// Switch statement
134    Switch(&'arena SwitchStmt<'arena, 'src>),
135
136    /// Goto statement
137    Goto(Ident<'src>),
138
139    /// Label statement
140    Label(&'arena str),
141
142    /// Declare statement
143    Declare(&'arena DeclareStmt<'arena, 'src>),
144
145    /// Unset statement
146    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),
147
148    /// Throw statement (also can be expression in PHP 8)
149    Throw(&'arena Expr<'arena, 'src>),
150
151    /// Try/catch/finally
152    TryCatch(&'arena TryCatchStmt<'arena, 'src>),
153
154    /// Global declaration
155    Global(ArenaVec<'arena, Expr<'arena, 'src>>),
156
157    /// Class declaration
158    Class(&'arena ClassDecl<'arena, 'src>),
159
160    /// Interface declaration
161    Interface(&'arena InterfaceDecl<'arena, 'src>),
162
163    /// Trait declaration
164    Trait(&'arena TraitDecl<'arena, 'src>),
165
166    /// Enum declaration
167    Enum(&'arena EnumDecl<'arena, 'src>),
168
169    /// Namespace declaration
170    Namespace(&'arena NamespaceDecl<'arena, 'src>),
171
172    /// Use declaration
173    Use(&'arena UseDecl<'arena, 'src>),
174
175    /// Top-level constant: `const FOO = expr;`
176    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),
177
178    /// Static variable declaration: `static $x = 1;`
179    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),
180
181    /// __halt_compiler(); with remaining data
182    HaltCompiler(&'src str),
183
184    /// Nop (empty statement `;`)
185    Nop,
186
187    /// Inline HTML
188    InlineHtml(&'src str),
189
190    /// Error placeholder — parser always produces a tree
191    Error,
192}
193
194/// An `if` statement with optional `elseif`/`else` branches.
195#[derive(Debug, Serialize)]
196pub struct IfStmt<'arena, 'src> {
197    /// `if` condition.
198    pub condition: Expr<'arena, 'src>,
199    /// Statement run when the condition holds.
200    pub then_branch: &'arena Stmt<'arena, 'src>,
201    /// `elseif` branches in order.
202    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
203    /// `else` statement, if any.
204    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
205    /// Start byte offset of the `else` keyword; `None` when there is no else branch.
206    #[serde(skip)]
207    pub else_kw_start: Option<u32>,
208    /// `true` when written in alternative syntax (`: ... endX;`).
209    #[serde(default, skip_serializing_if = "is_false")]
210    pub uses_alternative: bool,
211}
212
213/// An `elseif` (or `else if`) branch.
214#[derive(Debug, Serialize)]
215pub struct ElseIfBranch<'arena, 'src> {
216    /// Condition expression.
217    pub condition: Expr<'arena, 'src>,
218    /// Statement run when this branch matches.
219    pub body: Stmt<'arena, 'src>,
220    /// Source range of this node.
221    pub span: Span,
222}
223
224/// A `while` loop.
225#[derive(Debug, Serialize)]
226pub struct WhileStmt<'arena, 'src> {
227    /// Condition expression.
228    pub condition: Expr<'arena, 'src>,
229    /// Loop body.
230    pub body: &'arena Stmt<'arena, 'src>,
231    /// `true` when written in alternative syntax (`: ... endX;`).
232    #[serde(default, skip_serializing_if = "is_false")]
233    pub uses_alternative: bool,
234}
235
236/// A `for` loop.
237#[derive(Debug, Serialize)]
238pub struct ForStmt<'arena, 'src> {
239    /// Initializer expressions.
240    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
241    /// Condition expressions.
242    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
243    /// Update expressions.
244    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
245    /// Loop body.
246    pub body: &'arena Stmt<'arena, 'src>,
247    /// `true` when written in alternative syntax (`: ... endX;`).
248    #[serde(default, skip_serializing_if = "is_false")]
249    pub uses_alternative: bool,
250}
251
252/// A `foreach` loop.
253#[derive(Debug, Serialize)]
254pub struct ForeachStmt<'arena, 'src> {
255    /// Expression being iterated.
256    pub expr: Expr<'arena, 'src>,
257    /// Key target in `$k => $v`.
258    pub key: Option<Expr<'arena, 'src>>,
259    /// Value target.
260    pub value: Expr<'arena, 'src>,
261    /// Loop body.
262    pub body: &'arena Stmt<'arena, 'src>,
263    /// `true` when written in alternative syntax (`: ... endX;`).
264    #[serde(default, skip_serializing_if = "is_false")]
265    pub uses_alternative: bool,
266}
267
268/// A `do ... while` loop.
269#[derive(Debug, Serialize)]
270pub struct DoWhileStmt<'arena, 'src> {
271    /// Loop body.
272    pub body: &'arena Stmt<'arena, 'src>,
273    /// Condition checked after each iteration.
274    pub condition: Expr<'arena, 'src>,
275}
276
277/// The case list of a `switch`.
278#[derive(Debug, Serialize)]
279pub struct SwitchBody<'arena, 'src> {
280    /// Cases in source order.
281    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
282    /// Source range of this node.
283    #[serde(skip)]
284    pub span: Span,
285}
286
287/// A `switch` statement.
288#[derive(Debug, Serialize)]
289pub struct SwitchStmt<'arena, 'src> {
290    /// Subject expression.
291    pub expr: Expr<'arena, 'src>,
292    /// Case list, flattened on serialization.
293    #[serde(flatten)]
294    pub body: SwitchBody<'arena, 'src>,
295    /// `true` when written in alternative syntax (`: ... endX;`).
296    #[serde(default, skip_serializing_if = "is_false")]
297    pub uses_alternative: bool,
298}
299
300/// A `case` or `default` clause.
301#[derive(Debug, Serialize)]
302pub struct SwitchCase<'arena, 'src> {
303    /// Case expression; `None` for `default`.
304    pub value: Option<Expr<'arena, 'src>>,
305    /// Statements under this case.
306    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
307    /// Source range of this node.
308    pub span: Span,
309}
310
311/// A `try` statement with `catch` and `finally` clauses.
312#[derive(Debug, Serialize)]
313pub struct TryCatchStmt<'arena, 'src> {
314    /// `try` block.
315    pub body: &'arena Block<'arena, 'src>,
316    /// `catch` clauses in order.
317    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
318    /// `finally` block, if any.
319    pub finally: Option<&'arena Block<'arena, 'src>>,
320    /// Start byte offset of the `finally` keyword; `None` when there is no finally clause.
321    #[serde(skip)]
322    pub finally_kw_start: Option<u32>,
323}
324
325/// A `catch (Type $e) { ... }` clause.
326#[derive(Debug, Serialize)]
327pub struct CatchClause<'arena, 'src> {
328    /// Caught exception types (`A|B`).
329    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
330    /// Bound variable name; `None` if omitted (PHP 8.0+).
331    pub var: Option<&'src str>,
332    /// `catch` block.
333    pub body: &'arena Block<'arena, 'src>,
334    /// Source range of this node.
335    pub span: Span,
336}
337
338/// A `namespace` declaration.
339#[derive(Debug, Serialize)]
340pub struct NamespaceDecl<'arena, 'src> {
341    /// Namespace name; `None` for the global namespace.
342    pub name: Option<Name<'arena, 'src>>,
343    /// Braced or simple form.
344    pub body: NamespaceBody<'arena, 'src>,
345}
346
347/// Whether a namespace is braced or simple.
348#[derive(Debug, Serialize)]
349pub enum NamespaceBody<'arena, 'src> {
350    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
351    Braced(&'arena Block<'arena, 'src>),
352    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
353    Simple,
354}
355
356/// A `declare(...)` statement.
357#[derive(Debug, Serialize)]
358pub struct DeclareStmt<'arena, 'src> {
359    /// `name = value` directives.
360    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
361    /// Statement scoped by the directive, if any.
362    pub body: Option<&'arena Stmt<'arena, 'src>>,
363    /// `true` when written in alternative syntax (`: ... endX;`).
364    #[serde(default, skip_serializing_if = "is_false")]
365    pub uses_alternative: bool,
366}
367
368/// A `use` import statement.
369#[derive(Debug, Serialize)]
370pub struct UseDecl<'arena, 'src> {
371    /// Whether importing classes, functions or constants.
372    pub kind: UseKind,
373    /// Imported names.
374    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
375}
376
377/// What a `use` import brings in.
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
379pub enum UseKind {
380    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
381    Normal,
382    /// `use function Foo\bar` — imports a function.
383    Function,
384    /// `use const Foo\BAR` — imports a constant.
385    Const,
386}
387
388/// One imported name in a `use` statement.
389#[derive(Debug, Serialize)]
390pub struct UseItem<'arena, 'src> {
391    /// Imported name.
392    pub name: Name<'arena, 'src>,
393    /// Alias after `as`, if any.
394    pub alias: Option<&'src str>,
395    /// Per-item kind in a mixed group `use Foo\{function bar}`.
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub kind: Option<UseKind>,
398    /// Source range of this node.
399    pub span: Span,
400}
401
402/// One `NAME = value` item of a top-level `const` statement.
403#[derive(Debug, Serialize)]
404pub struct ConstItem<'arena, 'src> {
405    /// Constant name.
406    pub name: Ident<'src>,
407    /// Constant value expression.
408    pub value: Expr<'arena, 'src>,
409    /// `#[...]` attributes applied to this node.
410    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
411    /// Source range of this node.
412    pub span: Span,
413    /// Preceding `/** */` doc-block, if any.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub doc_comment: Option<Comment<'src>>,
416}
417
418/// One variable of a `static $x = 1;` statement.
419#[derive(Debug, Serialize)]
420pub struct StaticVar<'arena, 'src> {
421    /// Variable name without `$`.
422    pub name: Ident<'src>,
423    /// Initial value, if any.
424    pub default: Option<Expr<'arena, 'src>>,
425    /// Source range of this node.
426    pub span: Span,
427}