Skip to main content

php_ast/
ast.rs

1use std::borrow::Cow;
2
3use serde::Serialize;
4
5use crate::Span;
6
7/// Arena-allocated Vec. Thin newtype over bumpalo::collections::Vec that implements Serialize and Debug.
8pub struct ArenaVec<'arena, T>(bumpalo::collections::Vec<'arena, T>);
9
10impl<'arena, T> ArenaVec<'arena, T> {
11    #[inline]
12    pub fn new_in(arena: &'arena bumpalo::Bump) -> Self {
13        Self(bumpalo::collections::Vec::new_in(arena))
14    }
15    #[inline]
16    pub fn with_capacity_in(cap: usize, arena: &'arena bumpalo::Bump) -> Self {
17        Self(bumpalo::collections::Vec::with_capacity_in(cap, arena))
18    }
19    #[inline]
20    pub fn push(&mut self, val: T) {
21        self.0.push(val)
22    }
23    #[inline]
24    pub fn is_empty(&self) -> bool {
25        self.0.is_empty()
26    }
27    #[inline]
28    pub fn len(&self) -> usize {
29        self.0.len()
30    }
31    #[inline]
32    pub fn last(&self) -> Option<&T> {
33        self.0.last()
34    }
35}
36
37impl<'arena, T> IntoIterator for ArenaVec<'arena, T> {
38    type Item = T;
39    type IntoIter = bumpalo::collections::vec::IntoIter<'arena, T>;
40    #[inline]
41    fn into_iter(self) -> Self::IntoIter {
42        self.0.into_iter()
43    }
44}
45
46impl<'arena, T> std::ops::Deref for ArenaVec<'arena, T> {
47    type Target = [T];
48    #[inline]
49    fn deref(&self) -> &[T] {
50        &self.0
51    }
52}
53
54impl<'arena, T> std::ops::DerefMut for ArenaVec<'arena, T> {
55    #[inline]
56    fn deref_mut(&mut self) -> &mut [T] {
57        &mut self.0
58    }
59}
60
61impl<'arena, T: serde::Serialize> serde::Serialize for ArenaVec<'arena, T> {
62    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
63        self.0.as_slice().serialize(s)
64    }
65}
66
67impl<'arena, T: std::fmt::Debug> std::fmt::Debug for ArenaVec<'arena, T> {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        self.0.as_slice().fmt(f)
70    }
71}
72
73/// The root AST node representing a complete PHP file.
74#[derive(Debug, Serialize)]
75pub struct Program<'arena, 'src> {
76    pub stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
77    pub span: Span,
78}
79
80// =============================================================================
81// Names and Types
82// =============================================================================
83
84#[derive(Debug, Serialize)]
85pub struct Name<'arena, 'src> {
86    pub parts: ArenaVec<'arena, Cow<'src, str>>,
87    pub kind: NameKind,
88    pub span: Span,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
92pub enum NameKind {
93    Unqualified,
94    Qualified,
95    FullyQualified,
96    Relative,
97}
98
99#[derive(Debug, Serialize)]
100pub struct TypeHint<'arena, 'src> {
101    pub kind: TypeHintKind<'arena, 'src>,
102    pub span: Span,
103}
104
105#[derive(Debug, Serialize)]
106pub enum TypeHintKind<'arena, 'src> {
107    Named(Name<'arena, 'src>),
108    Nullable(&'arena TypeHint<'arena, 'src>),
109    Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
110    Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
111}
112
113// =============================================================================
114// Arguments
115// =============================================================================
116
117#[derive(Debug, Serialize)]
118pub struct Arg<'arena, 'src> {
119    pub name: Option<Cow<'src, str>>,
120    pub value: Expr<'arena, 'src>,
121    pub unpack: bool,
122    pub span: Span,
123}
124
125// =============================================================================
126// Attributes
127// =============================================================================
128
129#[derive(Debug, Serialize)]
130pub struct Attribute<'arena, 'src> {
131    pub name: Name<'arena, 'src>,
132    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
133    pub span: Span,
134}
135
136// =============================================================================
137// Statements
138// =============================================================================
139
140#[derive(Debug, Serialize)]
141pub struct Stmt<'arena, 'src> {
142    pub kind: StmtKind<'arena, 'src>,
143    pub span: Span,
144}
145
146#[derive(Debug, Serialize)]
147pub enum StmtKind<'arena, 'src> {
148    /// Expression statement (e.g. `foo();`)
149    Expression(&'arena Expr<'arena, 'src>),
150
151    /// Echo statement: `echo expr1, expr2;`
152    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),
153
154    /// Return statement: `return expr;`
155    Return(Option<&'arena Expr<'arena, 'src>>),
156
157    /// Block statement: `{ stmts }`
158    Block(ArenaVec<'arena, Stmt<'arena, 'src>>),
159
160    /// If statement
161    If(&'arena IfStmt<'arena, 'src>),
162
163    /// While loop
164    While(&'arena WhileStmt<'arena, 'src>),
165
166    /// For loop
167    For(&'arena ForStmt<'arena, 'src>),
168
169    /// Foreach loop
170    Foreach(&'arena ForeachStmt<'arena, 'src>),
171
172    /// Do-while loop
173    DoWhile(&'arena DoWhileStmt<'arena, 'src>),
174
175    /// Function declaration
176    Function(&'arena FunctionDecl<'arena, 'src>),
177
178    /// Break statement
179    Break(Option<&'arena Expr<'arena, 'src>>),
180
181    /// Continue statement
182    Continue(Option<&'arena Expr<'arena, 'src>>),
183
184    /// Switch statement
185    Switch(&'arena SwitchStmt<'arena, 'src>),
186
187    /// Goto statement
188    Goto(&'src str),
189
190    /// Label statement
191    Label(&'src str),
192
193    /// Declare statement
194    Declare(
195        ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
196        Option<&'arena Stmt<'arena, 'src>>,
197    ),
198
199    /// Unset statement
200    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),
201
202    /// Throw statement (also can be expression in PHP 8)
203    Throw(&'arena Expr<'arena, 'src>),
204
205    /// Try/catch/finally
206    TryCatch(&'arena TryCatchStmt<'arena, 'src>),
207
208    /// Global declaration
209    Global(ArenaVec<'arena, Expr<'arena, 'src>>),
210
211    /// Class declaration
212    Class(&'arena ClassDecl<'arena, 'src>),
213
214    /// Interface declaration
215    Interface(&'arena InterfaceDecl<'arena, 'src>),
216
217    /// Trait declaration
218    Trait(&'arena TraitDecl<'arena, 'src>),
219
220    /// Enum declaration
221    Enum(&'arena EnumDecl<'arena, 'src>),
222
223    /// Namespace declaration
224    Namespace(&'arena NamespaceDecl<'arena, 'src>),
225
226    /// Use declaration
227    Use(UseDecl<'arena, 'src>),
228
229    /// Top-level constant: `const FOO = expr;`
230    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),
231
232    /// Static variable declaration: `static $x = 1;`
233    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),
234
235    /// __halt_compiler(); with remaining data
236    HaltCompiler(&'src str),
237
238    /// Nop (empty statement `;`)
239    Nop,
240
241    /// Inline HTML
242    InlineHtml(&'src str),
243
244    /// Error placeholder — parser always produces a tree
245    Error,
246}
247
248#[derive(Debug, Serialize)]
249pub struct IfStmt<'arena, 'src> {
250    pub condition: Expr<'arena, 'src>,
251    pub then_branch: &'arena Stmt<'arena, 'src>,
252    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
253    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
254}
255
256#[derive(Debug, Serialize)]
257pub struct ElseIfBranch<'arena, 'src> {
258    pub condition: Expr<'arena, 'src>,
259    pub body: Stmt<'arena, 'src>,
260    pub span: Span,
261}
262
263#[derive(Debug, Serialize)]
264pub struct WhileStmt<'arena, 'src> {
265    pub condition: Expr<'arena, 'src>,
266    pub body: &'arena Stmt<'arena, 'src>,
267}
268
269#[derive(Debug, Serialize)]
270pub struct ForStmt<'arena, 'src> {
271    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
272    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
273    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
274    pub body: &'arena Stmt<'arena, 'src>,
275}
276
277#[derive(Debug, Serialize)]
278pub struct ForeachStmt<'arena, 'src> {
279    pub expr: Expr<'arena, 'src>,
280    pub key: Option<Expr<'arena, 'src>>,
281    pub value: Expr<'arena, 'src>,
282    pub body: &'arena Stmt<'arena, 'src>,
283}
284
285#[derive(Debug, Serialize)]
286pub struct DoWhileStmt<'arena, 'src> {
287    pub body: &'arena Stmt<'arena, 'src>,
288    pub condition: Expr<'arena, 'src>,
289}
290
291#[derive(Debug, Serialize)]
292pub struct FunctionDecl<'arena, 'src> {
293    pub name: &'src str,
294    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
295    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
296    pub return_type: Option<TypeHint<'arena, 'src>>,
297    pub by_ref: bool,
298    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
299}
300
301#[derive(Debug, Serialize)]
302pub struct Param<'arena, 'src> {
303    pub name: &'src str,
304    pub type_hint: Option<TypeHint<'arena, 'src>>,
305    pub default: Option<Expr<'arena, 'src>>,
306    pub by_ref: bool,
307    pub variadic: bool,
308    pub visibility: Option<Visibility>,
309    pub set_visibility: Option<Visibility>,
310    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
311    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
312    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
313    pub span: Span,
314}
315
316#[derive(Debug, Serialize)]
317pub struct SwitchStmt<'arena, 'src> {
318    pub expr: Expr<'arena, 'src>,
319    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
320}
321
322#[derive(Debug, Serialize)]
323pub struct SwitchCase<'arena, 'src> {
324    pub value: Option<Expr<'arena, 'src>>,
325    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
326    pub span: Span,
327}
328
329#[derive(Debug, Serialize)]
330pub struct TryCatchStmt<'arena, 'src> {
331    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
332    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
333    pub finally: Option<ArenaVec<'arena, Stmt<'arena, 'src>>>,
334}
335
336#[derive(Debug, Serialize)]
337pub struct CatchClause<'arena, 'src> {
338    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
339    pub var: Option<&'src str>,
340    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
341    pub span: Span,
342}
343
344// =============================================================================
345// OOP Declarations
346// =============================================================================
347
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
349pub enum Visibility {
350    Public,
351    Protected,
352    Private,
353}
354
355#[derive(Debug, Serialize)]
356pub struct ClassDecl<'arena, 'src> {
357    pub name: Option<&'src str>,
358    pub modifiers: ClassModifiers,
359    pub extends: Option<Name<'arena, 'src>>,
360    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
361    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
362    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
363}
364
365#[derive(Debug, Clone, Serialize, Default)]
366pub struct ClassModifiers {
367    pub is_abstract: bool,
368    pub is_final: bool,
369    pub is_readonly: bool,
370}
371
372#[derive(Debug, Serialize)]
373pub struct ClassMember<'arena, 'src> {
374    pub kind: ClassMemberKind<'arena, 'src>,
375    pub span: Span,
376}
377
378#[derive(Debug, Serialize)]
379pub enum ClassMemberKind<'arena, 'src> {
380    Property(PropertyDecl<'arena, 'src>),
381    Method(MethodDecl<'arena, 'src>),
382    ClassConst(ClassConstDecl<'arena, 'src>),
383    TraitUse(TraitUseDecl<'arena, 'src>),
384}
385
386#[derive(Debug, Serialize)]
387pub struct PropertyDecl<'arena, 'src> {
388    pub name: &'src str,
389    pub visibility: Option<Visibility>,
390    pub set_visibility: Option<Visibility>,
391    pub is_static: bool,
392    pub is_readonly: bool,
393    pub type_hint: Option<TypeHint<'arena, 'src>>,
394    pub default: Option<Expr<'arena, 'src>>,
395    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
396    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
397    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
398}
399
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
401pub enum PropertyHookKind {
402    Get,
403    Set,
404}
405
406#[derive(Debug, Serialize)]
407pub enum PropertyHookBody<'arena, 'src> {
408    Block(ArenaVec<'arena, Stmt<'arena, 'src>>),
409    Expression(Expr<'arena, 'src>),
410    Abstract,
411}
412
413#[derive(Debug, Serialize)]
414pub struct PropertyHook<'arena, 'src> {
415    pub kind: PropertyHookKind,
416    pub body: PropertyHookBody<'arena, 'src>,
417    pub is_final: bool,
418    pub by_ref: bool,
419    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
420    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
421    pub span: Span,
422}
423
424#[derive(Debug, Serialize)]
425pub struct MethodDecl<'arena, 'src> {
426    pub name: &'src str,
427    pub visibility: Option<Visibility>,
428    pub is_static: bool,
429    pub is_abstract: bool,
430    pub is_final: bool,
431    pub by_ref: bool,
432    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
433    pub return_type: Option<TypeHint<'arena, 'src>>,
434    pub body: Option<ArenaVec<'arena, Stmt<'arena, 'src>>>,
435    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
436}
437
438#[derive(Debug, Serialize)]
439pub struct ClassConstDecl<'arena, 'src> {
440    pub name: &'src str,
441    pub visibility: Option<Visibility>,
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub type_hint: Option<&'arena TypeHint<'arena, 'src>>,
444    pub value: Expr<'arena, 'src>,
445    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
446}
447
448#[derive(Debug, Serialize)]
449pub struct TraitUseDecl<'arena, 'src> {
450    pub traits: ArenaVec<'arena, Name<'arena, 'src>>,
451    pub adaptations: ArenaVec<'arena, TraitAdaptation<'arena, 'src>>,
452}
453
454#[derive(Debug, Serialize)]
455pub struct TraitAdaptation<'arena, 'src> {
456    pub kind: TraitAdaptationKind<'arena, 'src>,
457    pub span: Span,
458}
459
460#[derive(Debug, Serialize)]
461pub enum TraitAdaptationKind<'arena, 'src> {
462    /// `A::foo insteadof B, C;`
463    Precedence {
464        trait_name: Name<'arena, 'src>,
465        method: &'src str,
466        insteadof: ArenaVec<'arena, Name<'arena, 'src>>,
467    },
468    /// `foo as bar;` or `A::foo as protected bar;` or `foo as protected;`
469    Alias {
470        trait_name: Option<Name<'arena, 'src>>,
471        method: Cow<'src, str>,
472        new_modifier: Option<Visibility>,
473        new_name: Option<&'src str>,
474    },
475}
476
477#[derive(Debug, Serialize)]
478pub struct InterfaceDecl<'arena, 'src> {
479    pub name: &'src str,
480    pub extends: ArenaVec<'arena, Name<'arena, 'src>>,
481    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
482    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
483}
484
485#[derive(Debug, Serialize)]
486pub struct TraitDecl<'arena, 'src> {
487    pub name: &'src str,
488    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
489    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
490}
491
492#[derive(Debug, Serialize)]
493pub struct EnumDecl<'arena, 'src> {
494    pub name: &'src str,
495    pub scalar_type: Option<Name<'arena, 'src>>,
496    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
497    pub members: ArenaVec<'arena, EnumMember<'arena, 'src>>,
498    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
499}
500
501#[derive(Debug, Serialize)]
502pub struct EnumMember<'arena, 'src> {
503    pub kind: EnumMemberKind<'arena, 'src>,
504    pub span: Span,
505}
506
507#[derive(Debug, Serialize)]
508pub enum EnumMemberKind<'arena, 'src> {
509    Case(EnumCase<'arena, 'src>),
510    Method(MethodDecl<'arena, 'src>),
511    ClassConst(ClassConstDecl<'arena, 'src>),
512    TraitUse(TraitUseDecl<'arena, 'src>),
513}
514
515#[derive(Debug, Serialize)]
516pub struct EnumCase<'arena, 'src> {
517    pub name: &'src str,
518    pub value: Option<Expr<'arena, 'src>>,
519    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
520}
521
522// =============================================================================
523// Namespace & Use
524// =============================================================================
525
526#[derive(Debug, Serialize)]
527pub struct NamespaceDecl<'arena, 'src> {
528    pub name: Option<Name<'arena, 'src>>,
529    pub body: NamespaceBody<'arena, 'src>,
530}
531
532#[derive(Debug, Serialize)]
533pub enum NamespaceBody<'arena, 'src> {
534    Braced(ArenaVec<'arena, Stmt<'arena, 'src>>),
535    Simple,
536}
537
538#[derive(Debug, Serialize)]
539pub struct UseDecl<'arena, 'src> {
540    pub kind: UseKind,
541    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
545pub enum UseKind {
546    Normal,
547    Function,
548    Const,
549}
550
551#[derive(Debug, Serialize)]
552pub struct UseItem<'arena, 'src> {
553    pub name: Name<'arena, 'src>,
554    pub alias: Option<&'src str>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub kind: Option<UseKind>,
557    pub span: Span,
558}
559
560#[derive(Debug, Serialize)]
561pub struct ConstItem<'arena, 'src> {
562    pub name: &'src str,
563    pub value: Expr<'arena, 'src>,
564    pub span: Span,
565}
566
567#[derive(Debug, Serialize)]
568pub struct StaticVar<'arena, 'src> {
569    pub name: &'src str,
570    pub default: Option<Expr<'arena, 'src>>,
571    pub span: Span,
572}
573
574// =============================================================================
575// Expressions
576// =============================================================================
577
578#[derive(Debug, Serialize)]
579pub struct Expr<'arena, 'src> {
580    pub kind: ExprKind<'arena, 'src>,
581    pub span: Span,
582}
583
584#[derive(Debug, Serialize)]
585pub enum ExprKind<'arena, 'src> {
586    /// Integer literal
587    Int(i64),
588
589    /// Float literal
590    Float(f64),
591
592    /// String literal
593    String(Cow<'src, str>),
594
595    /// Interpolated string: `"Hello $name, you are {$age} years old"`
596    InterpolatedString(ArenaVec<'arena, StringPart<'arena, 'src>>),
597
598    /// Heredoc: `<<<EOT ... EOT`
599    Heredoc {
600        label: String,
601        parts: ArenaVec<'arena, StringPart<'arena, 'src>>,
602    },
603
604    /// Nowdoc: `<<<'EOT' ... EOT`
605    Nowdoc { label: String, value: String },
606
607    /// Shell execution: `` `command $var` ``
608    ShellExec(ArenaVec<'arena, StringPart<'arena, 'src>>),
609
610    /// Boolean literal
611    Bool(bool),
612
613    /// Null literal
614    Null,
615
616    /// Variable: `$name`
617    Variable(Cow<'src, str>),
618
619    /// Variable variable: `$$var`, `$$$var`, `${expr}`
620    VariableVariable(&'arena Expr<'arena, 'src>),
621
622    /// Identifier (bare name, e.g. function name in a call)
623    Identifier(Cow<'src, str>),
624
625    /// Assignment: `$x = expr` or `$x += expr`
626    Assign(AssignExpr<'arena, 'src>),
627
628    /// Binary operation: `expr op expr`
629    Binary(BinaryExpr<'arena, 'src>),
630
631    /// Unary prefix: `-expr`, `!expr`, `~expr`, `++$x`, `--$x`
632    UnaryPrefix(UnaryPrefixExpr<'arena, 'src>),
633
634    /// Unary postfix: `$x++`, `$x--`
635    UnaryPostfix(UnaryPostfixExpr<'arena, 'src>),
636
637    /// Ternary: `cond ? then : else` or short `cond ?: else`
638    Ternary(TernaryExpr<'arena, 'src>),
639
640    /// Null coalescing: `expr ?? fallback`
641    NullCoalesce(NullCoalesceExpr<'arena, 'src>),
642
643    /// Function call: `name(args)`
644    FunctionCall(FunctionCallExpr<'arena, 'src>),
645
646    /// Array literal: `[1, 2, 3]` or `['a' => 1]`
647    Array(ArenaVec<'arena, ArrayElement<'arena, 'src>>),
648
649    /// Array access: `$arr[index]`
650    ArrayAccess(ArrayAccessExpr<'arena, 'src>),
651
652    /// Print expression: `print expr`
653    Print(&'arena Expr<'arena, 'src>),
654
655    /// Parenthesized expression: `(expr)`
656    Parenthesized(&'arena Expr<'arena, 'src>),
657
658    /// Cast expression: `(int)$x`, `(string)$x`, etc.
659    Cast(CastKind, &'arena Expr<'arena, 'src>),
660
661    /// Error suppression: `@expr`
662    ErrorSuppress(&'arena Expr<'arena, 'src>),
663
664    /// Isset: `isset($a, $b)`
665    Isset(ArenaVec<'arena, Expr<'arena, 'src>>),
666
667    /// Empty: `empty($a)`
668    Empty(&'arena Expr<'arena, 'src>),
669
670    /// Include/require: `include 'file.php'`
671    Include(IncludeKind, &'arena Expr<'arena, 'src>),
672
673    /// Eval: `eval('code')`
674    Eval(&'arena Expr<'arena, 'src>),
675
676    /// Exit/die: `exit`, `exit(1)`, `die('msg')`
677    Exit(Option<&'arena Expr<'arena, 'src>>),
678
679    /// Magic constant: `__LINE__`, `__FILE__`, etc.
680    MagicConst(MagicConstKind),
681
682    /// Clone: `clone $obj`
683    Clone(&'arena Expr<'arena, 'src>),
684
685    /// New: `new Class(args)`
686    New(NewExpr<'arena, 'src>),
687
688    /// Property access: `$obj->prop`
689    PropertyAccess(PropertyAccessExpr<'arena, 'src>),
690
691    /// Nullsafe property access: `$obj?->prop`
692    NullsafePropertyAccess(PropertyAccessExpr<'arena, 'src>),
693
694    /// Method call: `$obj->method(args)`
695    MethodCall(MethodCallExpr<'arena, 'src>),
696
697    /// Nullsafe method call: `$obj?->method(args)`
698    NullsafeMethodCall(MethodCallExpr<'arena, 'src>),
699
700    /// Static property access: `Class::$prop`
701    StaticPropertyAccess(StaticAccessExpr<'arena, 'src>),
702
703    /// Static method call: `Class::method(args)`
704    StaticMethodCall(StaticMethodCallExpr<'arena, 'src>),
705
706    /// Class constant access: `Class::CONST`
707    ClassConstAccess(StaticAccessExpr<'arena, 'src>),
708
709    /// Dynamic class constant access: `Foo::{expr}`
710    ClassConstAccessDynamic {
711        class: &'arena Expr<'arena, 'src>,
712        member: &'arena Expr<'arena, 'src>,
713    },
714
715    /// Dynamic static property access: `A::$$b`, `A::${'b'}`
716    StaticPropertyAccessDynamic {
717        class: &'arena Expr<'arena, 'src>,
718        member: &'arena Expr<'arena, 'src>,
719    },
720
721    /// Closure: `function($x) use($y) { }`
722    Closure(&'arena ClosureExpr<'arena, 'src>),
723
724    /// Arrow function: `fn($x) => expr`
725    ArrowFunction(&'arena ArrowFunctionExpr<'arena, 'src>),
726
727    /// Match: `match(expr) { ... }`
728    Match(MatchExpr<'arena, 'src>),
729
730    /// Throw as expression (PHP 8)
731    ThrowExpr(&'arena Expr<'arena, 'src>),
732
733    /// Yield: `yield` / `yield $val` / `yield $key => $val`
734    Yield(YieldExpr<'arena, 'src>),
735
736    /// Anonymous class: `new class(args) extends Foo implements Bar { ... }`
737    AnonymousClass(&'arena ClassDecl<'arena, 'src>),
738
739    /// First-class callable: `strlen(...)`, `$obj->method(...)`, `Foo::bar(...)`
740    CallableCreate(CallableCreateExpr<'arena, 'src>),
741
742    /// Error placeholder
743    Error,
744}
745
746#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
747pub enum CastKind {
748    Int,
749    Float,
750    String,
751    Bool,
752    Array,
753    Object,
754    Unset,
755    Void,
756}
757
758#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
759pub enum IncludeKind {
760    Include,
761    IncludeOnce,
762    Require,
763    RequireOnce,
764}
765
766#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
767pub enum MagicConstKind {
768    Class,
769    Dir,
770    File,
771    Function,
772    Line,
773    Method,
774    Namespace,
775    Trait,
776    Property,
777}
778
779// --- Expression sub-types ---
780
781#[derive(Debug, Serialize)]
782pub struct AssignExpr<'arena, 'src> {
783    pub target: &'arena Expr<'arena, 'src>,
784    pub op: AssignOp,
785    pub value: &'arena Expr<'arena, 'src>,
786}
787
788#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
789pub enum AssignOp {
790    Assign,
791    Plus,
792    Minus,
793    Mul,
794    Div,
795    Mod,
796    Pow,
797    Concat,
798    BitwiseAnd,
799    BitwiseOr,
800    BitwiseXor,
801    ShiftLeft,
802    ShiftRight,
803    Coalesce,
804}
805
806#[derive(Debug, Serialize)]
807pub struct BinaryExpr<'arena, 'src> {
808    pub left: &'arena Expr<'arena, 'src>,
809    pub op: BinaryOp,
810    pub right: &'arena Expr<'arena, 'src>,
811}
812
813#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
814pub enum BinaryOp {
815    Add,
816    Sub,
817    Mul,
818    Div,
819    Mod,
820    Pow,
821    Concat,
822    Equal,
823    NotEqual,
824    Identical,
825    NotIdentical,
826    Less,
827    Greater,
828    LessOrEqual,
829    GreaterOrEqual,
830    Spaceship,
831    BooleanAnd,
832    BooleanOr,
833    BitwiseAnd,
834    BitwiseOr,
835    BitwiseXor,
836    ShiftLeft,
837    ShiftRight,
838    LogicalAnd,
839    LogicalOr,
840    LogicalXor,
841    Instanceof,
842    Pipe,
843}
844
845#[derive(Debug, Serialize)]
846pub struct UnaryPrefixExpr<'arena, 'src> {
847    pub op: UnaryPrefixOp,
848    pub operand: &'arena Expr<'arena, 'src>,
849}
850
851#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
852pub enum UnaryPrefixOp {
853    Negate,
854    Plus,
855    BooleanNot,
856    BitwiseNot,
857    PreIncrement,
858    PreDecrement,
859}
860
861#[derive(Debug, Serialize)]
862pub struct UnaryPostfixExpr<'arena, 'src> {
863    pub operand: &'arena Expr<'arena, 'src>,
864    pub op: UnaryPostfixOp,
865}
866
867#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
868pub enum UnaryPostfixOp {
869    PostIncrement,
870    PostDecrement,
871}
872
873#[derive(Debug, Serialize)]
874pub struct TernaryExpr<'arena, 'src> {
875    pub condition: &'arena Expr<'arena, 'src>,
876    /// None for short ternary `$x ?: $y`
877    pub then_expr: Option<&'arena Expr<'arena, 'src>>,
878    pub else_expr: &'arena Expr<'arena, 'src>,
879}
880
881#[derive(Debug, Serialize)]
882pub struct NullCoalesceExpr<'arena, 'src> {
883    pub left: &'arena Expr<'arena, 'src>,
884    pub right: &'arena Expr<'arena, 'src>,
885}
886
887#[derive(Debug, Serialize)]
888pub struct FunctionCallExpr<'arena, 'src> {
889    pub name: &'arena Expr<'arena, 'src>,
890    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
891}
892
893#[derive(Debug, Serialize)]
894pub struct ArrayElement<'arena, 'src> {
895    pub key: Option<Expr<'arena, 'src>>,
896    pub value: Expr<'arena, 'src>,
897    pub unpack: bool,
898    pub span: Span,
899}
900
901#[derive(Debug, Serialize)]
902pub struct ArrayAccessExpr<'arena, 'src> {
903    pub array: &'arena Expr<'arena, 'src>,
904    pub index: Option<&'arena Expr<'arena, 'src>>,
905}
906
907// --- OOP Expression sub-types ---
908
909#[derive(Debug, Serialize)]
910pub struct NewExpr<'arena, 'src> {
911    pub class: &'arena Expr<'arena, 'src>,
912    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
913}
914
915#[derive(Debug, Serialize)]
916pub struct PropertyAccessExpr<'arena, 'src> {
917    pub object: &'arena Expr<'arena, 'src>,
918    pub property: &'arena Expr<'arena, 'src>,
919}
920
921#[derive(Debug, Serialize)]
922pub struct MethodCallExpr<'arena, 'src> {
923    pub object: &'arena Expr<'arena, 'src>,
924    pub method: &'arena Expr<'arena, 'src>,
925    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
926}
927
928#[derive(Debug, Serialize)]
929pub struct StaticAccessExpr<'arena, 'src> {
930    pub class: &'arena Expr<'arena, 'src>,
931    pub member: Cow<'src, str>,
932}
933
934#[derive(Debug, Serialize)]
935pub struct StaticMethodCallExpr<'arena, 'src> {
936    pub class: &'arena Expr<'arena, 'src>,
937    pub method: Cow<'src, str>,
938    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
939}
940
941#[derive(Debug, Serialize)]
942pub struct ClosureExpr<'arena, 'src> {
943    pub is_static: bool,
944    pub by_ref: bool,
945    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
946    pub use_vars: ArenaVec<'arena, ClosureUseVar<'src>>,
947    pub return_type: Option<TypeHint<'arena, 'src>>,
948    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
949    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
950}
951
952#[derive(Debug, Clone, Serialize)]
953pub struct ClosureUseVar<'src> {
954    pub name: &'src str,
955    pub by_ref: bool,
956    pub span: Span,
957}
958
959#[derive(Debug, Serialize)]
960pub struct ArrowFunctionExpr<'arena, 'src> {
961    pub is_static: bool,
962    pub by_ref: bool,
963    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
964    pub return_type: Option<TypeHint<'arena, 'src>>,
965    pub body: &'arena Expr<'arena, 'src>,
966    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
967}
968
969#[derive(Debug, Serialize)]
970pub struct MatchExpr<'arena, 'src> {
971    pub subject: &'arena Expr<'arena, 'src>,
972    pub arms: ArenaVec<'arena, MatchArm<'arena, 'src>>,
973}
974
975#[derive(Debug, Serialize)]
976pub struct MatchArm<'arena, 'src> {
977    /// None for `default`
978    pub conditions: Option<ArenaVec<'arena, Expr<'arena, 'src>>>,
979    pub body: Expr<'arena, 'src>,
980    pub span: Span,
981}
982
983#[derive(Debug, Serialize)]
984pub struct YieldExpr<'arena, 'src> {
985    pub key: Option<&'arena Expr<'arena, 'src>>,
986    pub value: Option<&'arena Expr<'arena, 'src>>,
987}
988
989// --- First-class callable ---
990
991#[derive(Debug, Serialize)]
992pub struct CallableCreateExpr<'arena, 'src> {
993    pub kind: CallableCreateKind<'arena, 'src>,
994}
995
996#[derive(Debug, Serialize)]
997pub enum CallableCreateKind<'arena, 'src> {
998    /// `foo(...)`, `$var(...)`, `\Ns\func(...)`
999    Function(&'arena Expr<'arena, 'src>),
1000    /// `$obj->method(...)`
1001    Method {
1002        object: &'arena Expr<'arena, 'src>,
1003        method: &'arena Expr<'arena, 'src>,
1004    },
1005    /// `$obj?->method(...)`
1006    NullsafeMethod {
1007        object: &'arena Expr<'arena, 'src>,
1008        method: &'arena Expr<'arena, 'src>,
1009    },
1010    /// `Foo::bar(...)`
1011    StaticMethod {
1012        class: &'arena Expr<'arena, 'src>,
1013        method: Cow<'src, str>,
1014    },
1015}
1016
1017// --- String interpolation ---
1018
1019#[derive(Debug, Serialize)]
1020pub enum StringPart<'arena, 'src> {
1021    Literal(Cow<'src, str>),
1022    Expr(Expr<'arena, 'src>),
1023}