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}