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))]
203#[non_exhaustive]
204pub struct IfStmt<'arena, 'src> {
205    /// `if` condition.
206    pub condition: Expr<'arena, 'src>,
207    /// Statement run when the condition holds.
208    pub then_branch: &'arena Stmt<'arena, 'src>,
209    /// `elseif` branches in order.
210    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
211    /// `else` statement, if any.
212    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
213    /// The `else` keyword; `None` when there is no else branch. Printer layout detail, outside semver; not serialized.
214    #[cfg_attr(feature = "serde", serde(skip))]
215    #[doc(hidden)]
216    pub else_kw_span: Option<Span>,
217    /// `true` when written in alternative syntax (`: ... endX;`).
218    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
219    pub uses_alternative: bool,
220}
221
222impl<'arena, 'src> IfStmt<'arena, 'src> {
223    /// Builds a `IfStmt` from its identifying fields with every other field empty. Set
224    /// the rest by assignment, as with `Param::new`.
225    pub fn new(
226        arena: &'arena bumpalo::Bump,
227        condition: Expr<'arena, 'src>,
228        then_branch: &'arena Stmt<'arena, 'src>,
229    ) -> Self {
230        Self {
231            condition,
232            then_branch,
233            elseif_branches: ArenaVec::new_in(arena),
234            else_branch: None,
235            else_kw_span: None,
236            uses_alternative: false,
237        }
238    }
239}
240
241/// An `elseif` branch. `else if` parses as an `else` whose body is an `if` statement.
242#[derive(Debug)]
243#[cfg_attr(feature = "serde", derive(Serialize))]
244pub struct ElseIfBranch<'arena, 'src> {
245    /// Condition expression.
246    pub condition: Expr<'arena, 'src>,
247    /// Statement run when this branch matches.
248    pub body: Stmt<'arena, 'src>,
249    /// Source range of this node.
250    pub span: Span,
251}
252
253/// A `while` loop.
254#[derive(Debug)]
255#[cfg_attr(feature = "serde", derive(Serialize))]
256#[non_exhaustive]
257pub struct WhileStmt<'arena, 'src> {
258    /// Condition expression.
259    pub condition: Expr<'arena, 'src>,
260    /// Loop body.
261    pub body: &'arena Stmt<'arena, 'src>,
262    /// `true` when written in alternative syntax (`: ... endX;`).
263    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
264    pub uses_alternative: bool,
265}
266
267impl<'arena, 'src> WhileStmt<'arena, 'src> {
268    /// Builds a `WhileStmt` from its identifying fields with every other field empty. Set
269    /// the rest by assignment, as with `Param::new`.
270    pub fn new(condition: Expr<'arena, 'src>, body: &'arena Stmt<'arena, 'src>) -> Self {
271        Self {
272            condition,
273            body,
274            uses_alternative: false,
275        }
276    }
277}
278
279/// A `for` loop.
280#[derive(Debug)]
281#[cfg_attr(feature = "serde", derive(Serialize))]
282#[non_exhaustive]
283pub struct ForStmt<'arena, 'src> {
284    /// Initializer expressions.
285    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
286    /// Condition expressions.
287    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
288    /// Update expressions.
289    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
290    /// Loop body.
291    pub body: &'arena Stmt<'arena, 'src>,
292    /// `true` when written in alternative syntax (`: ... endX;`).
293    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
294    pub uses_alternative: bool,
295}
296
297impl<'arena, 'src> ForStmt<'arena, 'src> {
298    /// Builds a `ForStmt` from its identifying fields with every other field empty. Set
299    /// the rest by assignment, as with `Param::new`.
300    pub fn new(arena: &'arena bumpalo::Bump, body: &'arena Stmt<'arena, 'src>) -> Self {
301        Self {
302            init: ArenaVec::new_in(arena),
303            condition: ArenaVec::new_in(arena),
304            update: ArenaVec::new_in(arena),
305            body,
306            uses_alternative: false,
307        }
308    }
309}
310
311/// A `foreach` loop.
312#[derive(Debug)]
313#[cfg_attr(feature = "serde", derive(Serialize))]
314#[non_exhaustive]
315pub struct ForeachStmt<'arena, 'src> {
316    /// Expression being iterated.
317    pub expr: Expr<'arena, 'src>,
318    /// Key target in `$k => $v`.
319    pub key: Option<Expr<'arena, 'src>>,
320    /// Value target.
321    pub value: Expr<'arena, 'src>,
322    /// `true` when the value is taken by reference: `foreach ($a as &$v)`.
323    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
324    pub by_ref: bool,
325    /// Loop body.
326    pub body: &'arena Stmt<'arena, 'src>,
327    /// `true` when written in alternative syntax (`: ... endX;`).
328    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
329    pub uses_alternative: bool,
330}
331
332impl<'arena, 'src> ForeachStmt<'arena, 'src> {
333    /// Builds a `ForeachStmt` from its identifying fields with every other field empty. Set
334    /// the rest by assignment, as with `Param::new`.
335    pub fn new(
336        expr: Expr<'arena, 'src>,
337        value: Expr<'arena, 'src>,
338        body: &'arena Stmt<'arena, 'src>,
339    ) -> Self {
340        Self {
341            expr,
342            key: None,
343            value,
344            by_ref: false,
345            body,
346            uses_alternative: false,
347        }
348    }
349}
350
351/// A `do ... while` loop.
352#[derive(Debug)]
353#[cfg_attr(feature = "serde", derive(Serialize))]
354#[non_exhaustive]
355pub struct DoWhileStmt<'arena, 'src> {
356    /// Loop body.
357    pub body: &'arena Stmt<'arena, 'src>,
358    /// Condition checked after each iteration.
359    pub condition: Expr<'arena, 'src>,
360}
361
362impl<'arena, 'src> DoWhileStmt<'arena, 'src> {
363    /// Builds a `DoWhileStmt` from its identifying fields with every other field empty. Set
364    /// the rest by assignment, as with `Param::new`.
365    pub fn new(body: &'arena Stmt<'arena, 'src>, condition: Expr<'arena, 'src>) -> Self {
366        Self { body, condition }
367    }
368}
369
370/// The case list of a `switch`.
371#[derive(Debug)]
372#[cfg_attr(feature = "serde", derive(Serialize))]
373pub struct SwitchBody<'arena, 'src> {
374    /// Cases in source order.
375    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
376    /// Source range of this node.
377    #[cfg_attr(feature = "serde", serde(skip))]
378    pub span: Span,
379}
380
381/// A `switch` statement.
382#[derive(Debug)]
383#[cfg_attr(feature = "serde", derive(Serialize))]
384#[non_exhaustive]
385pub struct SwitchStmt<'arena, 'src> {
386    /// Subject expression.
387    pub expr: Expr<'arena, 'src>,
388    /// Case list, flattened on serialization.
389    #[cfg_attr(feature = "serde", serde(flatten))]
390    pub body: SwitchBody<'arena, 'src>,
391    /// `true` when written in alternative syntax (`: ... endX;`).
392    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
393    pub uses_alternative: bool,
394}
395
396impl<'arena, 'src> SwitchStmt<'arena, 'src> {
397    /// Builds a `SwitchStmt` from its identifying fields with every other field empty. Set
398    /// the rest by assignment, as with `Param::new`.
399    pub fn new(expr: Expr<'arena, 'src>, body: SwitchBody<'arena, 'src>) -> Self {
400        Self {
401            expr,
402            body,
403            uses_alternative: false,
404        }
405    }
406}
407
408/// A `case` or `default` clause.
409#[derive(Debug)]
410#[cfg_attr(feature = "serde", derive(Serialize))]
411pub struct SwitchCase<'arena, 'src> {
412    /// Case expression; `None` for `default`.
413    pub value: Option<Expr<'arena, 'src>>,
414    /// Statements under this case.
415    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
416    /// Source range of this node.
417    pub span: Span,
418}
419
420/// A `try` statement with `catch` and `finally` clauses.
421#[derive(Debug)]
422#[cfg_attr(feature = "serde", derive(Serialize))]
423#[non_exhaustive]
424pub struct TryCatchStmt<'arena, 'src> {
425    /// `try` block.
426    pub body: &'arena Block<'arena, 'src>,
427    /// `catch` clauses in order.
428    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
429    /// `finally` block, if any.
430    pub finally: Option<&'arena Block<'arena, 'src>>,
431    /// The `finally` keyword; `None` when there is no finally clause. Printer layout detail, outside semver; not serialized.
432    #[cfg_attr(feature = "serde", serde(skip))]
433    #[doc(hidden)]
434    pub finally_kw_span: Option<Span>,
435}
436
437impl<'arena, 'src> TryCatchStmt<'arena, 'src> {
438    /// Builds a `TryCatchStmt` from its identifying fields with every other field empty. Set
439    /// the rest by assignment, as with `Param::new`.
440    pub fn new(arena: &'arena bumpalo::Bump, body: &'arena Block<'arena, 'src>) -> Self {
441        Self {
442            body,
443            catches: ArenaVec::new_in(arena),
444            finally: None,
445            finally_kw_span: None,
446        }
447    }
448}
449
450/// A `catch (Type $e) { ... }` clause.
451#[derive(Debug)]
452#[cfg_attr(feature = "serde", derive(Serialize))]
453#[non_exhaustive]
454pub struct CatchClause<'arena, 'src> {
455    /// Caught exception types (`A|B`).
456    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
457    /// Bound variable name; `None` if omitted (PHP 8.0+).
458    pub var: Option<Ident<'src>>,
459    /// `catch` block.
460    pub body: &'arena Block<'arena, 'src>,
461    /// Source range of this node.
462    pub span: Span,
463}
464
465impl<'arena, 'src> CatchClause<'arena, 'src> {
466    /// Builds a `CatchClause` from its identifying fields with every other field empty. Set
467    /// the rest by assignment, as with `Param::new`.
468    pub fn new(
469        arena: &'arena bumpalo::Bump,
470        body: &'arena Block<'arena, 'src>,
471        span: Span,
472    ) -> Self {
473        Self {
474            types: ArenaVec::new_in(arena),
475            var: None,
476            body,
477            span,
478        }
479    }
480}
481
482/// A `namespace` declaration.
483#[derive(Debug)]
484#[cfg_attr(feature = "serde", derive(Serialize))]
485#[non_exhaustive]
486pub struct NamespaceDecl<'arena, 'src> {
487    /// Namespace name; `None` for the global namespace.
488    pub name: Option<Name<'arena, 'src>>,
489    /// Braced or simple form.
490    pub body: NamespaceBody<'arena, 'src>,
491}
492
493impl<'arena, 'src> NamespaceDecl<'arena, 'src> {
494    /// Builds a `NamespaceDecl` from its identifying fields with every other field empty. Set
495    /// the rest by assignment, as with `Param::new`.
496    pub fn new(name: Option<Name<'arena, 'src>>, body: NamespaceBody<'arena, 'src>) -> Self {
497        Self { name, body }
498    }
499}
500
501/// Whether a namespace is braced or simple.
502#[derive(Debug)]
503#[cfg_attr(feature = "serde", derive(Serialize))]
504#[non_exhaustive]
505pub enum NamespaceBody<'arena, 'src> {
506    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
507    Braced(&'arena Block<'arena, 'src>),
508    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
509    Simple,
510}
511
512/// A `declare(...)` statement.
513#[derive(Debug)]
514#[cfg_attr(feature = "serde", derive(Serialize))]
515#[non_exhaustive]
516pub struct DeclareStmt<'arena, 'src> {
517    /// `name = value` directives.
518    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
519    /// Statement scoped by the directive, if any.
520    pub body: Option<&'arena Stmt<'arena, 'src>>,
521    /// `true` when written in alternative syntax (`: ... endX;`).
522    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
523    pub uses_alternative: bool,
524}
525
526impl<'arena, 'src> DeclareStmt<'arena, 'src> {
527    /// Builds a `DeclareStmt` from its identifying fields with every other field empty. Set
528    /// the rest by assignment, as with `Param::new`.
529    pub fn new(directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>) -> Self {
530        Self {
531            directives,
532            body: None,
533            uses_alternative: false,
534        }
535    }
536}
537
538/// A `use` import statement.
539#[derive(Debug)]
540#[cfg_attr(feature = "serde", derive(Serialize))]
541#[non_exhaustive]
542pub struct UseDecl<'arena, 'src> {
543    /// Whether importing classes, functions or constants.
544    pub kind: UseKind,
545    /// Imported names.
546    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
547}
548
549impl<'arena, 'src> UseDecl<'arena, 'src> {
550    /// Builds a `UseDecl` from its identifying fields with every other field empty. Set
551    /// the rest by assignment, as with `Param::new`.
552    pub fn new(kind: UseKind, uses: ArenaVec<'arena, UseItem<'arena, 'src>>) -> Self {
553        Self { kind, uses }
554    }
555}
556
557/// What a `use` import brings in.
558#[derive(Debug, Clone, Copy, PartialEq, Eq)]
559#[cfg_attr(feature = "serde", derive(Serialize))]
560#[non_exhaustive]
561pub enum UseKind {
562    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
563    Normal,
564    /// `use function Foo\bar` — imports a function.
565    Function,
566    /// `use const Foo\BAR` — imports a constant.
567    Const,
568}
569
570/// One imported name in a `use` statement.
571#[derive(Debug)]
572#[cfg_attr(feature = "serde", derive(Serialize))]
573pub struct UseItem<'arena, 'src> {
574    /// Imported name.
575    pub name: Name<'arena, 'src>,
576    /// Alias after `as`, if any.
577    pub alias: Option<Ident<'src>>,
578    /// Per-item kind in a mixed group `use Foo\{function bar}`.
579    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
580    pub kind: Option<UseKind>,
581    /// Source range of this node.
582    pub span: Span,
583}
584
585/// One `NAME = value` item of a top-level `const` statement.
586#[derive(Debug)]
587#[cfg_attr(feature = "serde", derive(Serialize))]
588pub struct ConstItem<'arena, 'src> {
589    /// Constant name.
590    pub name: Ident<'src>,
591    /// Constant value expression.
592    pub value: Expr<'arena, 'src>,
593    /// `#[...]` attributes applied to this node.
594    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
595    /// Source range of this node.
596    pub span: Span,
597    /// Preceding `/** */` doc-block, if any.
598    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
599    pub doc_comment: Option<Comment<'src>>,
600}
601
602/// One variable of a `static $x = 1;` statement.
603#[derive(Debug)]
604#[cfg_attr(feature = "serde", derive(Serialize))]
605pub struct StaticVar<'arena, 'src> {
606    /// Variable name without `$`.
607    pub name: Ident<'src>,
608    /// Initial value, if any.
609    pub default: Option<Expr<'arena, 'src>>,
610    /// Source range of this node.
611    pub span: Span,
612}