Skip to main content

php_ast/ast/
exprs.rs

1use serde::Serialize;
2
3use crate::Span;
4
5use super::{is_false, ArenaVec, Arg, Attribute, Block, ClassDecl, Param, TypeHint};
6
7#[derive(Clone, Copy, PartialEq, Eq, Hash)]
8enum NameStrInner<'arena, 'src> {
9    Src(&'src str),
10    Arena(&'arena str),
11}
12
13/// A name string from either the source buffer or the bump arena. Use [`as_str`](NameStr::as_str)
14/// or `Deref` to get the string value — the allocation origin is an internal parser detail.
15///
16/// Using this as the payload for both `ExprKind::Variable` and `ExprKind::Identifier`
17/// gives them the same binding type, so or-patterns compile natively:
18///
19/// ```
20/// # use php_ast::ast::{ExprKind, NameStr};
21/// # fn example<'a, 'b>(kind: &ExprKind<'a, 'b>) {
22/// if let ExprKind::Variable(name) | ExprKind::Identifier(name) = kind {
23///     let _s: &str = name.as_str();
24/// }
25/// # }
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, Hash)]
28pub struct NameStr<'arena, 'src>(NameStrInner<'arena, 'src>);
29
30impl<'arena, 'src> NameStr<'arena, 'src> {
31    /// Borrowed directly from the source buffer.
32    #[doc(hidden)]
33    #[inline]
34    pub fn __src(s: &'src str) -> Self {
35        Self(NameStrInner::Src(s))
36    }
37
38    /// Allocated in the bump arena (e.g. a joined qualified name or a keyword).
39    #[doc(hidden)]
40    #[inline]
41    pub fn __arena(s: &'arena str) -> Self {
42        Self(NameStrInner::Arena(s))
43    }
44
45    /// Returns the arena slice if this value was arena-allocated, otherwise `None`.
46    /// Used internally to avoid re-allocating strings already in the arena.
47    #[doc(hidden)]
48    #[inline]
49    pub fn __into_arena_str(self) -> Option<&'arena str> {
50        match self.0 {
51            NameStrInner::Arena(s) => Some(s),
52            NameStrInner::Src(_) => None,
53        }
54    }
55
56    /// Returns the source slice if this value was borrowed from the source buffer, otherwise `None`.
57    /// Used by the fold infrastructure to preserve source-borrowed strings without re-allocation.
58    #[doc(hidden)]
59    #[inline]
60    pub fn __into_src_str(self) -> Option<&'src str> {
61        match self.0 {
62            NameStrInner::Src(s) => Some(s),
63            NameStrInner::Arena(_) => None,
64        }
65    }
66
67    /// The string value, regardless of where it is stored.
68    #[inline]
69    pub fn as_str(&self) -> &str {
70        match self.0 {
71            NameStrInner::Src(s) | NameStrInner::Arena(s) => s,
72        }
73    }
74}
75
76impl<'arena, 'src> std::ops::Deref for NameStr<'arena, 'src> {
77    type Target = str;
78    #[inline]
79    fn deref(&self) -> &str {
80        self.as_str()
81    }
82}
83
84impl<'arena, 'src> std::fmt::Debug for NameStr<'arena, 'src> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        self.as_str().fmt(f)
87    }
88}
89
90impl<'arena, 'src> serde::Serialize for NameStr<'arena, 'src> {
91    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
92        self.as_str().serialize(serializer)
93    }
94}
95
96/// An expression node.
97#[derive(Debug, Serialize)]
98pub struct Expr<'arena, 'src> {
99    /// Expression kind.
100    pub kind: ExprKind<'arena, 'src>,
101    /// Source range of this node.
102    pub span: Span,
103}
104
105/// The kinds of expression.
106#[derive(Debug, Serialize)]
107pub enum ExprKind<'arena, 'src> {
108    /// Integer literal
109    Int(i64),
110
111    /// Float literal
112    Float(f64),
113
114    /// String literal
115    String(&'arena str),
116
117    /// Interpolated string: `"Hello $name, you are {$age} years old"`
118    InterpolatedString(ArenaVec<'arena, StringPart<'arena, 'src>>),
119
120    /// Heredoc: `<<<EOT ... EOT`
121    Heredoc {
122        /// Heredoc/nowdoc label.
123        label: &'src str,
124        /// Literal and interpolated segments.
125        parts: ArenaVec<'arena, StringPart<'arena, 'src>>,
126    },
127
128    /// Nowdoc: `<<<'EOT' ... EOT`
129    Nowdoc {
130        /// Heredoc/nowdoc label.
131        label: &'src str,
132        /// Nowdoc contents.
133        value: &'arena str,
134    },
135
136    /// Shell execution: `` `command $var` ``
137    ShellExec(ArenaVec<'arena, StringPart<'arena, 'src>>),
138
139    /// Boolean literal
140    Bool(bool),
141
142    /// Null literal
143    Null,
144
145    /// Variable: `$name`
146    Variable(NameStr<'arena, 'src>),
147
148    /// Variable variable: `$$var`, `$$$var`, `${expr}`
149    VariableVariable(&'arena Expr<'arena, 'src>),
150
151    /// Identifier (bare name, e.g. function name in a call)
152    Identifier(NameStr<'arena, 'src>),
153
154    /// Assignment: `$x = expr` or `$x += expr`
155    Assign(AssignExpr<'arena, 'src>),
156
157    /// Binary operation: `expr op expr`
158    Binary(BinaryExpr<'arena, 'src>),
159
160    /// Unary prefix: `-expr`, `!expr`, `~expr`, `++$x`, `--$x`
161    UnaryPrefix(UnaryPrefixExpr<'arena, 'src>),
162
163    /// Unary postfix: `$x++`, `$x--`
164    UnaryPostfix(UnaryPostfixExpr<'arena, 'src>),
165
166    /// Ternary: `cond ? then : else` or short `cond ?: else`
167    Ternary(TernaryExpr<'arena, 'src>),
168
169    /// Null coalescing: `expr ?? fallback`
170    NullCoalesce(NullCoalesceExpr<'arena, 'src>),
171
172    /// Function call: `name(args)`
173    FunctionCall(FunctionCallExpr<'arena, 'src>),
174
175    /// Array literal: `[1, 2, 3]` or `['a' => 1]`
176    Array(ArenaVec<'arena, ArrayElement<'arena, 'src>>),
177
178    /// Array access: `$arr[index]`
179    ArrayAccess(ArrayAccessExpr<'arena, 'src>),
180
181    /// Print expression: `print expr`
182    Print(&'arena Expr<'arena, 'src>),
183
184    /// Parenthesized expression: `(expr)`
185    Parenthesized(&'arena Expr<'arena, 'src>),
186
187    /// Cast expression: `(int)$x`, `(string)$x`, etc.
188    Cast(CastKind, &'arena Expr<'arena, 'src>),
189
190    /// Error suppression: `@expr`
191    ErrorSuppress(&'arena Expr<'arena, 'src>),
192
193    /// Isset: `isset($a, $b)`
194    Isset(ArenaVec<'arena, Expr<'arena, 'src>>),
195
196    /// Empty: `empty($a)`
197    Empty(&'arena Expr<'arena, 'src>),
198
199    /// Include/require: `include 'file.php'`
200    Include(IncludeKind, &'arena Expr<'arena, 'src>),
201
202    /// Eval: `eval('code')`
203    Eval(&'arena Expr<'arena, 'src>),
204
205    /// Exit/die: `exit`, `exit(1)`, `die('msg')`
206    Exit(Option<&'arena Expr<'arena, 'src>>),
207
208    /// Magic constant: `__LINE__`, `__FILE__`, etc.
209    MagicConst(MagicConstKind),
210
211    /// Clone: `clone $obj`
212    Clone(&'arena Expr<'arena, 'src>),
213
214    /// Clone with property overrides: `clone($obj, ['prop' => $val])` — PHP 8.5+
215    CloneWith(&'arena Expr<'arena, 'src>, &'arena Expr<'arena, 'src>),
216
217    /// New: `new Class(args)`
218    New(NewExpr<'arena, 'src>),
219
220    /// Property access: `$obj->prop`
221    PropertyAccess(PropertyAccessExpr<'arena, 'src>),
222
223    /// Nullsafe property access: `$obj?->prop`
224    NullsafePropertyAccess(PropertyAccessExpr<'arena, 'src>),
225
226    /// Method call: `$obj->method(args)`
227    MethodCall(&'arena MethodCallExpr<'arena, 'src>),
228
229    /// Nullsafe method call: `$obj?->method(args)`
230    NullsafeMethodCall(&'arena MethodCallExpr<'arena, 'src>),
231
232    /// Static property access: `Class::$prop`
233    StaticPropertyAccess(StaticAccessExpr<'arena, 'src>),
234
235    /// Static method call: `Class::method(args)`
236    StaticMethodCall(&'arena StaticMethodCallExpr<'arena, 'src>),
237
238    /// Dynamic static method call: `Class::$method(args)`
239    StaticDynMethodCall(&'arena StaticDynMethodCallExpr<'arena, 'src>),
240
241    /// Class constant access: `Class::CONST`
242    ClassConstAccess(StaticAccessExpr<'arena, 'src>),
243
244    /// Dynamic class constant access: `Foo::{expr}`
245    ClassConstAccessDynamic {
246        /// Class expression.
247        class: &'arena Expr<'arena, 'src>,
248        /// Member expression.
249        member: &'arena Expr<'arena, 'src>,
250    },
251
252    /// Dynamic static property access: `A::$$b`, `A::${'b'}`
253    StaticPropertyAccessDynamic {
254        /// Class expression.
255        class: &'arena Expr<'arena, 'src>,
256        /// Member expression.
257        member: &'arena Expr<'arena, 'src>,
258    },
259
260    /// Closure: `function($x) use($y) { }`
261    Closure(&'arena ClosureExpr<'arena, 'src>),
262
263    /// Arrow function: `fn($x) => expr`
264    ArrowFunction(&'arena ArrowFunctionExpr<'arena, 'src>),
265
266    /// Match: `match(expr) { ... }`
267    Match(MatchExpr<'arena, 'src>),
268
269    /// Throw as expression (PHP 8)
270    ThrowExpr(&'arena Expr<'arena, 'src>),
271
272    /// Yield: `yield` / `yield $val` / `yield $key => $val`
273    Yield(YieldExpr<'arena, 'src>),
274
275    /// Anonymous class: `new class(args) extends Foo implements Bar { ... }`
276    AnonymousClass(&'arena ClassDecl<'arena, 'src>),
277
278    /// First-class callable: `strlen(...)`, `$obj->method(...)`, `Foo::bar(...)`
279    CallableCreate(CallableCreateExpr<'arena, 'src>),
280
281    /// Omitted element in destructuring: `[$a, , $c]` or `list($a, , $c)`
282    Omit,
283
284    /// Error placeholder
285    Error,
286}
287
288impl<'arena, 'src> Expr<'arena, 'src> {
289    /// Returns the name string for `Variable` and `Identifier` nodes, `None` for everything else.
290    pub fn name_str(&self) -> Option<&str> {
291        match &self.kind {
292            ExprKind::Variable(s) | ExprKind::Identifier(s) => Some(s.as_str()),
293            _ => None,
294        }
295    }
296}
297
298/// The type of a cast expression.
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
300pub enum CastKind {
301    /// `(int)` or `(integer)` cast.
302    Int,
303    /// `(float)`, `(double)`, or `(real)` cast.
304    Float,
305    /// `(string)` cast.
306    String,
307    /// `(bool)` or `(boolean)` cast.
308    Bool,
309    /// `(array)` cast.
310    Array,
311    /// `(object)` cast.
312    Object,
313    /// `(unset)` cast — deprecated; casts to `null`.
314    Unset,
315    /// `(void)` cast — non-standard; treated as discarding the value.
316    Void,
317}
318
319/// The include/require keyword used.
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
321pub enum IncludeKind {
322    /// `include 'file.php'` — emits a warning if the file is not found.
323    Include,
324    /// `include_once 'file.php'` — like `include`, but skipped if the file has already been included.
325    IncludeOnce,
326    /// `require 'file.php'` — fatal error if the file is not found.
327    Require,
328    /// `require_once 'file.php'` — like `require`, but skipped if the file has already been included.
329    RequireOnce,
330}
331
332/// The magic constants.
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
334pub enum MagicConstKind {
335    /// `__CLASS__` — name of the current class, or empty string outside a class.
336    Class,
337    /// `__DIR__` — directory of the current file.
338    Dir,
339    /// `__FILE__` — absolute path of the current file.
340    File,
341    /// `__FUNCTION__` — name of the current function or closure.
342    Function,
343    /// `__LINE__` — current line number in the source file.
344    Line,
345    /// `__METHOD__` — name of the current method including its class: `ClassName::methodName`.
346    Method,
347    /// `__NAMESPACE__` — name of the current namespace, or empty string in the global namespace.
348    Namespace,
349    /// `__TRAIT__` — name of the current trait, or empty string outside a trait.
350    Trait,
351    /// `__PROPERTY__` — name of the current property inside a property hook (PHP 8.4+).
352    Property,
353}
354
355// --- Expression sub-types ---
356
357/// An assignment expression.
358#[derive(Debug, Serialize)]
359pub struct AssignExpr<'arena, 'src> {
360    /// Assignment target.
361    pub target: &'arena Expr<'arena, 'src>,
362    /// Assignment operator.
363    pub op: AssignOp,
364    /// Assigned value.
365    pub value: &'arena Expr<'arena, 'src>,
366    /// `true` for reference assignment `=&`.
367    #[serde(skip_serializing_if = "is_false")]
368    pub by_ref: bool,
369}
370
371/// Assignment operators.
372#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
373pub enum AssignOp {
374    /// `=`
375    Assign,
376    /// `+=`
377    Plus,
378    /// `-=`
379    Minus,
380    /// `*=`
381    Mul,
382    /// `/=`
383    Div,
384    /// `%=`
385    Mod,
386    /// `**=`
387    Pow,
388    /// `.=`
389    Concat,
390    /// `&=`
391    BitwiseAnd,
392    /// `|=`
393    BitwiseOr,
394    /// `^=`
395    BitwiseXor,
396    /// `<<=`
397    ShiftLeft,
398    /// `>>=`
399    ShiftRight,
400    /// `??=`
401    Coalesce,
402}
403
404/// A binary operation.
405#[derive(Debug, Serialize)]
406pub struct BinaryExpr<'arena, 'src> {
407    /// Left operand.
408    pub left: &'arena Expr<'arena, 'src>,
409    /// Binary operator.
410    pub op: BinaryOp,
411    /// Right operand.
412    pub right: &'arena Expr<'arena, 'src>,
413}
414
415/// Binary operators.
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
417pub enum BinaryOp {
418    /// `+`
419    Add,
420    /// `-`
421    Sub,
422    /// `*`
423    Mul,
424    /// `/`
425    Div,
426    /// `%`
427    Mod,
428    /// `**`
429    Pow,
430    /// `.` — string concatenation.
431    Concat,
432    /// `==` — loose equality (type-coercing).
433    Equal,
434    /// `!=` or `<>` — loose inequality.
435    NotEqual,
436    /// `===` — strict equality (type and value).
437    Identical,
438    /// `!==` — strict inequality.
439    NotIdentical,
440    /// `<`
441    Less,
442    /// `>`
443    Greater,
444    /// `<=`
445    LessOrEqual,
446    /// `>=`
447    GreaterOrEqual,
448    /// `<=>` — spaceship / three-way comparison; returns -1, 0, or 1.
449    Spaceship,
450    /// `&&` — short-circuit boolean AND (higher precedence than `and`).
451    BooleanAnd,
452    /// `||` — short-circuit boolean OR (higher precedence than `or`).
453    BooleanOr,
454    /// `&` — bitwise AND.
455    BitwiseAnd,
456    /// `|` — bitwise OR.
457    BitwiseOr,
458    /// `^` — bitwise XOR.
459    BitwiseXor,
460    /// `<<` — left bit-shift.
461    ShiftLeft,
462    /// `>>` — right bit-shift.
463    ShiftRight,
464    /// `and` — boolean AND (lower precedence than `&&`).
465    LogicalAnd,
466    /// `or` — boolean OR (lower precedence than `||`).
467    LogicalOr,
468    /// `xor` — boolean XOR.
469    LogicalXor,
470    /// `instanceof` — type-check operator; `$x instanceof Foo`.
471    Instanceof,
472    /// `|>` — pipe operator (PHP 8.5+); passes the left operand as the first argument of the right callable.
473    Pipe,
474}
475
476/// A prefix unary operation.
477#[derive(Debug, Serialize)]
478pub struct UnaryPrefixExpr<'arena, 'src> {
479    /// Prefix operator.
480    pub op: UnaryPrefixOp,
481    /// Operand expression.
482    pub operand: &'arena Expr<'arena, 'src>,
483}
484
485/// Prefix unary operators.
486#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
487pub enum UnaryPrefixOp {
488    /// `-expr` — arithmetic negation.
489    Negate,
490    /// `+expr` — unary plus (no-op for numbers, promotes to numeric).
491    Plus,
492    /// `!expr` — boolean NOT.
493    BooleanNot,
494    /// `~expr` — bitwise NOT.
495    BitwiseNot,
496    /// `++$x` — pre-increment; increments then returns the new value.
497    PreIncrement,
498    /// `--$x` — pre-decrement; decrements then returns the new value.
499    PreDecrement,
500}
501
502/// A postfix unary operation.
503#[derive(Debug, Serialize)]
504pub struct UnaryPostfixExpr<'arena, 'src> {
505    /// Operand expression.
506    pub operand: &'arena Expr<'arena, 'src>,
507    /// Postfix operator.
508    pub op: UnaryPostfixOp,
509}
510
511/// Postfix unary operators.
512#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
513pub enum UnaryPostfixOp {
514    /// `$x++` — post-increment; returns the current value then increments.
515    PostIncrement,
516    /// `$x--` — post-decrement; returns the current value then decrements.
517    PostDecrement,
518}
519
520/// A ternary `?:` expression.
521#[derive(Debug, Serialize)]
522pub struct TernaryExpr<'arena, 'src> {
523    /// Condition tested.
524    pub condition: &'arena Expr<'arena, 'src>,
525    /// None for short ternary `$x ?: $y`
526    pub then_expr: Option<&'arena Expr<'arena, 'src>>,
527    /// Value when the condition is falsy.
528    pub else_expr: &'arena Expr<'arena, 'src>,
529}
530
531/// A `??` expression.
532#[derive(Debug, Serialize)]
533pub struct NullCoalesceExpr<'arena, 'src> {
534    /// Value tested for null.
535    pub left: &'arena Expr<'arena, 'src>,
536    /// Fallback when the left side is null or unset.
537    pub right: &'arena Expr<'arena, 'src>,
538}
539
540/// A function call: `name(args)`.
541#[derive(Debug, Serialize)]
542pub struct FunctionCallExpr<'arena, 'src> {
543    /// Callee expression.
544    pub name: &'arena Expr<'arena, 'src>,
545    /// Call arguments.
546    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
547}
548
549/// An element of an array literal.
550#[derive(Debug, Serialize)]
551pub struct ArrayElement<'arena, 'src> {
552    /// Key expression, if any.
553    pub key: Option<Expr<'arena, 'src>>,
554    /// Element value.
555    pub value: Expr<'arena, 'src>,
556    /// `true` for a spread element (`...$x`).
557    pub unpack: bool,
558    /// `true` for a reference element (`&$x`).
559    #[serde(skip_serializing_if = "is_false")]
560    pub by_ref: bool,
561    /// Source range of this node.
562    pub span: Span,
563}
564
565/// An array index access: `$a[i]`.
566#[derive(Debug, Serialize)]
567pub struct ArrayAccessExpr<'arena, 'src> {
568    /// Array being indexed.
569    pub array: &'arena Expr<'arena, 'src>,
570    /// Index; `None` for `$a[]`.
571    pub index: Option<&'arena Expr<'arena, 'src>>,
572}
573
574// --- OOP Expression sub-types ---
575
576/// An instantiation: `new Foo(args)`.
577#[derive(Debug, Serialize)]
578pub struct NewExpr<'arena, 'src> {
579    /// Class expression being instantiated.
580    pub class: &'arena Expr<'arena, 'src>,
581    /// Call arguments.
582    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
583}
584
585/// A property access: `$obj->prop`.
586#[derive(Debug, Serialize)]
587pub struct PropertyAccessExpr<'arena, 'src> {
588    /// Object expression.
589    pub object: &'arena Expr<'arena, 'src>,
590    /// Property name expression.
591    pub property: &'arena Expr<'arena, 'src>,
592}
593
594/// A method call: `$obj->method(args)`.
595#[derive(Debug, Serialize)]
596pub struct MethodCallExpr<'arena, 'src> {
597    /// Object expression.
598    pub object: &'arena Expr<'arena, 'src>,
599    /// Method name expression.
600    pub method: &'arena Expr<'arena, 'src>,
601    /// Call arguments.
602    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
603}
604
605/// A static member access: `Class::$prop` or `Class::CONST`.
606#[derive(Debug, Serialize)]
607pub struct StaticAccessExpr<'arena, 'src> {
608    /// Class expression or name.
609    pub class: &'arena Expr<'arena, 'src>,
610    /// Constant or property name expression.
611    pub member: &'arena Expr<'arena, 'src>,
612}
613
614/// A static method call: `Class::method(args)`.
615#[derive(Debug, Serialize)]
616pub struct StaticMethodCallExpr<'arena, 'src> {
617    /// Class expression or name.
618    pub class: &'arena Expr<'arena, 'src>,
619    /// Method name expression.
620    pub method: &'arena Expr<'arena, 'src>,
621    /// Call arguments.
622    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
623}
624
625/// A static call with a dynamic method name: `Class::$method(args)`.
626#[derive(Debug, Serialize)]
627pub struct StaticDynMethodCallExpr<'arena, 'src> {
628    /// Class expression or name.
629    pub class: &'arena Expr<'arena, 'src>,
630    /// Expression yielding the method name.
631    pub method: &'arena Expr<'arena, 'src>,
632    /// Call arguments.
633    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
634}
635
636/// A closure: `function() use ($x) { ... }`.
637#[derive(Debug, Serialize)]
638pub struct ClosureExpr<'arena, 'src> {
639    /// `true` for `static`.
640    pub is_static: bool,
641    /// `true` when by reference (`&`).
642    pub by_ref: bool,
643    /// Declared parameters.
644    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
645    /// Variables captured by `use (...)`.
646    pub use_vars: ArenaVec<'arena, ClosureUseVar<'src>>,
647    /// Declared return type, if any.
648    pub return_type: Option<TypeHint<'arena, 'src>>,
649    /// Closure body.
650    pub body: &'arena Block<'arena, 'src>,
651    /// `#[...]` attributes applied to this node.
652    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
653}
654
655/// A variable captured in a closure `use` list.
656#[derive(Debug, Clone, Serialize)]
657pub struct ClosureUseVar<'src> {
658    /// Captured variable name without `$`.
659    pub name: &'src str,
660    /// `true` for `use (&$x)`.
661    pub by_ref: bool,
662    /// Source range of this node.
663    pub span: Span,
664}
665
666/// An arrow function: `fn($x) => expr`.
667#[derive(Debug, Serialize)]
668pub struct ArrowFunctionExpr<'arena, 'src> {
669    /// `true` for `static`.
670    pub is_static: bool,
671    /// `true` when by reference (`&`).
672    pub by_ref: bool,
673    /// Declared parameters.
674    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
675    /// Declared return type, if any.
676    pub return_type: Option<TypeHint<'arena, 'src>>,
677    /// Single result expression.
678    pub body: &'arena Expr<'arena, 'src>,
679    /// `#[...]` attributes applied to this node.
680    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
681}
682
683/// A `match` expression.
684#[derive(Debug, Serialize)]
685pub struct MatchExpr<'arena, 'src> {
686    /// Expression being matched.
687    pub subject: &'arena Expr<'arena, 'src>,
688    /// Match arms in order.
689    pub arms: ArenaVec<'arena, MatchArm<'arena, 'src>>,
690    /// Start byte offset of the `{` in `match (...) {`.
691    #[serde(skip)]
692    pub brace_start: u32,
693}
694
695/// One arm of a `match`.
696#[derive(Debug, Serialize)]
697pub struct MatchArm<'arena, 'src> {
698    /// None for `default`
699    pub conditions: Option<ArenaVec<'arena, Expr<'arena, 'src>>>,
700    /// Result expression.
701    pub body: Expr<'arena, 'src>,
702    /// Source range of this node.
703    pub span: Span,
704}
705
706/// A `yield` or `yield from` expression.
707#[derive(Debug, Serialize)]
708pub struct YieldExpr<'arena, 'src> {
709    /// Yielded key.
710    pub key: Option<&'arena Expr<'arena, 'src>>,
711    /// Yielded value.
712    pub value: Option<&'arena Expr<'arena, 'src>>,
713    /// `true` for `yield from expr` (generator delegation), `false` for plain `yield`
714    pub is_from: bool,
715}
716
717// --- First-class callable ---
718
719/// A first-class callable expression.
720#[derive(Debug, Serialize)]
721pub struct CallableCreateExpr<'arena, 'src> {
722    /// Kind of callable being created.
723    pub kind: CallableCreateKind<'arena, 'src>,
724}
725
726/// The callee forms of a first-class callable.
727#[derive(Debug, Serialize)]
728pub enum CallableCreateKind<'arena, 'src> {
729    /// `foo(...)`, `$var(...)`, `\Ns\func(...)`
730    Function(&'arena Expr<'arena, 'src>),
731    /// `$obj->method(...)`
732    Method {
733        /// Object expression.
734        object: &'arena Expr<'arena, 'src>,
735        /// Method name expression.
736        method: &'arena Expr<'arena, 'src>,
737    },
738    /// `$obj?->method(...)`
739    NullsafeMethod {
740        /// Object expression.
741        object: &'arena Expr<'arena, 'src>,
742        /// Method name expression.
743        method: &'arena Expr<'arena, 'src>,
744    },
745    /// `Foo::bar(...)`
746    StaticMethod {
747        /// Class expression.
748        class: &'arena Expr<'arena, 'src>,
749        /// Method name expression.
750        method: &'arena Expr<'arena, 'src>,
751    },
752}
753
754// --- String interpolation ---
755
756/// A segment of an interpolated string.
757#[derive(Debug, Serialize)]
758pub enum StringPart<'arena, 'src> {
759    /// A plain text segment of an interpolated string or heredoc.
760    Literal(&'arena str),
761    /// An embedded expression: `$var`, `{$expr}`, or `${var}`.
762    Expr(Expr<'arena, 'src>),
763}