Skip to main content

php_ast/owned/
mod.rs

1//! Owned (lifetime-free) versions of all AST types.
2//!
3//! Arena-allocated types (`Program<'arena, 'src>`, etc.) cannot be stored in a
4//! struct alongside the `bumpalo::Bump` arena that owns them — the borrow checker
5//! rejects the self-referential pattern. These owned mirrors solve that: they use
6//! `Box<str>` instead of `&str`, `Box<[T]>` instead of `ArenaVec<'arena, T>`, and
7//! `Box<T>` instead of `&'arena T`. The result is `'static` — no lifetime
8//! parameters, storeable in a `HashMap`, sendable across threads.
9//!
10//! Serialization is byte-for-byte identical to the arena types so existing JSON
11//! snapshots remain valid.
12//!
13//! # Entry points
14//!
15//! Call `php_rs_parser::parse()` — it returns a `php_rs_parser::ParseResult`
16//! whose `.program` field is an owned [`Program`]. To convert an
17//! arena-allocated [`Program`](crate::ast::Program) directly, use
18//! [`to_owned_program`].
19
20pub mod fold;
21pub mod visitor;
22
23pub use fold::{
24    fold_owned_arg, fold_owned_attribute, fold_owned_catch_clause, fold_owned_class_member,
25    fold_owned_closure_use_var, fold_owned_enum_member, fold_owned_expr, fold_owned_match_arm,
26    fold_owned_name, fold_owned_param, fold_owned_program, fold_owned_property_hook,
27    fold_owned_stmt, fold_owned_trait_adaptation, fold_owned_trait_use, fold_owned_type_hint,
28    FoldOwned,
29};
30pub use visitor::{
31    walk_owned_arg, walk_owned_attribute, walk_owned_catch_clause, walk_owned_class_member,
32    walk_owned_comments, walk_owned_enum_member, walk_owned_expr, walk_owned_match_arm,
33    walk_owned_name, walk_owned_param, walk_owned_program, walk_owned_property_hook,
34    walk_owned_stmt, walk_owned_trait_use, walk_owned_type_hint, OwnedScope, OwnedScopeVisitor,
35    OwnedScopeWalker, OwnedVisitor,
36};
37
38use std::fmt;
39
40use serde::Serialize;
41
42use crate::ast as arena_ast;
43use crate::ast::{
44    AssignOp, BinaryOp, BuiltinType, CastKind, ClassModifiers, CommentKind, IncludeKind,
45    MagicConstKind, NameKind, PropertyHookKind, UnaryPostfixOp, UnaryPrefixOp, UseKind, Visibility,
46};
47use crate::Span;
48
49// ---------------------------------------------------------------------------
50// Serde helpers
51// ---------------------------------------------------------------------------
52
53fn is_false(b: &bool) -> bool {
54    !*b
55}
56
57fn slice_is_empty<T>(v: &[T]) -> bool {
58    v.is_empty()
59}
60
61/// Owned equivalent of [`Ident`](crate::ast::Ident), with the same API and serialized form.
62#[derive(Clone, PartialEq, Eq, Hash, Serialize)]
63#[serde(transparent)]
64pub struct Ident(Option<Box<str>>);
65
66impl Ident {
67    /// Sentinel for "no identifier was parsed".
68    pub const ERROR: Self = Self(None);
69
70    /// Construct an identifier from a non-empty name.
71    pub fn name(s: impl Into<Box<str>>) -> Self {
72        let s = s.into();
73        debug_assert!(!s.is_empty(), "Ident::name() called with empty string");
74        Self(Some(s))
75    }
76
77    /// Returns `Some(s)` for a real identifier, `None` for the error state.
78    pub fn as_str(&self) -> Option<&str> {
79        self.0.as_deref()
80    }
81
82    /// Returns `true` if this identifier was synthesised during error recovery.
83    pub fn is_error(&self) -> bool {
84        self.0.is_none()
85    }
86
87    /// Returns the inner string, or `"<error>"` for the error state.
88    pub fn or_error(&self) -> &str {
89        self.0.as_deref().unwrap_or("<error>")
90    }
91}
92
93impl fmt::Debug for Ident {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match &self.0 {
96            Some(s) => f.debug_tuple("Ident").field(s).finish(),
97            None => f.write_str("Ident::ERROR"),
98        }
99    }
100}
101
102impl fmt::Display for Ident {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.write_str(self.or_error())
105    }
106}
107
108impl PartialEq<&str> for Ident {
109    fn eq(&self, other: &&str) -> bool {
110        self.as_str() == Some(*other)
111    }
112}
113
114// ---------------------------------------------------------------------------
115// Name
116//
117// `Name<'arena, 'src>` has three variants (Simple, Complex, Error) and a
118// custom Serialize that always emits `{"parts":[...],"kind":"...","span":{…}}`.
119// The owned version flattens them into a single struct and derives Serialize —
120// the JSON output is identical.
121// ---------------------------------------------------------------------------
122
123/// A possibly qualified PHP name.
124#[derive(Debug, Clone, Serialize)]
125pub struct Name {
126    /// Name segments split on `\`.
127    pub parts: Box<[Box<str>]>,
128    /// Qualification style.
129    pub kind: NameKind,
130    /// Source span.
131    pub span: Span,
132}
133
134// ---------------------------------------------------------------------------
135// Comment
136// ---------------------------------------------------------------------------
137
138/// A source comment.
139#[derive(Debug, Clone, Serialize)]
140pub struct Comment {
141    /// Comment syntax.
142    pub kind: CommentKind,
143    /// Raw comment text including delimiters.
144    pub text: Box<str>,
145    /// Source span.
146    pub span: Span,
147}
148
149// ---------------------------------------------------------------------------
150// TypeHint / TypeHintKind
151//
152// `TypeHintKind::Keyword` has a custom Serialize that emits the same JSON as
153// `Named`. We replicate that here.
154// ---------------------------------------------------------------------------
155
156/// A type declaration.
157#[derive(Debug, Clone, Serialize)]
158pub struct TypeHint {
159    /// Type shape.
160    pub kind: TypeHintKind,
161    /// Source span.
162    pub span: Span,
163}
164
165/// The shape of a type declaration.
166#[derive(Debug, Clone)]
167#[non_exhaustive]
168pub enum TypeHintKind {
169    /// A class or other user-defined type name.
170    Named(Name),
171    /// Serialises as `Named` — see [`TypeHintKind::Keyword`](crate::ast::TypeHintKind::Keyword).
172    Keyword(BuiltinType, Span),
173    /// `?T`.
174    Nullable(Box<TypeHint>),
175    /// `A|B`.
176    Union(Box<[TypeHint]>),
177    /// `A&B`.
178    Intersection(Box<[TypeHint]>),
179}
180
181impl Serialize for TypeHintKind {
182    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
183        match self {
184            Self::Named(name) => s.serialize_newtype_variant("TypeHintKind", 0, "Named", name),
185            Self::Nullable(inner) => {
186                s.serialize_newtype_variant("TypeHintKind", 2, "Nullable", inner)
187            }
188            Self::Union(types) => s.serialize_newtype_variant("TypeHintKind", 3, "Union", types),
189            Self::Intersection(types) => {
190                s.serialize_newtype_variant("TypeHintKind", 4, "Intersection", types)
191            }
192            Self::Keyword(builtin, span) => {
193                struct BuiltinNameRepr<'a>(&'a BuiltinType, &'a Span);
194                impl Serialize for BuiltinNameRepr<'_> {
195                    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
196                        use serde::ser::SerializeStruct;
197                        let mut st = s.serialize_struct("Name", 3)?;
198                        st.serialize_field("parts", &[self.0.as_str()])?;
199                        st.serialize_field("kind", &NameKind::Unqualified)?;
200                        st.serialize_field("span", self.1)?;
201                        st.end()
202                    }
203                }
204                s.serialize_newtype_variant(
205                    "TypeHintKind",
206                    0,
207                    "Named",
208                    &BuiltinNameRepr(builtin, span),
209                )
210            }
211        }
212    }
213}
214
215// ---------------------------------------------------------------------------
216// Arg / Attribute
217// ---------------------------------------------------------------------------
218
219/// A call argument.
220#[derive(Debug, Clone, Serialize)]
221pub struct Arg {
222    /// Name of a named argument.
223    pub name: Option<Name>,
224    /// `None` is a PHP 8.6 partial-application placeholder (`?`, or `...` when `unpack` is set).
225    pub value: Option<Expr>,
226    /// Whether the argument is spread with `...`.
227    pub unpack: bool,
228    /// Whether the argument is prefixed with `&`.
229    pub by_ref: bool,
230    /// Source span.
231    pub span: Span,
232}
233
234/// A `#[...]` attribute.
235#[derive(Debug, Clone, Serialize)]
236pub struct Attribute {
237    /// Attribute class name.
238    pub name: Name,
239    /// Arguments passed to the attribute.
240    pub args: Box<[Arg]>,
241    /// Source span.
242    pub span: Span,
243}
244
245// ---------------------------------------------------------------------------
246// Program
247// ---------------------------------------------------------------------------
248
249/// Root of a parsed file.
250#[derive(Debug, Clone, Serialize)]
251pub struct Program {
252    /// Top-level statements.
253    pub stmts: Box<[Stmt]>,
254    /// Source span.
255    pub span: Span,
256}
257
258// ---------------------------------------------------------------------------
259// Stmt / StmtKind
260// ---------------------------------------------------------------------------
261
262/// A statement.
263#[derive(Debug, Clone, Serialize)]
264pub struct Stmt {
265    /// Statement kind.
266    pub kind: StmtKind,
267    /// Source span.
268    pub span: Span,
269    /// Preceding doc comment.
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub doc_comment: Option<Comment>,
272}
273
274impl Stmt {
275    /// The leading `/** */` doc-block for this statement, regardless of where
276    /// it is stored.  See [`crate::ast::Stmt::leading_doc_comment`] for the
277    /// full contract.
278    pub fn leading_doc_comment(&self) -> Option<&Comment> {
279        if let Some(doc) = &self.doc_comment {
280            return Some(doc);
281        }
282        match &self.kind {
283            StmtKind::Function(f) => f.doc_comment.as_ref(),
284            StmtKind::Class(c) => c.doc_comment.as_ref(),
285            StmtKind::Interface(i) => i.doc_comment.as_ref(),
286            StmtKind::Trait(t) => t.doc_comment.as_ref(),
287            StmtKind::Enum(e) => e.doc_comment.as_ref(),
288            StmtKind::Const(items) => items.first().and_then(|i| i.doc_comment.as_ref()),
289            _ => None,
290        }
291    }
292}
293
294/// Owned mirror of [`crate::ast::Block`] — a brace-delimited statement block.
295#[derive(Debug, Clone, Serialize)]
296#[serde(transparent)]
297pub struct Block {
298    /// Statements in the block.
299    pub stmts: Box<[Stmt]>,
300    /// Source span.
301    #[serde(skip)]
302    pub span: Span,
303}
304
305/// The kind of a statement.
306#[derive(Debug, Clone, Serialize)]
307#[non_exhaustive]
308pub enum StmtKind {
309    /// An expression statement.
310    Expression(Box<Expr>),
311    /// `echo` with one or more expressions.
312    Echo(Box<[Expr]>),
313    /// `return` with optional value.
314    Return(Option<Box<Expr>>),
315    /// A nested block.
316    Block(Box<Block>),
317    /// An `if` statement.
318    If(Box<IfStmt>),
319    /// A `while` loop.
320    While(Box<WhileStmt>),
321    /// A `for` loop.
322    For(Box<ForStmt>),
323    /// A `foreach` loop.
324    Foreach(Box<ForeachStmt>),
325    /// A `do ... while` loop.
326    DoWhile(Box<DoWhileStmt>),
327    /// A function declaration.
328    Function(Box<FunctionDecl>),
329    /// `break` with optional level.
330    Break(Option<Box<Expr>>),
331    /// `continue` with optional level.
332    Continue(Option<Box<Expr>>),
333    /// A `switch` statement.
334    Switch(Box<SwitchStmt>),
335    /// `goto` with target label.
336    Goto(Ident),
337    /// A `label:` definition.
338    Label(Ident),
339    /// A `declare(...)` statement.
340    Declare(Box<DeclareStmt>),
341    /// `unset(...)` targets.
342    Unset(Box<[Expr]>),
343    /// A `throw` statement.
344    Throw(Box<Expr>),
345    /// A `try`/`catch`/`finally` statement.
346    TryCatch(Box<TryCatchStmt>),
347    /// `global` variables.
348    Global(Box<[Expr]>),
349    /// A class declaration.
350    Class(Box<ClassDecl>),
351    /// An interface declaration.
352    Interface(Box<InterfaceDecl>),
353    /// A trait declaration.
354    Trait(Box<TraitDecl>),
355    /// An enum declaration.
356    Enum(Box<EnumDecl>),
357    /// A namespace declaration.
358    Namespace(Box<NamespaceDecl>),
359    /// A `use` import.
360    Use(Box<UseDecl>),
361    /// A top-level `const` statement.
362    Const(Box<[ConstItem]>),
363    /// A `static` variable statement.
364    StaticVar(Box<[StaticVar]>),
365    /// `__halt_compiler()` with the trailing raw data.
366    HaltCompiler(Box<str>),
367    /// An empty statement (`;`).
368    Nop,
369    /// Text outside PHP tags.
370    InlineHtml(Box<str>),
371    /// Placeholder for a statement that failed to parse.
372    Error,
373}
374
375/// An `if` statement.
376#[derive(Debug, Clone, Serialize)]
377pub struct IfStmt {
378    /// The `if` condition.
379    pub condition: Expr,
380    /// Statement run when the condition holds.
381    pub then_branch: Box<Stmt>,
382    /// `elseif` branches in source order.
383    pub elseif_branches: Box<[ElseIfBranch]>,
384    /// The `else` statement, if any.
385    pub else_branch: Option<Box<Stmt>>,
386    /// The `else` keyword. Not serialized.
387    #[serde(skip)]
388    pub else_kw_span: Option<Span>,
389    /// Whether the `if: ... endif;` syntax is used.
390    #[serde(default, skip_serializing_if = "is_false")]
391    pub uses_alternative: bool,
392}
393
394/// An `elseif` branch.
395#[derive(Debug, Clone, Serialize)]
396pub struct ElseIfBranch {
397    /// The `elseif` condition.
398    pub condition: Expr,
399    /// Statement run when the condition holds.
400    pub body: Stmt,
401    /// Source span.
402    pub span: Span,
403}
404
405/// A `while` loop.
406#[derive(Debug, Clone, Serialize)]
407pub struct WhileStmt {
408    /// The loop condition.
409    pub condition: Expr,
410    /// The loop body.
411    pub body: Box<Stmt>,
412    /// Whether the `while: ... endwhile;` syntax is used.
413    #[serde(default, skip_serializing_if = "is_false")]
414    pub uses_alternative: bool,
415}
416
417/// A `for` loop.
418#[derive(Debug, Clone, Serialize)]
419pub struct ForStmt {
420    /// Initializer expressions.
421    pub init: Box<[Expr]>,
422    /// Condition expressions.
423    pub condition: Box<[Expr]>,
424    /// Update expressions.
425    pub update: Box<[Expr]>,
426    /// The loop body.
427    pub body: Box<Stmt>,
428    /// Whether the `for: ... endfor;` syntax is used.
429    #[serde(default, skip_serializing_if = "is_false")]
430    pub uses_alternative: bool,
431}
432
433/// A `foreach` loop.
434#[derive(Debug, Clone, Serialize)]
435pub struct ForeachStmt {
436    /// The iterated expression.
437    pub expr: Expr,
438    /// The key target, if any.
439    pub key: Option<Expr>,
440    /// The value target.
441    pub value: Expr,
442    /// The loop body.
443    pub body: Box<Stmt>,
444    /// Whether the `foreach: ... endforeach;` syntax is used.
445    #[serde(default, skip_serializing_if = "is_false")]
446    pub uses_alternative: bool,
447}
448
449/// A `do ... while` loop.
450#[derive(Debug, Clone, Serialize)]
451pub struct DoWhileStmt {
452    /// The loop body.
453    pub body: Box<Stmt>,
454    /// The loop condition.
455    pub condition: Expr,
456}
457
458/// The braced body of a `switch`.
459#[derive(Debug, Clone, Serialize)]
460pub struct SwitchBody {
461    /// The cases in source order.
462    pub cases: Box<[SwitchCase]>,
463    /// Source span.
464    #[serde(skip)]
465    pub span: Span,
466}
467
468/// A `switch` statement.
469#[derive(Debug, Clone, Serialize)]
470pub struct SwitchStmt {
471    /// The switched-on expression.
472    pub expr: Expr,
473    /// The braced case list.
474    #[serde(flatten)]
475    pub body: SwitchBody,
476    /// Whether the `switch: ... endswitch;` syntax is used.
477    #[serde(default, skip_serializing_if = "is_false")]
478    pub uses_alternative: bool,
479}
480
481/// A `case` or `default` arm.
482#[derive(Debug, Clone, Serialize)]
483pub struct SwitchCase {
484    /// Case value; `None` for `default`.
485    pub value: Option<Expr>,
486    /// Statements of the case.
487    pub body: Box<[Stmt]>,
488    /// Source span.
489    pub span: Span,
490}
491
492/// A `try` statement.
493#[derive(Debug, Clone, Serialize)]
494pub struct TryCatchStmt {
495    /// The `try` block.
496    pub body: Box<Block>,
497    /// The `catch` clauses.
498    pub catches: Box<[CatchClause]>,
499    /// The `finally` block, if any.
500    pub finally: Option<Box<Block>>,
501    /// The `finally` keyword. Not serialized.
502    #[serde(skip)]
503    pub finally_kw_span: Option<Span>,
504}
505
506/// A `catch` clause.
507#[derive(Debug, Clone, Serialize)]
508pub struct CatchClause {
509    /// Caught exception types (multi-catch allowed).
510    pub types: Box<[Name]>,
511    /// Bound variable name without `$`, if any.
512    pub var: Option<Box<str>>,
513    /// The `catch` block.
514    pub body: Box<Block>,
515    /// Source span.
516    pub span: Span,
517}
518
519/// A `namespace` declaration.
520#[derive(Debug, Clone, Serialize)]
521pub struct NamespaceDecl {
522    /// Namespace name; `None` for the global namespace.
523    pub name: Option<Name>,
524    /// Braced or simple form.
525    pub body: NamespaceBody,
526}
527
528/// Whether a namespace is braced or statement-terminated.
529#[derive(Debug, Clone, Serialize)]
530#[non_exhaustive]
531pub enum NamespaceBody {
532    /// `namespace X { ... }`.
533    Braced(Box<Block>),
534    /// `namespace X;`.
535    Simple,
536}
537
538/// A `declare(...)` statement.
539#[derive(Debug, Clone, Serialize)]
540pub struct DeclareStmt {
541    /// Directive name and value pairs.
542    pub directives: Box<[(Box<str>, Expr)]>,
543    /// Statement governed by the declare, if any.
544    pub body: Option<Box<Stmt>>,
545    /// Whether the `declare: ... enddeclare;` syntax is used.
546    #[serde(default, skip_serializing_if = "is_false")]
547    pub uses_alternative: bool,
548}
549
550/// A `use` import declaration.
551#[derive(Debug, Clone, Serialize)]
552pub struct UseDecl {
553    /// Import kind (class, function or const).
554    pub kind: UseKind,
555    /// The imported items.
556    pub uses: Box<[UseItem]>,
557}
558
559/// One imported name in a `use` declaration.
560#[derive(Debug, Clone, Serialize)]
561pub struct UseItem {
562    /// The imported name.
563    pub name: Name,
564    /// Local alias from `as`.
565    pub alias: Option<Box<str>>,
566    /// Per-item kind in a mixed group use.
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub kind: Option<UseKind>,
569    /// Source span.
570    pub span: Span,
571}
572
573/// One item of a top-level `const` statement.
574#[derive(Debug, Clone, Serialize)]
575pub struct ConstItem {
576    /// Constant name.
577    pub name: Ident,
578    /// Constant value.
579    pub value: Expr,
580    /// Attributes on the constant.
581    pub attributes: Box<[Attribute]>,
582    /// Source span.
583    pub span: Span,
584    /// Preceding doc comment.
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub doc_comment: Option<Comment>,
587}
588
589/// One variable of a `static` statement.
590#[derive(Debug, Clone, Serialize)]
591pub struct StaticVar {
592    /// Variable name without `$`.
593    pub name: Ident,
594    /// Initial value, if any.
595    pub default: Option<Expr>,
596    /// Source span.
597    pub span: Span,
598}
599
600// ---------------------------------------------------------------------------
601// Expr / ExprKind
602// ---------------------------------------------------------------------------
603
604/// An expression.
605#[derive(Debug, Clone, Serialize)]
606pub struct Expr {
607    /// Expression kind.
608    pub kind: ExprKind,
609    /// Source span.
610    pub span: Span,
611}
612
613/// The kind of an expression.
614#[derive(Debug, Clone, Serialize)]
615#[non_exhaustive]
616pub enum ExprKind {
617    /// Integer literal.
618    Int(i64),
619    /// Float literal.
620    Float(f64),
621    /// String literal without interpolation.
622    String(Box<str>),
623    /// Double-quoted string with interpolation.
624    InterpolatedString(Box<[StringPart]>),
625    /// Heredoc string.
626    Heredoc {
627        /// Heredoc delimiter.
628        label: Box<str>,
629        /// Body segments.
630        parts: Box<[StringPart]>,
631    },
632    /// Nowdoc string.
633    Nowdoc {
634        /// Nowdoc delimiter.
635        label: Box<str>,
636        /// Raw body text.
637        value: Box<str>,
638    },
639    /// Backtick shell command.
640    ShellExec(Box<[StringPart]>),
641    /// `true` or `false`.
642    Bool(bool),
643    /// `null`.
644    Null,
645    /// Variable name without `$`.
646    Variable(Box<str>),
647    /// `$$x` or `${expr}`.
648    VariableVariable(Box<Expr>),
649    /// Bare identifier such as a constant name.
650    Identifier(Box<str>),
651    /// Assignment.
652    Assign(AssignExpr),
653    /// Binary operation.
654    Binary(BinaryExpr),
655    /// Prefix unary operation.
656    UnaryPrefix(UnaryPrefixExpr),
657    /// Postfix unary operation.
658    UnaryPostfix(UnaryPostfixExpr),
659    /// Ternary conditional.
660    Ternary(TernaryExpr),
661    /// `??` expression.
662    NullCoalesce(NullCoalesceExpr),
663    /// Function call.
664    FunctionCall(FunctionCallExpr),
665    /// Array literal.
666    Array(Box<[ArrayElement]>),
667    /// Array offset access.
668    ArrayAccess(ArrayAccessExpr),
669    /// `print` expression.
670    Print(Box<Expr>),
671    /// Parenthesized expression.
672    Parenthesized(Box<Expr>),
673    /// Type cast.
674    Cast(CastKind, Box<Expr>),
675    /// `@` error suppression.
676    ErrorSuppress(Box<Expr>),
677    /// `isset(...)`.
678    Isset(Box<[Expr]>),
679    /// `empty(...)`.
680    Empty(Box<Expr>),
681    /// `include`/`require` variant.
682    Include(IncludeKind, Box<Expr>),
683    /// `eval(...)`.
684    Eval(Box<Expr>),
685    /// `exit`/`die` with optional argument.
686    Exit(Option<Box<Expr>>),
687    /// Magic constant such as `__LINE__`.
688    MagicConst(MagicConstKind),
689    /// `clone` expression.
690    Clone(Box<Expr>),
691    /// `clone` with property overrides.
692    CloneWith(Box<Expr>, Box<Expr>),
693    /// `new` expression.
694    New(NewExpr),
695    /// `->` property access.
696    PropertyAccess(PropertyAccessExpr),
697    /// `?->` property access.
698    NullsafePropertyAccess(PropertyAccessExpr),
699    /// `->` method call.
700    MethodCall(Box<MethodCallExpr>),
701    /// `?->` method call.
702    NullsafeMethodCall(Box<MethodCallExpr>),
703    /// `Class::$prop` access.
704    StaticPropertyAccess(StaticAccessExpr),
705    /// `Class::method()` call.
706    StaticMethodCall(Box<StaticMethodCallExpr>),
707    /// `Class::$method()` call.
708    StaticMethodCallDynamic(Box<StaticMethodCallDynamicExpr>),
709    /// `Class::CONST` access.
710    ClassConstAccess(StaticAccessExpr),
711    /// `Class::{expr}` constant access.
712    ClassConstAccessDynamic {
713        /// Class expression.
714        class: Box<Expr>,
715        /// Constant name expression.
716        member: Box<Expr>,
717    },
718    /// `Class::$$member` property access.
719    StaticPropertyAccessDynamic {
720        /// Class expression.
721        class: Box<Expr>,
722        /// Property name expression.
723        member: Box<Expr>,
724    },
725    /// Anonymous function.
726    Closure(Box<ClosureExpr>),
727    /// Arrow function.
728    ArrowFunction(Box<ArrowFunctionExpr>),
729    /// `match` expression.
730    Match(MatchExpr),
731    /// `throw` used as an expression.
732    Throw(Box<Expr>),
733    /// `yield` expression.
734    Yield(YieldExpr),
735    /// `new class { ... }`.
736    AnonymousClass(Box<ClassDecl>),
737    /// First-class callable syntax.
738    CallableCreate(CallableCreateExpr),
739    /// Skipped slot in a list destructuring pattern.
740    Omit,
741    /// Placeholder for an expression that failed to parse.
742    Error,
743}
744
745// ---------------------------------------------------------------------------
746// Expression chains
747//
748// The parser builds left-associative operator chains and postfix chains
749// (`$a . $b . …`, `$x->a()->b()…`, `$a[0][1]…`) in a loop rather than by
750// recursion, so their length is limited only by the input. Converting or
751// dropping such a chain one recursive call per link would overflow the stack,
752// so these helpers expose the operand each link continues from and the
753// conversions and `Drop` walk it iteratively. The `*_is_chain`,
754// `*_chain_operand*` and `*_link_kind` functions must agree on the variants and
755// on which field is the operand.
756// ---------------------------------------------------------------------------
757
758impl Drop for Expr {
759    #[inline(always)]
760    fn drop(&mut self) {
761        // Only the bit test is inlined into the drop glue of every expression.
762        if owned_is_chain(&self.kind) {
763            unlink_chain(&mut self.kind);
764        }
765    }
766}
767
768/// Detach the chain below the link `kind` so that it is dropped iteratively.
769#[inline(never)]
770fn unlink_chain(kind: &mut ExprKind) {
771    let Some(operand) = owned_chain_operand_mut(kind) else {
772        return;
773    };
774    // A single link drops fine recursively, and most chains are that short.
775    if !owned_is_chain(&operand.kind) {
776        return;
777    }
778    // Detach each operand's kind before the node holding it is dropped, so
779    // every drop below only sees a shallow `Error` in the chain position.
780    let mut kind = std::mem::replace(&mut operand.kind, ExprKind::Error);
781    while let Some(operand) = owned_chain_operand_mut(&mut kind) {
782        let next = std::mem::replace(&mut operand.kind, ExprKind::Error);
783        kind = next;
784    }
785}
786
787/// A stack of chain links that only allocates for chains longer than its
788/// inline capacity; almost every chain in real code fits.
789struct LinkStack<T: Copy> {
790    inline: [Option<T>; 8],
791    len: usize,
792    spilled: Vec<T>,
793}
794
795impl<T: Copy> LinkStack<T> {
796    fn new() -> Self {
797        Self {
798            inline: [None; 8],
799            len: 0,
800            spilled: Vec::new(),
801        }
802    }
803
804    fn push(&mut self, link: T) {
805        if self.len < self.inline.len() {
806            self.inline[self.len] = Some(link);
807            self.len += 1;
808        } else {
809            self.spilled.push(link);
810        }
811    }
812
813    fn pop(&mut self) -> Option<T> {
814        if let Some(link) = self.spilled.pop() {
815            return Some(link);
816        }
817        self.len = self.len.checked_sub(1)?;
818        self.inline[self.len]
819    }
820}
821
822/// Whether `k` is a chain node. Cheaper than [`arena_chain_operand`] (a bit
823/// test rather than a jump table), so leaves take the fast path.
824#[inline]
825fn arena_is_chain(k: &arena_ast::ExprKind<'_, '_>) -> bool {
826    matches!(
827        k,
828        arena_ast::ExprKind::Assign(_)
829            | arena_ast::ExprKind::Binary(_)
830            | arena_ast::ExprKind::UnaryPostfix(_)
831            | arena_ast::ExprKind::Ternary(_)
832            | arena_ast::ExprKind::NullCoalesce(_)
833            | arena_ast::ExprKind::FunctionCall(_)
834            | arena_ast::ExprKind::ArrayAccess(_)
835            | arena_ast::ExprKind::PropertyAccess(_)
836            | arena_ast::ExprKind::NullsafePropertyAccess(_)
837            | arena_ast::ExprKind::MethodCall(_)
838            | arena_ast::ExprKind::NullsafeMethodCall(_)
839            | arena_ast::ExprKind::StaticPropertyAccess(_)
840            | arena_ast::ExprKind::ClassConstAccess(_)
841            | arena_ast::ExprKind::StaticMethodCall(_)
842            | arena_ast::ExprKind::StaticMethodCallDynamic(_)
843            | arena_ast::ExprKind::ClassConstAccessDynamic { .. }
844            | arena_ast::ExprKind::StaticPropertyAccessDynamic { .. }
845            | arena_ast::ExprKind::CallableCreate(_)
846    )
847}
848
849/// Whether `k` is a chain node; see [`arena_is_chain`].
850#[inline]
851fn owned_is_chain(k: &ExprKind) -> bool {
852    matches!(
853        k,
854        ExprKind::Assign(_)
855            | ExprKind::Binary(_)
856            | ExprKind::UnaryPostfix(_)
857            | ExprKind::Ternary(_)
858            | ExprKind::NullCoalesce(_)
859            | ExprKind::FunctionCall(_)
860            | ExprKind::ArrayAccess(_)
861            | ExprKind::PropertyAccess(_)
862            | ExprKind::NullsafePropertyAccess(_)
863            | ExprKind::MethodCall(_)
864            | ExprKind::NullsafeMethodCall(_)
865            | ExprKind::StaticPropertyAccess(_)
866            | ExprKind::ClassConstAccess(_)
867            | ExprKind::StaticMethodCall(_)
868            | ExprKind::StaticMethodCallDynamic(_)
869            | ExprKind::ClassConstAccessDynamic { .. }
870            | ExprKind::StaticPropertyAccessDynamic { .. }
871            | ExprKind::CallableCreate(_)
872    )
873}
874
875#[inline]
876fn arena_chain_operand<'a, 'arena, 'src>(
877    k: &'a arena_ast::ExprKind<'arena, 'src>,
878) -> Option<&'a arena_ast::Expr<'arena, 'src>> {
879    use arena_ast::{CallableCreateKind as C, ExprKind as K};
880    match k {
881        K::Assign(a) => Some(a.target),
882        K::Binary(b) => Some(b.left),
883        K::UnaryPostfix(u) => Some(u.operand),
884        K::Ternary(t) => Some(t.condition),
885        K::NullCoalesce(n) => Some(n.left),
886        K::FunctionCall(f) => Some(f.name),
887        K::ArrayAccess(a) => Some(a.array),
888        K::PropertyAccess(p) | K::NullsafePropertyAccess(p) => Some(p.object),
889        K::MethodCall(m) | K::NullsafeMethodCall(m) => Some(m.object),
890        K::StaticPropertyAccess(s) | K::ClassConstAccess(s) => Some(s.class),
891        K::StaticMethodCall(s) => Some(s.class),
892        K::StaticMethodCallDynamic(s) => Some(s.class),
893        K::ClassConstAccessDynamic { class, .. } | K::StaticPropertyAccessDynamic { class, .. } => {
894            Some(class)
895        }
896        K::CallableCreate(c) => Some(match &c.kind {
897            C::Function(e) => e,
898            C::Method { object, .. } | C::NullsafeMethod { object, .. } => object,
899            C::StaticMethod { class, .. } => class,
900        }),
901        _ => None,
902    }
903}
904
905#[inline]
906fn owned_chain_operand(k: &ExprKind) -> Option<&Expr> {
907    use CallableCreateKind as C;
908    match k {
909        ExprKind::Assign(a) => Some(&a.target),
910        ExprKind::Binary(b) => Some(&b.left),
911        ExprKind::UnaryPostfix(u) => Some(&u.operand),
912        ExprKind::Ternary(t) => Some(&t.condition),
913        ExprKind::NullCoalesce(n) => Some(&n.left),
914        ExprKind::FunctionCall(f) => Some(&f.name),
915        ExprKind::ArrayAccess(a) => Some(&a.array),
916        ExprKind::PropertyAccess(p) | ExprKind::NullsafePropertyAccess(p) => Some(&p.object),
917        ExprKind::MethodCall(m) | ExprKind::NullsafeMethodCall(m) => Some(&m.object),
918        ExprKind::StaticPropertyAccess(s) | ExprKind::ClassConstAccess(s) => Some(&s.class),
919        ExprKind::StaticMethodCall(s) => Some(&s.class),
920        ExprKind::StaticMethodCallDynamic(s) => Some(&s.class),
921        ExprKind::ClassConstAccessDynamic { class, .. }
922        | ExprKind::StaticPropertyAccessDynamic { class, .. } => Some(class),
923        ExprKind::CallableCreate(c) => Some(match &c.kind {
924            C::Function(e) => e,
925            C::Method { object, .. } | C::NullsafeMethod { object, .. } => object,
926            C::StaticMethod { class, .. } => class,
927        }),
928        _ => None,
929    }
930}
931
932#[inline]
933fn owned_chain_operand_mut(k: &mut ExprKind) -> Option<&mut Expr> {
934    use CallableCreateKind as C;
935    match k {
936        ExprKind::Assign(a) => Some(&mut a.target),
937        ExprKind::Binary(b) => Some(&mut b.left),
938        ExprKind::UnaryPostfix(u) => Some(&mut u.operand),
939        ExprKind::Ternary(t) => Some(&mut t.condition),
940        ExprKind::NullCoalesce(n) => Some(&mut n.left),
941        ExprKind::FunctionCall(f) => Some(&mut f.name),
942        ExprKind::ArrayAccess(a) => Some(&mut a.array),
943        ExprKind::PropertyAccess(p) | ExprKind::NullsafePropertyAccess(p) => Some(&mut p.object),
944        ExprKind::MethodCall(m) | ExprKind::NullsafeMethodCall(m) => Some(&mut m.object),
945        ExprKind::StaticPropertyAccess(s) | ExprKind::ClassConstAccess(s) => Some(&mut s.class),
946        ExprKind::StaticMethodCall(s) => Some(&mut s.class),
947        ExprKind::StaticMethodCallDynamic(s) => Some(&mut s.class),
948        ExprKind::ClassConstAccessDynamic { class, .. }
949        | ExprKind::StaticPropertyAccessDynamic { class, .. } => Some(class),
950        ExprKind::CallableCreate(c) => Some(match &mut c.kind {
951            C::Function(e) => e,
952            C::Method { object, .. } | C::NullsafeMethod { object, .. } => object,
953            C::StaticMethod { class, .. } => class,
954        }),
955        _ => None,
956    }
957}
958
959/// An assignment.
960#[derive(Debug, Clone, Serialize)]
961pub struct AssignExpr {
962    /// Assignment target.
963    pub target: Box<Expr>,
964    /// Assignment operator.
965    pub op: AssignOp,
966    /// Assigned value.
967    pub value: Box<Expr>,
968    /// Whether this is a reference assignment (`=&`).
969    #[serde(skip_serializing_if = "is_false")]
970    pub by_ref: bool,
971}
972
973/// A binary operation.
974#[derive(Debug, Clone, Serialize)]
975pub struct BinaryExpr {
976    /// Left operand.
977    pub left: Box<Expr>,
978    /// Operator.
979    pub op: BinaryOp,
980    /// Right operand.
981    pub right: Box<Expr>,
982}
983
984/// A prefix unary operation.
985#[derive(Debug, Clone, Serialize)]
986pub struct UnaryPrefixExpr {
987    /// Operator.
988    pub op: UnaryPrefixOp,
989    /// Operand.
990    pub operand: Box<Expr>,
991}
992
993/// A postfix unary operation.
994#[derive(Debug, Clone, Serialize)]
995pub struct UnaryPostfixExpr {
996    /// Operand.
997    pub operand: Box<Expr>,
998    /// Operator.
999    pub op: UnaryPostfixOp,
1000}
1001
1002/// A ternary or short-ternary conditional.
1003#[derive(Debug, Clone, Serialize)]
1004pub struct TernaryExpr {
1005    /// Condition.
1006    pub condition: Box<Expr>,
1007    /// Value when true; `None` for `?:`.
1008    pub then_expr: Option<Box<Expr>>,
1009    /// Value when false.
1010    pub else_expr: Box<Expr>,
1011}
1012
1013/// A `??` expression.
1014#[derive(Debug, Clone, Serialize)]
1015pub struct NullCoalesceExpr {
1016    /// Value tested for null.
1017    pub left: Box<Expr>,
1018    /// Fallback value.
1019    pub right: Box<Expr>,
1020}
1021
1022/// A function call.
1023#[derive(Debug, Clone, Serialize)]
1024pub struct FunctionCallExpr {
1025    /// Callee expression.
1026    pub name: Box<Expr>,
1027    /// Call arguments.
1028    pub args: Box<[Arg]>,
1029}
1030
1031/// An element of an array literal.
1032#[derive(Debug, Clone, Serialize)]
1033pub struct ArrayElement {
1034    /// Key expression, if any.
1035    pub key: Option<Expr>,
1036    /// Element value.
1037    pub value: Expr,
1038    /// Whether the element is spread with `...`.
1039    pub unpack: bool,
1040    /// Whether the value is taken by reference.
1041    #[serde(skip_serializing_if = "is_false")]
1042    pub by_ref: bool,
1043    /// Source span.
1044    pub span: Span,
1045}
1046
1047/// An array offset access.
1048#[derive(Debug, Clone, Serialize)]
1049pub struct ArrayAccessExpr {
1050    /// Accessed expression.
1051    pub array: Box<Expr>,
1052    /// Offset; `None` for `$a[]`.
1053    pub index: Option<Box<Expr>>,
1054}
1055
1056/// A `new` expression.
1057#[derive(Debug, Clone, Serialize)]
1058pub struct NewExpr {
1059    /// Class name or expression.
1060    pub class: Box<Expr>,
1061    /// Constructor arguments.
1062    pub args: Box<[Arg]>,
1063}
1064
1065/// A `->` or `?->` property access.
1066#[derive(Debug, Clone, Serialize)]
1067pub struct PropertyAccessExpr {
1068    /// Object expression.
1069    pub object: Box<Expr>,
1070    /// Property name or expression.
1071    pub property: Box<Expr>,
1072}
1073
1074/// A `->` or `?->` method call.
1075#[derive(Debug, Clone, Serialize)]
1076pub struct MethodCallExpr {
1077    /// Object expression.
1078    pub object: Box<Expr>,
1079    /// Method name or expression.
1080    pub method: Box<Expr>,
1081    /// Call arguments.
1082    pub args: Box<[Arg]>,
1083}
1084
1085/// A `::` static property or constant access.
1086#[derive(Debug, Clone, Serialize)]
1087pub struct StaticAccessExpr {
1088    /// Class expression.
1089    pub class: Box<Expr>,
1090    /// Member name or expression.
1091    pub member: Box<Expr>,
1092}
1093
1094/// A `Class::method()` call.
1095#[derive(Debug, Clone, Serialize)]
1096pub struct StaticMethodCallExpr {
1097    /// Class expression.
1098    pub class: Box<Expr>,
1099    /// Method name.
1100    pub method: Box<Expr>,
1101    /// Call arguments.
1102    pub args: Box<[Arg]>,
1103}
1104
1105/// A static call with a dynamic method expression.
1106#[derive(Debug, Clone, Serialize)]
1107pub struct StaticMethodCallDynamicExpr {
1108    /// Class expression.
1109    pub class: Box<Expr>,
1110    /// Method name expression.
1111    pub method: Box<Expr>,
1112    /// Call arguments.
1113    pub args: Box<[Arg]>,
1114}
1115
1116/// An anonymous function.
1117#[derive(Debug, Clone, Serialize)]
1118pub struct ClosureExpr {
1119    /// Whether declared `static`.
1120    pub is_static: bool,
1121    /// Whether it returns by reference.
1122    pub by_ref: bool,
1123    /// Parameters.
1124    pub params: Box<[Param]>,
1125    /// Captured variables.
1126    pub use_vars: Box<[ClosureUseVar]>,
1127    /// Declared return type.
1128    pub return_type: Option<TypeHint>,
1129    /// Function body.
1130    pub body: Box<Block>,
1131    /// Attributes on the closure.
1132    pub attributes: Box<[Attribute]>,
1133}
1134
1135/// A variable captured by a closure `use` clause.
1136#[derive(Debug, Clone, Serialize)]
1137pub struct ClosureUseVar {
1138    /// Captured variable name without `$`.
1139    pub name: Box<str>,
1140    /// Whether captured by reference.
1141    pub by_ref: bool,
1142    /// Source span.
1143    pub span: Span,
1144}
1145
1146/// An `fn` arrow function.
1147#[derive(Debug, Clone, Serialize)]
1148pub struct ArrowFunctionExpr {
1149    /// Whether declared `static`.
1150    pub is_static: bool,
1151    /// Whether it returns by reference.
1152    pub by_ref: bool,
1153    /// Parameters.
1154    pub params: Box<[Param]>,
1155    /// Declared return type.
1156    pub return_type: Option<TypeHint>,
1157    /// Result expression.
1158    pub body: Box<Expr>,
1159    /// Attributes on the arrow function.
1160    pub attributes: Box<[Attribute]>,
1161}
1162
1163/// A `match` expression.
1164#[derive(Debug, Clone, Serialize)]
1165pub struct MatchExpr {
1166    /// Matched expression.
1167    pub subject: Box<Expr>,
1168    /// Match arms.
1169    pub arms: Box<[MatchArm]>,
1170    /// The opening `{`. Not serialized.
1171    #[serde(skip)]
1172    pub brace_span: Span,
1173}
1174
1175/// An arm of a `match` expression.
1176#[derive(Debug, Clone, Serialize)]
1177pub struct MatchArm {
1178    /// Conditions; `None` for `default`.
1179    pub conditions: Option<Box<[Expr]>>,
1180    /// Result expression.
1181    pub body: Expr,
1182    /// Source span.
1183    pub span: Span,
1184}
1185
1186/// A `yield` or `yield from` expression.
1187#[derive(Debug, Clone, Serialize)]
1188pub struct YieldExpr {
1189    /// Yielded key, if any.
1190    pub key: Option<Box<Expr>>,
1191    /// Yielded value, if any.
1192    pub value: Option<Box<Expr>>,
1193    /// Whether this is `yield from`.
1194    pub is_from: bool,
1195}
1196
1197/// A first-class callable, e.g. `f(...)`.
1198#[derive(Debug, Clone, Serialize)]
1199pub struct CallableCreateExpr {
1200    /// The callee.
1201    pub kind: CallableCreateKind,
1202}
1203
1204/// The callee of a first-class callable.
1205#[derive(Debug, Clone, Serialize)]
1206#[non_exhaustive]
1207pub enum CallableCreateKind {
1208    /// `f(...)`.
1209    Function(Box<Expr>),
1210    /// `$obj->method(...)`.
1211    Method {
1212        /// Object expression.
1213        object: Box<Expr>,
1214        /// Method name or expression.
1215        method: Box<Expr>,
1216    },
1217    /// `$obj?->method(...)`.
1218    NullsafeMethod {
1219        /// Object expression.
1220        object: Box<Expr>,
1221        /// Method name or expression.
1222        method: Box<Expr>,
1223    },
1224    /// `Class::method(...)`.
1225    StaticMethod {
1226        /// Class expression.
1227        class: Box<Expr>,
1228        /// Method name or expression.
1229        method: Box<Expr>,
1230    },
1231}
1232
1233/// A segment of an interpolated string.
1234#[derive(Debug, Clone, Serialize)]
1235#[non_exhaustive]
1236pub enum StringPart {
1237    /// Literal text.
1238    Literal(Box<str>),
1239    /// Interpolated expression.
1240    Expr(Expr),
1241}
1242
1243// ---------------------------------------------------------------------------
1244// Owned declaration types
1245// ---------------------------------------------------------------------------
1246
1247/// A function declaration.
1248#[derive(Debug, Clone, Serialize)]
1249pub struct FunctionDecl {
1250    /// Function name.
1251    pub name: Ident,
1252    /// Parameters.
1253    pub params: Box<[Param]>,
1254    /// Function body.
1255    pub body: Box<Block>,
1256    /// Declared return type.
1257    pub return_type: Option<TypeHint>,
1258    /// Whether it returns by reference.
1259    pub by_ref: bool,
1260    /// Attributes on the function.
1261    pub attributes: Box<[Attribute]>,
1262    /// Preceding doc comment.
1263    #[serde(skip_serializing_if = "Option::is_none")]
1264    pub doc_comment: Option<Comment>,
1265}
1266
1267/// A function or method parameter.
1268#[derive(Debug, Clone, Serialize)]
1269#[non_exhaustive]
1270pub struct Param {
1271    /// Parameter name without `$`.
1272    pub name: Ident,
1273    /// Declared type.
1274    pub type_hint: Option<TypeHint>,
1275    /// Default value.
1276    pub default: Option<Expr>,
1277    /// Whether passed by reference.
1278    pub by_ref: bool,
1279    /// Whether variadic (`...`).
1280    pub variadic: bool,
1281    /// Whether a `readonly` promoted property.
1282    pub is_readonly: bool,
1283    /// Whether a `final` promoted property.
1284    pub is_final: bool,
1285    /// Promotion visibility.
1286    pub visibility: Option<Visibility>,
1287    /// Asymmetric set visibility of a promoted property.
1288    pub set_visibility: Option<Visibility>,
1289    /// Attributes on the parameter.
1290    pub attributes: Box<[Attribute]>,
1291    /// Property hooks of a promoted property.
1292    #[serde(skip_serializing_if = "slice_is_empty")]
1293    pub hooks: Box<[PropertyHook]>,
1294    /// Preceding doc comment.
1295    #[serde(skip_serializing_if = "Option::is_none")]
1296    pub doc_comment: Option<Comment>,
1297    /// Source span.
1298    pub span: Span,
1299}
1300
1301impl Param {
1302    /// A parameter with `name` and `span` set and every other field empty.
1303    /// The fields are public, so set the rest by assignment; `Param` is
1304    /// `#[non_exhaustive]` and cannot be built with a struct literal outside
1305    /// this crate.
1306    pub fn new(name: Ident, span: Span) -> Self {
1307        Self {
1308            name,
1309            type_hint: None,
1310            default: None,
1311            by_ref: false,
1312            variadic: false,
1313            is_readonly: false,
1314            is_final: false,
1315            visibility: None,
1316            set_visibility: None,
1317            attributes: Box::new([]),
1318            hooks: Box::new([]),
1319            doc_comment: None,
1320            span,
1321        }
1322    }
1323}
1324
1325/// The braced member list of a class-like declaration.
1326#[derive(Debug, Clone, Serialize)]
1327pub struct ClassBody {
1328    /// Members in source order.
1329    pub members: Box<[ClassMember]>,
1330    /// Source span.
1331    #[serde(skip)]
1332    pub span: Span,
1333}
1334
1335/// A class declaration.
1336#[derive(Debug, Clone, Serialize)]
1337pub struct ClassDecl {
1338    /// Class name; `None` for anonymous classes.
1339    pub name: Option<Ident>,
1340    /// Class modifiers.
1341    pub modifiers: ClassModifiers,
1342    /// Parent class.
1343    pub extends: Option<Name>,
1344    /// Implemented interfaces.
1345    pub implements: Box<[Name]>,
1346    /// Class body.
1347    #[serde(flatten)]
1348    pub body: ClassBody,
1349    /// Attributes on the class.
1350    pub attributes: Box<[Attribute]>,
1351    /// Preceding doc comment.
1352    #[serde(skip_serializing_if = "Option::is_none")]
1353    pub doc_comment: Option<Comment>,
1354}
1355
1356/// A member of a class-like body.
1357#[derive(Debug, Clone, Serialize)]
1358pub struct ClassMember {
1359    /// Member kind.
1360    pub kind: ClassMemberKind,
1361    /// Source span.
1362    pub span: Span,
1363}
1364
1365/// The kind of a class member.
1366#[derive(Debug, Clone, Serialize)]
1367#[non_exhaustive]
1368pub enum ClassMemberKind {
1369    /// Property declaration.
1370    Property(PropertyDecl),
1371    /// Method declaration.
1372    Method(MethodDecl),
1373    /// Class constant declaration.
1374    ClassConst(ClassConstDecl),
1375    /// Trait use clause.
1376    TraitUse(TraitUseDecl),
1377}
1378
1379/// A property declaration.
1380#[derive(Debug, Clone, Serialize)]
1381pub struct PropertyDecl {
1382    /// Property name without `$`.
1383    pub name: Ident,
1384    /// Read visibility.
1385    pub visibility: Option<Visibility>,
1386    /// Asymmetric set visibility.
1387    pub set_visibility: Option<Visibility>,
1388    /// Whether `static`.
1389    pub is_static: bool,
1390    /// Whether `readonly`.
1391    pub is_readonly: bool,
1392    /// Declared type.
1393    pub type_hint: Option<TypeHint>,
1394    /// Default value.
1395    pub default: Option<Expr>,
1396    /// Attributes on the property.
1397    pub attributes: Box<[Attribute]>,
1398    /// Property hooks.
1399    #[serde(skip_serializing_if = "slice_is_empty")]
1400    pub hooks: Box<[PropertyHook]>,
1401    /// Preceding doc comment.
1402    #[serde(skip_serializing_if = "Option::is_none")]
1403    pub doc_comment: Option<Comment>,
1404}
1405
1406/// A property `get` or `set` hook.
1407#[derive(Debug, Clone, Serialize)]
1408pub struct PropertyHook {
1409    /// `get` or `set`.
1410    pub kind: PropertyHookKind,
1411    /// Hook body.
1412    pub body: PropertyHookBody,
1413    /// Whether `final`.
1414    pub is_final: bool,
1415    /// Whether it returns by reference.
1416    pub by_ref: bool,
1417    /// Parameters of a `set` hook.
1418    pub params: Box<[Param]>,
1419    /// Attributes on the hook.
1420    pub attributes: Box<[Attribute]>,
1421    /// Source span.
1422    pub span: Span,
1423}
1424
1425/// The body of a property hook.
1426#[derive(Debug, Clone, Serialize)]
1427#[non_exhaustive]
1428pub enum PropertyHookBody {
1429    /// Braced body.
1430    Block(Box<Block>),
1431    /// Arrow expression body (`=> expr`).
1432    Expression(Expr),
1433    /// No body (`;`).
1434    Abstract,
1435}
1436
1437/// A method declaration.
1438#[derive(Debug, Clone, Serialize)]
1439#[non_exhaustive]
1440pub struct MethodDecl {
1441    /// Method name.
1442    pub name: Ident,
1443    /// Visibility modifier.
1444    pub visibility: Option<Visibility>,
1445    /// Whether `static`.
1446    pub is_static: bool,
1447    /// Whether `abstract`.
1448    pub is_abstract: bool,
1449    /// Whether `final`.
1450    pub is_final: bool,
1451    /// Whether it returns by reference.
1452    pub by_ref: bool,
1453    /// Parameters.
1454    pub params: Box<[Param]>,
1455    /// Declared return type.
1456    pub return_type: Option<TypeHint>,
1457    /// Method body; `None` for abstract or interface methods.
1458    pub body: Option<Box<Block>>,
1459    /// Attributes on the method.
1460    pub attributes: Box<[Attribute]>,
1461    /// Preceding doc comment.
1462    #[serde(skip_serializing_if = "Option::is_none")]
1463    pub doc_comment: Option<Comment>,
1464}
1465
1466impl MethodDecl {
1467    /// A method with `name` set and every other field empty. Set the rest by
1468    /// assignment; see [`Param::new`].
1469    pub fn new(name: Ident) -> Self {
1470        Self {
1471            name,
1472            visibility: None,
1473            is_static: false,
1474            is_abstract: false,
1475            is_final: false,
1476            by_ref: false,
1477            params: Box::new([]),
1478            return_type: None,
1479            body: None,
1480            attributes: Box::new([]),
1481            doc_comment: None,
1482        }
1483    }
1484}
1485
1486/// A class constant declaration.
1487#[derive(Debug, Clone, Serialize)]
1488pub struct ClassConstDecl {
1489    /// Constant name.
1490    pub name: Ident,
1491    /// Visibility modifier.
1492    pub visibility: Option<Visibility>,
1493    /// Whether `final`.
1494    pub is_final: bool,
1495    /// Declared type.
1496    #[serde(skip_serializing_if = "Option::is_none")]
1497    pub type_hint: Option<Box<TypeHint>>,
1498    /// Constant value.
1499    pub value: Expr,
1500    /// Attributes on the constant.
1501    pub attributes: Box<[Attribute]>,
1502    /// Preceding doc comment.
1503    #[serde(skip_serializing_if = "Option::is_none")]
1504    pub doc_comment: Option<Comment>,
1505}
1506
1507/// A `use Trait;` clause inside a class.
1508#[derive(Debug, Clone, Serialize)]
1509pub struct TraitUseDecl {
1510    /// Used traits.
1511    pub traits: Box<[Name]>,
1512    /// Rules in the `{ ... }` block.
1513    pub adaptations: Box<[TraitAdaptation]>,
1514    /// The opening `{` of the adaptations block. Not serialized.
1515    #[serde(skip)]
1516    pub adaptations_brace_span: Option<Span>,
1517    /// Preceding doc comment.
1518    #[serde(skip_serializing_if = "Option::is_none")]
1519    pub doc_comment: Option<Comment>,
1520}
1521
1522/// A `insteadof` or `as` rule in a trait use.
1523#[derive(Debug, Clone, Serialize)]
1524pub struct TraitAdaptation {
1525    /// Rule kind.
1526    pub kind: TraitAdaptationKind,
1527    /// Source span.
1528    pub span: Span,
1529}
1530
1531/// The kind of trait adaptation rule.
1532#[derive(Debug, Clone, Serialize)]
1533#[non_exhaustive]
1534pub enum TraitAdaptationKind {
1535    /// `A::m insteadof B;`.
1536    Precedence {
1537        /// Trait providing the method.
1538        trait_name: Name,
1539        /// Method name.
1540        method: Name,
1541        /// Traits whose method is excluded.
1542        insteadof: Box<[Name]>,
1543    },
1544    /// `m as [visibility] [alias];`.
1545    Alias {
1546        /// Explicit trait qualifier, if any.
1547        trait_name: Option<Name>,
1548        /// Method name.
1549        method: Name,
1550        /// New visibility, if any.
1551        new_modifier: Option<Visibility>,
1552        /// Alias name, if any.
1553        new_name: Option<Name>,
1554    },
1555}
1556
1557/// An interface declaration.
1558#[derive(Debug, Clone, Serialize)]
1559pub struct InterfaceDecl {
1560    /// Interface name.
1561    pub name: Ident,
1562    /// Extended interfaces.
1563    pub extends: Box<[Name]>,
1564    /// Interface body.
1565    #[serde(flatten)]
1566    pub body: ClassBody,
1567    /// Attributes on the interface.
1568    pub attributes: Box<[Attribute]>,
1569    /// Preceding doc comment.
1570    #[serde(skip_serializing_if = "Option::is_none")]
1571    pub doc_comment: Option<Comment>,
1572}
1573
1574/// A trait declaration.
1575#[derive(Debug, Clone, Serialize)]
1576pub struct TraitDecl {
1577    /// Trait name.
1578    pub name: Ident,
1579    /// Trait body.
1580    #[serde(flatten)]
1581    pub body: ClassBody,
1582    /// Attributes on the trait.
1583    pub attributes: Box<[Attribute]>,
1584    /// Preceding doc comment.
1585    #[serde(skip_serializing_if = "Option::is_none")]
1586    pub doc_comment: Option<Comment>,
1587}
1588
1589/// The braced member list of an enum.
1590#[derive(Debug, Clone, Serialize)]
1591pub struct EnumBody {
1592    /// Members in source order.
1593    pub members: Box<[EnumMember]>,
1594    /// Source span.
1595    #[serde(skip)]
1596    pub span: Span,
1597}
1598
1599/// An enum declaration.
1600#[derive(Debug, Clone, Serialize)]
1601pub struct EnumDecl {
1602    /// Enum name.
1603    pub name: Ident,
1604    /// Backing type of a backed enum.
1605    pub scalar_type: Option<Name>,
1606    /// Implemented interfaces.
1607    pub implements: Box<[Name]>,
1608    /// Enum body.
1609    #[serde(flatten)]
1610    pub body: EnumBody,
1611    /// Attributes on the enum.
1612    pub attributes: Box<[Attribute]>,
1613    /// Preceding doc comment.
1614    #[serde(skip_serializing_if = "Option::is_none")]
1615    pub doc_comment: Option<Comment>,
1616}
1617
1618/// A member of an enum body.
1619#[derive(Debug, Clone, Serialize)]
1620pub struct EnumMember {
1621    /// Member kind.
1622    pub kind: EnumMemberKind,
1623    /// Source span.
1624    pub span: Span,
1625}
1626
1627/// The kind of an enum member.
1628#[derive(Debug, Clone, Serialize)]
1629#[non_exhaustive]
1630pub enum EnumMemberKind {
1631    /// Enum case.
1632    Case(EnumCase),
1633    /// Method declaration.
1634    Method(MethodDecl),
1635    /// Class constant declaration.
1636    ClassConst(ClassConstDecl),
1637    /// Trait use clause.
1638    TraitUse(TraitUseDecl),
1639}
1640
1641/// An enum `case`.
1642#[derive(Debug, Clone, Serialize)]
1643pub struct EnumCase {
1644    /// Case name.
1645    pub name: Ident,
1646    /// Backing value.
1647    pub value: Option<Expr>,
1648    /// Attributes on the case.
1649    pub attributes: Box<[Attribute]>,
1650    /// Preceding doc comment.
1651    #[serde(skip_serializing_if = "Option::is_none")]
1652    pub doc_comment: Option<Comment>,
1653}
1654
1655// ===========================================================================
1656// Conversion functions
1657// ===========================================================================
1658
1659fn owned_ident(ident: arena_ast::Ident<'_>) -> Ident {
1660    ident.as_str().map_or(Ident::ERROR, Ident::name)
1661}
1662
1663fn owned_name(name: &arena_ast::Name<'_, '_>) -> Name {
1664    match name {
1665        arena_ast::Name::Simple { value, span } => Name {
1666            parts: vec![Box::from(*value)].into_boxed_slice(),
1667            kind: NameKind::Unqualified,
1668            span: *span,
1669        },
1670        arena_ast::Name::Complex { parts, kind, span } => Name {
1671            parts: parts
1672                .iter()
1673                .map(|s| Box::from(*s))
1674                .collect::<Vec<_>>()
1675                .into_boxed_slice(),
1676            kind: *kind,
1677            span: *span,
1678        },
1679        arena_ast::Name::Error { span } => Name {
1680            parts: Box::from([]),
1681            kind: NameKind::Error,
1682            span: *span,
1683        },
1684    }
1685}
1686
1687fn owned_comment(c: &arena_ast::Comment<'_>) -> Comment {
1688    Comment {
1689        kind: c.kind,
1690        text: c.text.into(),
1691        span: c.span,
1692    }
1693}
1694
1695fn owned_opt_comment(c: &Option<arena_ast::Comment<'_>>) -> Option<Comment> {
1696    c.as_ref().map(owned_comment)
1697}
1698
1699fn owned_type_hint(th: &arena_ast::TypeHint<'_, '_>) -> TypeHint {
1700    TypeHint {
1701        kind: owned_type_hint_kind(&th.kind),
1702        span: th.span,
1703    }
1704}
1705
1706fn owned_type_hint_kind(k: &arena_ast::TypeHintKind<'_, '_>) -> TypeHintKind {
1707    match k {
1708        arena_ast::TypeHintKind::Named(n) => TypeHintKind::Named(owned_name(n)),
1709        arena_ast::TypeHintKind::Keyword(b, span) => TypeHintKind::Keyword(*b, *span),
1710        arena_ast::TypeHintKind::Nullable(inner) => {
1711            TypeHintKind::Nullable(Box::new(owned_type_hint(inner)))
1712        }
1713        arena_ast::TypeHintKind::Union(types) => TypeHintKind::Union(
1714            types
1715                .iter()
1716                .map(owned_type_hint)
1717                .collect::<Vec<_>>()
1718                .into_boxed_slice(),
1719        ),
1720        arena_ast::TypeHintKind::Intersection(types) => TypeHintKind::Intersection(
1721            types
1722                .iter()
1723                .map(owned_type_hint)
1724                .collect::<Vec<_>>()
1725                .into_boxed_slice(),
1726        ),
1727    }
1728}
1729
1730fn owned_arg(arg: &arena_ast::Arg<'_, '_>) -> Arg {
1731    Arg {
1732        name: arg.name.as_ref().map(owned_name),
1733        value: arg.value.as_ref().map(owned_expr),
1734        unpack: arg.unpack,
1735        by_ref: arg.by_ref,
1736        span: arg.span,
1737    }
1738}
1739
1740#[inline(never)]
1741fn owned_args(args: &[arena_ast::Arg<'_, '_>]) -> Box<[Arg]> {
1742    args.iter()
1743        .map(owned_arg)
1744        .collect::<Vec<_>>()
1745        .into_boxed_slice()
1746}
1747
1748fn owned_attr(attr: &arena_ast::Attribute<'_, '_>) -> Attribute {
1749    Attribute {
1750        name: owned_name(&attr.name),
1751        args: owned_args(&attr.args),
1752        span: attr.span,
1753    }
1754}
1755
1756fn owned_attrs(attrs: &[arena_ast::Attribute<'_, '_>]) -> Box<[Attribute]> {
1757    attrs
1758        .iter()
1759        .map(owned_attr)
1760        .collect::<Vec<_>>()
1761        .into_boxed_slice()
1762}
1763
1764#[inline(never)]
1765fn owned_string_parts(parts: &[arena_ast::StringPart<'_, '_>]) -> Box<[StringPart]> {
1766    parts
1767        .iter()
1768        .map(|p| match p {
1769            arena_ast::StringPart::Literal(s) => StringPart::Literal(Box::from(*s)),
1770            arena_ast::StringPart::Expr(e) => StringPart::Expr(owned_expr(e)),
1771        })
1772        .collect::<Vec<_>>()
1773        .into_boxed_slice()
1774}
1775
1776/// Convert an arena-allocated [`Expr`](crate::ast::Expr) into an [`Expr`].
1777pub fn to_owned_expr(expr: &arena_ast::Expr<'_, '_>) -> Expr {
1778    owned_expr(expr)
1779}
1780
1781/// Convert an arena-allocated [`Name`](crate::ast::Name) into a [`Name`].
1782pub fn to_owned_name(name: &arena_ast::Name<'_, '_>) -> Name {
1783    owned_name(name)
1784}
1785
1786/// Convert an arena-allocated [`TypeHint`](crate::ast::TypeHint) into a [`TypeHint`].
1787pub fn to_owned_type_hint(hint: &arena_ast::TypeHint<'_, '_>) -> TypeHint {
1788    owned_type_hint(hint)
1789}
1790
1791#[inline]
1792fn owned_expr(expr: &arena_ast::Expr<'_, '_>) -> Expr {
1793    if !arena_is_chain(&expr.kind) {
1794        return Expr {
1795            kind: owned_leaf_kind(&expr.kind),
1796            span: expr.span,
1797        };
1798    }
1799    owned_chain_expr(expr)
1800}
1801
1802/// Convert a chain bottom-up from an explicit stack: its length is set by the
1803/// input, so recursing once per link could overflow the stack.
1804#[inline(never)]
1805fn owned_chain_expr(expr: &arena_ast::Expr<'_, '_>) -> Expr {
1806    let first = arena_chain_operand(&expr.kind).expect("chain link has an operand");
1807    if !arena_is_chain(&first.kind) {
1808        // A single link, by far the most common chain: no stack needed.
1809        let operand = Expr {
1810            kind: owned_leaf_kind(&first.kind),
1811            span: first.span,
1812        };
1813        return Expr {
1814            kind: owned_link_kind(&expr.kind, operand),
1815            span: expr.span,
1816        };
1817    }
1818    let mut chain = LinkStack::new();
1819    chain.push(expr);
1820    let mut leaf = first;
1821    while let Some(operand) = arena_chain_operand(&leaf.kind) {
1822        chain.push(leaf);
1823        leaf = operand;
1824    }
1825    let mut converted = Expr {
1826        kind: owned_leaf_kind(&leaf.kind),
1827        span: leaf.span,
1828    };
1829    while let Some(node) = chain.pop() {
1830        converted = Expr {
1831            kind: owned_link_kind(&node.kind, converted),
1832            span: node.span,
1833        };
1834    }
1835    converted
1836}
1837
1838#[inline(never)]
1839fn owned_exprs(exprs: &[arena_ast::Expr<'_, '_>]) -> Box<[Expr]> {
1840    exprs
1841        .iter()
1842        .map(owned_expr)
1843        .collect::<Vec<_>>()
1844        .into_boxed_slice()
1845}
1846
1847/// Convert an expression kind that is not a chain link (see [`arena_chain_operand`]).
1848fn owned_leaf_kind(k: &arena_ast::ExprKind<'_, '_>) -> ExprKind {
1849    match k {
1850        arena_ast::ExprKind::Int(v) => ExprKind::Int(*v),
1851        arena_ast::ExprKind::Float(v) => ExprKind::Float(*v),
1852        arena_ast::ExprKind::String(s) => ExprKind::String(Box::from(s.as_str())),
1853        arena_ast::ExprKind::InterpolatedString(parts) => {
1854            ExprKind::InterpolatedString(owned_string_parts(parts))
1855        }
1856        arena_ast::ExprKind::Heredoc { label, parts } => ExprKind::Heredoc {
1857            label: Box::from(*label),
1858            parts: owned_string_parts(parts),
1859        },
1860        arena_ast::ExprKind::Nowdoc { label, value } => ExprKind::Nowdoc {
1861            label: Box::from(*label),
1862            value: Box::from(*value),
1863        },
1864        arena_ast::ExprKind::ShellExec(parts) => ExprKind::ShellExec(owned_string_parts(parts)),
1865        arena_ast::ExprKind::Bool(v) => ExprKind::Bool(*v),
1866        arena_ast::ExprKind::Null => ExprKind::Null,
1867        arena_ast::ExprKind::Variable(s) => ExprKind::Variable(s.as_str().into()),
1868        arena_ast::ExprKind::VariableVariable(inner) => {
1869            ExprKind::VariableVariable(Box::new(owned_expr(inner)))
1870        }
1871        arena_ast::ExprKind::Identifier(s) => ExprKind::Identifier(s.as_str().into()),
1872        arena_ast::ExprKind::UnaryPrefix(u) => ExprKind::UnaryPrefix(UnaryPrefixExpr {
1873            op: u.op,
1874            operand: Box::new(owned_expr(u.operand)),
1875        }),
1876        arena_ast::ExprKind::Array(elems) => ExprKind::Array(
1877            elems
1878                .iter()
1879                .map(|e| ArrayElement {
1880                    key: e.key.as_ref().map(owned_expr),
1881                    value: owned_expr(&e.value),
1882                    unpack: e.unpack,
1883                    by_ref: e.by_ref,
1884                    span: e.span,
1885                })
1886                .collect::<Vec<_>>()
1887                .into_boxed_slice(),
1888        ),
1889        arena_ast::ExprKind::Print(e) => ExprKind::Print(Box::new(owned_expr(e))),
1890        arena_ast::ExprKind::Parenthesized(e) => ExprKind::Parenthesized(Box::new(owned_expr(e))),
1891        arena_ast::ExprKind::Cast(kind, e) => ExprKind::Cast(*kind, Box::new(owned_expr(e))),
1892        arena_ast::ExprKind::ErrorSuppress(e) => ExprKind::ErrorSuppress(Box::new(owned_expr(e))),
1893        arena_ast::ExprKind::Isset(exprs) => ExprKind::Isset(owned_exprs(exprs)),
1894        arena_ast::ExprKind::Empty(e) => ExprKind::Empty(Box::new(owned_expr(e))),
1895        arena_ast::ExprKind::Include(kind, e) => ExprKind::Include(*kind, Box::new(owned_expr(e))),
1896        arena_ast::ExprKind::Eval(e) => ExprKind::Eval(Box::new(owned_expr(e))),
1897        arena_ast::ExprKind::Exit(e) => ExprKind::Exit(e.map(|e| Box::new(owned_expr(e)))),
1898        arena_ast::ExprKind::MagicConst(m) => ExprKind::MagicConst(*m),
1899        arena_ast::ExprKind::Clone(e) => ExprKind::Clone(Box::new(owned_expr(e))),
1900        arena_ast::ExprKind::CloneWith(obj, props) => {
1901            ExprKind::CloneWith(Box::new(owned_expr(obj)), Box::new(owned_expr(props)))
1902        }
1903        arena_ast::ExprKind::New(n) => ExprKind::New(NewExpr {
1904            class: Box::new(owned_expr(n.class)),
1905            args: owned_args(&n.args),
1906        }),
1907        arena_ast::ExprKind::Closure(c) => ExprKind::Closure(Box::new(ClosureExpr {
1908            is_static: c.is_static,
1909            by_ref: c.by_ref,
1910            params: owned_params(&c.params),
1911            use_vars: c
1912                .use_vars
1913                .iter()
1914                .map(|v| ClosureUseVar {
1915                    name: v.name.into(),
1916                    by_ref: v.by_ref,
1917                    span: v.span,
1918                })
1919                .collect::<Vec<_>>()
1920                .into_boxed_slice(),
1921            return_type: c.return_type.as_ref().map(owned_type_hint),
1922            body: Box::new(owned_block(c.body)),
1923            attributes: owned_attrs(&c.attributes),
1924        })),
1925        arena_ast::ExprKind::ArrowFunction(f) => {
1926            ExprKind::ArrowFunction(Box::new(ArrowFunctionExpr {
1927                is_static: f.is_static,
1928                by_ref: f.by_ref,
1929                params: owned_params(&f.params),
1930                return_type: f.return_type.as_ref().map(owned_type_hint),
1931                body: Box::new(owned_expr(f.body)),
1932                attributes: owned_attrs(&f.attributes),
1933            }))
1934        }
1935        arena_ast::ExprKind::Match(m) => ExprKind::Match(MatchExpr {
1936            subject: Box::new(owned_expr(m.subject)),
1937            arms: m
1938                .arms
1939                .iter()
1940                .map(|arm| MatchArm {
1941                    conditions: arm.conditions.as_ref().map(|conds| owned_exprs(conds)),
1942                    body: owned_expr(&arm.body),
1943                    span: arm.span,
1944                })
1945                .collect::<Vec<_>>()
1946                .into_boxed_slice(),
1947            brace_span: m.brace_span,
1948        }),
1949        arena_ast::ExprKind::Throw(e) => ExprKind::Throw(Box::new(owned_expr(e))),
1950        arena_ast::ExprKind::Yield(y) => ExprKind::Yield(YieldExpr {
1951            key: y.key.map(|e| Box::new(owned_expr(e))),
1952            value: y.value.map(|e| Box::new(owned_expr(e))),
1953            is_from: y.is_from,
1954        }),
1955        arena_ast::ExprKind::AnonymousClass(cls) => {
1956            ExprKind::AnonymousClass(Box::new(owned_class_decl(cls)))
1957        }
1958        arena_ast::ExprKind::Omit => ExprKind::Omit,
1959        arena_ast::ExprKind::Error => ExprKind::Error,
1960        arena_ast::ExprKind::Assign(_)
1961        | arena_ast::ExprKind::Binary(_)
1962        | arena_ast::ExprKind::UnaryPostfix(_)
1963        | arena_ast::ExprKind::Ternary(_)
1964        | arena_ast::ExprKind::NullCoalesce(_)
1965        | arena_ast::ExprKind::FunctionCall(_)
1966        | arena_ast::ExprKind::ArrayAccess(_)
1967        | arena_ast::ExprKind::PropertyAccess(_)
1968        | arena_ast::ExprKind::NullsafePropertyAccess(_)
1969        | arena_ast::ExprKind::MethodCall(_)
1970        | arena_ast::ExprKind::NullsafeMethodCall(_)
1971        | arena_ast::ExprKind::StaticPropertyAccess(_)
1972        | arena_ast::ExprKind::ClassConstAccess(_)
1973        | arena_ast::ExprKind::StaticMethodCall(_)
1974        | arena_ast::ExprKind::StaticMethodCallDynamic(_)
1975        | arena_ast::ExprKind::ClassConstAccessDynamic { .. }
1976        | arena_ast::ExprKind::StaticPropertyAccessDynamic { .. }
1977        | arena_ast::ExprKind::CallableCreate(_) => {
1978            unreachable!("chain links are converted by the link function")
1979        }
1980    }
1981}
1982
1983/// Convert a chain link, given its already-converted chain operand.
1984fn owned_link_kind(k: &arena_ast::ExprKind<'_, '_>, operand: Expr) -> ExprKind {
1985    match k {
1986        arena_ast::ExprKind::Assign(a) => ExprKind::Assign(AssignExpr {
1987            target: Box::new(operand),
1988            op: a.op,
1989            value: Box::new(owned_expr(a.value)),
1990            by_ref: a.by_ref,
1991        }),
1992        arena_ast::ExprKind::Binary(b) => ExprKind::Binary(BinaryExpr {
1993            left: Box::new(operand),
1994            op: b.op,
1995            right: Box::new(owned_expr(b.right)),
1996        }),
1997        arena_ast::ExprKind::UnaryPostfix(u) => ExprKind::UnaryPostfix(UnaryPostfixExpr {
1998            operand: Box::new(operand),
1999            op: u.op,
2000        }),
2001        arena_ast::ExprKind::Ternary(t) => ExprKind::Ternary(TernaryExpr {
2002            condition: Box::new(operand),
2003            then_expr: t.then_expr.map(|e| Box::new(owned_expr(e))),
2004            else_expr: Box::new(owned_expr(t.else_expr)),
2005        }),
2006        arena_ast::ExprKind::NullCoalesce(n) => ExprKind::NullCoalesce(NullCoalesceExpr {
2007            left: Box::new(operand),
2008            right: Box::new(owned_expr(n.right)),
2009        }),
2010        arena_ast::ExprKind::FunctionCall(f) => ExprKind::FunctionCall(FunctionCallExpr {
2011            name: Box::new(operand),
2012            args: owned_args(&f.args),
2013        }),
2014        arena_ast::ExprKind::ArrayAccess(a) => ExprKind::ArrayAccess(ArrayAccessExpr {
2015            array: Box::new(operand),
2016            index: a.index.map(|e| Box::new(owned_expr(e))),
2017        }),
2018        arena_ast::ExprKind::PropertyAccess(p) => ExprKind::PropertyAccess(PropertyAccessExpr {
2019            object: Box::new(operand),
2020            property: Box::new(owned_expr(p.property)),
2021        }),
2022        arena_ast::ExprKind::NullsafePropertyAccess(p) => {
2023            ExprKind::NullsafePropertyAccess(PropertyAccessExpr {
2024                object: Box::new(operand),
2025                property: Box::new(owned_expr(p.property)),
2026            })
2027        }
2028        arena_ast::ExprKind::MethodCall(m) => ExprKind::MethodCall(Box::new(MethodCallExpr {
2029            object: Box::new(operand),
2030            method: Box::new(owned_expr(m.method)),
2031            args: owned_args(&m.args),
2032        })),
2033        arena_ast::ExprKind::NullsafeMethodCall(m) => {
2034            ExprKind::NullsafeMethodCall(Box::new(MethodCallExpr {
2035                object: Box::new(operand),
2036                method: Box::new(owned_expr(m.method)),
2037                args: owned_args(&m.args),
2038            }))
2039        }
2040        arena_ast::ExprKind::StaticPropertyAccess(s) => {
2041            ExprKind::StaticPropertyAccess(StaticAccessExpr {
2042                class: Box::new(operand),
2043                member: Box::new(owned_expr(s.member)),
2044            })
2045        }
2046        arena_ast::ExprKind::StaticMethodCall(s) => {
2047            ExprKind::StaticMethodCall(Box::new(StaticMethodCallExpr {
2048                class: Box::new(operand),
2049                method: Box::new(owned_expr(s.method)),
2050                args: owned_args(&s.args),
2051            }))
2052        }
2053        arena_ast::ExprKind::StaticMethodCallDynamic(s) => {
2054            ExprKind::StaticMethodCallDynamic(Box::new(StaticMethodCallDynamicExpr {
2055                class: Box::new(operand),
2056                method: Box::new(owned_expr(s.method)),
2057                args: owned_args(&s.args),
2058            }))
2059        }
2060        arena_ast::ExprKind::ClassConstAccess(s) => ExprKind::ClassConstAccess(StaticAccessExpr {
2061            class: Box::new(operand),
2062            member: Box::new(owned_expr(s.member)),
2063        }),
2064        arena_ast::ExprKind::ClassConstAccessDynamic { member, .. } => {
2065            ExprKind::ClassConstAccessDynamic {
2066                class: Box::new(operand),
2067                member: Box::new(owned_expr(member)),
2068            }
2069        }
2070        arena_ast::ExprKind::StaticPropertyAccessDynamic { member, .. } => {
2071            ExprKind::StaticPropertyAccessDynamic {
2072                class: Box::new(operand),
2073                member: Box::new(owned_expr(member)),
2074            }
2075        }
2076        arena_ast::ExprKind::CallableCreate(c) => ExprKind::CallableCreate(CallableCreateExpr {
2077            kind: match &c.kind {
2078                arena_ast::CallableCreateKind::Function(_) => {
2079                    CallableCreateKind::Function(Box::new(operand))
2080                }
2081                arena_ast::CallableCreateKind::Method { method, .. } => {
2082                    CallableCreateKind::Method {
2083                        object: Box::new(operand),
2084                        method: Box::new(owned_expr(method)),
2085                    }
2086                }
2087                arena_ast::CallableCreateKind::NullsafeMethod { method, .. } => {
2088                    CallableCreateKind::NullsafeMethod {
2089                        object: Box::new(operand),
2090                        method: Box::new(owned_expr(method)),
2091                    }
2092                }
2093                arena_ast::CallableCreateKind::StaticMethod { method, .. } => {
2094                    CallableCreateKind::StaticMethod {
2095                        class: Box::new(operand),
2096                        method: Box::new(owned_expr(method)),
2097                    }
2098                }
2099            },
2100        }),
2101        _ => unreachable!("not a chain link"),
2102    }
2103}
2104
2105fn owned_param(p: &arena_ast::Param<'_, '_>) -> Param {
2106    Param {
2107        name: owned_ident(p.name),
2108        type_hint: p.type_hint.as_ref().map(owned_type_hint),
2109        default: p.default.as_ref().map(owned_expr),
2110        by_ref: p.by_ref,
2111        variadic: p.variadic,
2112        is_readonly: p.is_readonly,
2113        is_final: p.is_final,
2114        visibility: p.visibility,
2115        set_visibility: p.set_visibility,
2116        attributes: owned_attrs(&p.attributes),
2117        hooks: owned_hooks(&p.hooks),
2118        doc_comment: p.doc_comment.as_ref().map(owned_comment),
2119        span: p.span,
2120    }
2121}
2122
2123#[inline(never)]
2124fn owned_params(params: &[arena_ast::Param<'_, '_>]) -> Box<[Param]> {
2125    params
2126        .iter()
2127        .map(owned_param)
2128        .collect::<Vec<_>>()
2129        .into_boxed_slice()
2130}
2131
2132fn owned_hook(h: &arena_ast::PropertyHook<'_, '_>) -> PropertyHook {
2133    PropertyHook {
2134        kind: h.kind,
2135        body: match &h.body {
2136            arena_ast::PropertyHookBody::Block(block) => {
2137                PropertyHookBody::Block(Box::new(owned_block(block)))
2138            }
2139            arena_ast::PropertyHookBody::Expression(e) => {
2140                PropertyHookBody::Expression(owned_expr(e))
2141            }
2142            arena_ast::PropertyHookBody::Abstract => PropertyHookBody::Abstract,
2143        },
2144        is_final: h.is_final,
2145        by_ref: h.by_ref,
2146        params: owned_params(&h.params),
2147        attributes: owned_attrs(&h.attributes),
2148        span: h.span,
2149    }
2150}
2151
2152#[inline(never)]
2153fn owned_hooks(hooks: &[arena_ast::PropertyHook<'_, '_>]) -> Box<[PropertyHook]> {
2154    hooks
2155        .iter()
2156        .map(owned_hook)
2157        .collect::<Vec<_>>()
2158        .into_boxed_slice()
2159}
2160
2161fn owned_stmts(stmts: &[arena_ast::Stmt<'_, '_>]) -> Box<[Stmt]> {
2162    stmts
2163        .iter()
2164        .map(owned_stmt)
2165        .collect::<Vec<_>>()
2166        .into_boxed_slice()
2167}
2168
2169/// Convert an arena-allocated [`Stmt`](crate::ast::Stmt) into a [`Stmt`].
2170pub fn to_owned_stmt(stmt: &arena_ast::Stmt<'_, '_>) -> Stmt {
2171    owned_stmt(stmt)
2172}
2173
2174fn owned_block(block: &arena_ast::Block<'_, '_>) -> Block {
2175    Block {
2176        stmts: owned_stmts(&block.stmts),
2177        span: block.span,
2178    }
2179}
2180
2181fn owned_stmt(stmt: &arena_ast::Stmt<'_, '_>) -> Stmt {
2182    Stmt {
2183        kind: owned_stmt_kind(&stmt.kind),
2184        span: stmt.span,
2185        doc_comment: stmt.doc_comment.map(owned_comment),
2186    }
2187}
2188
2189fn owned_stmt_kind(k: &arena_ast::StmtKind<'_, '_>) -> StmtKind {
2190    match k {
2191        arena_ast::StmtKind::Expression(e) => StmtKind::Expression(Box::new(owned_expr(e))),
2192        arena_ast::StmtKind::Echo(exprs) => StmtKind::Echo(owned_exprs(exprs)),
2193        arena_ast::StmtKind::Return(e) => StmtKind::Return(e.map(|e| Box::new(owned_expr(e)))),
2194        arena_ast::StmtKind::Block(block) => StmtKind::Block(Box::new(owned_block(block))),
2195        arena_ast::StmtKind::If(s) => StmtKind::If(Box::new(IfStmt {
2196            condition: owned_expr(&s.condition),
2197            then_branch: Box::new(owned_stmt(s.then_branch)),
2198            elseif_branches: s
2199                .elseif_branches
2200                .iter()
2201                .map(|b| ElseIfBranch {
2202                    condition: owned_expr(&b.condition),
2203                    body: owned_stmt(&b.body),
2204                    span: b.span,
2205                })
2206                .collect::<Vec<_>>()
2207                .into_boxed_slice(),
2208            else_branch: s.else_branch.map(|b| Box::new(owned_stmt(b))),
2209            else_kw_span: s.else_kw_span,
2210            uses_alternative: s.uses_alternative,
2211        })),
2212        arena_ast::StmtKind::While(s) => StmtKind::While(Box::new(WhileStmt {
2213            condition: owned_expr(&s.condition),
2214            body: Box::new(owned_stmt(s.body)),
2215            uses_alternative: s.uses_alternative,
2216        })),
2217        arena_ast::StmtKind::For(s) => StmtKind::For(Box::new(ForStmt {
2218            init: owned_exprs(&s.init),
2219            condition: owned_exprs(&s.condition),
2220            update: owned_exprs(&s.update),
2221            body: Box::new(owned_stmt(s.body)),
2222            uses_alternative: s.uses_alternative,
2223        })),
2224        arena_ast::StmtKind::Foreach(s) => StmtKind::Foreach(Box::new(ForeachStmt {
2225            expr: owned_expr(&s.expr),
2226            key: s.key.as_ref().map(owned_expr),
2227            value: owned_expr(&s.value),
2228            body: Box::new(owned_stmt(s.body)),
2229            uses_alternative: s.uses_alternative,
2230        })),
2231        arena_ast::StmtKind::DoWhile(s) => StmtKind::DoWhile(Box::new(DoWhileStmt {
2232            body: Box::new(owned_stmt(s.body)),
2233            condition: owned_expr(&s.condition),
2234        })),
2235        arena_ast::StmtKind::Function(f) => StmtKind::Function(Box::new(owned_function_decl(f))),
2236        arena_ast::StmtKind::Break(e) => StmtKind::Break(e.map(|e| Box::new(owned_expr(e)))),
2237        arena_ast::StmtKind::Continue(e) => StmtKind::Continue(e.map(|e| Box::new(owned_expr(e)))),
2238        arena_ast::StmtKind::Switch(s) => StmtKind::Switch(Box::new(SwitchStmt {
2239            expr: owned_expr(&s.expr),
2240            body: SwitchBody {
2241                cases: s
2242                    .body
2243                    .cases
2244                    .iter()
2245                    .map(|c| SwitchCase {
2246                        value: c.value.as_ref().map(owned_expr),
2247                        body: owned_stmts(&c.body),
2248                        span: c.span,
2249                    })
2250                    .collect::<Vec<_>>()
2251                    .into_boxed_slice(),
2252                span: s.body.span,
2253            },
2254            uses_alternative: s.uses_alternative,
2255        })),
2256        arena_ast::StmtKind::Goto(ident) => StmtKind::Goto(owned_ident(*ident)),
2257        arena_ast::StmtKind::Label(ident) => StmtKind::Label(owned_ident(*ident)),
2258        arena_ast::StmtKind::Declare(d) => StmtKind::Declare(Box::new(DeclareStmt {
2259            directives: d
2260                .directives
2261                .iter()
2262                .map(|(k, v)| (Box::from(*k), owned_expr(v)))
2263                .collect::<Vec<_>>()
2264                .into_boxed_slice(),
2265            body: d.body.map(|b| Box::new(owned_stmt(b))),
2266            uses_alternative: d.uses_alternative,
2267        })),
2268        arena_ast::StmtKind::Unset(exprs) => StmtKind::Unset(owned_exprs(exprs)),
2269        arena_ast::StmtKind::Throw(e) => StmtKind::Throw(Box::new(owned_expr(e))),
2270        arena_ast::StmtKind::TryCatch(t) => StmtKind::TryCatch(Box::new(TryCatchStmt {
2271            body: Box::new(owned_block(t.body)),
2272            catches: t
2273                .catches
2274                .iter()
2275                .map(|c| CatchClause {
2276                    types: c
2277                        .types
2278                        .iter()
2279                        .map(owned_name)
2280                        .collect::<Vec<_>>()
2281                        .into_boxed_slice(),
2282                    var: c.var.map(Box::from),
2283                    body: Box::new(owned_block(c.body)),
2284                    span: c.span,
2285                })
2286                .collect::<Vec<_>>()
2287                .into_boxed_slice(),
2288            finally: t.finally.map(|f| Box::new(owned_block(f))),
2289            finally_kw_span: t.finally_kw_span,
2290        })),
2291        arena_ast::StmtKind::Global(exprs) => StmtKind::Global(owned_exprs(exprs)),
2292        arena_ast::StmtKind::Class(cls) => StmtKind::Class(Box::new(owned_class_decl(cls))),
2293        arena_ast::StmtKind::Interface(iface) => {
2294            StmtKind::Interface(Box::new(owned_interface_decl(iface)))
2295        }
2296        arena_ast::StmtKind::Trait(tr) => StmtKind::Trait(Box::new(owned_trait_decl(tr))),
2297        arena_ast::StmtKind::Enum(en) => StmtKind::Enum(Box::new(owned_enum_decl(en))),
2298        arena_ast::StmtKind::Namespace(ns) => StmtKind::Namespace(Box::new(NamespaceDecl {
2299            name: ns.name.as_ref().map(owned_name),
2300            body: match &ns.body {
2301                arena_ast::NamespaceBody::Braced(block) => {
2302                    NamespaceBody::Braced(Box::new(owned_block(block)))
2303                }
2304                arena_ast::NamespaceBody::Simple => NamespaceBody::Simple,
2305            },
2306        })),
2307        arena_ast::StmtKind::Use(u) => StmtKind::Use(Box::new(UseDecl {
2308            kind: u.kind,
2309            uses: u
2310                .uses
2311                .iter()
2312                .map(|item| UseItem {
2313                    name: owned_name(&item.name),
2314                    alias: item.alias.map(Box::from),
2315                    kind: item.kind,
2316                    span: item.span,
2317                })
2318                .collect::<Vec<_>>()
2319                .into_boxed_slice(),
2320        })),
2321        arena_ast::StmtKind::Const(items) => StmtKind::Const(
2322            items
2323                .iter()
2324                .map(|item| ConstItem {
2325                    name: owned_ident(item.name),
2326                    value: owned_expr(&item.value),
2327                    attributes: owned_attrs(&item.attributes),
2328                    span: item.span,
2329                    doc_comment: owned_opt_comment(&item.doc_comment),
2330                })
2331                .collect::<Vec<_>>()
2332                .into_boxed_slice(),
2333        ),
2334        arena_ast::StmtKind::StaticVar(vars) => StmtKind::StaticVar(
2335            vars.iter()
2336                .map(|v| StaticVar {
2337                    name: owned_ident(v.name),
2338                    default: v.default.as_ref().map(owned_expr),
2339                    span: v.span,
2340                })
2341                .collect::<Vec<_>>()
2342                .into_boxed_slice(),
2343        ),
2344        arena_ast::StmtKind::HaltCompiler(s) => StmtKind::HaltCompiler(Box::from(*s)),
2345        arena_ast::StmtKind::Nop => StmtKind::Nop,
2346        arena_ast::StmtKind::InlineHtml(s) => StmtKind::InlineHtml(Box::from(*s)),
2347        arena_ast::StmtKind::Error => StmtKind::Error,
2348    }
2349}
2350
2351fn owned_function_decl(f: &arena_ast::FunctionDecl<'_, '_>) -> FunctionDecl {
2352    FunctionDecl {
2353        name: owned_ident(f.name),
2354        params: owned_params(&f.params),
2355        body: Box::new(owned_block(f.body)),
2356        return_type: f.return_type.as_ref().map(owned_type_hint),
2357        by_ref: f.by_ref,
2358        attributes: owned_attrs(&f.attributes),
2359        doc_comment: owned_opt_comment(&f.doc_comment),
2360    }
2361}
2362
2363fn owned_class_member(m: &arena_ast::ClassMember<'_, '_>) -> ClassMember {
2364    ClassMember {
2365        kind: match &m.kind {
2366            arena_ast::ClassMemberKind::Property(p) => ClassMemberKind::Property(PropertyDecl {
2367                name: owned_ident(p.name),
2368                visibility: p.visibility,
2369                set_visibility: p.set_visibility,
2370                is_static: p.is_static,
2371                is_readonly: p.is_readonly,
2372                type_hint: p.type_hint.as_ref().map(owned_type_hint),
2373                default: p.default.as_ref().map(owned_expr),
2374                attributes: owned_attrs(&p.attributes),
2375                hooks: owned_hooks(&p.hooks),
2376                doc_comment: owned_opt_comment(&p.doc_comment),
2377            }),
2378            arena_ast::ClassMemberKind::Method(m) => ClassMemberKind::Method(MethodDecl {
2379                name: owned_ident(m.name),
2380                visibility: m.visibility,
2381                is_static: m.is_static,
2382                is_abstract: m.is_abstract,
2383                is_final: m.is_final,
2384                by_ref: m.by_ref,
2385                params: owned_params(&m.params),
2386                return_type: m.return_type.as_ref().map(owned_type_hint),
2387                body: m.body.map(|b| Box::new(owned_block(b))),
2388                attributes: owned_attrs(&m.attributes),
2389                doc_comment: owned_opt_comment(&m.doc_comment),
2390            }),
2391            arena_ast::ClassMemberKind::ClassConst(c) => {
2392                ClassMemberKind::ClassConst(owned_class_const(c))
2393            }
2394            arena_ast::ClassMemberKind::TraitUse(t) => {
2395                ClassMemberKind::TraitUse(owned_trait_use(t))
2396            }
2397        },
2398        span: m.span,
2399    }
2400}
2401
2402fn owned_class_members(members: &[arena_ast::ClassMember<'_, '_>]) -> Box<[ClassMember]> {
2403    members
2404        .iter()
2405        .map(owned_class_member)
2406        .collect::<Vec<_>>()
2407        .into_boxed_slice()
2408}
2409
2410fn owned_class_const(c: &arena_ast::ClassConstDecl<'_, '_>) -> ClassConstDecl {
2411    ClassConstDecl {
2412        name: owned_ident(c.name),
2413        visibility: c.visibility,
2414        is_final: c.is_final,
2415        type_hint: c.type_hint.map(|th| Box::new(owned_type_hint(th))),
2416        value: owned_expr(&c.value),
2417        attributes: owned_attrs(&c.attributes),
2418        doc_comment: owned_opt_comment(&c.doc_comment),
2419    }
2420}
2421
2422fn owned_trait_use(t: &arena_ast::TraitUseDecl<'_, '_>) -> TraitUseDecl {
2423    TraitUseDecl {
2424        traits: t
2425            .traits
2426            .iter()
2427            .map(owned_name)
2428            .collect::<Vec<_>>()
2429            .into_boxed_slice(),
2430        adaptations: t
2431            .adaptations
2432            .iter()
2433            .map(|a| TraitAdaptation {
2434                kind: match &a.kind {
2435                    arena_ast::TraitAdaptationKind::Precedence {
2436                        trait_name,
2437                        method,
2438                        insteadof,
2439                    } => TraitAdaptationKind::Precedence {
2440                        trait_name: owned_name(trait_name),
2441                        method: owned_name(method),
2442                        insteadof: insteadof
2443                            .iter()
2444                            .map(owned_name)
2445                            .collect::<Vec<_>>()
2446                            .into_boxed_slice(),
2447                    },
2448                    arena_ast::TraitAdaptationKind::Alias {
2449                        trait_name,
2450                        method,
2451                        new_modifier,
2452                        new_name,
2453                    } => TraitAdaptationKind::Alias {
2454                        trait_name: trait_name.as_ref().map(owned_name),
2455                        method: owned_name(method),
2456                        new_modifier: *new_modifier,
2457                        new_name: new_name.as_ref().map(owned_name),
2458                    },
2459                },
2460                span: a.span,
2461            })
2462            .collect::<Vec<_>>()
2463            .into_boxed_slice(),
2464        adaptations_brace_span: t.adaptations_brace_span,
2465        doc_comment: owned_opt_comment(&t.doc_comment),
2466    }
2467}
2468
2469fn owned_class_decl(cls: &arena_ast::ClassDecl<'_, '_>) -> ClassDecl {
2470    ClassDecl {
2471        name: cls
2472            .name
2473            .map(|ident| Some(owned_ident(ident)))
2474            .unwrap_or(None),
2475        modifiers: cls.modifiers.clone(),
2476        extends: cls.extends.as_ref().map(owned_name),
2477        implements: cls
2478            .implements
2479            .iter()
2480            .map(owned_name)
2481            .collect::<Vec<_>>()
2482            .into_boxed_slice(),
2483        body: ClassBody {
2484            members: owned_class_members(&cls.body.members),
2485            span: cls.body.span,
2486        },
2487        attributes: owned_attrs(&cls.attributes),
2488        doc_comment: owned_opt_comment(&cls.doc_comment),
2489    }
2490}
2491
2492fn owned_interface_decl(iface: &arena_ast::InterfaceDecl<'_, '_>) -> InterfaceDecl {
2493    InterfaceDecl {
2494        name: owned_ident(iface.name),
2495        extends: iface
2496            .extends
2497            .iter()
2498            .map(owned_name)
2499            .collect::<Vec<_>>()
2500            .into_boxed_slice(),
2501        body: ClassBody {
2502            members: owned_class_members(&iface.body.members),
2503            span: iface.body.span,
2504        },
2505        attributes: owned_attrs(&iface.attributes),
2506        doc_comment: owned_opt_comment(&iface.doc_comment),
2507    }
2508}
2509
2510fn owned_trait_decl(tr: &arena_ast::TraitDecl<'_, '_>) -> TraitDecl {
2511    TraitDecl {
2512        name: owned_ident(tr.name),
2513        body: ClassBody {
2514            members: owned_class_members(&tr.body.members),
2515            span: tr.body.span,
2516        },
2517        attributes: owned_attrs(&tr.attributes),
2518        doc_comment: owned_opt_comment(&tr.doc_comment),
2519    }
2520}
2521
2522fn owned_enum_decl(en: &arena_ast::EnumDecl<'_, '_>) -> EnumDecl {
2523    EnumDecl {
2524        name: owned_ident(en.name),
2525        scalar_type: en.scalar_type.as_ref().map(owned_name),
2526        implements: en
2527            .implements
2528            .iter()
2529            .map(owned_name)
2530            .collect::<Vec<_>>()
2531            .into_boxed_slice(),
2532        body: EnumBody {
2533            members: en
2534                .body
2535                .members
2536                .iter()
2537                .map(|m| EnumMember {
2538                    kind: match &m.kind {
2539                        arena_ast::EnumMemberKind::Case(c) => EnumMemberKind::Case(EnumCase {
2540                            name: owned_ident(c.name),
2541                            value: c.value.as_ref().map(owned_expr),
2542                            attributes: owned_attrs(&c.attributes),
2543                            doc_comment: owned_opt_comment(&c.doc_comment),
2544                        }),
2545                        arena_ast::EnumMemberKind::Method(m) => {
2546                            EnumMemberKind::Method(MethodDecl {
2547                                name: owned_ident(m.name),
2548                                visibility: m.visibility,
2549                                is_static: m.is_static,
2550                                is_abstract: m.is_abstract,
2551                                is_final: m.is_final,
2552                                by_ref: m.by_ref,
2553                                params: owned_params(&m.params),
2554                                return_type: m.return_type.as_ref().map(owned_type_hint),
2555                                body: m.body.map(|b| Box::new(owned_block(b))),
2556                                attributes: owned_attrs(&m.attributes),
2557                                doc_comment: owned_opt_comment(&m.doc_comment),
2558                            })
2559                        }
2560                        arena_ast::EnumMemberKind::ClassConst(c) => {
2561                            EnumMemberKind::ClassConst(owned_class_const(c))
2562                        }
2563                        arena_ast::EnumMemberKind::TraitUse(t) => {
2564                            EnumMemberKind::TraitUse(owned_trait_use(t))
2565                        }
2566                    },
2567                    span: m.span,
2568                })
2569                .collect::<Vec<_>>()
2570                .into_boxed_slice(),
2571            span: en.body.span,
2572        },
2573        attributes: owned_attrs(&en.attributes),
2574        doc_comment: owned_opt_comment(&en.doc_comment),
2575    }
2576}
2577
2578/// Convert an arena-allocated [`Program`](crate::ast::Program) into a [`Program`].
2579pub fn to_owned_program(program: &arena_ast::Program<'_, '_>) -> Program {
2580    Program {
2581        stmts: owned_stmts(&program.stmts),
2582        span: program.span,
2583    }
2584}
2585
2586// ===========================================================================
2587// Reverse conversion: owned → arena
2588// ===========================================================================
2589
2590/// Convert an owned [`Program`] into an arena-allocated
2591/// [`Program`](crate::ast::Program).
2592///
2593/// All strings are copied into `arena`. Both lifetime parameters of the
2594/// returned type are `'arena` because every string originates in the arena
2595/// rather than a source buffer.
2596///
2597/// Used internally by the pretty printer so callers can pass an owned program
2598/// without round-tripping through `parse_arena()`. Not part of the stable API.
2599#[doc(hidden)]
2600pub fn from_owned_program<'arena>(
2601    arena: &'arena bumpalo::Bump,
2602    program: &Program,
2603) -> arena_ast::Program<'arena, 'arena> {
2604    arena_ast::Program {
2605        stmts: av_stmts(arena, &program.stmts),
2606        span: program.span,
2607    }
2608}
2609
2610// Helper: allocate a string in the arena.
2611#[inline]
2612fn av_str<'a>(arena: &'a bumpalo::Bump, s: &str) -> &'a str {
2613    arena.alloc_str(s)
2614}
2615
2616fn av_ident<'a>(arena: &'a bumpalo::Bump, ident: &Ident) -> arena_ast::Ident<'a> {
2617    match ident.as_str() {
2618        Some(s) => arena_ast::Ident::name(arena.alloc_str(s)),
2619        None => arena_ast::Ident::ERROR,
2620    }
2621}
2622
2623fn av_name<'a>(arena: &'a bumpalo::Bump, name: &Name) -> arena_ast::Name<'a, 'a> {
2624    if name.kind == NameKind::Error {
2625        return arena_ast::Name::Error { span: name.span };
2626    }
2627    if name.parts.len() == 1 && name.kind == NameKind::Unqualified {
2628        return arena_ast::Name::Simple {
2629            value: arena.alloc_str(&name.parts[0]),
2630            span: name.span,
2631        };
2632    }
2633    let mut parts = arena_ast::ArenaVec::with_capacity_in(name.parts.len(), arena);
2634    for p in name.parts.iter() {
2635        parts.push(arena.alloc_str(p) as &str);
2636    }
2637    arena_ast::Name::Complex {
2638        parts,
2639        kind: name.kind,
2640        span: name.span,
2641    }
2642}
2643
2644fn av_type_hint<'a>(arena: &'a bumpalo::Bump, th: &TypeHint) -> arena_ast::TypeHint<'a, 'a> {
2645    arena_ast::TypeHint {
2646        kind: av_type_hint_kind(arena, &th.kind),
2647        span: th.span,
2648    }
2649}
2650
2651fn av_type_hint_kind<'a>(
2652    arena: &'a bumpalo::Bump,
2653    k: &TypeHintKind,
2654) -> arena_ast::TypeHintKind<'a, 'a> {
2655    match k {
2656        TypeHintKind::Named(n) => arena_ast::TypeHintKind::Named(av_name(arena, n)),
2657        TypeHintKind::Keyword(b, span) => arena_ast::TypeHintKind::Keyword(*b, *span),
2658        TypeHintKind::Nullable(inner) => {
2659            arena_ast::TypeHintKind::Nullable(arena.alloc(av_type_hint(arena, inner)))
2660        }
2661        TypeHintKind::Union(types) => {
2662            let mut v = arena_ast::ArenaVec::with_capacity_in(types.len(), arena);
2663            for t in types.iter() {
2664                v.push(av_type_hint(arena, t));
2665            }
2666            arena_ast::TypeHintKind::Union(v)
2667        }
2668        TypeHintKind::Intersection(types) => {
2669            let mut v = arena_ast::ArenaVec::with_capacity_in(types.len(), arena);
2670            for t in types.iter() {
2671                v.push(av_type_hint(arena, t));
2672            }
2673            arena_ast::TypeHintKind::Intersection(v)
2674        }
2675    }
2676}
2677
2678fn av_arg<'a>(arena: &'a bumpalo::Bump, arg: &Arg) -> arena_ast::Arg<'a, 'a> {
2679    arena_ast::Arg {
2680        name: arg.name.as_ref().map(|n| av_name(arena, n)),
2681        value: arg.value.as_ref().map(|v| av_expr(arena, v)),
2682        unpack: arg.unpack,
2683        by_ref: arg.by_ref,
2684        span: arg.span,
2685    }
2686}
2687
2688#[inline(never)]
2689fn av_args<'a>(
2690    arena: &'a bumpalo::Bump,
2691    args: &[Arg],
2692) -> arena_ast::ArenaVec<'a, arena_ast::Arg<'a, 'a>> {
2693    let mut v = arena_ast::ArenaVec::with_capacity_in(args.len(), arena);
2694    for a in args.iter() {
2695        v.push(av_arg(arena, a));
2696    }
2697    v
2698}
2699
2700fn av_attr<'a>(arena: &'a bumpalo::Bump, attr: &Attribute) -> arena_ast::Attribute<'a, 'a> {
2701    arena_ast::Attribute {
2702        name: av_name(arena, &attr.name),
2703        args: av_args(arena, &attr.args),
2704        span: attr.span,
2705    }
2706}
2707
2708#[inline(never)]
2709fn av_attrs<'a>(
2710    arena: &'a bumpalo::Bump,
2711    attrs: &[Attribute],
2712) -> arena_ast::ArenaVec<'a, arena_ast::Attribute<'a, 'a>> {
2713    let mut v = arena_ast::ArenaVec::with_capacity_in(attrs.len(), arena);
2714    for a in attrs.iter() {
2715        v.push(av_attr(arena, a));
2716    }
2717    v
2718}
2719
2720fn av_comment<'a>(arena: &'a bumpalo::Bump, c: &Comment) -> arena_ast::Comment<'a> {
2721    arena_ast::Comment {
2722        kind: c.kind,
2723        text: arena.alloc_str(&c.text),
2724        span: c.span,
2725    }
2726}
2727
2728fn av_opt_comment<'a>(
2729    arena: &'a bumpalo::Bump,
2730    c: &Option<Comment>,
2731) -> Option<arena_ast::Comment<'a>> {
2732    c.as_ref().map(|c| av_comment(arena, c))
2733}
2734
2735fn av_string_parts<'a>(
2736    arena: &'a bumpalo::Bump,
2737    parts: &[StringPart],
2738) -> arena_ast::ArenaVec<'a, arena_ast::StringPart<'a, 'a>> {
2739    let mut v = arena_ast::ArenaVec::with_capacity_in(parts.len(), arena);
2740    for p in parts.iter() {
2741        v.push(match p {
2742            StringPart::Literal(s) => arena_ast::StringPart::Literal(arena.alloc_str(s)),
2743            StringPart::Expr(e) => arena_ast::StringPart::Expr(av_expr(arena, e)),
2744        });
2745    }
2746    v
2747}
2748
2749fn av_expr<'a>(arena: &'a bumpalo::Bump, expr: &Expr) -> arena_ast::Expr<'a, 'a> {
2750    if !owned_is_chain(&expr.kind) {
2751        return arena_ast::Expr {
2752            kind: av_leaf_kind(arena, &expr.kind),
2753            span: expr.span,
2754        };
2755    }
2756    av_chain_expr(arena, expr)
2757}
2758
2759/// Bottom-up over an explicit stack, as in `owned_chain_expr`.
2760#[inline(never)]
2761fn av_chain_expr<'a>(arena: &'a bumpalo::Bump, expr: &Expr) -> arena_ast::Expr<'a, 'a> {
2762    let first = owned_chain_operand(&expr.kind).expect("chain link has an operand");
2763    if !owned_is_chain(&first.kind) {
2764        let operand = arena_ast::Expr {
2765            kind: av_leaf_kind(arena, &first.kind),
2766            span: first.span,
2767        };
2768        return arena_ast::Expr {
2769            kind: av_link_kind(arena, &expr.kind, operand),
2770            span: expr.span,
2771        };
2772    }
2773    let mut chain = LinkStack::new();
2774    chain.push(expr);
2775    let mut leaf = first;
2776    while let Some(operand) = owned_chain_operand(&leaf.kind) {
2777        chain.push(leaf);
2778        leaf = operand;
2779    }
2780    let mut converted = arena_ast::Expr {
2781        kind: av_leaf_kind(arena, &leaf.kind),
2782        span: leaf.span,
2783    };
2784    while let Some(node) = chain.pop() {
2785        converted = arena_ast::Expr {
2786            kind: av_link_kind(arena, &node.kind, converted),
2787            span: node.span,
2788        };
2789    }
2790    converted
2791}
2792
2793#[inline(never)]
2794fn av_exprs<'a>(
2795    arena: &'a bumpalo::Bump,
2796    exprs: &[Expr],
2797) -> arena_ast::ArenaVec<'a, arena_ast::Expr<'a, 'a>> {
2798    let mut v = arena_ast::ArenaVec::with_capacity_in(exprs.len(), arena);
2799    for e in exprs.iter() {
2800        v.push(av_expr(arena, e));
2801    }
2802    v
2803}
2804
2805fn av_namestr<'a>(arena: &'a bumpalo::Bump, s: &str) -> arena_ast::NameStr<'a, 'a> {
2806    arena_ast::NameStr::__arena(arena.alloc_str(s))
2807}
2808
2809/// Convert an expression kind that is not a chain link (see [`owned_chain_operand`]).
2810fn av_leaf_kind<'a>(arena: &'a bumpalo::Bump, k: &ExprKind) -> arena_ast::ExprKind<'a, 'a> {
2811    match k {
2812        ExprKind::Int(v) => arena_ast::ExprKind::Int(*v),
2813        ExprKind::Float(v) => arena_ast::ExprKind::Float(*v),
2814        ExprKind::String(s) => arena_ast::ExprKind::String(av_namestr(arena, s)),
2815        ExprKind::InterpolatedString(parts) => {
2816            arena_ast::ExprKind::InterpolatedString(av_string_parts(arena, parts))
2817        }
2818        ExprKind::Heredoc { label, parts } => arena_ast::ExprKind::Heredoc {
2819            label: arena.alloc_str(label),
2820            parts: av_string_parts(arena, parts),
2821        },
2822        ExprKind::Nowdoc { label, value } => arena_ast::ExprKind::Nowdoc {
2823            label: arena.alloc_str(label),
2824            value: arena.alloc_str(value),
2825        },
2826        ExprKind::ShellExec(parts) => arena_ast::ExprKind::ShellExec(av_string_parts(arena, parts)),
2827        ExprKind::Bool(v) => arena_ast::ExprKind::Bool(*v),
2828        ExprKind::Null => arena_ast::ExprKind::Null,
2829        ExprKind::Variable(s) => arena_ast::ExprKind::Variable(av_namestr(arena, s)),
2830        ExprKind::VariableVariable(inner) => {
2831            arena_ast::ExprKind::VariableVariable(arena.alloc(av_expr(arena, inner)))
2832        }
2833        ExprKind::Identifier(s) => arena_ast::ExprKind::Identifier(av_namestr(arena, s)),
2834        ExprKind::UnaryPrefix(u) => arena_ast::ExprKind::UnaryPrefix(arena_ast::UnaryPrefixExpr {
2835            op: u.op,
2836            operand: arena.alloc(av_expr(arena, &u.operand)),
2837        }),
2838        ExprKind::Array(elems) => {
2839            let mut v = arena_ast::ArenaVec::with_capacity_in(elems.len(), arena);
2840            for e in elems.iter() {
2841                v.push(arena_ast::ArrayElement {
2842                    key: e.key.as_ref().map(|k| av_expr(arena, k)),
2843                    value: av_expr(arena, &e.value),
2844                    unpack: e.unpack,
2845                    by_ref: e.by_ref,
2846                    span: e.span,
2847                });
2848            }
2849            arena_ast::ExprKind::Array(v)
2850        }
2851        ExprKind::Print(e) => arena_ast::ExprKind::Print(arena.alloc(av_expr(arena, e))),
2852        ExprKind::Parenthesized(e) => {
2853            arena_ast::ExprKind::Parenthesized(arena.alloc(av_expr(arena, e)))
2854        }
2855        ExprKind::Cast(kind, e) => arena_ast::ExprKind::Cast(*kind, arena.alloc(av_expr(arena, e))),
2856        ExprKind::ErrorSuppress(e) => {
2857            arena_ast::ExprKind::ErrorSuppress(arena.alloc(av_expr(arena, e)))
2858        }
2859        ExprKind::Isset(exprs) => arena_ast::ExprKind::Isset(av_exprs(arena, exprs)),
2860        ExprKind::Empty(e) => arena_ast::ExprKind::Empty(arena.alloc(av_expr(arena, e))),
2861        ExprKind::Include(kind, e) => {
2862            arena_ast::ExprKind::Include(*kind, arena.alloc(av_expr(arena, e)))
2863        }
2864        ExprKind::Eval(e) => arena_ast::ExprKind::Eval(arena.alloc(av_expr(arena, e))),
2865        ExprKind::Exit(e) => {
2866            arena_ast::ExprKind::Exit(e.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))))
2867        }
2868        ExprKind::MagicConst(m) => arena_ast::ExprKind::MagicConst(*m),
2869        ExprKind::Clone(e) => arena_ast::ExprKind::Clone(arena.alloc(av_expr(arena, e))),
2870        ExprKind::CloneWith(obj, props) => arena_ast::ExprKind::CloneWith(
2871            arena.alloc(av_expr(arena, obj)),
2872            arena.alloc(av_expr(arena, props)),
2873        ),
2874        ExprKind::New(n) => arena_ast::ExprKind::New(arena_ast::NewExpr {
2875            class: arena.alloc(av_expr(arena, &n.class)),
2876            args: av_args(arena, &n.args),
2877        }),
2878        ExprKind::Closure(c) => arena_ast::ExprKind::Closure(arena.alloc(arena_ast::ClosureExpr {
2879            is_static: c.is_static,
2880            by_ref: c.by_ref,
2881            params: av_params(arena, &c.params),
2882            use_vars: {
2883                let mut v = arena_ast::ArenaVec::with_capacity_in(c.use_vars.len(), arena);
2884                for uv in c.use_vars.iter() {
2885                    v.push(arena_ast::ClosureUseVar {
2886                        name: arena.alloc_str(&uv.name),
2887                        by_ref: uv.by_ref,
2888                        span: uv.span,
2889                    });
2890                }
2891                v
2892            },
2893            return_type: c.return_type.as_ref().map(|t| av_type_hint(arena, t)),
2894            body: &*arena.alloc(av_block(arena, &c.body)),
2895            attributes: av_attrs(arena, &c.attributes),
2896        })),
2897        ExprKind::ArrowFunction(f) => {
2898            arena_ast::ExprKind::ArrowFunction(arena.alloc(arena_ast::ArrowFunctionExpr {
2899                is_static: f.is_static,
2900                by_ref: f.by_ref,
2901                params: av_params(arena, &f.params),
2902                return_type: f.return_type.as_ref().map(|t| av_type_hint(arena, t)),
2903                body: arena.alloc(av_expr(arena, &f.body)),
2904                attributes: av_attrs(arena, &f.attributes),
2905            }))
2906        }
2907        ExprKind::Match(m) => arena_ast::ExprKind::Match(arena_ast::MatchExpr {
2908            subject: arena.alloc(av_expr(arena, &m.subject)),
2909            arms: {
2910                let mut v = arena_ast::ArenaVec::with_capacity_in(m.arms.len(), arena);
2911                for arm in m.arms.iter() {
2912                    v.push(arena_ast::MatchArm {
2913                        conditions: arm.conditions.as_ref().map(|conds| av_exprs(arena, conds)),
2914                        body: av_expr(arena, &arm.body),
2915                        span: arm.span,
2916                    });
2917                }
2918                v
2919            },
2920            brace_span: m.brace_span,
2921        }),
2922        ExprKind::Throw(e) => arena_ast::ExprKind::Throw(arena.alloc(av_expr(arena, e))),
2923        ExprKind::Yield(y) => arena_ast::ExprKind::Yield(arena_ast::YieldExpr {
2924            key: y.key.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))),
2925            value: y.value.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))),
2926            is_from: y.is_from,
2927        }),
2928        ExprKind::AnonymousClass(cls) => {
2929            arena_ast::ExprKind::AnonymousClass(arena.alloc(av_class_decl(arena, cls)))
2930        }
2931        ExprKind::Omit => arena_ast::ExprKind::Omit,
2932        ExprKind::Error => arena_ast::ExprKind::Error,
2933        ExprKind::Assign(_)
2934        | ExprKind::Binary(_)
2935        | ExprKind::UnaryPostfix(_)
2936        | ExprKind::Ternary(_)
2937        | ExprKind::NullCoalesce(_)
2938        | ExprKind::FunctionCall(_)
2939        | ExprKind::ArrayAccess(_)
2940        | ExprKind::PropertyAccess(_)
2941        | ExprKind::NullsafePropertyAccess(_)
2942        | ExprKind::MethodCall(_)
2943        | ExprKind::NullsafeMethodCall(_)
2944        | ExprKind::StaticPropertyAccess(_)
2945        | ExprKind::ClassConstAccess(_)
2946        | ExprKind::StaticMethodCall(_)
2947        | ExprKind::StaticMethodCallDynamic(_)
2948        | ExprKind::ClassConstAccessDynamic { .. }
2949        | ExprKind::StaticPropertyAccessDynamic { .. }
2950        | ExprKind::CallableCreate(_) => {
2951            unreachable!("chain links are converted by the link function")
2952        }
2953    }
2954}
2955
2956/// Convert a chain link, given its already-converted chain operand.
2957fn av_link_kind<'a>(
2958    arena: &'a bumpalo::Bump,
2959    k: &ExprKind,
2960    operand: arena_ast::Expr<'a, 'a>,
2961) -> arena_ast::ExprKind<'a, 'a> {
2962    match k {
2963        ExprKind::Assign(a) => arena_ast::ExprKind::Assign(arena_ast::AssignExpr {
2964            target: arena.alloc(operand),
2965            op: a.op,
2966            value: arena.alloc(av_expr(arena, &a.value)),
2967            by_ref: a.by_ref,
2968        }),
2969        ExprKind::Binary(b) => arena_ast::ExprKind::Binary(arena_ast::BinaryExpr {
2970            left: arena.alloc(operand),
2971            op: b.op,
2972            right: arena.alloc(av_expr(arena, &b.right)),
2973        }),
2974        ExprKind::UnaryPostfix(u) => {
2975            arena_ast::ExprKind::UnaryPostfix(arena_ast::UnaryPostfixExpr {
2976                operand: arena.alloc(operand),
2977                op: u.op,
2978            })
2979        }
2980        ExprKind::Ternary(t) => arena_ast::ExprKind::Ternary(arena_ast::TernaryExpr {
2981            condition: arena.alloc(operand),
2982            then_expr: t
2983                .then_expr
2984                .as_ref()
2985                .map(|e| &*arena.alloc(av_expr(arena, e))),
2986            else_expr: arena.alloc(av_expr(arena, &t.else_expr)),
2987        }),
2988        ExprKind::NullCoalesce(n) => {
2989            arena_ast::ExprKind::NullCoalesce(arena_ast::NullCoalesceExpr {
2990                left: arena.alloc(operand),
2991                right: arena.alloc(av_expr(arena, &n.right)),
2992            })
2993        }
2994        ExprKind::FunctionCall(f) => {
2995            arena_ast::ExprKind::FunctionCall(arena_ast::FunctionCallExpr {
2996                name: arena.alloc(operand),
2997                args: av_args(arena, &f.args),
2998            })
2999        }
3000        ExprKind::ArrayAccess(a) => arena_ast::ExprKind::ArrayAccess(arena_ast::ArrayAccessExpr {
3001            array: arena.alloc(operand),
3002            index: a.index.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))),
3003        }),
3004        ExprKind::PropertyAccess(p) => {
3005            arena_ast::ExprKind::PropertyAccess(arena_ast::PropertyAccessExpr {
3006                object: arena.alloc(operand),
3007                property: arena.alloc(av_expr(arena, &p.property)),
3008            })
3009        }
3010        ExprKind::NullsafePropertyAccess(p) => {
3011            arena_ast::ExprKind::NullsafePropertyAccess(arena_ast::PropertyAccessExpr {
3012                object: arena.alloc(operand),
3013                property: arena.alloc(av_expr(arena, &p.property)),
3014            })
3015        }
3016        ExprKind::MethodCall(m) => {
3017            arena_ast::ExprKind::MethodCall(arena.alloc(arena_ast::MethodCallExpr {
3018                object: arena.alloc(operand),
3019                method: arena.alloc(av_expr(arena, &m.method)),
3020                args: av_args(arena, &m.args),
3021            }))
3022        }
3023        ExprKind::NullsafeMethodCall(m) => {
3024            arena_ast::ExprKind::NullsafeMethodCall(arena.alloc(arena_ast::MethodCallExpr {
3025                object: arena.alloc(operand),
3026                method: arena.alloc(av_expr(arena, &m.method)),
3027                args: av_args(arena, &m.args),
3028            }))
3029        }
3030        ExprKind::StaticPropertyAccess(s) => {
3031            arena_ast::ExprKind::StaticPropertyAccess(arena_ast::StaticAccessExpr {
3032                class: arena.alloc(operand),
3033                member: arena.alloc(av_expr(arena, &s.member)),
3034            })
3035        }
3036        ExprKind::StaticMethodCall(s) => {
3037            arena_ast::ExprKind::StaticMethodCall(arena.alloc(arena_ast::StaticMethodCallExpr {
3038                class: arena.alloc(operand),
3039                method: arena.alloc(av_expr(arena, &s.method)),
3040                args: av_args(arena, &s.args),
3041            }))
3042        }
3043        ExprKind::StaticMethodCallDynamic(s) => arena_ast::ExprKind::StaticMethodCallDynamic(
3044            arena.alloc(arena_ast::StaticMethodCallDynamicExpr {
3045                class: arena.alloc(operand),
3046                method: arena.alloc(av_expr(arena, &s.method)),
3047                args: av_args(arena, &s.args),
3048            }),
3049        ),
3050        ExprKind::ClassConstAccess(s) => {
3051            arena_ast::ExprKind::ClassConstAccess(arena_ast::StaticAccessExpr {
3052                class: arena.alloc(operand),
3053                member: arena.alloc(av_expr(arena, &s.member)),
3054            })
3055        }
3056        ExprKind::ClassConstAccessDynamic { member, .. } => {
3057            arena_ast::ExprKind::ClassConstAccessDynamic {
3058                class: arena.alloc(operand),
3059                member: arena.alloc(av_expr(arena, member)),
3060            }
3061        }
3062        ExprKind::StaticPropertyAccessDynamic { member, .. } => {
3063            arena_ast::ExprKind::StaticPropertyAccessDynamic {
3064                class: arena.alloc(operand),
3065                member: arena.alloc(av_expr(arena, member)),
3066            }
3067        }
3068        ExprKind::CallableCreate(c) => {
3069            arena_ast::ExprKind::CallableCreate(arena_ast::CallableCreateExpr {
3070                kind: match &c.kind {
3071                    CallableCreateKind::Function(_) => {
3072                        arena_ast::CallableCreateKind::Function(arena.alloc(operand))
3073                    }
3074                    CallableCreateKind::Method { method, .. } => {
3075                        arena_ast::CallableCreateKind::Method {
3076                            object: arena.alloc(operand),
3077                            method: arena.alloc(av_expr(arena, method)),
3078                        }
3079                    }
3080                    CallableCreateKind::NullsafeMethod { method, .. } => {
3081                        arena_ast::CallableCreateKind::NullsafeMethod {
3082                            object: arena.alloc(operand),
3083                            method: arena.alloc(av_expr(arena, method)),
3084                        }
3085                    }
3086                    CallableCreateKind::StaticMethod { method, .. } => {
3087                        arena_ast::CallableCreateKind::StaticMethod {
3088                            class: arena.alloc(operand),
3089                            method: arena.alloc(av_expr(arena, method)),
3090                        }
3091                    }
3092                },
3093            })
3094        }
3095        _ => unreachable!("not a chain link"),
3096    }
3097}
3098
3099fn av_param<'a>(arena: &'a bumpalo::Bump, p: &Param) -> arena_ast::Param<'a, 'a> {
3100    arena_ast::Param {
3101        name: av_ident(arena, &p.name),
3102        type_hint: p.type_hint.as_ref().map(|t| av_type_hint(arena, t)),
3103        default: p.default.as_ref().map(|e| av_expr(arena, e)),
3104        by_ref: p.by_ref,
3105        variadic: p.variadic,
3106        is_readonly: p.is_readonly,
3107        is_final: p.is_final,
3108        visibility: p.visibility,
3109        set_visibility: p.set_visibility,
3110        attributes: av_attrs(arena, &p.attributes),
3111        hooks: av_hooks(arena, &p.hooks),
3112        doc_comment: av_opt_comment(arena, &p.doc_comment),
3113        span: p.span,
3114    }
3115}
3116
3117#[inline(never)]
3118fn av_params<'a>(
3119    arena: &'a bumpalo::Bump,
3120    params: &[Param],
3121) -> arena_ast::ArenaVec<'a, arena_ast::Param<'a, 'a>> {
3122    let mut v = arena_ast::ArenaVec::with_capacity_in(params.len(), arena);
3123    for p in params.iter() {
3124        v.push(av_param(arena, p));
3125    }
3126    v
3127}
3128
3129fn av_hook<'a>(arena: &'a bumpalo::Bump, h: &PropertyHook) -> arena_ast::PropertyHook<'a, 'a> {
3130    arena_ast::PropertyHook {
3131        kind: h.kind,
3132        body: match &h.body {
3133            PropertyHookBody::Block(block) => {
3134                arena_ast::PropertyHookBody::Block(arena.alloc(av_block(arena, block)))
3135            }
3136            PropertyHookBody::Expression(e) => {
3137                arena_ast::PropertyHookBody::Expression(av_expr(arena, e))
3138            }
3139            PropertyHookBody::Abstract => arena_ast::PropertyHookBody::Abstract,
3140        },
3141        is_final: h.is_final,
3142        by_ref: h.by_ref,
3143        params: av_params(arena, &h.params),
3144        attributes: av_attrs(arena, &h.attributes),
3145        span: h.span,
3146    }
3147}
3148
3149fn av_hooks<'a>(
3150    arena: &'a bumpalo::Bump,
3151    hooks: &[PropertyHook],
3152) -> arena_ast::ArenaVec<'a, arena_ast::PropertyHook<'a, 'a>> {
3153    let mut v = arena_ast::ArenaVec::with_capacity_in(hooks.len(), arena);
3154    for h in hooks.iter() {
3155        v.push(av_hook(arena, h));
3156    }
3157    v
3158}
3159
3160fn av_stmt<'a>(arena: &'a bumpalo::Bump, stmt: &Stmt) -> arena_ast::Stmt<'a, 'a> {
3161    arena_ast::Stmt {
3162        kind: av_stmt_kind(arena, &stmt.kind),
3163        span: stmt.span,
3164        doc_comment: stmt.doc_comment.as_ref().map(|c| {
3165            &*arena.alloc(arena_ast::Comment {
3166                kind: c.kind,
3167                text: arena.alloc_str(&c.text),
3168                span: c.span,
3169            })
3170        }),
3171    }
3172}
3173
3174fn av_stmts<'a>(
3175    arena: &'a bumpalo::Bump,
3176    stmts: &[Stmt],
3177) -> arena_ast::ArenaVec<'a, arena_ast::Stmt<'a, 'a>> {
3178    let mut v = arena_ast::ArenaVec::with_capacity_in(stmts.len(), arena);
3179    for s in stmts.iter() {
3180        v.push(av_stmt(arena, s));
3181    }
3182    v
3183}
3184
3185fn av_block<'a>(arena: &'a bumpalo::Bump, block: &Block) -> arena_ast::Block<'a, 'a> {
3186    arena_ast::Block {
3187        stmts: av_stmts(arena, &block.stmts),
3188        span: block.span,
3189    }
3190}
3191
3192fn av_stmt_kind<'a>(arena: &'a bumpalo::Bump, k: &StmtKind) -> arena_ast::StmtKind<'a, 'a> {
3193    match k {
3194        StmtKind::Expression(e) => arena_ast::StmtKind::Expression(arena.alloc(av_expr(arena, e))),
3195        StmtKind::Echo(exprs) => arena_ast::StmtKind::Echo(av_exprs(arena, exprs)),
3196        StmtKind::Return(e) => {
3197            arena_ast::StmtKind::Return(e.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))))
3198        }
3199        StmtKind::Block(block) => arena_ast::StmtKind::Block(arena.alloc(av_block(arena, block))),
3200        StmtKind::If(s) => arena_ast::StmtKind::If(arena.alloc(av_if_stmt(arena, s))),
3201        StmtKind::While(s) => arena_ast::StmtKind::While(arena.alloc(av_while_stmt(arena, s))),
3202        StmtKind::For(s) => arena_ast::StmtKind::For(arena.alloc(av_for_stmt(arena, s))),
3203        StmtKind::Foreach(s) => {
3204            arena_ast::StmtKind::Foreach(arena.alloc(av_foreach_stmt(arena, s)))
3205        }
3206        StmtKind::DoWhile(s) => {
3207            arena_ast::StmtKind::DoWhile(arena.alloc(av_do_while_stmt(arena, s)))
3208        }
3209        StmtKind::Function(f) => {
3210            arena_ast::StmtKind::Function(arena.alloc(av_function_decl(arena, f)))
3211        }
3212        StmtKind::Break(e) => {
3213            arena_ast::StmtKind::Break(e.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))))
3214        }
3215        StmtKind::Continue(e) => {
3216            arena_ast::StmtKind::Continue(e.as_ref().map(|e| &*arena.alloc(av_expr(arena, e))))
3217        }
3218        StmtKind::Switch(s) => arena_ast::StmtKind::Switch(arena.alloc(av_switch_stmt(arena, s))),
3219        StmtKind::Goto(ident) => arena_ast::StmtKind::Goto(av_ident(arena, ident)),
3220        StmtKind::Label(ident) => arena_ast::StmtKind::Label(av_ident(arena, ident)),
3221        StmtKind::Declare(d) => {
3222            arena_ast::StmtKind::Declare(arena.alloc(av_declare_stmt(arena, d)))
3223        }
3224        StmtKind::Unset(exprs) => arena_ast::StmtKind::Unset(av_exprs(arena, exprs)),
3225        StmtKind::Throw(e) => arena_ast::StmtKind::Throw(arena.alloc(av_expr(arena, e))),
3226        StmtKind::TryCatch(t) => {
3227            arena_ast::StmtKind::TryCatch(arena.alloc(av_try_catch_stmt(arena, t)))
3228        }
3229        StmtKind::Global(exprs) => arena_ast::StmtKind::Global(av_exprs(arena, exprs)),
3230        StmtKind::Class(cls) => arena_ast::StmtKind::Class(arena.alloc(av_class_decl(arena, cls))),
3231        StmtKind::Interface(iface) => {
3232            arena_ast::StmtKind::Interface(arena.alloc(av_interface_decl(arena, iface)))
3233        }
3234        StmtKind::Trait(tr) => arena_ast::StmtKind::Trait(arena.alloc(av_trait_decl(arena, tr))),
3235        StmtKind::Enum(en) => arena_ast::StmtKind::Enum(arena.alloc(av_enum_decl(arena, en))),
3236        StmtKind::Namespace(ns) => {
3237            arena_ast::StmtKind::Namespace(arena.alloc(av_namespace_decl(arena, ns)))
3238        }
3239        StmtKind::Use(u) => arena_ast::StmtKind::Use(arena.alloc(av_use_decl(arena, u))),
3240        StmtKind::Const(items) => arena_ast::StmtKind::Const(av_const_items(arena, items)),
3241        StmtKind::StaticVar(vars) => arena_ast::StmtKind::StaticVar(av_static_vars(arena, vars)),
3242        StmtKind::HaltCompiler(s) => arena_ast::StmtKind::HaltCompiler(av_str(arena, s)),
3243        StmtKind::Nop => arena_ast::StmtKind::Nop,
3244        StmtKind::InlineHtml(s) => arena_ast::StmtKind::InlineHtml(av_str(arena, s)),
3245        StmtKind::Error => arena_ast::StmtKind::Error,
3246    }
3247}
3248
3249// Kept out of line so `av_stmt_kind`, which nested statements recurse through,
3250// does not carry every variant's temporaries in its own stack frame.
3251#[inline(never)]
3252fn av_if_stmt<'a>(arena: &'a bumpalo::Bump, s: &IfStmt) -> arena_ast::IfStmt<'a, 'a> {
3253    arena_ast::IfStmt {
3254        condition: av_expr(arena, &s.condition),
3255        then_branch: arena.alloc(av_stmt(arena, &s.then_branch)),
3256        elseif_branches: {
3257            let mut v = arena_ast::ArenaVec::with_capacity_in(s.elseif_branches.len(), arena);
3258            for b in s.elseif_branches.iter() {
3259                v.push(arena_ast::ElseIfBranch {
3260                    condition: av_expr(arena, &b.condition),
3261                    body: av_stmt(arena, &b.body),
3262                    span: b.span,
3263                });
3264            }
3265            v
3266        },
3267        else_branch: s
3268            .else_branch
3269            .as_ref()
3270            .map(|b| &*arena.alloc(av_stmt(arena, b))),
3271        else_kw_span: s.else_kw_span,
3272        uses_alternative: s.uses_alternative,
3273    }
3274}
3275
3276#[inline(never)]
3277fn av_while_stmt<'a>(arena: &'a bumpalo::Bump, s: &WhileStmt) -> arena_ast::WhileStmt<'a, 'a> {
3278    arena_ast::WhileStmt {
3279        condition: av_expr(arena, &s.condition),
3280        body: arena.alloc(av_stmt(arena, &s.body)),
3281        uses_alternative: s.uses_alternative,
3282    }
3283}
3284
3285#[inline(never)]
3286fn av_for_stmt<'a>(arena: &'a bumpalo::Bump, s: &ForStmt) -> arena_ast::ForStmt<'a, 'a> {
3287    arena_ast::ForStmt {
3288        init: av_exprs(arena, &s.init),
3289        condition: av_exprs(arena, &s.condition),
3290        update: av_exprs(arena, &s.update),
3291        body: arena.alloc(av_stmt(arena, &s.body)),
3292        uses_alternative: s.uses_alternative,
3293    }
3294}
3295
3296#[inline(never)]
3297fn av_foreach_stmt<'a>(
3298    arena: &'a bumpalo::Bump,
3299    s: &ForeachStmt,
3300) -> arena_ast::ForeachStmt<'a, 'a> {
3301    arena_ast::ForeachStmt {
3302        expr: av_expr(arena, &s.expr),
3303        key: s.key.as_ref().map(|e| av_expr(arena, e)),
3304        value: av_expr(arena, &s.value),
3305        body: arena.alloc(av_stmt(arena, &s.body)),
3306        uses_alternative: s.uses_alternative,
3307    }
3308}
3309
3310#[inline(never)]
3311fn av_do_while_stmt<'a>(
3312    arena: &'a bumpalo::Bump,
3313    s: &DoWhileStmt,
3314) -> arena_ast::DoWhileStmt<'a, 'a> {
3315    arena_ast::DoWhileStmt {
3316        body: arena.alloc(av_stmt(arena, &s.body)),
3317        condition: av_expr(arena, &s.condition),
3318    }
3319}
3320
3321#[inline(never)]
3322fn av_switch_stmt<'a>(arena: &'a bumpalo::Bump, s: &SwitchStmt) -> arena_ast::SwitchStmt<'a, 'a> {
3323    arena_ast::SwitchStmt {
3324        expr: av_expr(arena, &s.expr),
3325        body: {
3326            let mut cases = arena_ast::ArenaVec::with_capacity_in(s.body.cases.len(), arena);
3327            for c in s.body.cases.iter() {
3328                cases.push(arena_ast::SwitchCase {
3329                    value: c.value.as_ref().map(|vl| av_expr(arena, vl)),
3330                    body: av_stmts(arena, &c.body),
3331                    span: c.span,
3332                });
3333            }
3334            arena_ast::SwitchBody {
3335                cases,
3336                span: s.body.span,
3337            }
3338        },
3339        uses_alternative: s.uses_alternative,
3340    }
3341}
3342
3343#[inline(never)]
3344fn av_declare_stmt<'a>(
3345    arena: &'a bumpalo::Bump,
3346    d: &DeclareStmt,
3347) -> arena_ast::DeclareStmt<'a, 'a> {
3348    arena_ast::DeclareStmt {
3349        directives: {
3350            let mut v = arena_ast::ArenaVec::with_capacity_in(d.directives.len(), arena);
3351            for (k, val) in d.directives.iter() {
3352                v.push((arena.alloc_str(k) as &str, av_expr(arena, val)));
3353            }
3354            v
3355        },
3356        body: d.body.as_ref().map(|b| &*arena.alloc(av_stmt(arena, b))),
3357        uses_alternative: d.uses_alternative,
3358    }
3359}
3360
3361#[inline(never)]
3362fn av_try_catch_stmt<'a>(
3363    arena: &'a bumpalo::Bump,
3364    t: &TryCatchStmt,
3365) -> arena_ast::TryCatchStmt<'a, 'a> {
3366    arena_ast::TryCatchStmt {
3367        body: arena.alloc(av_block(arena, &t.body)),
3368        catches: {
3369            let mut v = arena_ast::ArenaVec::with_capacity_in(t.catches.len(), arena);
3370            for c in t.catches.iter() {
3371                let mut types = arena_ast::ArenaVec::with_capacity_in(c.types.len(), arena);
3372                for n in c.types.iter() {
3373                    types.push(av_name(arena, n));
3374                }
3375                v.push(arena_ast::CatchClause {
3376                    types,
3377                    var: c.var.as_deref().map(|s| av_str(arena, s)),
3378                    body: arena.alloc(av_block(arena, &c.body)),
3379                    span: c.span,
3380                });
3381            }
3382            v
3383        },
3384        finally: t
3385            .finally
3386            .as_deref()
3387            .map(|f| &*arena.alloc(av_block(arena, f))),
3388        finally_kw_span: t.finally_kw_span,
3389    }
3390}
3391
3392#[inline(never)]
3393fn av_namespace_decl<'a>(
3394    arena: &'a bumpalo::Bump,
3395    ns: &NamespaceDecl,
3396) -> arena_ast::NamespaceDecl<'a, 'a> {
3397    arena_ast::NamespaceDecl {
3398        name: ns.name.as_ref().map(|n| av_name(arena, n)),
3399        body: match &ns.body {
3400            NamespaceBody::Braced(block) => {
3401                arena_ast::NamespaceBody::Braced(&*arena.alloc(av_block(arena, block)))
3402            }
3403            NamespaceBody::Simple => arena_ast::NamespaceBody::Simple,
3404        },
3405    }
3406}
3407
3408#[inline(never)]
3409fn av_use_decl<'a>(arena: &'a bumpalo::Bump, u: &UseDecl) -> arena_ast::UseDecl<'a, 'a> {
3410    arena_ast::UseDecl {
3411        kind: u.kind,
3412        uses: {
3413            let mut v = arena_ast::ArenaVec::with_capacity_in(u.uses.len(), arena);
3414            for item in u.uses.iter() {
3415                v.push(arena_ast::UseItem {
3416                    name: av_name(arena, &item.name),
3417                    alias: item.alias.as_deref().map(|s| av_str(arena, s)),
3418                    kind: item.kind,
3419                    span: item.span,
3420                });
3421            }
3422            v
3423        },
3424    }
3425}
3426
3427#[inline(never)]
3428fn av_const_items<'a>(
3429    arena: &'a bumpalo::Bump,
3430    items: &[ConstItem],
3431) -> arena_ast::ArenaVec<'a, arena_ast::ConstItem<'a, 'a>> {
3432    let mut v = arena_ast::ArenaVec::with_capacity_in(items.len(), arena);
3433    for item in items.iter() {
3434        v.push(arena_ast::ConstItem {
3435            name: av_ident(arena, &item.name),
3436            value: av_expr(arena, &item.value),
3437            attributes: av_attrs(arena, &item.attributes),
3438            span: item.span,
3439            doc_comment: av_opt_comment(arena, &item.doc_comment),
3440        });
3441    }
3442    v
3443}
3444
3445#[inline(never)]
3446fn av_static_vars<'a>(
3447    arena: &'a bumpalo::Bump,
3448    vars: &[StaticVar],
3449) -> arena_ast::ArenaVec<'a, arena_ast::StaticVar<'a, 'a>> {
3450    let mut v = arena_ast::ArenaVec::with_capacity_in(vars.len(), arena);
3451    for var in vars.iter() {
3452        v.push(arena_ast::StaticVar {
3453            name: av_ident(arena, &var.name),
3454            default: var.default.as_ref().map(|e| av_expr(arena, e)),
3455            span: var.span,
3456        });
3457    }
3458    v
3459}
3460
3461#[inline(never)]
3462fn av_function_decl<'a>(
3463    arena: &'a bumpalo::Bump,
3464    f: &FunctionDecl,
3465) -> arena_ast::FunctionDecl<'a, 'a> {
3466    arena_ast::FunctionDecl {
3467        name: av_ident(arena, &f.name),
3468        params: av_params(arena, &f.params),
3469        body: &*arena.alloc(av_block(arena, &f.body)),
3470        return_type: f.return_type.as_ref().map(|t| av_type_hint(arena, t)),
3471        by_ref: f.by_ref,
3472        attributes: av_attrs(arena, &f.attributes),
3473        doc_comment: av_opt_comment(arena, &f.doc_comment),
3474    }
3475}
3476
3477fn av_class_member<'a>(
3478    arena: &'a bumpalo::Bump,
3479    m: &ClassMember,
3480) -> arena_ast::ClassMember<'a, 'a> {
3481    arena_ast::ClassMember {
3482        kind: match &m.kind {
3483            ClassMemberKind::Property(p) => {
3484                arena_ast::ClassMemberKind::Property(arena_ast::PropertyDecl {
3485                    name: av_ident(arena, &p.name),
3486                    visibility: p.visibility,
3487                    set_visibility: p.set_visibility,
3488                    is_static: p.is_static,
3489                    is_readonly: p.is_readonly,
3490                    type_hint: p.type_hint.as_ref().map(|t| av_type_hint(arena, t)),
3491                    default: p.default.as_ref().map(|e| av_expr(arena, e)),
3492                    attributes: av_attrs(arena, &p.attributes),
3493                    hooks: av_hooks(arena, &p.hooks),
3494                    doc_comment: av_opt_comment(arena, &p.doc_comment),
3495                })
3496            }
3497            ClassMemberKind::Method(m) => {
3498                arena_ast::ClassMemberKind::Method(arena_ast::MethodDecl {
3499                    name: av_ident(arena, &m.name),
3500                    visibility: m.visibility,
3501                    is_static: m.is_static,
3502                    is_abstract: m.is_abstract,
3503                    is_final: m.is_final,
3504                    by_ref: m.by_ref,
3505                    params: av_params(arena, &m.params),
3506                    return_type: m.return_type.as_ref().map(|t| av_type_hint(arena, t)),
3507                    body: m.body.as_deref().map(|b| &*arena.alloc(av_block(arena, b))),
3508                    attributes: av_attrs(arena, &m.attributes),
3509                    doc_comment: av_opt_comment(arena, &m.doc_comment),
3510                })
3511            }
3512            ClassMemberKind::ClassConst(c) => {
3513                arena_ast::ClassMemberKind::ClassConst(av_class_const(arena, c))
3514            }
3515            ClassMemberKind::TraitUse(t) => {
3516                arena_ast::ClassMemberKind::TraitUse(av_trait_use(arena, t))
3517            }
3518        },
3519        span: m.span,
3520    }
3521}
3522
3523fn av_class_members<'a>(
3524    arena: &'a bumpalo::Bump,
3525    members: &[ClassMember],
3526) -> arena_ast::ArenaVec<'a, arena_ast::ClassMember<'a, 'a>> {
3527    let mut v = arena_ast::ArenaVec::with_capacity_in(members.len(), arena);
3528    for m in members.iter() {
3529        v.push(av_class_member(arena, m));
3530    }
3531    v
3532}
3533
3534fn av_class_const<'a>(
3535    arena: &'a bumpalo::Bump,
3536    c: &ClassConstDecl,
3537) -> arena_ast::ClassConstDecl<'a, 'a> {
3538    arena_ast::ClassConstDecl {
3539        name: av_ident(arena, &c.name),
3540        visibility: c.visibility,
3541        is_final: c.is_final,
3542        type_hint: c
3543            .type_hint
3544            .as_ref()
3545            .map(|th| arena.alloc(av_type_hint(arena, th)) as &_),
3546        value: av_expr(arena, &c.value),
3547        attributes: av_attrs(arena, &c.attributes),
3548        doc_comment: av_opt_comment(arena, &c.doc_comment),
3549    }
3550}
3551
3552fn av_trait_use<'a>(arena: &'a bumpalo::Bump, t: &TraitUseDecl) -> arena_ast::TraitUseDecl<'a, 'a> {
3553    let mut traits = arena_ast::ArenaVec::with_capacity_in(t.traits.len(), arena);
3554    for n in t.traits.iter() {
3555        traits.push(av_name(arena, n));
3556    }
3557    let mut adaptations = arena_ast::ArenaVec::with_capacity_in(t.adaptations.len(), arena);
3558    for a in t.adaptations.iter() {
3559        adaptations.push(arena_ast::TraitAdaptation {
3560            kind: match &a.kind {
3561                TraitAdaptationKind::Precedence {
3562                    trait_name,
3563                    method,
3564                    insteadof,
3565                } => {
3566                    let mut instead = arena_ast::ArenaVec::with_capacity_in(insteadof.len(), arena);
3567                    for n in insteadof.iter() {
3568                        instead.push(av_name(arena, n));
3569                    }
3570                    arena_ast::TraitAdaptationKind::Precedence {
3571                        trait_name: av_name(arena, trait_name),
3572                        method: av_name(arena, method),
3573                        insteadof: instead,
3574                    }
3575                }
3576                TraitAdaptationKind::Alias {
3577                    trait_name,
3578                    method,
3579                    new_modifier,
3580                    new_name,
3581                } => arena_ast::TraitAdaptationKind::Alias {
3582                    trait_name: trait_name.as_ref().map(|n| av_name(arena, n)),
3583                    method: av_name(arena, method),
3584                    new_modifier: *new_modifier,
3585                    new_name: new_name.as_ref().map(|n| av_name(arena, n)),
3586                },
3587            },
3588            span: a.span,
3589        });
3590    }
3591    arena_ast::TraitUseDecl {
3592        traits,
3593        adaptations,
3594        adaptations_brace_span: t.adaptations_brace_span,
3595        doc_comment: av_opt_comment(arena, &t.doc_comment),
3596    }
3597}
3598
3599fn av_class_decl<'a>(arena: &'a bumpalo::Bump, cls: &ClassDecl) -> arena_ast::ClassDecl<'a, 'a> {
3600    let mut implements = arena_ast::ArenaVec::with_capacity_in(cls.implements.len(), arena);
3601    for n in cls.implements.iter() {
3602        implements.push(av_name(arena, n));
3603    }
3604    arena_ast::ClassDecl {
3605        name: cls.name.as_ref().map(|ident| av_ident(arena, ident)),
3606        modifiers: cls.modifiers.clone(),
3607        extends: cls.extends.as_ref().map(|n| av_name(arena, n)),
3608        implements,
3609        body: arena_ast::ClassBody {
3610            members: av_class_members(arena, &cls.body.members),
3611            span: cls.body.span,
3612        },
3613        attributes: av_attrs(arena, &cls.attributes),
3614        doc_comment: av_opt_comment(arena, &cls.doc_comment),
3615    }
3616}
3617
3618#[inline(never)]
3619fn av_interface_decl<'a>(
3620    arena: &'a bumpalo::Bump,
3621    iface: &InterfaceDecl,
3622) -> arena_ast::InterfaceDecl<'a, 'a> {
3623    let mut extends = arena_ast::ArenaVec::with_capacity_in(iface.extends.len(), arena);
3624    for n in iface.extends.iter() {
3625        extends.push(av_name(arena, n));
3626    }
3627    arena_ast::InterfaceDecl {
3628        name: av_ident(arena, &iface.name),
3629        extends,
3630        body: arena_ast::ClassBody {
3631            members: av_class_members(arena, &iface.body.members),
3632            span: iface.body.span,
3633        },
3634        attributes: av_attrs(arena, &iface.attributes),
3635        doc_comment: av_opt_comment(arena, &iface.doc_comment),
3636    }
3637}
3638
3639#[inline(never)]
3640fn av_trait_decl<'a>(arena: &'a bumpalo::Bump, tr: &TraitDecl) -> arena_ast::TraitDecl<'a, 'a> {
3641    arena_ast::TraitDecl {
3642        name: av_ident(arena, &tr.name),
3643        body: arena_ast::ClassBody {
3644            members: av_class_members(arena, &tr.body.members),
3645            span: tr.body.span,
3646        },
3647        attributes: av_attrs(arena, &tr.attributes),
3648        doc_comment: av_opt_comment(arena, &tr.doc_comment),
3649    }
3650}
3651
3652#[inline(never)]
3653fn av_enum_decl<'a>(arena: &'a bumpalo::Bump, en: &EnumDecl) -> arena_ast::EnumDecl<'a, 'a> {
3654    let mut implements = arena_ast::ArenaVec::with_capacity_in(en.implements.len(), arena);
3655    for n in en.implements.iter() {
3656        implements.push(av_name(arena, n));
3657    }
3658    let mut members = arena_ast::ArenaVec::with_capacity_in(en.body.members.len(), arena);
3659    for m in en.body.members.iter() {
3660        members.push(arena_ast::EnumMember {
3661            kind: match &m.kind {
3662                EnumMemberKind::Case(c) => arena_ast::EnumMemberKind::Case(arena_ast::EnumCase {
3663                    name: av_ident(arena, &c.name),
3664                    value: c.value.as_ref().map(|e| av_expr(arena, e)),
3665                    attributes: av_attrs(arena, &c.attributes),
3666                    doc_comment: av_opt_comment(arena, &c.doc_comment),
3667                }),
3668                EnumMemberKind::Method(m) => {
3669                    arena_ast::EnumMemberKind::Method(arena_ast::MethodDecl {
3670                        name: av_ident(arena, &m.name),
3671                        visibility: m.visibility,
3672                        is_static: m.is_static,
3673                        is_abstract: m.is_abstract,
3674                        is_final: m.is_final,
3675                        by_ref: m.by_ref,
3676                        params: av_params(arena, &m.params),
3677                        return_type: m.return_type.as_ref().map(|t| av_type_hint(arena, t)),
3678                        body: m.body.as_deref().map(|b| &*arena.alloc(av_block(arena, b))),
3679                        attributes: av_attrs(arena, &m.attributes),
3680                        doc_comment: av_opt_comment(arena, &m.doc_comment),
3681                    })
3682                }
3683                EnumMemberKind::ClassConst(c) => {
3684                    arena_ast::EnumMemberKind::ClassConst(av_class_const(arena, c))
3685                }
3686                EnumMemberKind::TraitUse(t) => {
3687                    arena_ast::EnumMemberKind::TraitUse(av_trait_use(arena, t))
3688                }
3689            },
3690            span: m.span,
3691        });
3692    }
3693    arena_ast::EnumDecl {
3694        name: av_ident(arena, &en.name),
3695        scalar_type: en.scalar_type.as_ref().map(|n| av_name(arena, n)),
3696        implements,
3697        body: arena_ast::EnumBody {
3698            members,
3699            span: en.body.span,
3700        },
3701        attributes: av_attrs(arena, &en.attributes),
3702        doc_comment: av_opt_comment(arena, &en.doc_comment),
3703    }
3704}