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