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 span: Span,
776}
777
778#[derive(Debug, Serialize)]
779pub struct StaticVar<'arena, 'src> {
780    pub name: &'src str,
781    pub default: Option<Expr<'arena, 'src>>,
782    pub span: Span,
783}
784
785// =============================================================================
786// Expressions
787// =============================================================================
788
789#[derive(Debug, Serialize)]
790pub struct Expr<'arena, 'src> {
791    pub kind: ExprKind<'arena, 'src>,
792    pub span: Span,
793}
794
795#[derive(Debug, Serialize)]
796pub enum ExprKind<'arena, 'src> {
797    /// Integer literal
798    Int(i64),
799
800    /// Float literal
801    Float(f64),
802
803    /// String literal
804    String(Cow<'src, str>),
805
806    /// Interpolated string: `"Hello $name, you are {$age} years old"`
807    InterpolatedString(ArenaVec<'arena, StringPart<'arena, 'src>>),
808
809    /// Heredoc: `<<<EOT ... EOT`
810    Heredoc {
811        label: &'src str,
812        parts: ArenaVec<'arena, StringPart<'arena, 'src>>,
813    },
814
815    /// Nowdoc: `<<<'EOT' ... EOT`
816    Nowdoc {
817        label: &'src str,
818        value: Cow<'src, str>,
819    },
820
821    /// Shell execution: `` `command $var` ``
822    ShellExec(ArenaVec<'arena, StringPart<'arena, 'src>>),
823
824    /// Boolean literal
825    Bool(bool),
826
827    /// Null literal
828    Null,
829
830    /// Variable: `$name`
831    Variable(Cow<'src, str>),
832
833    /// Variable variable: `$$var`, `$$$var`, `${expr}`
834    VariableVariable(&'arena Expr<'arena, 'src>),
835
836    /// Identifier (bare name, e.g. function name in a call)
837    Identifier(Cow<'src, str>),
838
839    /// Assignment: `$x = expr` or `$x += expr`
840    Assign(AssignExpr<'arena, 'src>),
841
842    /// Binary operation: `expr op expr`
843    Binary(BinaryExpr<'arena, 'src>),
844
845    /// Unary prefix: `-expr`, `!expr`, `~expr`, `++$x`, `--$x`
846    UnaryPrefix(UnaryPrefixExpr<'arena, 'src>),
847
848    /// Unary postfix: `$x++`, `$x--`
849    UnaryPostfix(UnaryPostfixExpr<'arena, 'src>),
850
851    /// Ternary: `cond ? then : else` or short `cond ?: else`
852    Ternary(TernaryExpr<'arena, 'src>),
853
854    /// Null coalescing: `expr ?? fallback`
855    NullCoalesce(NullCoalesceExpr<'arena, 'src>),
856
857    /// Function call: `name(args)`
858    FunctionCall(FunctionCallExpr<'arena, 'src>),
859
860    /// Array literal: `[1, 2, 3]` or `['a' => 1]`
861    Array(ArenaVec<'arena, ArrayElement<'arena, 'src>>),
862
863    /// Array access: `$arr[index]`
864    ArrayAccess(ArrayAccessExpr<'arena, 'src>),
865
866    /// Print expression: `print expr`
867    Print(&'arena Expr<'arena, 'src>),
868
869    /// Parenthesized expression: `(expr)`
870    Parenthesized(&'arena Expr<'arena, 'src>),
871
872    /// Cast expression: `(int)$x`, `(string)$x`, etc.
873    Cast(CastKind, &'arena Expr<'arena, 'src>),
874
875    /// Error suppression: `@expr`
876    ErrorSuppress(&'arena Expr<'arena, 'src>),
877
878    /// Isset: `isset($a, $b)`
879    Isset(ArenaVec<'arena, Expr<'arena, 'src>>),
880
881    /// Empty: `empty($a)`
882    Empty(&'arena Expr<'arena, 'src>),
883
884    /// Include/require: `include 'file.php'`
885    Include(IncludeKind, &'arena Expr<'arena, 'src>),
886
887    /// Eval: `eval('code')`
888    Eval(&'arena Expr<'arena, 'src>),
889
890    /// Exit/die: `exit`, `exit(1)`, `die('msg')`
891    Exit(Option<&'arena Expr<'arena, 'src>>),
892
893    /// Magic constant: `__LINE__`, `__FILE__`, etc.
894    MagicConst(MagicConstKind),
895
896    /// Clone: `clone $obj`
897    Clone(&'arena Expr<'arena, 'src>),
898
899    /// New: `new Class(args)`
900    New(NewExpr<'arena, 'src>),
901
902    /// Property access: `$obj->prop`
903    PropertyAccess(PropertyAccessExpr<'arena, 'src>),
904
905    /// Nullsafe property access: `$obj?->prop`
906    NullsafePropertyAccess(PropertyAccessExpr<'arena, 'src>),
907
908    /// Method call: `$obj->method(args)`
909    MethodCall(&'arena MethodCallExpr<'arena, 'src>),
910
911    /// Nullsafe method call: `$obj?->method(args)`
912    NullsafeMethodCall(&'arena MethodCallExpr<'arena, 'src>),
913
914    /// Static property access: `Class::$prop`
915    StaticPropertyAccess(StaticAccessExpr<'arena, 'src>),
916
917    /// Static method call: `Class::method(args)`
918    StaticMethodCall(&'arena StaticMethodCallExpr<'arena, 'src>),
919
920    /// Class constant access: `Class::CONST`
921    ClassConstAccess(StaticAccessExpr<'arena, 'src>),
922
923    /// Dynamic class constant access: `Foo::{expr}`
924    ClassConstAccessDynamic {
925        class: &'arena Expr<'arena, 'src>,
926        member: &'arena Expr<'arena, 'src>,
927    },
928
929    /// Dynamic static property access: `A::$$b`, `A::${'b'}`
930    StaticPropertyAccessDynamic {
931        class: &'arena Expr<'arena, 'src>,
932        member: &'arena Expr<'arena, 'src>,
933    },
934
935    /// Closure: `function($x) use($y) { }`
936    Closure(&'arena ClosureExpr<'arena, 'src>),
937
938    /// Arrow function: `fn($x) => expr`
939    ArrowFunction(&'arena ArrowFunctionExpr<'arena, 'src>),
940
941    /// Match: `match(expr) { ... }`
942    Match(MatchExpr<'arena, 'src>),
943
944    /// Throw as expression (PHP 8)
945    ThrowExpr(&'arena Expr<'arena, 'src>),
946
947    /// Yield: `yield` / `yield $val` / `yield $key => $val`
948    Yield(YieldExpr<'arena, 'src>),
949
950    /// Anonymous class: `new class(args) extends Foo implements Bar { ... }`
951    AnonymousClass(&'arena ClassDecl<'arena, 'src>),
952
953    /// First-class callable: `strlen(...)`, `$obj->method(...)`, `Foo::bar(...)`
954    CallableCreate(CallableCreateExpr<'arena, 'src>),
955
956    /// Error placeholder
957    Error,
958}
959
960#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
961pub enum CastKind {
962    Int,
963    Float,
964    String,
965    Bool,
966    Array,
967    Object,
968    Unset,
969    Void,
970}
971
972#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
973pub enum IncludeKind {
974    Include,
975    IncludeOnce,
976    Require,
977    RequireOnce,
978}
979
980#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
981pub enum MagicConstKind {
982    Class,
983    Dir,
984    File,
985    Function,
986    Line,
987    Method,
988    Namespace,
989    Trait,
990    Property,
991}
992
993// --- Expression sub-types ---
994
995#[derive(Debug, Serialize)]
996pub struct AssignExpr<'arena, 'src> {
997    pub target: &'arena Expr<'arena, 'src>,
998    pub op: AssignOp,
999    pub value: &'arena Expr<'arena, 'src>,
1000}
1001
1002#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1003pub enum AssignOp {
1004    Assign,
1005    Plus,
1006    Minus,
1007    Mul,
1008    Div,
1009    Mod,
1010    Pow,
1011    Concat,
1012    BitwiseAnd,
1013    BitwiseOr,
1014    BitwiseXor,
1015    ShiftLeft,
1016    ShiftRight,
1017    Coalesce,
1018}
1019
1020#[derive(Debug, Serialize)]
1021pub struct BinaryExpr<'arena, 'src> {
1022    pub left: &'arena Expr<'arena, 'src>,
1023    pub op: BinaryOp,
1024    pub right: &'arena Expr<'arena, 'src>,
1025}
1026
1027#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1028pub enum BinaryOp {
1029    Add,
1030    Sub,
1031    Mul,
1032    Div,
1033    Mod,
1034    Pow,
1035    Concat,
1036    Equal,
1037    NotEqual,
1038    Identical,
1039    NotIdentical,
1040    Less,
1041    Greater,
1042    LessOrEqual,
1043    GreaterOrEqual,
1044    Spaceship,
1045    BooleanAnd,
1046    BooleanOr,
1047    BitwiseAnd,
1048    BitwiseOr,
1049    BitwiseXor,
1050    ShiftLeft,
1051    ShiftRight,
1052    LogicalAnd,
1053    LogicalOr,
1054    LogicalXor,
1055    Instanceof,
1056    Pipe,
1057}
1058
1059#[derive(Debug, Serialize)]
1060pub struct UnaryPrefixExpr<'arena, 'src> {
1061    pub op: UnaryPrefixOp,
1062    pub operand: &'arena Expr<'arena, 'src>,
1063}
1064
1065#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1066pub enum UnaryPrefixOp {
1067    Negate,
1068    Plus,
1069    BooleanNot,
1070    BitwiseNot,
1071    PreIncrement,
1072    PreDecrement,
1073}
1074
1075#[derive(Debug, Serialize)]
1076pub struct UnaryPostfixExpr<'arena, 'src> {
1077    pub operand: &'arena Expr<'arena, 'src>,
1078    pub op: UnaryPostfixOp,
1079}
1080
1081#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1082pub enum UnaryPostfixOp {
1083    PostIncrement,
1084    PostDecrement,
1085}
1086
1087#[derive(Debug, Serialize)]
1088pub struct TernaryExpr<'arena, 'src> {
1089    pub condition: &'arena Expr<'arena, 'src>,
1090    /// None for short ternary `$x ?: $y`
1091    pub then_expr: Option<&'arena Expr<'arena, 'src>>,
1092    pub else_expr: &'arena Expr<'arena, 'src>,
1093}
1094
1095#[derive(Debug, Serialize)]
1096pub struct NullCoalesceExpr<'arena, 'src> {
1097    pub left: &'arena Expr<'arena, 'src>,
1098    pub right: &'arena Expr<'arena, 'src>,
1099}
1100
1101#[derive(Debug, Serialize)]
1102pub struct FunctionCallExpr<'arena, 'src> {
1103    pub name: &'arena Expr<'arena, 'src>,
1104    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1105}
1106
1107#[derive(Debug, Serialize)]
1108pub struct ArrayElement<'arena, 'src> {
1109    pub key: Option<Expr<'arena, 'src>>,
1110    pub value: Expr<'arena, 'src>,
1111    pub unpack: bool,
1112    pub span: Span,
1113}
1114
1115#[derive(Debug, Serialize)]
1116pub struct ArrayAccessExpr<'arena, 'src> {
1117    pub array: &'arena Expr<'arena, 'src>,
1118    pub index: Option<&'arena Expr<'arena, 'src>>,
1119}
1120
1121// --- OOP Expression sub-types ---
1122
1123#[derive(Debug, Serialize)]
1124pub struct NewExpr<'arena, 'src> {
1125    pub class: &'arena Expr<'arena, 'src>,
1126    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1127}
1128
1129#[derive(Debug, Serialize)]
1130pub struct PropertyAccessExpr<'arena, 'src> {
1131    pub object: &'arena Expr<'arena, 'src>,
1132    pub property: &'arena Expr<'arena, 'src>,
1133}
1134
1135#[derive(Debug, Serialize)]
1136pub struct MethodCallExpr<'arena, 'src> {
1137    pub object: &'arena Expr<'arena, 'src>,
1138    pub method: &'arena Expr<'arena, 'src>,
1139    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1140}
1141
1142#[derive(Debug, Serialize)]
1143pub struct StaticAccessExpr<'arena, 'src> {
1144    pub class: &'arena Expr<'arena, 'src>,
1145    pub member: Cow<'src, str>,
1146}
1147
1148#[derive(Debug, Serialize)]
1149pub struct StaticMethodCallExpr<'arena, 'src> {
1150    pub class: &'arena Expr<'arena, 'src>,
1151    pub method: Cow<'src, str>,
1152    pub args: ArenaVec<'arena, Arg<'arena, 'src>>,
1153}
1154
1155#[derive(Debug, Serialize)]
1156pub struct ClosureExpr<'arena, 'src> {
1157    pub is_static: bool,
1158    pub by_ref: bool,
1159    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
1160    pub use_vars: ArenaVec<'arena, ClosureUseVar<'src>>,
1161    pub return_type: Option<TypeHint<'arena, 'src>>,
1162    pub body: ArenaVec<'arena, Stmt<'arena, 'src>>,
1163    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
1164}
1165
1166#[derive(Debug, Clone, Serialize)]
1167pub struct ClosureUseVar<'src> {
1168    pub name: &'src str,
1169    pub by_ref: bool,
1170    pub span: Span,
1171}
1172
1173#[derive(Debug, Serialize)]
1174pub struct ArrowFunctionExpr<'arena, 'src> {
1175    pub is_static: bool,
1176    pub by_ref: bool,
1177    pub params: ArenaVec<'arena, Param<'arena, 'src>>,
1178    pub return_type: Option<TypeHint<'arena, 'src>>,
1179    pub body: &'arena Expr<'arena, 'src>,
1180    pub attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
1181}
1182
1183#[derive(Debug, Serialize)]
1184pub struct MatchExpr<'arena, 'src> {
1185    pub subject: &'arena Expr<'arena, 'src>,
1186    pub arms: ArenaVec<'arena, MatchArm<'arena, 'src>>,
1187}
1188
1189#[derive(Debug, Serialize)]
1190pub struct MatchArm<'arena, 'src> {
1191    /// None for `default`
1192    pub conditions: Option<ArenaVec<'arena, Expr<'arena, 'src>>>,
1193    pub body: Expr<'arena, 'src>,
1194    pub span: Span,
1195}
1196
1197#[derive(Debug, Serialize)]
1198pub struct YieldExpr<'arena, 'src> {
1199    pub key: Option<&'arena Expr<'arena, 'src>>,
1200    pub value: Option<&'arena Expr<'arena, 'src>>,
1201}
1202
1203// --- First-class callable ---
1204
1205#[derive(Debug, Serialize)]
1206pub struct CallableCreateExpr<'arena, 'src> {
1207    pub kind: CallableCreateKind<'arena, 'src>,
1208}
1209
1210#[derive(Debug, Serialize)]
1211pub enum CallableCreateKind<'arena, 'src> {
1212    /// `foo(...)`, `$var(...)`, `\Ns\func(...)`
1213    Function(&'arena Expr<'arena, 'src>),
1214    /// `$obj->method(...)`
1215    Method {
1216        object: &'arena Expr<'arena, 'src>,
1217        method: &'arena Expr<'arena, 'src>,
1218    },
1219    /// `$obj?->method(...)`
1220    NullsafeMethod {
1221        object: &'arena Expr<'arena, 'src>,
1222        method: &'arena Expr<'arena, 'src>,
1223    },
1224    /// `Foo::bar(...)`
1225    StaticMethod {
1226        class: &'arena Expr<'arena, 'src>,
1227        method: Cow<'src, str>,
1228    },
1229}
1230
1231// --- String interpolation ---
1232
1233#[derive(Debug, Serialize)]
1234pub enum StringPart<'arena, 'src> {
1235    Literal(Cow<'src, str>),
1236    Expr(Expr<'arena, 'src>),
1237}