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