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