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    /// `true` for a key taken by reference, `foreach ($a as &$k => $v)`, which PHP rejects.
321    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
322    pub key_by_ref: bool,
323    /// Value target.
324    pub value: Expr<'arena, 'src>,
325    /// `true` when the value is taken by reference: `foreach ($a as &$v)`.
326    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
327    pub by_ref: bool,
328    /// The `&` of a by-reference value; `None` when `by_ref` is `false`.
329    #[cfg_attr(
330        feature = "serde",
331        serde(default, skip_serializing_if = "Option::is_none")
332    )]
333    pub by_ref_span: Option<Span>,
334    /// Loop body.
335    pub body: &'arena Stmt<'arena, 'src>,
336    /// `true` when written in alternative syntax (`: ... endX;`).
337    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
338    pub uses_alternative: bool,
339}
340
341impl<'arena, 'src> ForeachStmt<'arena, 'src> {
342    /// Builds a `ForeachStmt` from its identifying fields with every other field empty. Set
343    /// the rest by assignment, as with `Param::new`.
344    pub fn new(
345        expr: Expr<'arena, 'src>,
346        value: Expr<'arena, 'src>,
347        body: &'arena Stmt<'arena, 'src>,
348    ) -> Self {
349        Self {
350            expr,
351            key: None,
352            key_by_ref: false,
353            value,
354            by_ref: false,
355            by_ref_span: None,
356            body,
357            uses_alternative: false,
358        }
359    }
360}
361
362/// A `do ... while` loop.
363#[derive(Debug)]
364#[cfg_attr(feature = "serde", derive(Serialize))]
365#[non_exhaustive]
366pub struct DoWhileStmt<'arena, 'src> {
367    /// Loop body.
368    pub body: &'arena Stmt<'arena, 'src>,
369    /// Condition checked after each iteration.
370    pub condition: Expr<'arena, 'src>,
371}
372
373impl<'arena, 'src> DoWhileStmt<'arena, 'src> {
374    /// Builds a `DoWhileStmt` from its identifying fields with every other field empty. Set
375    /// the rest by assignment, as with `Param::new`.
376    pub fn new(body: &'arena Stmt<'arena, 'src>, condition: Expr<'arena, 'src>) -> Self {
377        Self { body, condition }
378    }
379}
380
381/// The case list of a `switch`.
382#[derive(Debug)]
383#[cfg_attr(feature = "serde", derive(Serialize))]
384pub struct SwitchBody<'arena, 'src> {
385    /// Cases in source order.
386    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
387    /// Source range of this node.
388    #[cfg_attr(feature = "serde", serde(skip))]
389    pub span: Span,
390}
391
392/// A `switch` statement.
393#[derive(Debug)]
394#[cfg_attr(feature = "serde", derive(Serialize))]
395#[non_exhaustive]
396pub struct SwitchStmt<'arena, 'src> {
397    /// Subject expression.
398    pub expr: Expr<'arena, 'src>,
399    /// Case list, flattened on serialization.
400    #[cfg_attr(feature = "serde", serde(flatten))]
401    pub body: SwitchBody<'arena, 'src>,
402    /// `true` when written in alternative syntax (`: ... endX;`).
403    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
404    pub uses_alternative: bool,
405}
406
407impl<'arena, 'src> SwitchStmt<'arena, 'src> {
408    /// Builds a `SwitchStmt` from its identifying fields with every other field empty. Set
409    /// the rest by assignment, as with `Param::new`.
410    pub fn new(expr: Expr<'arena, 'src>, body: SwitchBody<'arena, 'src>) -> Self {
411        Self {
412            expr,
413            body,
414            uses_alternative: false,
415        }
416    }
417}
418
419/// A `case` or `default` clause.
420#[derive(Debug)]
421#[cfg_attr(feature = "serde", derive(Serialize))]
422pub struct SwitchCase<'arena, 'src> {
423    /// Case expression; `None` for `default`.
424    pub value: Option<Expr<'arena, 'src>>,
425    /// Statements under this case.
426    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
427    /// Source range of this node.
428    pub span: Span,
429}
430
431/// A `try` statement with `catch` and `finally` clauses.
432#[derive(Debug)]
433#[cfg_attr(feature = "serde", derive(Serialize))]
434#[non_exhaustive]
435pub struct TryCatchStmt<'arena, 'src> {
436    /// `try` block.
437    pub body: &'arena Block<'arena, 'src>,
438    /// `catch` clauses in order.
439    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
440    /// `finally` block, if any.
441    pub finally: Option<&'arena Block<'arena, 'src>>,
442    /// The `finally` keyword; `None` when there is no finally clause. Printer layout detail, outside semver; not serialized.
443    #[cfg_attr(feature = "serde", serde(skip))]
444    #[doc(hidden)]
445    pub finally_kw_span: Option<Span>,
446}
447
448impl<'arena, 'src> TryCatchStmt<'arena, 'src> {
449    /// Builds a `TryCatchStmt` from its identifying fields with every other field empty. Set
450    /// the rest by assignment, as with `Param::new`.
451    pub fn new(arena: &'arena bumpalo::Bump, body: &'arena Block<'arena, 'src>) -> Self {
452        Self {
453            body,
454            catches: ArenaVec::new_in(arena),
455            finally: None,
456            finally_kw_span: None,
457        }
458    }
459}
460
461/// A `catch (Type $e) { ... }` clause.
462#[derive(Debug)]
463#[cfg_attr(feature = "serde", derive(Serialize))]
464#[non_exhaustive]
465pub struct CatchClause<'arena, 'src> {
466    /// Caught exception types (`A|B`).
467    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
468    /// Bound variable name; `None` if omitted (PHP 8.0+).
469    pub var: Option<Ident<'src>>,
470    /// `catch` block.
471    pub body: &'arena Block<'arena, 'src>,
472    /// Source range of this node.
473    pub span: Span,
474}
475
476impl<'arena, 'src> CatchClause<'arena, 'src> {
477    /// Builds a `CatchClause` from its identifying fields with every other field empty. Set
478    /// the rest by assignment, as with `Param::new`.
479    pub fn new(
480        arena: &'arena bumpalo::Bump,
481        body: &'arena Block<'arena, 'src>,
482        span: Span,
483    ) -> Self {
484        Self {
485            types: ArenaVec::new_in(arena),
486            var: None,
487            body,
488            span,
489        }
490    }
491}
492
493/// A `namespace` declaration.
494#[derive(Debug)]
495#[cfg_attr(feature = "serde", derive(Serialize))]
496#[non_exhaustive]
497pub struct NamespaceDecl<'arena, 'src> {
498    /// Namespace name; `None` for the global namespace.
499    pub name: Option<Name<'arena, 'src>>,
500    /// Braced or simple form.
501    pub body: NamespaceBody<'arena, 'src>,
502}
503
504impl<'arena, 'src> NamespaceDecl<'arena, 'src> {
505    /// Builds a `NamespaceDecl` from its identifying fields with every other field empty. Set
506    /// the rest by assignment, as with `Param::new`.
507    pub fn new(name: Option<Name<'arena, 'src>>, body: NamespaceBody<'arena, 'src>) -> Self {
508        Self { name, body }
509    }
510}
511
512/// Whether a namespace is braced or simple.
513#[derive(Debug)]
514#[cfg_attr(feature = "serde", derive(Serialize))]
515#[non_exhaustive]
516pub enum NamespaceBody<'arena, 'src> {
517    /// `namespace Foo { … }` — braced form; the statements are scoped to this namespace.
518    Braced(&'arena Block<'arena, 'src>),
519    /// `namespace Foo;` — simple form; all subsequent statements until the next `namespace` or EOF are in scope.
520    Simple,
521}
522
523/// A `declare(...)` statement.
524#[derive(Debug)]
525#[cfg_attr(feature = "serde", derive(Serialize))]
526#[non_exhaustive]
527pub struct DeclareStmt<'arena, 'src> {
528    /// `name = value` directives.
529    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
530    /// Statement scoped by the directive, if any.
531    pub body: Option<&'arena Stmt<'arena, 'src>>,
532    /// `true` when written in alternative syntax (`: ... endX;`).
533    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
534    pub uses_alternative: bool,
535}
536
537impl<'arena, 'src> DeclareStmt<'arena, 'src> {
538    /// Builds a `DeclareStmt` from its identifying fields with every other field empty. Set
539    /// the rest by assignment, as with `Param::new`.
540    pub fn new(directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>) -> Self {
541        Self {
542            directives,
543            body: None,
544            uses_alternative: false,
545        }
546    }
547}
548
549/// A `use` import statement.
550#[derive(Debug)]
551#[cfg_attr(feature = "serde", derive(Serialize))]
552#[non_exhaustive]
553pub struct UseDecl<'arena, 'src> {
554    /// Whether importing classes, functions or constants.
555    pub kind: UseKind,
556    /// Imported names.
557    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
558}
559
560impl<'arena, 'src> UseDecl<'arena, 'src> {
561    /// Builds a `UseDecl` from its identifying fields with every other field empty. Set
562    /// the rest by assignment, as with `Param::new`.
563    pub fn new(kind: UseKind, uses: ArenaVec<'arena, UseItem<'arena, 'src>>) -> Self {
564        Self { kind, uses }
565    }
566}
567
568/// What a `use` import brings in.
569#[derive(Debug, Clone, Copy, PartialEq, Eq)]
570#[cfg_attr(feature = "serde", derive(Serialize))]
571#[non_exhaustive]
572pub enum UseKind {
573    /// `use Foo\Bar` — imports a class, interface, trait, or enum.
574    Normal,
575    /// `use function Foo\bar` — imports a function.
576    Function,
577    /// `use const Foo\BAR` — imports a constant.
578    Const,
579}
580
581/// One imported name in a `use` statement.
582#[derive(Debug)]
583#[cfg_attr(feature = "serde", derive(Serialize))]
584pub struct UseItem<'arena, 'src> {
585    /// Imported name.
586    pub name: Name<'arena, 'src>,
587    /// Alias after `as`, if any.
588    pub alias: Option<Ident<'src>>,
589    /// Per-item kind in a mixed group `use Foo\{function bar}`.
590    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
591    pub kind: Option<UseKind>,
592    /// Source range of this node.
593    pub span: Span,
594}
595
596/// One `NAME = value` item of a top-level `const` statement.
597#[derive(Debug)]
598#[cfg_attr(feature = "serde", derive(Serialize))]
599pub struct ConstItem<'arena, 'src> {
600    /// Constant name.
601    pub name: Ident<'src>,
602    /// Constant value expression.
603    pub value: Expr<'arena, 'src>,
604    /// `#[...]` attributes applied to this node.
605    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
606    /// Source range of this node.
607    pub span: Span,
608    /// Preceding `/** */` doc-block, if any.
609    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
610    pub doc_comment: Option<Comment<'src>>,
611}
612
613/// One variable of a `static $x = 1;` statement.
614#[derive(Debug)]
615#[cfg_attr(feature = "serde", derive(Serialize))]
616pub struct StaticVar<'arena, 'src> {
617    /// Variable name without `$`.
618    pub name: Ident<'src>,
619    /// Initial value, if any.
620    pub default: Option<Expr<'arena, 'src>>,
621    /// Source range of this node.
622    pub span: Span,
623}