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