Skip to main content

php_ast/ast/
stmts.rs

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