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}