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#[derive(Debug, Serialize)]
15pub struct Stmt<'arena, 'src> {
16    pub kind: StmtKind<'arena, 'src>,
17    pub span: Span,
18    /// The immediately preceding `/** */` doc-block, if any.
19    ///
20    /// Only `/** */` (doc-block) comments are attached here; `//`, `#`, and
21    /// `/* */` comments remain in [`ParseResult::comments`].  When present,
22    /// this comment is **removed** from `ParseResult::comments` — the two
23    /// collections are disjoint.  A doc-block that has no following statement
24    /// before the enclosing `}` or EOF is not attached and stays in
25    /// `ParseResult::comments`.
26    ///
27    /// For declaration statements (`function`, `class`, `interface`, …) the
28    /// doc-block is attached to the *inner* declaration node (e.g.
29    /// [`FunctionDecl::doc_comment`]) and this field will always be `None`.
30    ///
31    /// Stored as a pointer into the arena rather than inline so that the
32    /// `None` case (the vast majority of statements) costs only 8 bytes
33    /// instead of the 32 bytes an inline `Option<Comment>` would require.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub doc_comment: Option<&'arena Comment<'src>>,
36}
37
38impl<'arena, 'src> Stmt<'arena, 'src> {
39    /// The leading `/** */` doc-block for this statement, regardless of where
40    /// it is stored.
41    ///
42    /// For non-declaration statements (`foreach`, `if`, assignments, …) the
43    /// comment lives on [`Stmt::doc_comment`] and is returned directly.
44    ///
45    /// For declaration statements the comment is stored on the inner
46    /// declaration node — this method checks each variant so callers do not
47    /// need to match on [`StmtKind`]:
48    ///
49    /// | `StmtKind` variant | source field |
50    /// |--------------------|--------------|
51    /// | `Function`         | [`FunctionDecl::doc_comment`] |
52    /// | `Class`            | [`ClassDecl::doc_comment`] |
53    /// | `Interface`        | [`InterfaceDecl::doc_comment`] |
54    /// | `Trait`            | [`TraitDecl::doc_comment`] |
55    /// | `Enum`             | [`EnumDecl::doc_comment`] |
56    /// | `Const`            | first [`ConstItem::doc_comment`] |
57    ///
58    /// Returns `None` when no doc-block precedes the statement.
59    pub fn leading_doc_comment(&self) -> Option<&Comment<'src>> {
60        if let Some(doc) = self.doc_comment {
61            return Some(doc);
62        }
63        match &self.kind {
64            StmtKind::Function(f) => f.doc_comment.as_ref(),
65            StmtKind::Class(c) => c.doc_comment.as_ref(),
66            StmtKind::Interface(i) => i.doc_comment.as_ref(),
67            StmtKind::Trait(t) => t.doc_comment.as_ref(),
68            StmtKind::Enum(e) => e.doc_comment.as_ref(),
69            StmtKind::Const(items) => items.first().and_then(|i| i.doc_comment.as_ref()),
70            _ => None,
71        }
72    }
73}
74
75/// A brace-delimited statement block. Used both as a standalone block
76/// statement ([`StmtKind::Block`]) and as the body of constructs that are
77/// *always* braced (functions, methods, closures, `try`/`catch`/`finally`,
78/// braced namespaces, property-hook blocks) — those positions hold a
79/// `&Block` so the "it's a block" invariant is enforced by the type.
80#[derive(Debug, Serialize)]
81#[serde(transparent)]
82pub struct Block<'arena, 'src> {
83    pub stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
84    /// Span covering `{`..`}` (or the keyword-delimited region for the
85    /// alternative-syntax blocks reachable only via [`StmtKind::Block`]).
86    #[serde(skip)]
87    pub span: Span,
88}
89
90#[derive(Debug, Serialize)]
91pub enum StmtKind<'arena, 'src> {
92    /// Expression statement (e.g. `foo();`)
93    Expression(&'arena Expr<'arena, 'src>),
94
95    /// Echo statement: `echo expr1, expr2;`
96    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),
97
98    /// Return statement: `return expr;`
99    Return(Option<&'arena Expr<'arena, 'src>>),
100
101    /// Block statement: `{ stmts }`
102    Block(&'arena Block<'arena, 'src>),
103
104    /// If statement
105    If(&'arena IfStmt<'arena, 'src>),
106
107    /// While loop
108    While(&'arena WhileStmt<'arena, 'src>),
109
110    /// For loop
111    For(&'arena ForStmt<'arena, 'src>),
112
113    /// Foreach loop
114    Foreach(&'arena ForeachStmt<'arena, 'src>),
115
116    /// Do-while loop
117    DoWhile(&'arena DoWhileStmt<'arena, 'src>),
118
119    /// Function declaration
120    Function(&'arena FunctionDecl<'arena, 'src>),
121
122    /// Break statement
123    Break(Option<&'arena Expr<'arena, 'src>>),
124
125    /// Continue statement
126    Continue(Option<&'arena Expr<'arena, 'src>>),
127
128    /// Switch statement
129    Switch(&'arena SwitchStmt<'arena, 'src>),
130
131    /// Goto statement
132    Goto(Ident<'src>),
133
134    /// Label statement
135    Label(&'arena str),
136
137    /// Declare statement
138    Declare(&'arena DeclareStmt<'arena, 'src>),
139
140    /// Unset statement
141    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),
142
143    /// Throw statement (also can be expression in PHP 8)
144    Throw(&'arena Expr<'arena, 'src>),
145
146    /// Try/catch/finally
147    TryCatch(&'arena TryCatchStmt<'arena, 'src>),
148
149    /// Global declaration
150    Global(ArenaVec<'arena, Expr<'arena, 'src>>),
151
152    /// Class declaration
153    Class(&'arena ClassDecl<'arena, 'src>),
154
155    /// Interface declaration
156    Interface(&'arena InterfaceDecl<'arena, 'src>),
157
158    /// Trait declaration
159    Trait(&'arena TraitDecl<'arena, 'src>),
160
161    /// Enum declaration
162    Enum(&'arena EnumDecl<'arena, 'src>),
163
164    /// Namespace declaration
165    Namespace(&'arena NamespaceDecl<'arena, 'src>),
166
167    /// Use declaration
168    Use(&'arena UseDecl<'arena, 'src>),
169
170    /// Top-level constant: `const FOO = expr;`
171    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),
172
173    /// Static variable declaration: `static $x = 1;`
174    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),
175
176    /// __halt_compiler(); with remaining data
177    HaltCompiler(&'src str),
178
179    /// Nop (empty statement `;`)
180    Nop,
181
182    /// Inline HTML
183    InlineHtml(&'src str),
184
185    /// Error placeholder — parser always produces a tree
186    Error,
187}
188
189#[derive(Debug, Serialize)]
190pub struct IfStmt<'arena, 'src> {
191    pub condition: Expr<'arena, 'src>,
192    pub then_branch: &'arena Stmt<'arena, 'src>,
193    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
194    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
195    /// Start byte offset of the `else` keyword; `None` when there is no else branch.
196    #[serde(skip)]
197    pub else_kw_start: Option<u32>,
198    #[serde(default, skip_serializing_if = "is_false")]
199    pub uses_alternative: bool,
200}
201
202#[derive(Debug, Serialize)]
203pub struct ElseIfBranch<'arena, 'src> {
204    pub condition: Expr<'arena, 'src>,
205    pub body: Stmt<'arena, 'src>,
206    pub span: Span,
207}
208
209#[derive(Debug, Serialize)]
210pub struct WhileStmt<'arena, 'src> {
211    pub condition: Expr<'arena, 'src>,
212    pub body: &'arena Stmt<'arena, 'src>,
213    #[serde(default, skip_serializing_if = "is_false")]
214    pub uses_alternative: bool,
215}
216
217#[derive(Debug, Serialize)]
218pub struct ForStmt<'arena, 'src> {
219    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
220    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
221    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
222    pub body: &'arena Stmt<'arena, 'src>,
223    #[serde(default, skip_serializing_if = "is_false")]
224    pub uses_alternative: bool,
225}
226
227#[derive(Debug, Serialize)]
228pub struct ForeachStmt<'arena, 'src> {
229    pub expr: Expr<'arena, 'src>,
230    pub key: Option<Expr<'arena, 'src>>,
231    pub value: Expr<'arena, 'src>,
232    pub body: &'arena Stmt<'arena, 'src>,
233    #[serde(default, skip_serializing_if = "is_false")]
234    pub uses_alternative: bool,
235}
236
237#[derive(Debug, Serialize)]
238pub struct DoWhileStmt<'arena, 'src> {
239    pub body: &'arena Stmt<'arena, 'src>,
240    pub condition: Expr<'arena, 'src>,
241}
242
243#[derive(Debug, Serialize)]
244pub struct SwitchBody<'arena, 'src> {
245    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
246    #[serde(skip)]
247    pub span: Span,
248}
249
250#[derive(Debug, Serialize)]
251pub struct SwitchStmt<'arena, 'src> {
252    pub expr: Expr<'arena, 'src>,
253    #[serde(flatten)]
254    pub body: SwitchBody<'arena, 'src>,
255    #[serde(default, skip_serializing_if = "is_false")]
256    pub uses_alternative: bool,
257}
258
259#[derive(Debug, Serialize)]
260pub struct SwitchCase<'arena, 'src> {
261    pub value: Option<Expr<'arena, 'src>>,
262    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
263    pub span: Span,
264}
265
266#[derive(Debug, Serialize)]
267pub struct TryCatchStmt<'arena, 'src> {
268    pub body: &'arena Block<'arena, 'src>,
269    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
270    pub finally: Option<&'arena Block<'arena, 'src>>,
271    /// Start byte offset of the `finally` keyword; `None` when there is no finally clause.
272    #[serde(skip)]
273    pub finally_kw_start: Option<u32>,
274}
275
276#[derive(Debug, Serialize)]
277pub struct CatchClause<'arena, 'src> {
278    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
279    pub var: Option<&'src str>,
280    pub body: &'arena Block<'arena, 'src>,
281    pub span: Span,
282}
283
284#[derive(Debug, Serialize)]
285pub struct NamespaceDecl<'arena, 'src> {
286    pub name: Option<Name<'arena, 'src>>,
287    pub body: NamespaceBody<'arena, 'src>,
288}
289
290#[derive(Debug, Serialize)]
291pub enum NamespaceBody<'arena, 'src> {
292    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
293    Braced(&'arena Block<'arena, 'src>),
294    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
295    Simple,
296}
297
298#[derive(Debug, Serialize)]
299pub struct DeclareStmt<'arena, 'src> {
300    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
301    pub body: Option<&'arena Stmt<'arena, 'src>>,
302    #[serde(default, skip_serializing_if = "is_false")]
303    pub uses_alternative: bool,
304}
305
306#[derive(Debug, Serialize)]
307pub struct UseDecl<'arena, 'src> {
308    pub kind: UseKind,
309    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
313pub enum UseKind {
314    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
315    Normal,
316    /// `use function Foo\bar` — imports a function.
317    Function,
318    /// `use const Foo\BAR` — imports a constant.
319    Const,
320}
321
322#[derive(Debug, Serialize)]
323pub struct UseItem<'arena, 'src> {
324    pub name: Name<'arena, 'src>,
325    pub alias: Option<&'src str>,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub kind: Option<UseKind>,
328    pub span: Span,
329}
330
331#[derive(Debug, Serialize)]
332pub struct ConstItem<'arena, 'src> {
333    pub name: Ident<'src>,
334    pub value: Expr<'arena, 'src>,
335    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
336    pub span: Span,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub doc_comment: Option<Comment<'src>>,
339}
340
341#[derive(Debug, Serialize)]
342pub struct StaticVar<'arena, 'src> {
343    pub name: Ident<'src>,
344    pub default: Option<Expr<'arena, 'src>>,
345    pub span: Span,
346}