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