Skip to main content

php_ast/ast/
exprs.rs

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