Skip to main content

php_ast/
ast.rs

1use std::borrow::Cow;
2
3use serde::Serialize;
4
5use crate::Span;
6
7fn is_false(b: &bool) -> bool {
8    !*b
9}
10
11/// Arena-allocated Vec. Thin newtype over bumpalo::collections::Vec that implements Serialize and Debug.
12pub struct ArenaVec<'arena, T>(bumpalo::collections::Vec<'arena, T>);
13
14impl<'arena, T> ArenaVec<'arena, T> {
15    #[inline]
16    pub fn new_in(arena: &'arena bumpalo::Bump) -> Self {
17        Self(bumpalo::collections::Vec::new_in(arena))
18    }
19    #[inline]
20    pub fn with_capacity_in(cap: usize, arena: &'arena bumpalo::Bump) -> Self {
21        Self(bumpalo::collections::Vec::with_capacity_in(cap, arena))
22    }
23    #[inline]
24    pub fn push(&mut self, val: T) {
25        self.0.push(val)
26    }
27    #[inline]
28    pub fn is_empty(&self) -> bool {
29        self.0.is_empty()
30    }
31    #[inline]
32    pub fn len(&self) -> usize {
33        self.0.len()
34    }
35    #[inline]
36    pub fn last(&self) -> Option<&T> {
37        self.0.last()
38    }
39}
40
41impl<'arena, T> IntoIterator for ArenaVec<'arena, T> {
42    type Item = T;
43    type IntoIter = bumpalo::collections::vec::IntoIter<'arena, T>;
44    #[inline]
45    fn into_iter(self) -> Self::IntoIter {
46        self.0.into_iter()
47    }
48}
49
50impl<'arena, T> std::ops::Deref for ArenaVec<'arena, T> {
51    type Target = [T];
52    #[inline]
53    fn deref(&self) -> &[T] {
54        &self.0
55    }
56}
57
58impl<'arena, T> std::ops::DerefMut for ArenaVec<'arena, T> {
59    #[inline]
60    fn deref_mut(&mut self) -> &mut [T] {
61        &mut self.0
62    }
63}
64
65impl<'arena, T: serde::Serialize> serde::Serialize for ArenaVec<'arena, T> {
66    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
67        self.0.as_slice().serialize(s)
68    }
69}
70
71impl<'arena, T: std::fmt::Debug> std::fmt::Debug for ArenaVec<'arena, T> {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        self.0.as_slice().fmt(f)
74    }
75}
76
77/// The root AST node representing a complete PHP file.
78#[derive(Debug, Serialize)]
79pub struct Program<'arena, 'src> {
80    pub stmts: ArenaVec<'arena, Stmt<'arena, 'src>>,
81    pub span: Span,
82}
83
84// =============================================================================
85// Names and Types
86// =============================================================================
87
88/// A PHP name (identifier, qualified name, fully-qualified name, or relative name).
89///
90/// The `Simple` variant is the fast path for the common case (~95%) of single
91/// unqualified identifiers like `strlen`, `Foo`, `MyClass`. It avoids allocating
92/// an `ArenaVec` entirely.
93///
94/// The `Complex` variant handles qualified (`Foo\Bar`), fully-qualified (`\Foo\Bar`),
95/// and relative (`namespace\Foo`) names.
96pub enum Name<'arena, 'src> {
97    /// Single unqualified identifier — no `ArenaVec` allocation.
98    /// `&'src str` instead of `Cow` since this is always a borrowed slice of the source.
99    Simple { value: &'src str, span: Span },
100    /// Multi-part or prefixed name (`Foo\Bar`, `\Foo`, `namespace\Foo`).
101    Complex {
102        parts: ArenaVec<'arena, &'src str>,
103        kind: NameKind,
104        span: Span,
105    },
106}
107
108impl<'arena, 'src> Name<'arena, 'src> {
109    #[inline]
110    pub fn span(&self) -> Span {
111        match self {
112            Self::Simple { span, .. } | Self::Complex { span, .. } => *span,
113        }
114    }
115
116    #[inline]
117    pub fn kind(&self) -> NameKind {
118        match self {
119            Self::Simple { .. } => NameKind::Unqualified,
120            Self::Complex { kind, .. } => *kind,
121        }
122    }
123
124    /// Joins all parts with `\` and prepends `\` if fully qualified.
125    /// Returns `Cow::Borrowed` for simple names (zero allocation).
126    #[inline]
127    pub fn to_string_repr(&self) -> Cow<'src, str> {
128        match self {
129            Self::Simple { value, .. } => Cow::Borrowed(value),
130            Self::Complex { parts, kind, .. } => {
131                let joined = parts.join("\\");
132                if *kind == NameKind::FullyQualified {
133                    Cow::Owned(format!("\\{}", joined))
134                } else {
135                    Cow::Owned(joined)
136                }
137            }
138        }
139    }
140
141    /// Joins all parts with `\` without any leading backslash.
142    /// Returns `Cow::Borrowed` for simple names (zero allocation).
143    #[inline]
144    pub fn join_parts(&self) -> Cow<'src, str> {
145        match self {
146            Self::Simple { value, .. } => Cow::Borrowed(value),
147            Self::Complex { parts, .. } => Cow::Owned(parts.join("\\")),
148        }
149    }
150
151    /// Returns the parts as a slice.
152    /// For `Simple`, returns a single-element slice of the value.
153    #[inline]
154    pub fn parts_slice(&self) -> &[&'src str] {
155        match self {
156            Self::Simple { value, .. } => std::slice::from_ref(value),
157            Self::Complex { parts, .. } => parts,
158        }
159    }
160}
161
162impl<'arena, 'src> std::fmt::Debug for Name<'arena, 'src> {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            Self::Simple { value, span } => f
166                .debug_struct("Name")
167                .field("parts", &std::slice::from_ref(value))
168                .field("kind", &NameKind::Unqualified)
169                .field("span", span)
170                .finish(),
171            Self::Complex { parts, kind, span } => f
172                .debug_struct("Name")
173                .field("parts", parts)
174                .field("kind", kind)
175                .field("span", span)
176                .finish(),
177        }
178    }
179}
180
181impl<'arena, 'src> serde::Serialize for Name<'arena, 'src> {
182    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
183        use serde::ser::SerializeStruct;
184        let mut st = s.serialize_struct("Name", 3)?;
185        match self {
186            Self::Simple { value, span } => {
187                st.serialize_field("parts", std::slice::from_ref(value))?;
188                st.serialize_field("kind", &NameKind::Unqualified)?;
189                st.serialize_field("span", span)?;
190            }
191            Self::Complex { parts, kind, span } => {
192                st.serialize_field("parts", parts)?;
193                st.serialize_field("kind", kind)?;
194                st.serialize_field("span", span)?;
195            }
196        }
197        st.end()
198    }
199}
200
201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
202pub enum NameKind {
203    Unqualified,
204    Qualified,
205    FullyQualified,
206    Relative,
207}
208
209/// PHP built-in type keyword — zero-cost alternative to `Name::Simple` for the
210/// 20 reserved type names. One byte instead of a `Cow<str>` + `Span` in the AST.
211#[repr(u8)]
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
213pub enum BuiltinType {
214    Int,
215    Integer,
216    Float,
217    Double,
218    String,
219    Bool,
220    Boolean,
221    Void,
222    Never,
223    Mixed,
224    Object,
225    Iterable,
226    Callable,
227    Array,
228    Self_,
229    Parent_,
230    Static,
231    Null,
232    True,
233    False,
234}
235
236impl BuiltinType {
237    /// Returns the canonical lowercase spelling used in PHP and in serialized output.
238    #[inline]
239    pub fn as_str(self) -> &'static str {
240        match self {
241            Self::Int => "int",
242            Self::Integer => "integer",
243            Self::Float => "float",
244            Self::Double => "double",
245            Self::String => "string",
246            Self::Bool => "bool",
247            Self::Boolean => "boolean",
248            Self::Void => "void",
249            Self::Never => "never",
250            Self::Mixed => "mixed",
251            Self::Object => "object",
252            Self::Iterable => "iterable",
253            Self::Callable => "callable",
254            Self::Array => "array",
255            Self::Self_ => "self",
256            Self::Parent_ => "parent",
257            Self::Static => "static",
258            Self::Null => "null",
259            Self::True => "true",
260            Self::False => "false",
261        }
262    }
263}
264
265#[derive(Debug, Serialize)]
266pub struct TypeHint<'arena, 'src> {
267    pub kind: TypeHintKind<'arena, 'src>,
268    pub span: Span,
269}
270
271/// A PHP type hint.
272///
273/// `Keyword` is the fast path for the 20 built-in type names (`int`, `string`,
274/// `bool`, `self`, `array`, etc.). It stores only a 1-byte discriminant and a
275/// `Span`, avoiding the `Cow<str>` that `Named(Name::Simple)` would require.
276///
277/// Serialises identically to `Named` so all existing snapshots remain unchanged.
278#[derive(Debug)]
279pub enum TypeHintKind<'arena, 'src> {
280    Named(Name<'arena, 'src>),
281    /// Built-in type keyword — serialises as `Named` for snapshot compatibility.
282    Keyword(BuiltinType, Span),
283    Nullable(&'arena TypeHint<'arena, 'src>),
284    Union(ArenaVec<'arena, TypeHint<'arena, 'src>>),
285    Intersection(ArenaVec<'arena, TypeHint<'arena, 'src>>),
286}
287
288impl<'arena, 'src> serde::Serialize for TypeHintKind<'arena, 'src> {
289    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
290        match self {
291            // Standard variants — match what #[derive(Serialize)] would produce.
292            Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
293            Self::Nullable(inner) => {
294                s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
295            }
296            Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
297            Self::Intersection(types) => {
298                s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
299            }
300            // Keyword — serialise as if it were Named(Name::Simple { value: kw.as_str(), span }).
301            // This preserves all existing snapshot output.
302            Self::Keyword(builtin, span) => {
303                struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
304                impl serde::Serialize for BuiltinNameRepr<'_> {
305                    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
306                        use serde::ser::SerializeStruct;
307                        let mut st = s.serialize_struct("Name", 3)?;
308                        st.serialize_field("parts", &[self.0.as_str()])?;
309                        st.serialize_field("kind", &NameKind::Unqualified)?;
310                        st.serialize_field("span", self.1)?;
311                        st.end()
312                    }
313                }
314                s.serialize_newtype_variant(
315                    "TypeHintKind",
316                    0,
317                    "Named",
318                    &BuiltinNameRepr(builtin, span),
319                )
320            }
321        }
322    }
323}
324
325// =============================================================================
326// Arguments
327// =============================================================================
328
329#[derive(Debug, Serialize)]
330pub struct Arg<'arena, 'src> {
331    pub name: Option<Cow<'src, str>>,
332    pub value: Expr<'arena, 'src>,
333    pub unpack: bool,
334    pub by_ref: bool,
335    pub span: Span,
336}
337
338// =============================================================================
339// Attributes
340// =============================================================================
341
342#[derive(Debug, Serialize)]
343pub struct Attribute<'arena, 'src> {
344    pub name: Name<'arena, 'src>,
345    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
346    pub span: Span,
347}
348
349// =============================================================================
350// Statements
351// =============================================================================
352
353#[derive(Debug, Serialize)]
354pub struct Stmt<'arena, 'src> {
355    pub kind: StmtKind<'arena, 'src>,
356    pub span: Span,
357}
358
359#[derive(Debug, Serialize)]
360pub enum StmtKind<'arena, 'src> {
361    /// Expression statement (e.g. `foo();`)
362    Expression(&'arena Expr<'arena, 'src>),
363
364    /// Echo statement: `echo expr1, expr2;`
365    Echo(ArenaVec<'arena, Expr<'arena, 'src>>),
366
367    /// Return statement: `return expr;`
368    Return(Option<&'arena Expr<'arena, 'src>>),
369
370    /// Block statement: `{ stmts }`
371    Block(ArenaVec<'arena, Stmt<'arena, 'src>>),
372
373    /// If statement
374    If(&'arena IfStmt<'arena, 'src>),
375
376    /// While loop
377    While(&'arena WhileStmt<'arena, 'src>),
378
379    /// For loop
380    For(&'arena ForStmt<'arena, 'src>),
381
382    /// Foreach loop
383    Foreach(&'arena ForeachStmt<'arena, 'src>),
384
385    /// Do-while loop
386    DoWhile(&'arena DoWhileStmt<'arena, 'src>),
387
388    /// Function declaration
389    Function(&'arena FunctionDecl<'arena, 'src>),
390
391    /// Break statement
392    Break(Option<&'arena Expr<'arena, 'src>>),
393
394    /// Continue statement
395    Continue(Option<&'arena Expr<'arena, 'src>>),
396
397    /// Switch statement
398    Switch(&'arena SwitchStmt<'arena, 'src>),
399
400    /// Goto statement
401    Goto(&'src str),
402
403    /// Label statement
404    Label(&'arena str),
405
406    /// Declare statement
407    Declare(&'arena DeclareStmt<'arena, 'src>),
408
409    /// Unset statement
410    Unset(ArenaVec<'arena, Expr<'arena, 'src>>),
411
412    /// Throw statement (also can be expression in PHP 8)
413    Throw(&'arena Expr<'arena, 'src>),
414
415    /// Try/catch/finally
416    TryCatch(&'arena TryCatchStmt<'arena, 'src>),
417
418    /// Global declaration
419    Global(ArenaVec<'arena, Expr<'arena, 'src>>),
420
421    /// Class declaration
422    Class(&'arena ClassDecl<'arena, 'src>),
423
424    /// Interface declaration
425    Interface(&'arena InterfaceDecl<'arena, 'src>),
426
427    /// Trait declaration
428    Trait(&'arena TraitDecl<'arena, 'src>),
429
430    /// Enum declaration
431    Enum(&'arena EnumDecl<'arena, 'src>),
432
433    /// Namespace declaration
434    Namespace(&'arena NamespaceDecl<'arena, 'src>),
435
436    /// Use declaration
437    Use(&'arena UseDecl<'arena, 'src>),
438
439    /// Top-level constant: `const FOO = expr;`
440    Const(ArenaVec<'arena, ConstItem<'arena, 'src>>),
441
442    /// Static variable declaration: `static $x = 1;`
443    StaticVar(ArenaVec<'arena, StaticVar<'arena, 'src>>),
444
445    /// __halt_compiler(); with remaining data
446    HaltCompiler(&'src str),
447
448    /// Nop (empty statement `;`)
449    Nop,
450
451    /// Inline HTML
452    InlineHtml(&'src str),
453
454    /// Error placeholder — parser always produces a tree
455    Error,
456}
457
458#[derive(Debug, Serialize)]
459pub struct IfStmt<'arena, 'src> {
460    pub condition: Expr<'arena, 'src>,
461    pub then_branch: &'arena Stmt<'arena, 'src>,
462    pub elseif_branches: ArenaVec<'arena, ElseIfBranch<'arena, 'src>>,
463    pub else_branch: Option<&'arena Stmt<'arena, 'src>>,
464}
465
466#[derive(Debug, Serialize)]
467pub struct ElseIfBranch<'arena, 'src> {
468    pub condition: Expr<'arena, 'src>,
469    pub body: Stmt<'arena, 'src>,
470    pub span: Span,
471}
472
473#[derive(Debug, Serialize)]
474pub struct WhileStmt<'arena, 'src> {
475    pub condition: Expr<'arena, 'src>,
476    pub body: &'arena Stmt<'arena, 'src>,
477}
478
479#[derive(Debug, Serialize)]
480pub struct ForStmt<'arena, 'src> {
481    pub init: ArenaVec<'arena, Expr<'arena, 'src>>,
482    pub condition: ArenaVec<'arena, Expr<'arena, 'src>>,
483    pub update: ArenaVec<'arena, Expr<'arena, 'src>>,
484    pub body: &'arena Stmt<'arena, 'src>,
485}
486
487#[derive(Debug, Serialize)]
488pub struct ForeachStmt<'arena, 'src> {
489    pub expr: Expr<'arena, 'src>,
490    pub key: Option<Expr<'arena, 'src>>,
491    pub value: Expr<'arena, 'src>,
492    pub body: &'arena Stmt<'arena, 'src>,
493}
494
495#[derive(Debug, Serialize)]
496pub struct DoWhileStmt<'arena, 'src> {
497    pub body: &'arena Stmt<'arena, 'src>,
498    pub condition: Expr<'arena, 'src>,
499}
500
501#[derive(Debug, Serialize)]
502pub struct FunctionDecl<'arena, 'src> {
503    pub name: &'src str,
504    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
505    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
506    pub return_type: Option<TypeHint<'arena, 'src>>,
507    pub by_ref: bool,
508    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
509}
510
511#[derive(Debug, Serialize)]
512pub struct Param<'arena, 'src> {
513    pub name: &'src str,
514    pub type_hint: Option<TypeHint<'arena, 'src>>,
515    pub default: Option<Expr<'arena, 'src>>,
516    pub by_ref: bool,
517    pub variadic: bool,
518    pub is_readonly: bool,
519    pub is_final: bool,
520    pub visibility: Option<Visibility>,
521    pub set_visibility: Option<Visibility>,
522    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
523    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
524    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
525    pub span: Span,
526}
527
528#[derive(Debug, Serialize)]
529pub struct SwitchStmt<'arena, 'src> {
530    pub expr: Expr<'arena, 'src>,
531    pub cases: ArenaVec<'arena, SwitchCase<'arena, 'src>>,
532}
533
534#[derive(Debug, Serialize)]
535pub struct SwitchCase<'arena, 'src> {
536    pub value: Option<Expr<'arena, 'src>>,
537    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
538    pub span: Span,
539}
540
541#[derive(Debug, Serialize)]
542pub struct TryCatchStmt<'arena, 'src> {
543    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
544    pub catches: ArenaVec<'arena, CatchClause<'arena, 'src>>,
545    pub finally: Option<ArenaVec<'arena, Stmt<'arena, 'src>>>,
546}
547
548#[derive(Debug, Serialize)]
549pub struct CatchClause<'arena, 'src> {
550    pub types: ArenaVec<'arena, Name<'arena, 'src>>,
551    pub var: Option<&'src str>,
552    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
553    pub span: Span,
554}
555
556// =============================================================================
557// OOP Declarations
558// =============================================================================
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
561pub enum Visibility {
562    Public,
563    Protected,
564    Private,
565}
566
567#[derive(Debug, Serialize)]
568pub struct ClassDecl<'arena, 'src> {
569    pub name: Option<&'src str>,
570    pub modifiers: ClassModifiers,
571    pub extends: Option<Name<'arena, 'src>>,
572    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
573    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
574    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
575}
576
577#[derive(Debug, Clone, Serialize, Default)]
578pub struct ClassModifiers {
579    pub is_abstract: bool,
580    pub is_final: bool,
581    pub is_readonly: bool,
582}
583
584#[derive(Debug, Serialize)]
585pub struct ClassMember<'arena, 'src> {
586    pub kind: ClassMemberKind<'arena, 'src>,
587    pub span: Span,
588}
589
590#[derive(Debug, Serialize)]
591pub enum ClassMemberKind<'arena, 'src> {
592    Property(PropertyDecl<'arena, 'src>),
593    Method(MethodDecl<'arena, 'src>),
594    ClassConst(ClassConstDecl<'arena, 'src>),
595    TraitUse(TraitUseDecl<'arena, 'src>),
596}
597
598#[derive(Debug, Serialize)]
599pub struct PropertyDecl<'arena, 'src> {
600    pub name: &'src str,
601    pub visibility: Option<Visibility>,
602    pub set_visibility: Option<Visibility>,
603    pub is_static: bool,
604    pub is_readonly: bool,
605    pub type_hint: Option<TypeHint<'arena, 'src>>,
606    pub default: Option<Expr<'arena, 'src>>,
607    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
608    #[serde(skip_serializing_if = "ArenaVec::is_empty")]
609    pub hooks: ArenaVec<'arena, PropertyHook<'arena, 'src>>,
610}
611
612#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
613pub enum PropertyHookKind {
614    Get,
615    Set,
616}
617
618#[derive(Debug, Serialize)]
619pub enum PropertyHookBody<'arena, 'src> {
620    Block(ArenaVec<'arena, Stmt<'arena, 'src>>),
621    Expression(Expr<'arena, 'src>),
622    Abstract,
623}
624
625#[derive(Debug, Serialize)]
626pub struct PropertyHook<'arena, 'src> {
627    pub kind: PropertyHookKind,
628    pub body: PropertyHookBody<'arena, 'src>,
629    pub is_final: bool,
630    pub by_ref: bool,
631    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
632    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
633    pub span: Span,
634}
635
636#[derive(Debug, Serialize)]
637pub struct MethodDecl<'arena, 'src> {
638    pub name: &'src str,
639    pub visibility: Option<Visibility>,
640    pub is_static: bool,
641    pub is_abstract: bool,
642    pub is_final: bool,
643    pub by_ref: bool,
644    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
645    pub return_type: Option<TypeHint<'arena, 'src>>,
646    pub body: Option<ArenaVec<'arena, Stmt<'arena, 'src>>>,
647    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
648}
649
650#[derive(Debug, Serialize)]
651pub struct ClassConstDecl<'arena, 'src> {
652    pub name: &'src str,
653    pub visibility: Option<Visibility>,
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub type_hint: Option<&'arena TypeHint<'arena, 'src>>,
656    pub value: Expr<'arena, 'src>,
657    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
658}
659
660#[derive(Debug, Serialize)]
661pub struct TraitUseDecl<'arena, 'src> {
662    pub traits: ArenaVec<'arena, Name<'arena, 'src>>,
663    pub adaptations: ArenaVec<'arena, TraitAdaptation<'arena, 'src>>,
664}
665
666#[derive(Debug, Serialize)]
667pub struct TraitAdaptation<'arena, 'src> {
668    pub kind: TraitAdaptationKind<'arena, 'src>,
669    pub span: Span,
670}
671
672#[derive(Debug, Serialize)]
673pub enum TraitAdaptationKind<'arena, 'src> {
674    /// `A::foo insteadof B, C;`
675    Precedence {
676        trait_name: Name<'arena, 'src>,
677        method: &'src str,
678        insteadof: ArenaVec<'arena, Name<'arena, 'src>>,
679    },
680    /// `foo as bar;` or `A::foo as protected bar;` or `foo as protected;`
681    Alias {
682        trait_name: Option<Name<'arena, 'src>>,
683        method: Cow<'src, str>,
684        new_modifier: Option<Visibility>,
685        new_name: Option<&'src str>,
686    },
687}
688
689#[derive(Debug, Serialize)]
690pub struct InterfaceDecl<'arena, 'src> {
691    pub name: &'src str,
692    pub extends: ArenaVec<'arena, Name<'arena, 'src>>,
693    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
694    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
695}
696
697#[derive(Debug, Serialize)]
698pub struct TraitDecl<'arena, 'src> {
699    pub name: &'src str,
700    pub members: ArenaVec<'arena, ClassMember<'arena, 'src>>,
701    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
702}
703
704#[derive(Debug, Serialize)]
705pub struct EnumDecl<'arena, 'src> {
706    pub name: &'src str,
707    pub scalar_type: Option<Name<'arena, 'src>>,
708    pub implements: ArenaVec<'arena, Name<'arena, 'src>>,
709    pub members: ArenaVec<'arena, EnumMember<'arena, 'src>>,
710    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
711}
712
713#[derive(Debug, Serialize)]
714pub struct EnumMember<'arena, 'src> {
715    pub kind: EnumMemberKind<'arena, 'src>,
716    pub span: Span,
717}
718
719#[derive(Debug, Serialize)]
720pub enum EnumMemberKind<'arena, 'src> {
721    Case(EnumCase<'arena, 'src>),
722    Method(MethodDecl<'arena, 'src>),
723    ClassConst(ClassConstDecl<'arena, 'src>),
724    TraitUse(TraitUseDecl<'arena, 'src>),
725}
726
727#[derive(Debug, Serialize)]
728pub struct EnumCase<'arena, 'src> {
729    pub name: &'src str,
730    pub value: Option<Expr<'arena, 'src>>,
731    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
732}
733
734// =============================================================================
735// Namespace & Use
736// =============================================================================
737
738#[derive(Debug, Serialize)]
739pub struct NamespaceDecl<'arena, 'src> {
740    pub name: Option<Name<'arena, 'src>>,
741    pub body: NamespaceBody<'arena, 'src>,
742}
743
744#[derive(Debug, Serialize)]
745pub enum NamespaceBody<'arena, 'src> {
746    Braced(ArenaVec<'arena, Stmt<'arena, 'src>>),
747    Simple,
748}
749
750#[derive(Debug, Serialize)]
751pub struct DeclareStmt<'arena, 'src> {
752    pub directives: ArenaVec<'arena, (&'src str, Expr<'arena, 'src>)>,
753    pub body: Option<&'arena Stmt<'arena, 'src>>,
754}
755
756#[derive(Debug, Serialize)]
757pub struct UseDecl<'arena, 'src> {
758    pub kind: UseKind,
759    pub uses: ArenaVec<'arena, UseItem<'arena, 'src>>,
760}
761
762#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
763pub enum UseKind {
764    Normal,
765    Function,
766    Const,
767}
768
769#[derive(Debug, Serialize)]
770pub struct UseItem<'arena, 'src> {
771    pub name: Name<'arena, 'src>,
772    pub alias: Option<&'src str>,
773    #[serde(skip_serializing_if = "Option::is_none")]
774    pub kind: Option<UseKind>,
775    pub span: Span,
776}
777
778#[derive(Debug, Serialize)]
779pub struct ConstItem<'arena, 'src> {
780    pub name: &'src str,
781    pub value: Expr<'arena, 'src>,
782    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
783    pub span: Span,
784}
785
786#[derive(Debug, Serialize)]
787pub struct StaticVar<'arena, 'src> {
788    pub name: &'src str,
789    pub default: Option<Expr<'arena, 'src>>,
790    pub span: Span,
791}
792
793// =============================================================================
794// Expressions
795// =============================================================================
796
797#[derive(Debug, Serialize)]
798pub struct Expr<'arena, 'src> {
799    pub kind: ExprKind<'arena, 'src>,
800    pub span: Span,
801}
802
803#[derive(Debug, Serialize)]
804pub enum ExprKind<'arena, 'src> {
805    /// Integer literal
806    Int(i64),
807
808    /// Float literal
809    Float(f64),
810
811    /// String literal
812    String(&'arena str),
813
814    /// Interpolated string: `"Hello $name, you are {$age} years old"`
815    InterpolatedString(ArenaVec<'arena, StringPart<'arena, 'src>>),
816
817    /// Heredoc: `<<<EOT ... EOT`
818    Heredoc {
819        label: &'src str,
820        parts: ArenaVec<'arena, StringPart<'arena, 'src>>,
821    },
822
823    /// Nowdoc: `<<<'EOT' ... EOT`
824    Nowdoc {
825        label: &'src str,
826        value: &'arena str,
827    },
828
829    /// Shell execution: `` `command $var` ``
830    ShellExec(ArenaVec<'arena, StringPart<'arena, 'src>>),
831
832    /// Boolean literal
833    Bool(bool),
834
835    /// Null literal
836    Null,
837
838    /// Variable: `$name`
839    Variable(Cow<'src, str>),
840
841    /// Variable variable: `$$var`, `$$$var`, `${expr}`
842    VariableVariable(&'arena Expr<'arena, 'src>),
843
844    /// Identifier (bare name, e.g. function name in a call)
845    Identifier(&'arena str),
846
847    /// Assignment: `$x = expr` or `$x += expr`
848    Assign(AssignExpr<'arena, 'src>),
849
850    /// Binary operation: `expr op expr`
851    Binary(BinaryExpr<'arena, 'src>),
852
853    /// Unary prefix: `-expr`, `!expr`, `~expr`, `++$x`, `--$x`
854    UnaryPrefix(UnaryPrefixExpr<'arena, 'src>),
855
856    /// Unary postfix: `$x++`, `$x--`
857    UnaryPostfix(UnaryPostfixExpr<'arena, 'src>),
858
859    /// Ternary: `cond ? then : else` or short `cond ?: else`
860    Ternary(TernaryExpr<'arena, 'src>),
861
862    /// Null coalescing: `expr ?? fallback`
863    NullCoalesce(NullCoalesceExpr<'arena, 'src>),
864
865    /// Function call: `name(args)`
866    FunctionCall(FunctionCallExpr<'arena, 'src>),
867
868    /// Array literal: `[1, 2, 3]` or `['a' => 1]`
869    Array(ArenaVec<'arena, ArrayElement<'arena, 'src>>),
870
871    /// Array access: `$arr[index]`
872    ArrayAccess(ArrayAccessExpr<'arena, 'src>),
873
874    /// Print expression: `print expr`
875    Print(&'arena Expr<'arena, 'src>),
876
877    /// Parenthesized expression: `(expr)`
878    Parenthesized(&'arena Expr<'arena, 'src>),
879
880    /// Cast expression: `(int)$x`, `(string)$x`, etc.
881    Cast(CastKind, &'arena Expr<'arena, 'src>),
882
883    /// Error suppression: `@expr`
884    ErrorSuppress(&'arena Expr<'arena, 'src>),
885
886    /// Isset: `isset($a, $b)`
887    Isset(ArenaVec<'arena, Expr<'arena, 'src>>),
888
889    /// Empty: `empty($a)`
890    Empty(&'arena Expr<'arena, 'src>),
891
892    /// Include/require: `include 'file.php'`
893    Include(IncludeKind, &'arena Expr<'arena, 'src>),
894
895    /// Eval: `eval('code')`
896    Eval(&'arena Expr<'arena, 'src>),
897
898    /// Exit/die: `exit`, `exit(1)`, `die('msg')`
899    Exit(Option<&'arena Expr<'arena, 'src>>),
900
901    /// Magic constant: `__LINE__`, `__FILE__`, etc.
902    MagicConst(MagicConstKind),
903
904    /// Clone: `clone $obj`
905    Clone(&'arena Expr<'arena, 'src>),
906
907    /// Clone with property overrides: `clone($obj, ['prop' => $val])` — PHP 8.5+
908    CloneWith(&'arena Expr<'arena, 'src>, &'arena Expr<'arena, 'src>),
909
910    /// New: `new Class(args)`
911    New(NewExpr<'arena, 'src>),
912
913    /// Property access: `$obj->prop`
914    PropertyAccess(PropertyAccessExpr<'arena, 'src>),
915
916    /// Nullsafe property access: `$obj?->prop`
917    NullsafePropertyAccess(PropertyAccessExpr<'arena, 'src>),
918
919    /// Method call: `$obj->method(args)`
920    MethodCall(&'arena MethodCallExpr<'arena, 'src>),
921
922    /// Nullsafe method call: `$obj?->method(args)`
923    NullsafeMethodCall(&'arena MethodCallExpr<'arena, 'src>),
924
925    /// Static property access: `Class::$prop`
926    StaticPropertyAccess(StaticAccessExpr<'arena, 'src>),
927
928    /// Static method call: `Class::method(args)`
929    StaticMethodCall(&'arena StaticMethodCallExpr<'arena, 'src>),
930
931    /// Class constant access: `Class::CONST`
932    ClassConstAccess(StaticAccessExpr<'arena, 'src>),
933
934    /// Dynamic class constant access: `Foo::{expr}`
935    ClassConstAccessDynamic {
936        class: &'arena Expr<'arena, 'src>,
937        member: &'arena Expr<'arena, 'src>,
938    },
939
940    /// Dynamic static property access: `A::$$b`, `A::${'b'}`
941    StaticPropertyAccessDynamic {
942        class: &'arena Expr<'arena, 'src>,
943        member: &'arena Expr<'arena, 'src>,
944    },
945
946    /// Closure: `function($x) use($y) { }`
947    Closure(&'arena ClosureExpr<'arena, 'src>),
948
949    /// Arrow function: `fn($x) => expr`
950    ArrowFunction(&'arena ArrowFunctionExpr<'arena, 'src>),
951
952    /// Match: `match(expr) { ... }`
953    Match(MatchExpr<'arena, 'src>),
954
955    /// Throw as expression (PHP 8)
956    ThrowExpr(&'arena Expr<'arena, 'src>),
957
958    /// Yield: `yield` / `yield $val` / `yield $key => $val`
959    Yield(YieldExpr<'arena, 'src>),
960
961    /// Anonymous class: `new class(args) extends Foo implements Bar { ... }`
962    AnonymousClass(&'arena ClassDecl<'arena, 'src>),
963
964    /// First-class callable: `strlen(...)`, `$obj->method(...)`, `Foo::bar(...)`
965    CallableCreate(CallableCreateExpr<'arena, 'src>),
966
967    /// Omitted element in destructuring: `[$a, , $c]` or `list($a, , $c)`
968    Omit,
969
970    /// Error placeholder
971    Error,
972}
973
974#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
975pub enum CastKind {
976    Int,
977    Float,
978    String,
979    Bool,
980    Array,
981    Object,
982    Unset,
983    Void,
984}
985
986#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
987pub enum IncludeKind {
988    Include,
989    IncludeOnce,
990    Require,
991    RequireOnce,
992}
993
994#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
995pub enum MagicConstKind {
996    Class,
997    Dir,
998    File,
999    Function,
1000    Line,
1001    Method,
1002    Namespace,
1003    Trait,
1004    Property,
1005}
1006
1007// --- Expression sub-types ---
1008
1009#[derive(Debug, Serialize)]
1010pub struct AssignExpr<'arena, 'src> {
1011    pub target: &'arena Expr<'arena, 'src>,
1012    pub op: AssignOp,
1013    pub value: &'arena Expr<'arena, 'src>,
1014    #[serde(skip_serializing_if = "is_false")]
1015    pub by_ref: bool,
1016}
1017
1018#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1019pub enum AssignOp {
1020    Assign,
1021    Plus,
1022    Minus,
1023    Mul,
1024    Div,
1025    Mod,
1026    Pow,
1027    Concat,
1028    BitwiseAnd,
1029    BitwiseOr,
1030    BitwiseXor,
1031    ShiftLeft,
1032    ShiftRight,
1033    Coalesce,
1034}
1035
1036#[derive(Debug, Serialize)]
1037pub struct BinaryExpr<'arena, 'src> {
1038    pub left: &'arena Expr<'arena, 'src>,
1039    pub op: BinaryOp,
1040    pub right: &'arena Expr<'arena, 'src>,
1041}
1042
1043#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1044pub enum BinaryOp {
1045    Add,
1046    Sub,
1047    Mul,
1048    Div,
1049    Mod,
1050    Pow,
1051    Concat,
1052    Equal,
1053    NotEqual,
1054    Identical,
1055    NotIdentical,
1056    Less,
1057    Greater,
1058    LessOrEqual,
1059    GreaterOrEqual,
1060    Spaceship,
1061    BooleanAnd,
1062    BooleanOr,
1063    BitwiseAnd,
1064    BitwiseOr,
1065    BitwiseXor,
1066    ShiftLeft,
1067    ShiftRight,
1068    LogicalAnd,
1069    LogicalOr,
1070    LogicalXor,
1071    Instanceof,
1072    Pipe,
1073}
1074
1075#[derive(Debug, Serialize)]
1076pub struct UnaryPrefixExpr<'arena, 'src> {
1077    pub op: UnaryPrefixOp,
1078    pub operand: &'arena Expr<'arena, 'src>,
1079}
1080
1081#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1082pub enum UnaryPrefixOp {
1083    Negate,
1084    Plus,
1085    BooleanNot,
1086    BitwiseNot,
1087    PreIncrement,
1088    PreDecrement,
1089}
1090
1091#[derive(Debug, Serialize)]
1092pub struct UnaryPostfixExpr<'arena, 'src> {
1093    pub operand: &'arena Expr<'arena, 'src>,
1094    pub op: UnaryPostfixOp,
1095}
1096
1097#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1098pub enum UnaryPostfixOp {
1099    PostIncrement,
1100    PostDecrement,
1101}
1102
1103#[derive(Debug, Serialize)]
1104pub struct TernaryExpr<'arena, 'src> {
1105    pub condition: &'arena Expr<'arena, 'src>,
1106    /// None for short ternary `$x ?: $y`
1107    pub then_expr: Option<&'arena Expr<'arena, 'src>>,
1108    pub else_expr: &'arena Expr<'arena, 'src>,
1109}
1110
1111#[derive(Debug, Serialize)]
1112pub struct NullCoalesceExpr<'arena, 'src> {
1113    pub left: &'arena Expr<'arena, 'src>,
1114    pub right: &'arena Expr<'arena, 'src>,
1115}
1116
1117#[derive(Debug, Serialize)]
1118pub struct FunctionCallExpr<'arena, 'src> {
1119    pub name: &'arena Expr<'arena, 'src>,
1120    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1121}
1122
1123#[derive(Debug, Serialize)]
1124pub struct ArrayElement<'arena, 'src> {
1125    pub key: Option<Expr<'arena, 'src>>,
1126    pub value: Expr<'arena, 'src>,
1127    pub unpack: bool,
1128    #[serde(skip_serializing_if = "is_false")]
1129    pub by_ref: bool,
1130    pub span: Span,
1131}
1132
1133#[derive(Debug, Serialize)]
1134pub struct ArrayAccessExpr<'arena, 'src> {
1135    pub array: &'arena Expr<'arena, 'src>,
1136    pub index: Option<&'arena Expr<'arena, 'src>>,
1137}
1138
1139// --- OOP Expression sub-types ---
1140
1141#[derive(Debug, Serialize)]
1142pub struct NewExpr<'arena, 'src> {
1143    pub class: &'arena Expr<'arena, 'src>,
1144    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1145}
1146
1147#[derive(Debug, Serialize)]
1148pub struct PropertyAccessExpr<'arena, 'src> {
1149    pub object: &'arena Expr<'arena, 'src>,
1150    pub property: &'arena Expr<'arena, 'src>,
1151}
1152
1153#[derive(Debug, Serialize)]
1154pub struct MethodCallExpr<'arena, 'src> {
1155    pub object: &'arena Expr<'arena, 'src>,
1156    pub method: &'arena Expr<'arena, 'src>,
1157    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1158}
1159
1160#[derive(Debug, Serialize)]
1161pub struct StaticAccessExpr<'arena, 'src> {
1162    pub class: &'arena Expr<'arena, 'src>,
1163    pub member: Cow<'src, str>,
1164}
1165
1166#[derive(Debug, Serialize)]
1167pub struct StaticMethodCallExpr<'arena, 'src> {
1168    pub class: &'arena Expr<'arena, 'src>,
1169    pub method: Cow<'src, str>,
1170    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1171}
1172
1173#[derive(Debug, Serialize)]
1174pub struct ClosureExpr<'arena, 'src> {
1175    pub is_static: bool,
1176    pub by_ref: bool,
1177    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
1178    pub use_vars: ArenaVec<'arena, ClosureUseVar<'src>>,
1179    pub return_type: Option<TypeHint<'arena, 'src>>,
1180    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
1181    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
1182}
1183
1184#[derive(Debug, Clone, Serialize)]
1185pub struct ClosureUseVar<'src> {
1186    pub name: &'src str,
1187    pub by_ref: bool,
1188    pub span: Span,
1189}
1190
1191#[derive(Debug, Serialize)]
1192pub struct ArrowFunctionExpr<'arena, 'src> {
1193    pub is_static: bool,
1194    pub by_ref: bool,
1195    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
1196    pub return_type: Option<TypeHint<'arena, 'src>>,
1197    pub body: &'arena Expr<'arena, 'src>,
1198    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
1199}
1200
1201#[derive(Debug, Serialize)]
1202pub struct MatchExpr<'arena, 'src> {
1203    pub subject: &'arena Expr<'arena, 'src>,
1204    pub arms: ArenaVec<'arena, MatchArm<'arena, 'src>>,
1205}
1206
1207#[derive(Debug, Serialize)]
1208pub struct MatchArm<'arena, 'src> {
1209    /// None for `default`
1210    pub conditions: Option<ArenaVec<'arena, Expr<'arena, 'src>>>,
1211    pub body: Expr<'arena, 'src>,
1212    pub span: Span,
1213}
1214
1215#[derive(Debug, Serialize)]
1216pub struct YieldExpr<'arena, 'src> {
1217    pub key: Option<&'arena Expr<'arena, 'src>>,
1218    pub value: Option<&'arena Expr<'arena, 'src>>,
1219    /// `true` for `yield from expr` (generator delegation), `false` for plain `yield`
1220    pub is_from: bool,
1221}
1222
1223// --- First-class callable ---
1224
1225#[derive(Debug, Serialize)]
1226pub struct CallableCreateExpr<'arena, 'src> {
1227    pub kind: CallableCreateKind<'arena, 'src>,
1228}
1229
1230#[derive(Debug, Serialize)]
1231pub enum CallableCreateKind<'arena, 'src> {
1232    /// `foo(...)`, `$var(...)`, `\Ns\func(...)`
1233    Function(&'arena Expr<'arena, 'src>),
1234    /// `$obj->method(...)`
1235    Method {
1236        object: &'arena Expr<'arena, 'src>,
1237        method: &'arena Expr<'arena, 'src>,
1238    },
1239    /// `$obj?->method(...)`
1240    NullsafeMethod {
1241        object: &'arena Expr<'arena, 'src>,
1242        method: &'arena Expr<'arena, 'src>,
1243    },
1244    /// `Foo::bar(...)`
1245    StaticMethod {
1246        class: &'arena Expr<'arena, 'src>,
1247        method: Cow<'src, str>,
1248    },
1249}
1250
1251// --- String interpolation ---
1252
1253#[derive(Debug, Serialize)]
1254pub enum StringPart<'arena, 'src> {
1255    Literal(&'arena str),
1256    Expr(Expr<'arena, 'src>),
1257}