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)]
96#[non_exhaustive]
97pub enum StmtKind<'arena, 'src> {
98    /// Expression statement (e.g. `foo();`)
99    Expression(&'arena Expr<'arena, 'src>),
100
101    /// Echo statement: `echo expr1, expr2;`
102    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),
103
104    /// Return statement: `return expr;`
105    Return(Option<&'arena Expr<'arena, 'src>>),
106
107    /// Block statement: `{ stmts }`
108    Block(&'arena Block<'arena, 'src>),
109
110    /// If statement
111    If(&'arena IfStmt<'arena, 'src>),
112
113    /// While loop
114    While(&'arena WhileStmt<'arena, 'src>),
115
116    /// For loop
117    For(&'arena ForStmt<'arena, 'src>),
118
119    /// Foreach loop
120    Foreach(&'arena ForeachStmt<'arena, 'src>),
121
122    /// Do-while loop
123    DoWhile(&'arena DoWhileStmt<'arena, 'src>),
124
125    /// Function declaration
126    Function(&'arena FunctionDecl<'arena, 'src>),
127
128    /// Break statement
129    Break(Option<&'arena Expr<'arena, 'src>>),
130
131    /// Continue statement
132    Continue(Option<&'arena Expr<'arena, 'src>>),
133
134    /// Switch statement
135    Switch(&'arena SwitchStmt<'arena, 'src>),
136
137    /// Goto statement
138    Goto(Ident<'src>),
139
140    /// Label statement
141    Label(Ident<'src>),
142
143    /// Declare statement
144    Declare(&'arena DeclareStmt<'arena, 'src>),
145
146    /// Unset statement
147    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),
148
149    /// Throw statement (also can be expression in PHP 8)
150    Throw(&'arena Expr<'arena, 'src>),
151
152    /// Try/catch/finally
153    TryCatch(&'arena TryCatchStmt<'arena, 'src>),
154
155    /// Global declaration
156    Global(ArenaVec<'arena, Expr<'arena, 'src>>),
157
158    /// Class declaration
159    Class(&'arena ClassDecl<'arena, 'src>),
160
161    /// Interface declaration
162    Interface(&'arena InterfaceDecl<'arena, 'src>),
163
164    /// Trait declaration
165    Trait(&'arena TraitDecl<'arena, 'src>),
166
167    /// Enum declaration
168    Enum(&'arena EnumDecl<'arena, 'src>),
169
170    /// Namespace declaration
171    Namespace(&'arena NamespaceDecl<'arena, 'src>),
172
173    /// Use declaration
174    Use(&'arena UseDecl<'arena, 'src>),
175
176    /// Top-level constant: `const FOO = expr;`
177    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),
178
179    /// Static variable declaration: `static $x = 1;`
180    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),
181
182    /// __halt_compiler(); with remaining data
183    HaltCompiler(&'src str),
184
185    /// Nop (empty statement `;`)
186    Nop,
187
188    /// Inline HTML
189    InlineHtml(&'src str),
190
191    /// Error placeholder — parser always produces a tree
192    Error,
193}
194
195/// An `if` statement with optional `elseif`/`else` branches.
196#[derive(Debug, Serialize)]
197pub struct IfStmt<'arena, 'src> {
198    /// `if` condition.
199    pub condition: Expr<'arena, 'src>,
200    /// Statement run when the condition holds.
201    pub then_branch: &'arena Stmt<'arena, 'src>,
202    /// `elseif` branches in order.
203    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
204    /// `else` statement, if any.
205    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
206    /// The `else` keyword; `None` when there is no else branch. Not serialized.
207    #[serde(skip)]
208    pub else_kw_span: Option<Span>,
209    /// `true` when written in alternative syntax (`: ... endX;`).
210    #[serde(default, skip_serializing_if = "is_false")]
211    pub uses_alternative: bool,
212}
213
214/// An `elseif` (or `else if`) branch.
215#[derive(Debug, Serialize)]
216pub struct ElseIfBranch<'arena, 'src> {
217    /// Condition expression.
218    pub condition: Expr<'arena, 'src>,
219    /// Statement run when this branch matches.
220    pub body: Stmt<'arena, 'src>,
221    /// Source range of this node.
222    pub span: Span,
223}
224
225/// A `while` loop.
226#[derive(Debug, Serialize)]
227pub struct WhileStmt<'arena, 'src> {
228    /// Condition expression.
229    pub condition: Expr<'arena, 'src>,
230    /// Loop body.
231    pub body: &'arena Stmt<'arena, 'src>,
232    /// `true` when written in alternative syntax (`: ... endX;`).
233    #[serde(default, skip_serializing_if = "is_false")]
234    pub uses_alternative: bool,
235}
236
237/// A `for` loop.
238#[derive(Debug, Serialize)]
239pub struct ForStmt<'arena, 'src> {
240    /// Initializer expressions.
241    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
242    /// Condition expressions.
243    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
244    /// Update expressions.
245    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
246    /// Loop body.
247    pub body: &'arena Stmt<'arena, 'src>,
248    /// `true` when written in alternative syntax (`: ... endX;`).
249    #[serde(default, skip_serializing_if = "is_false")]
250    pub uses_alternative: bool,
251}
252
253/// A `foreach` loop.
254#[derive(Debug, Serialize)]
255pub struct ForeachStmt<'arena, 'src> {
256    /// Expression being iterated.
257    pub expr: Expr<'arena, 'src>,
258    /// Key target in `$k => $v`.
259    pub key: Option<Expr<'arena, 'src>>,
260    /// Value target.
261    pub value: Expr<'arena, 'src>,
262    /// Loop body.
263    pub body: &'arena Stmt<'arena, 'src>,
264    /// `true` when written in alternative syntax (`: ... endX;`).
265    #[serde(default, skip_serializing_if = "is_false")]
266    pub uses_alternative: bool,
267}
268
269/// A `do ... while` loop.
270#[derive(Debug, Serialize)]
271pub struct DoWhileStmt<'arena, 'src> {
272    /// Loop body.
273    pub body: &'arena Stmt<'arena, 'src>,
274    /// Condition checked after each iteration.
275    pub condition: Expr<'arena, 'src>,
276}
277
278/// The case list of a `switch`.
279#[derive(Debug, Serialize)]
280pub struct SwitchBody<'arena, 'src> {
281    /// Cases in source order.
282    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
283    /// Source range of this node.
284    #[serde(skip)]
285    pub span: Span,
286}
287
288/// A `switch` statement.
289#[derive(Debug, Serialize)]
290pub struct SwitchStmt<'arena, 'src> {
291    /// Subject expression.
292    pub expr: Expr<'arena, 'src>,
293    /// Case list, flattened on serialization.
294    #[serde(flatten)]
295    pub body: SwitchBody<'arena, 'src>,
296    /// `true` when written in alternative syntax (`: ... endX;`).
297    #[serde(default, skip_serializing_if = "is_false")]
298    pub uses_alternative: bool,
299}
300
301/// A `case` or `default` clause.
302#[derive(Debug, Serialize)]
303pub struct SwitchCase<'arena, 'src> {
304    /// Case expression; `None` for `default`.
305    pub value: Option<Expr<'arena, 'src>>,
306    /// Statements under this case.
307    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
308    /// Source range of this node.
309    pub span: Span,
310}
311
312/// A `try` statement with `catch` and `finally` clauses.
313#[derive(Debug, Serialize)]
314pub struct TryCatchStmt<'arena, 'src> {
315    /// `try` block.
316    pub body: &'arena Block<'arena, 'src>,
317    /// `catch` clauses in order.
318    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
319    /// `finally` block, if any.
320    pub finally: Option<&'arena Block<'arena, 'src>>,
321    /// The `finally` keyword; `None` when there is no finally clause. Not serialized.
322    #[serde(skip)]
323    pub finally_kw_span: Option<Span>,
324}
325
326/// A `catch (Type $e) { ... }` clause.
327#[derive(Debug, Serialize)]
328pub struct CatchClause<'arena, 'src> {
329    /// Caught exception types (`A|B`).
330    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
331    /// Bound variable name; `None` if omitted (PHP 8.0+).
332    pub var: Option<&'src str>,
333    /// `catch` block.
334    pub body: &'arena Block<'arena, 'src>,
335    /// Source range of this node.
336    pub span: Span,
337}
338
339/// A `namespace` declaration.
340#[derive(Debug, Serialize)]
341pub struct NamespaceDecl<'arena, 'src> {
342    /// Namespace name; `None` for the global namespace.
343    pub name: Option<Name<'arena, 'src>>,
344    /// Braced or simple form.
345    pub body: NamespaceBody<'arena, 'src>,
346}
347
348/// Whether a namespace is braced or simple.
349#[derive(Debug, Serialize)]
350#[non_exhaustive]
351pub enum NamespaceBody<'arena, 'src> {
352    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
353    Braced(&'arena Block<'arena, 'src>),
354    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
355    Simple,
356}
357
358/// A `declare(...)` statement.
359#[derive(Debug, Serialize)]
360pub struct DeclareStmt<'arena, 'src> {
361    /// `name = value` directives.
362    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
363    /// Statement scoped by the directive, if any.
364    pub body: Option<&'arena Stmt<'arena, 'src>>,
365    /// `true` when written in alternative syntax (`: ... endX;`).
366    #[serde(default, skip_serializing_if = "is_false")]
367    pub uses_alternative: bool,
368}
369
370/// A `use` import statement.
371#[derive(Debug, Serialize)]
372pub struct UseDecl<'arena, 'src> {
373    /// Whether importing classes, functions or constants.
374    pub kind: UseKind,
375    /// Imported names.
376    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
377}
378
379/// What a `use` import brings in.
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
381#[non_exhaustive]
382pub enum UseKind {
383    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
384    Normal,
385    /// `use function Foo\bar` — imports a function.
386    Function,
387    /// `use const Foo\BAR` — imports a constant.
388    Const,
389}
390
391/// One imported name in a `use` statement.
392#[derive(Debug, Serialize)]
393pub struct UseItem<'arena, 'src> {
394    /// Imported name.
395    pub name: Name<'arena, 'src>,
396    /// Alias after `as`, if any.
397    pub alias: Option<&'src str>,
398    /// Per-item kind in a mixed group `use Foo\{function bar}`.
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub kind: Option<UseKind>,
401    /// Source range of this node.
402    pub span: Span,
403}
404
405/// One `NAME = value` item of a top-level `const` statement.
406#[derive(Debug, Serialize)]
407pub struct ConstItem<'arena, 'src> {
408    /// Constant name.
409    pub name: Ident<'src>,
410    /// Constant value expression.
411    pub value: Expr<'arena, 'src>,
412    /// `#[...]` attributes applied to this node.
413    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
414    /// Source range of this node.
415    pub span: Span,
416    /// Preceding `/** */` doc-block, if any.
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub doc_comment: Option<Comment<'src>>,
419}
420
421/// One variable of a `static $x = 1;` statement.
422#[derive(Debug, Serialize)]
423pub struct StaticVar<'arena, 'src> {
424    /// Variable name without `$`.
425    pub name: Ident<'src>,
426    /// Initial value, if any.
427    pub default: Option<Expr<'arena, 'src>>,
428    /// Source range of this node.
429    pub span: Span,
430}