Skip to main content

php_ast/
fold.rs

1//! Arena-to-arena AST transformation via the [`Fold`] trait.
2//!
3//! # Overview
4//!
5//! [`Fold`] is the transformation counterpart of [`crate::visitor::Visitor`].
6//! Where `Visitor` walks a tree read-only, `Fold` rebuilds it — reading from an
7//! input node (in any arena `'_`) and writing into a caller-supplied output arena
8//! `'new`.  This is the only sound design for arena-allocated ASTs: in-place
9//! mutation (`VisitorMut`) would let you write a `&'new Expr` into a slot that
10//! requires a `&'old Expr`, silently breaking the arena lifetime invariant.
11//!
12//! # Lifetimes
13//!
14//! * `'src` — the source-text lifetime, bound on the `Fold` trait.  `&'src str`
15//!   slices that point directly into the original PHP source are passed through
16//!   unchanged across the fold; they are never re-allocated.
17//! * `'new` — the output arena lifetime; a generic on each method so one
18//!   `impl Fold<'src>` can fold into different arenas over its lifetime.
19//! * `'_` — the input arena lifetime, intentionally erased.  The fold never
20//!   reads back through output-arena pointers, so the input and output arenas
21//!   are fully independent.
22//!
23//! # Arena-allocated strings
24//!
25//! Several AST nodes store `&'arena str` values that were *not* borrowed from
26//! the source buffer — for example string literals and [`ExprKind::Nowdoc`]
27//! values.  The identity fold re-allocates these via
28//! `arena.alloc_str(s)` so the output nodes are self-contained within the
29//! new arena.  Source-borrowed [`NameStr`] values (`NameStr::__src`) are
30//! preserved as-is without copying.
31//!
32//! # Usage
33//!
34//! ```
35//! use bumpalo::Bump;
36//! use php_ast::fold::{Fold, fold_program};
37//! use php_ast::ast::*;
38//!
39//! /// An identity fold — rebuilds the AST without changes.
40//! struct Identity;
41//! impl<'src> Fold<'src> for Identity {}
42//!
43//! // Fold `program` (in `src_arena`) into `out_arena`:
44//! // let folded = Identity.fold_program(&out_arena, &program);
45//! ```
46//!
47//! To transform specific nodes, override the corresponding method and call the
48//! free `fold_*` function for the default recursion:
49//!
50//! ```
51//! use bumpalo::Bump;
52//! use php_ast::fold::{Fold, fold_expr};
53//! use php_ast::ast::*;
54//!
55//! struct NegateInts;
56//!
57//! impl<'src> Fold<'src> for NegateInts {
58//!     fn fold_expr<'new>(&mut self, arena: &'new Bump, expr: &Expr<'_, 'src>) -> Expr<'new, 'src> {
59//!         if let ExprKind::Int(n) = expr.kind {
60//!             return Expr { kind: ExprKind::Int(-n), span: expr.span };
61//!         }
62//!         fold_expr(self, arena, expr)
63//!     }
64//! }
65//! ```
66
67use bumpalo::Bump;
68
69use crate::ast::*;
70
71// =============================================================================
72// Fold trait
73// =============================================================================
74
75/// Trait for arena-to-arena PHP AST transformation.
76///
77/// All methods have identity-fold default implementations that call the
78/// corresponding free `fold_*` function.  Override only the node types you want
79/// to change; the rest recurse automatically.
80///
81/// See the [module documentation](self) for design rationale and lifetime notes.
82pub trait Fold<'src> {
83    /// Folds the root program node.
84    fn fold_program<'new>(
85        &mut self,
86        arena: &'new Bump,
87        program: &Program<'_, 'src>,
88    ) -> Program<'new, 'src> {
89        fold_program(self, arena, program)
90    }
91
92    /// Folds a statement.
93    fn fold_stmt<'new>(&mut self, arena: &'new Bump, stmt: &Stmt<'_, 'src>) -> Stmt<'new, 'src> {
94        fold_stmt(self, arena, stmt)
95    }
96
97    /// Folds a statement list (program, block and `case` bodies). Override to delete, split or
98    /// insert statements.
99    fn fold_stmts<'new>(
100        &mut self,
101        arena: &'new Bump,
102        stmts: &[Stmt<'_, 'src>],
103    ) -> ArenaVec<'new, Stmt<'new, 'src>> {
104        fold_stmts(self, arena, stmts)
105    }
106
107    /// Folds a block of statements.
108    fn fold_block<'new>(
109        &mut self,
110        arena: &'new Bump,
111        block: &Block<'_, 'src>,
112    ) -> Block<'new, 'src> {
113        fold_block(self, arena, block)
114    }
115
116    /// Folds an expression.
117    fn fold_expr<'new>(&mut self, arena: &'new Bump, expr: &Expr<'_, 'src>) -> Expr<'new, 'src> {
118        fold_expr(self, arena, expr)
119    }
120
121    /// Folds a function parameter.
122    fn fold_param<'new>(
123        &mut self,
124        arena: &'new Bump,
125        param: &Param<'_, 'src>,
126    ) -> Param<'new, 'src> {
127        fold_param(self, arena, param)
128    }
129
130    /// Folds a call argument.
131    fn fold_arg<'new>(&mut self, arena: &'new Bump, arg: &Arg<'_, 'src>) -> Arg<'new, 'src> {
132        fold_arg(self, arena, arg)
133    }
134
135    /// Folds a class-like member.
136    fn fold_class_member<'new>(
137        &mut self,
138        arena: &'new Bump,
139        member: &ClassMember<'_, 'src>,
140    ) -> ClassMember<'new, 'src> {
141        fold_class_member(self, arena, member)
142    }
143
144    /// Folds an enum member.
145    fn fold_enum_member<'new>(
146        &mut self,
147        arena: &'new Bump,
148        member: &EnumMember<'_, 'src>,
149    ) -> EnumMember<'new, 'src> {
150        fold_enum_member(self, arena, member)
151    }
152
153    /// Folds a property hook.
154    fn fold_property_hook<'new>(
155        &mut self,
156        arena: &'new Bump,
157        hook: &PropertyHook<'_, 'src>,
158    ) -> PropertyHook<'new, 'src> {
159        fold_property_hook(self, arena, hook)
160    }
161
162    /// Folds a type hint.
163    fn fold_type_hint<'new>(
164        &mut self,
165        arena: &'new Bump,
166        type_hint: &TypeHint<'_, 'src>,
167    ) -> TypeHint<'new, 'src> {
168        fold_type_hint(self, arena, type_hint)
169    }
170
171    /// Folds an attribute.
172    fn fold_attribute<'new>(
173        &mut self,
174        arena: &'new Bump,
175        attribute: &Attribute<'_, 'src>,
176    ) -> Attribute<'new, 'src> {
177        fold_attribute(self, arena, attribute)
178    }
179
180    /// Folds a `catch` clause.
181    fn fold_catch_clause<'new>(
182        &mut self,
183        arena: &'new Bump,
184        catch: &CatchClause<'_, 'src>,
185    ) -> CatchClause<'new, 'src> {
186        fold_catch_clause(self, arena, catch)
187    }
188
189    /// Folds a `match` arm.
190    fn fold_match_arm<'new>(
191        &mut self,
192        arena: &'new Bump,
193        arm: &MatchArm<'_, 'src>,
194    ) -> MatchArm<'new, 'src> {
195        fold_match_arm(self, arena, arm)
196    }
197
198    /// Folds a closure `use` variable.
199    fn fold_closure_use_var(
200        &mut self,
201        _arena: &Bump,
202        var: &ClosureUseVar<'src>,
203    ) -> ClosureUseVar<'src> {
204        var.clone()
205    }
206
207    /// Folds a `use Trait;` declaration.
208    fn fold_trait_use<'new>(
209        &mut self,
210        arena: &'new Bump,
211        trait_use: &TraitUseDecl<'_, 'src>,
212    ) -> TraitUseDecl<'new, 'src> {
213        fold_trait_use(self, arena, trait_use)
214    }
215
216    /// Folds a trait `insteadof`/`as` adaptation.
217    fn fold_trait_adaptation<'new>(
218        &mut self,
219        arena: &'new Bump,
220        adaptation: &TraitAdaptation<'_, 'src>,
221    ) -> TraitAdaptation<'new, 'src> {
222        fold_trait_adaptation(self, arena, adaptation)
223    }
224
225    /// Folds a name reference.
226    fn fold_name<'new>(&mut self, arena: &'new Bump, name: &Name<'_, 'src>) -> Name<'new, 'src> {
227        fold_name(self, arena, name)
228    }
229}
230
231// =============================================================================
232// Public free functions — default recursion for each trait method
233// =============================================================================
234
235/// Folds the root program node.
236pub fn fold_program<'new, 'src, F: Fold<'src> + ?Sized>(
237    folder: &mut F,
238    arena: &'new Bump,
239    program: &Program<'_, 'src>,
240) -> Program<'new, 'src> {
241    Program {
242        stmts: folder.fold_stmts(arena, &program.stmts),
243        span: program.span,
244    }
245}
246
247/// Folds a statement.
248pub fn fold_stmt<'new, 'src, F: Fold<'src> + ?Sized>(
249    folder: &mut F,
250    arena: &'new Bump,
251    stmt: &Stmt<'_, 'src>,
252) -> Stmt<'new, 'src> {
253    let kind = match &stmt.kind {
254        StmtKind::Expression(expr) => {
255            StmtKind::Expression(arena.alloc(folder.fold_expr(arena, expr)))
256        }
257        StmtKind::Echo(exprs) => StmtKind::Echo(fold_exprs(folder, arena, exprs)),
258        StmtKind::Return(expr) => {
259            StmtKind::Return(expr.map(|e| &*arena.alloc(folder.fold_expr(arena, e))))
260        }
261        StmtKind::Block(block) => StmtKind::Block(arena.alloc(folder.fold_block(arena, block))),
262        StmtKind::If(if_stmt) => {
263            let mut elseif_branches =
264                ArenaVec::with_capacity_in(if_stmt.elseif_branches.len(), arena);
265            for branch in if_stmt.elseif_branches.iter() {
266                elseif_branches.push(ElseIfBranch {
267                    condition: folder.fold_expr(arena, &branch.condition),
268                    body: folder.fold_stmt(arena, &branch.body),
269                    span: branch.span,
270                });
271            }
272            let new_if = arena.alloc(IfStmt {
273                condition: folder.fold_expr(arena, &if_stmt.condition),
274                then_branch: arena.alloc(folder.fold_stmt(arena, if_stmt.then_branch)),
275                elseif_branches,
276                else_branch: if_stmt
277                    .else_branch
278                    .map(|b| &*arena.alloc(folder.fold_stmt(arena, b))),
279                else_kw_span: if_stmt.else_kw_span,
280                uses_alternative: if_stmt.uses_alternative,
281            });
282            StmtKind::If(new_if)
283        }
284        StmtKind::While(w) => {
285            let new_w = arena.alloc(WhileStmt {
286                condition: folder.fold_expr(arena, &w.condition),
287                body: arena.alloc(folder.fold_stmt(arena, w.body)),
288                uses_alternative: w.uses_alternative,
289            });
290            StmtKind::While(new_w)
291        }
292        StmtKind::For(f) => {
293            let new_f = arena.alloc(ForStmt {
294                init: fold_exprs(folder, arena, &f.init),
295                condition: fold_exprs(folder, arena, &f.condition),
296                update: fold_exprs(folder, arena, &f.update),
297                body: arena.alloc(folder.fold_stmt(arena, f.body)),
298                uses_alternative: f.uses_alternative,
299            });
300            StmtKind::For(new_f)
301        }
302        StmtKind::Foreach(fe) => {
303            let new_fe = arena.alloc(ForeachStmt {
304                expr: folder.fold_expr(arena, &fe.expr),
305                key: fe.key.as_ref().map(|k| folder.fold_expr(arena, k)),
306                value: folder.fold_expr(arena, &fe.value),
307                by_ref: fe.by_ref,
308                by_ref_span: fe.by_ref_span,
309                body: arena.alloc(folder.fold_stmt(arena, fe.body)),
310                uses_alternative: fe.uses_alternative,
311            });
312            StmtKind::Foreach(new_fe)
313        }
314        StmtKind::DoWhile(dw) => {
315            let new_dw = arena.alloc(DoWhileStmt {
316                body: arena.alloc(folder.fold_stmt(arena, dw.body)),
317                condition: folder.fold_expr(arena, &dw.condition),
318            });
319            StmtKind::DoWhile(new_dw)
320        }
321        StmtKind::Function(func) => {
322            StmtKind::Function(arena.alloc(fold_function_decl(folder, arena, func)))
323        }
324        StmtKind::Break(expr) => {
325            StmtKind::Break(expr.map(|e| &*arena.alloc(folder.fold_expr(arena, e))))
326        }
327        StmtKind::Continue(expr) => {
328            StmtKind::Continue(expr.map(|e| &*arena.alloc(folder.fold_expr(arena, e))))
329        }
330        StmtKind::Switch(sw) => {
331            let mut cases = ArenaVec::with_capacity_in(sw.body.cases.len(), arena);
332            for case in sw.body.cases.iter() {
333                cases.push(SwitchCase {
334                    value: case.value.as_ref().map(|v| folder.fold_expr(arena, v)),
335                    body: folder.fold_stmts(arena, &case.body),
336                    span: case.span,
337                });
338            }
339            let new_sw = arena.alloc(SwitchStmt {
340                expr: folder.fold_expr(arena, &sw.expr),
341                body: SwitchBody {
342                    cases,
343                    span: sw.body.span,
344                },
345                uses_alternative: sw.uses_alternative,
346            });
347            StmtKind::Switch(new_sw)
348        }
349        StmtKind::Goto(ident) => StmtKind::Goto(*ident),
350        StmtKind::Label(ident) => StmtKind::Label(*ident),
351        StmtKind::Declare(decl) => {
352            let mut directives = ArenaVec::with_capacity_in(decl.directives.len(), arena);
353            for (name, expr) in decl.directives.iter() {
354                directives.push((*name, folder.fold_expr(arena, expr)));
355            }
356            let new_decl = arena.alloc(DeclareStmt {
357                directives,
358                body: decl.body.map(|b| &*arena.alloc(folder.fold_stmt(arena, b))),
359                uses_alternative: decl.uses_alternative,
360            });
361            StmtKind::Declare(new_decl)
362        }
363        StmtKind::Unset(exprs) => StmtKind::Unset(fold_exprs(folder, arena, exprs)),
364        StmtKind::Throw(expr) => StmtKind::Throw(arena.alloc(folder.fold_expr(arena, expr))),
365        StmtKind::TryCatch(tc) => {
366            let mut catches = ArenaVec::with_capacity_in(tc.catches.len(), arena);
367            for catch in tc.catches.iter() {
368                catches.push(folder.fold_catch_clause(arena, catch));
369            }
370            let new_tc = arena.alloc(TryCatchStmt {
371                body: arena.alloc(folder.fold_block(arena, tc.body)),
372                catches,
373                finally: tc
374                    .finally
375                    .map(|f| &*arena.alloc(folder.fold_block(arena, f))),
376                finally_kw_span: tc.finally_kw_span,
377            });
378            StmtKind::TryCatch(new_tc)
379        }
380        StmtKind::Global(exprs) => StmtKind::Global(fold_exprs(folder, arena, exprs)),
381        StmtKind::Class(class) => {
382            StmtKind::Class(arena.alloc(fold_class_decl(folder, arena, class)))
383        }
384        StmtKind::Interface(iface) => {
385            StmtKind::Interface(arena.alloc(fold_interface_decl(folder, arena, iface)))
386        }
387        StmtKind::Trait(t) => StmtKind::Trait(arena.alloc(fold_trait_decl(folder, arena, t))),
388        StmtKind::Enum(e) => StmtKind::Enum(arena.alloc(fold_enum_decl(folder, arena, e))),
389        StmtKind::Namespace(ns) => {
390            let new_ns = arena.alloc(NamespaceDecl {
391                name: ns.name.as_ref().map(|n| folder.fold_name(arena, n)),
392                body: match &ns.body {
393                    NamespaceBody::Braced(block) => {
394                        NamespaceBody::Braced(arena.alloc(folder.fold_block(arena, block)))
395                    }
396                    NamespaceBody::Simple => NamespaceBody::Simple,
397                },
398            });
399            StmtKind::Namespace(new_ns)
400        }
401        StmtKind::Use(use_decl) => {
402            let mut uses = ArenaVec::with_capacity_in(use_decl.uses.len(), arena);
403            for item in use_decl.uses.iter() {
404                uses.push(UseItem {
405                    name: folder.fold_name(arena, &item.name),
406                    alias: item.alias,
407                    kind: item.kind,
408                    span: item.span,
409                });
410            }
411            let new_use = arena.alloc(UseDecl {
412                kind: use_decl.kind,
413                uses,
414            });
415            StmtKind::Use(new_use)
416        }
417        StmtKind::Const(items) => {
418            let mut new_items = ArenaVec::with_capacity_in(items.len(), arena);
419            for item in items.iter() {
420                new_items.push(ConstItem {
421                    name: item.name,
422                    value: folder.fold_expr(arena, &item.value),
423                    attributes: fold_attrs(folder, arena, &item.attributes),
424                    span: item.span,
425                    doc_comment: item.doc_comment.as_ref().map(fold_comment),
426                });
427            }
428            StmtKind::Const(new_items)
429        }
430        StmtKind::StaticVar(vars) => {
431            let mut new_vars = ArenaVec::with_capacity_in(vars.len(), arena);
432            for var in vars.iter() {
433                new_vars.push(StaticVar {
434                    name: var.name,
435                    default: var.default.as_ref().map(|d| folder.fold_expr(arena, d)),
436                    span: var.span,
437                });
438            }
439            StmtKind::StaticVar(new_vars)
440        }
441        StmtKind::HaltCompiler(s) => StmtKind::HaltCompiler(s),
442        StmtKind::Nop => StmtKind::Nop,
443        StmtKind::InlineHtml(s) => StmtKind::InlineHtml(s),
444        StmtKind::Error => StmtKind::Error,
445    };
446    Stmt {
447        kind,
448        span: stmt.span,
449        doc_comment: stmt.doc_comment.map(|c| &*arena.alloc(fold_comment(c))),
450    }
451}
452
453/// Folds an expression.
454pub fn fold_expr<'new, 'src, F: Fold<'src> + ?Sized>(
455    folder: &mut F,
456    arena: &'new Bump,
457    expr: &Expr<'_, 'src>,
458) -> Expr<'new, 'src> {
459    let kind = match &expr.kind {
460        ExprKind::Int(n) => ExprKind::Int(*n),
461        ExprKind::Float(f) => ExprKind::Float(*f),
462        ExprKind::String(s) => ExprKind::String(fold_byte_str(*s, arena)),
463        ExprKind::InterpolatedString(parts) => {
464            ExprKind::InterpolatedString(fold_string_parts(folder, arena, parts))
465        }
466        ExprKind::Heredoc { label, parts } => ExprKind::Heredoc {
467            label,
468            parts: fold_string_parts(folder, arena, parts),
469        },
470        ExprKind::Nowdoc { label, value } => ExprKind::Nowdoc {
471            label,
472            value: arena.alloc_str(value),
473        },
474        ExprKind::ShellExec(parts) => ExprKind::ShellExec(fold_string_parts(folder, arena, parts)),
475        ExprKind::Bool(b) => ExprKind::Bool(*b),
476        ExprKind::Null => ExprKind::Null,
477        ExprKind::Variable(name) => ExprKind::Variable(fold_name_str(*name, arena)),
478        ExprKind::VariableVariable(inner) => {
479            ExprKind::VariableVariable(arena.alloc(folder.fold_expr(arena, inner)))
480        }
481        ExprKind::Identifier(name) => ExprKind::Identifier(fold_name_str(*name, arena)),
482        ExprKind::Name(name) => ExprKind::Name(folder.fold_name(arena, name)),
483        ExprKind::Assign(assign) => ExprKind::Assign(AssignExpr {
484            target: arena.alloc(folder.fold_expr(arena, assign.target)),
485            op: assign.op,
486            value: arena.alloc(folder.fold_expr(arena, assign.value)),
487            by_ref: assign.by_ref,
488        }),
489        ExprKind::Binary(binary) => ExprKind::Binary(BinaryExpr {
490            left: arena.alloc(folder.fold_expr(arena, binary.left)),
491            op: binary.op,
492            right: arena.alloc(folder.fold_expr(arena, binary.right)),
493        }),
494        ExprKind::UnaryPrefix(u) => ExprKind::UnaryPrefix(UnaryPrefixExpr {
495            op: u.op,
496            operand: arena.alloc(folder.fold_expr(arena, u.operand)),
497        }),
498        ExprKind::UnaryPostfix(u) => ExprKind::UnaryPostfix(UnaryPostfixExpr {
499            operand: arena.alloc(folder.fold_expr(arena, u.operand)),
500            op: u.op,
501        }),
502        ExprKind::Ternary(t) => ExprKind::Ternary(TernaryExpr {
503            condition: arena.alloc(folder.fold_expr(arena, t.condition)),
504            then_expr: t
505                .then_expr
506                .map(|e| &*arena.alloc(folder.fold_expr(arena, e))),
507            else_expr: arena.alloc(folder.fold_expr(arena, t.else_expr)),
508        }),
509        ExprKind::NullCoalesce(nc) => ExprKind::NullCoalesce(NullCoalesceExpr {
510            left: arena.alloc(folder.fold_expr(arena, nc.left)),
511            right: arena.alloc(folder.fold_expr(arena, nc.right)),
512        }),
513        ExprKind::FunctionCall(call) => ExprKind::FunctionCall(FunctionCallExpr {
514            name: arena.alloc(folder.fold_expr(arena, call.name)),
515            args: fold_args(folder, arena, &call.args),
516        }),
517        ExprKind::Array(elements) => {
518            let mut new_elements = ArenaVec::with_capacity_in(elements.len(), arena);
519            for elem in elements.iter() {
520                new_elements.push(ArrayElement {
521                    key: elem.key.as_ref().map(|k| folder.fold_expr(arena, k)),
522                    kind: match &elem.kind {
523                        ArrayElementKind::Value(e) => {
524                            ArrayElementKind::Value(folder.fold_expr(arena, e))
525                        }
526                        ArrayElementKind::Spread(e) => {
527                            ArrayElementKind::Spread(folder.fold_expr(arena, e))
528                        }
529                        ArrayElementKind::ByRef(e) => {
530                            ArrayElementKind::ByRef(folder.fold_expr(arena, e))
531                        }
532                    },
533                    span: elem.span,
534                });
535            }
536            ExprKind::Array(new_elements)
537        }
538        ExprKind::ArrayAccess(access) => ExprKind::ArrayAccess(ArrayAccessExpr {
539            array: arena.alloc(folder.fold_expr(arena, access.array)),
540            index: access
541                .index
542                .map(|i| &*arena.alloc(folder.fold_expr(arena, i))),
543        }),
544        ExprKind::Print(e) => ExprKind::Print(arena.alloc(folder.fold_expr(arena, e))),
545        ExprKind::Parenthesized(e) => {
546            ExprKind::Parenthesized(arena.alloc(folder.fold_expr(arena, e)))
547        }
548        ExprKind::Cast(kind, e) => ExprKind::Cast(*kind, arena.alloc(folder.fold_expr(arena, e))),
549        ExprKind::ErrorSuppress(e) => {
550            ExprKind::ErrorSuppress(arena.alloc(folder.fold_expr(arena, e)))
551        }
552        ExprKind::Isset(exprs) => ExprKind::Isset(fold_exprs(folder, arena, exprs)),
553        ExprKind::Empty(e) => ExprKind::Empty(arena.alloc(folder.fold_expr(arena, e))),
554        ExprKind::Include(kind, e) => {
555            ExprKind::Include(*kind, arena.alloc(folder.fold_expr(arena, e)))
556        }
557        ExprKind::Eval(e) => ExprKind::Eval(arena.alloc(folder.fold_expr(arena, e))),
558        ExprKind::Exit(e) => ExprKind::Exit(e.map(|e| &*arena.alloc(folder.fold_expr(arena, e)))),
559        ExprKind::MagicConst(k) => ExprKind::MagicConst(*k),
560        ExprKind::Clone(e) => ExprKind::Clone(arena.alloc(folder.fold_expr(arena, e))),
561        ExprKind::CloneWith(obj, overrides) => ExprKind::CloneWith(
562            arena.alloc(folder.fold_expr(arena, obj)),
563            arena.alloc(folder.fold_expr(arena, overrides)),
564        ),
565        ExprKind::New(new_expr) => ExprKind::New(NewExpr {
566            class: arena.alloc(folder.fold_expr(arena, new_expr.class)),
567            args: fold_args(folder, arena, &new_expr.args),
568        }),
569        ExprKind::PropertyAccess(access) => ExprKind::PropertyAccess(PropertyAccessExpr {
570            object: arena.alloc(folder.fold_expr(arena, access.object)),
571            property: arena.alloc(folder.fold_expr(arena, access.property)),
572        }),
573        ExprKind::NullsafePropertyAccess(access) => {
574            ExprKind::NullsafePropertyAccess(PropertyAccessExpr {
575                object: arena.alloc(folder.fold_expr(arena, access.object)),
576                property: arena.alloc(folder.fold_expr(arena, access.property)),
577            })
578        }
579        ExprKind::MethodCall(call) => ExprKind::MethodCall(arena.alloc(MethodCallExpr {
580            object: arena.alloc(folder.fold_expr(arena, call.object)),
581            method: arena.alloc(folder.fold_expr(arena, call.method)),
582            args: fold_args(folder, arena, &call.args),
583        })),
584        ExprKind::NullsafeMethodCall(call) => {
585            ExprKind::NullsafeMethodCall(arena.alloc(MethodCallExpr {
586                object: arena.alloc(folder.fold_expr(arena, call.object)),
587                method: arena.alloc(folder.fold_expr(arena, call.method)),
588                args: fold_args(folder, arena, &call.args),
589            }))
590        }
591        ExprKind::StaticPropertyAccess(access) => {
592            ExprKind::StaticPropertyAccess(StaticAccessExpr {
593                class: arena.alloc(folder.fold_expr(arena, access.class)),
594                member: arena.alloc(folder.fold_expr(arena, access.member)),
595            })
596        }
597        ExprKind::StaticMethodCall(call) => {
598            ExprKind::StaticMethodCall(arena.alloc(StaticMethodCallExpr {
599                class: arena.alloc(folder.fold_expr(arena, call.class)),
600                method: arena.alloc(folder.fold_expr(arena, call.method)),
601                args: fold_args(folder, arena, &call.args),
602            }))
603        }
604        ExprKind::StaticMethodCallDynamic(call) => {
605            ExprKind::StaticMethodCallDynamic(arena.alloc(StaticMethodCallDynamicExpr {
606                class: arena.alloc(folder.fold_expr(arena, call.class)),
607                method: arena.alloc(folder.fold_expr(arena, call.method)),
608                args: fold_args(folder, arena, &call.args),
609            }))
610        }
611        ExprKind::ClassConstAccess(access) => ExprKind::ClassConstAccess(StaticAccessExpr {
612            class: arena.alloc(folder.fold_expr(arena, access.class)),
613            member: arena.alloc(folder.fold_expr(arena, access.member)),
614        }),
615        ExprKind::ClassConstAccessDynamic { class, member } => ExprKind::ClassConstAccessDynamic {
616            class: arena.alloc(folder.fold_expr(arena, class)),
617            member: arena.alloc(folder.fold_expr(arena, member)),
618        },
619        ExprKind::StaticPropertyAccessDynamic { class, member } => {
620            ExprKind::StaticPropertyAccessDynamic {
621                class: arena.alloc(folder.fold_expr(arena, class)),
622                member: arena.alloc(folder.fold_expr(arena, member)),
623            }
624        }
625        ExprKind::Closure(closure) => {
626            let mut use_vars = ArenaVec::with_capacity_in(closure.use_vars.len(), arena);
627            for var in closure.use_vars.iter() {
628                use_vars.push(folder.fold_closure_use_var(arena, var));
629            }
630            let new_closure = arena.alloc(ClosureExpr {
631                is_static: closure.is_static,
632                by_ref: closure.by_ref,
633                params: fold_params(folder, arena, &closure.params),
634                missing_param_list: closure.missing_param_list,
635                use_vars,
636                return_type: closure
637                    .return_type
638                    .as_ref()
639                    .map(|t| folder.fold_type_hint(arena, t)),
640                body: arena.alloc(folder.fold_block(arena, closure.body)),
641                attributes: fold_attrs(folder, arena, &closure.attributes),
642            });
643            ExprKind::Closure(new_closure)
644        }
645        ExprKind::ArrowFunction(arrow) => {
646            let new_arrow = arena.alloc(ArrowFunctionExpr {
647                is_static: arrow.is_static,
648                by_ref: arrow.by_ref,
649                params: fold_params(folder, arena, &arrow.params),
650                missing_param_list: arrow.missing_param_list,
651                return_type: arrow
652                    .return_type
653                    .as_ref()
654                    .map(|t| folder.fold_type_hint(arena, t)),
655                body: arena.alloc(folder.fold_expr(arena, arrow.body)),
656                attributes: fold_attrs(folder, arena, &arrow.attributes),
657            });
658            ExprKind::ArrowFunction(new_arrow)
659        }
660        ExprKind::Match(match_expr) => ExprKind::Match(MatchExpr {
661            subject: arena.alloc(folder.fold_expr(arena, match_expr.subject)),
662            arms: {
663                let mut arms = ArenaVec::with_capacity_in(match_expr.arms.len(), arena);
664                for arm in match_expr.arms.iter() {
665                    arms.push(folder.fold_match_arm(arena, arm));
666                }
667                arms
668            },
669            brace_span: match_expr.brace_span,
670        }),
671        ExprKind::Throw(e) => ExprKind::Throw(arena.alloc(folder.fold_expr(arena, e))),
672        ExprKind::Yield(y) => ExprKind::Yield(YieldExpr {
673            key: y.key.map(|k| &*arena.alloc(folder.fold_expr(arena, k))),
674            value: y.value.map(|v| &*arena.alloc(folder.fold_expr(arena, v))),
675            is_from: y.is_from,
676        }),
677        ExprKind::AnonymousClass(class) => {
678            ExprKind::AnonymousClass(arena.alloc(fold_class_decl(folder, arena, class)))
679        }
680        ExprKind::CallableCreate(cc) => {
681            let kind = match &cc.kind {
682                CallableCreateKind::Function(name) => {
683                    CallableCreateKind::Function(arena.alloc(folder.fold_expr(arena, name)))
684                }
685                CallableCreateKind::Method { object, method } => CallableCreateKind::Method {
686                    object: arena.alloc(folder.fold_expr(arena, object)),
687                    method: arena.alloc(folder.fold_expr(arena, method)),
688                },
689                CallableCreateKind::NullsafeMethod { object, method } => {
690                    CallableCreateKind::NullsafeMethod {
691                        object: arena.alloc(folder.fold_expr(arena, object)),
692                        method: arena.alloc(folder.fold_expr(arena, method)),
693                    }
694                }
695                CallableCreateKind::StaticMethod { class, method } => {
696                    CallableCreateKind::StaticMethod {
697                        class: arena.alloc(folder.fold_expr(arena, class)),
698                        method: arena.alloc(folder.fold_expr(arena, method)),
699                    }
700                }
701            };
702            ExprKind::CallableCreate(CallableCreateExpr { kind })
703        }
704        ExprKind::Omit => ExprKind::Omit,
705        ExprKind::Error => ExprKind::Error,
706    };
707    Expr {
708        kind,
709        span: expr.span,
710    }
711}
712
713/// Folds a function parameter.
714pub fn fold_param<'new, 'src, F: Fold<'src> + ?Sized>(
715    folder: &mut F,
716    arena: &'new Bump,
717    param: &Param<'_, 'src>,
718) -> Param<'new, 'src> {
719    Param {
720        name: param.name,
721        type_hint: param
722            .type_hint
723            .as_ref()
724            .map(|t| folder.fold_type_hint(arena, t)),
725        default: param.default.as_ref().map(|d| folder.fold_expr(arena, d)),
726        by_ref: param.by_ref,
727        variadic: param.variadic,
728        modifiers: param.modifiers,
729        attributes: fold_attrs(folder, arena, &param.attributes),
730        hooks: fold_hooks(folder, arena, &param.hooks),
731        empty_hook_list: param.empty_hook_list,
732        doc_comment: param.doc_comment,
733        span: param.span,
734    }
735}
736
737/// Folds a call argument.
738pub fn fold_arg<'new, 'src, F: Fold<'src> + ?Sized>(
739    folder: &mut F,
740    arena: &'new Bump,
741    arg: &Arg<'_, 'src>,
742) -> Arg<'new, 'src> {
743    Arg {
744        name: arg.name.as_ref().map(|n| folder.fold_name(arena, n)),
745        kind: match &arg.kind {
746            ArgKind::Value(e) => ArgKind::Value(folder.fold_expr(arena, e)),
747            ArgKind::Spread(e) => ArgKind::Spread(folder.fold_expr(arena, e)),
748            ArgKind::ByRef(e) => ArgKind::ByRef(folder.fold_expr(arena, e)),
749            ArgKind::Placeholder => ArgKind::Placeholder,
750            ArgKind::SpreadPlaceholder => ArgKind::SpreadPlaceholder,
751        },
752        span: arg.span,
753    }
754}
755
756/// Folds a class-like member.
757pub fn fold_class_member<'new, 'src, F: Fold<'src> + ?Sized>(
758    folder: &mut F,
759    arena: &'new Bump,
760    member: &ClassMember<'_, 'src>,
761) -> ClassMember<'new, 'src> {
762    let kind = match &member.kind {
763        ClassMemberKind::Property(prop) => {
764            ClassMemberKind::Property(fold_property_decl(folder, arena, prop))
765        }
766        ClassMemberKind::Method(method) => {
767            ClassMemberKind::Method(fold_method_decl(folder, arena, method))
768        }
769        ClassMemberKind::ClassConst(cc) => {
770            ClassMemberKind::ClassConst(fold_class_const_decl(folder, arena, cc))
771        }
772        ClassMemberKind::TraitUse(tu) => {
773            ClassMemberKind::TraitUse(folder.fold_trait_use(arena, tu))
774        }
775    };
776    ClassMember {
777        kind,
778        span: member.span,
779    }
780}
781
782/// Folds an enum member.
783pub fn fold_enum_member<'new, 'src, F: Fold<'src> + ?Sized>(
784    folder: &mut F,
785    arena: &'new Bump,
786    member: &EnumMember<'_, 'src>,
787) -> EnumMember<'new, 'src> {
788    let kind = match &member.kind {
789        EnumMemberKind::Case(case) => EnumMemberKind::Case(EnumCase {
790            name: case.name,
791            value: case.value.as_ref().map(|v| folder.fold_expr(arena, v)),
792            attributes: fold_attrs(folder, arena, &case.attributes),
793            doc_comment: case.doc_comment.as_ref().map(fold_comment),
794        }),
795        EnumMemberKind::Method(method) => {
796            EnumMemberKind::Method(fold_method_decl(folder, arena, method))
797        }
798        EnumMemberKind::ClassConst(cc) => {
799            EnumMemberKind::ClassConst(fold_class_const_decl(folder, arena, cc))
800        }
801        EnumMemberKind::TraitUse(tu) => EnumMemberKind::TraitUse(folder.fold_trait_use(arena, tu)),
802        EnumMemberKind::Property(prop) => {
803            EnumMemberKind::Property(fold_property_decl(folder, arena, prop))
804        }
805    };
806    EnumMember {
807        kind,
808        span: member.span,
809    }
810}
811
812/// Folds a property hook.
813pub fn fold_property_hook<'new, 'src, F: Fold<'src> + ?Sized>(
814    folder: &mut F,
815    arena: &'new Bump,
816    hook: &PropertyHook<'_, 'src>,
817) -> PropertyHook<'new, 'src> {
818    let body = match &hook.body {
819        PropertyHookBody::Block(block) => {
820            PropertyHookBody::Block(arena.alloc(fold_block(folder, arena, block)))
821        }
822        PropertyHookBody::Expression(expr) => {
823            PropertyHookBody::Expression(folder.fold_expr(arena, expr))
824        }
825        PropertyHookBody::Abstract => PropertyHookBody::Abstract,
826    };
827    PropertyHook {
828        kind: hook.kind,
829        body,
830        modifiers: hook.modifiers,
831        by_ref: hook.by_ref,
832        params: fold_params(folder, arena, &hook.params),
833        attributes: fold_attrs(folder, arena, &hook.attributes),
834        span: hook.span,
835    }
836}
837
838/// Folds a type hint.
839pub fn fold_type_hint<'new, 'src, F: Fold<'src> + ?Sized>(
840    folder: &mut F,
841    arena: &'new Bump,
842    type_hint: &TypeHint<'_, 'src>,
843) -> TypeHint<'new, 'src> {
844    let kind = match &type_hint.kind {
845        TypeHintKind::Named(name) => TypeHintKind::Named(folder.fold_name(arena, name)),
846        TypeHintKind::Keyword(builtin, span) => TypeHintKind::Keyword(*builtin, *span),
847        TypeHintKind::Nullable(inner) => {
848            TypeHintKind::Nullable(arena.alloc(folder.fold_type_hint(arena, inner)))
849        }
850        TypeHintKind::Union(types) => {
851            let mut new_types = ArenaVec::with_capacity_in(types.len(), arena);
852            for t in types.iter() {
853                new_types.push(folder.fold_type_hint(arena, t));
854            }
855            TypeHintKind::Union(new_types)
856        }
857        TypeHintKind::Intersection(types) => {
858            let mut new_types = ArenaVec::with_capacity_in(types.len(), arena);
859            for t in types.iter() {
860                new_types.push(folder.fold_type_hint(arena, t));
861            }
862            TypeHintKind::Intersection(new_types)
863        }
864    };
865    TypeHint {
866        kind,
867        span: type_hint.span,
868    }
869}
870
871/// Folds an attribute.
872pub fn fold_attribute<'new, 'src, F: Fold<'src> + ?Sized>(
873    folder: &mut F,
874    arena: &'new Bump,
875    attribute: &Attribute<'_, 'src>,
876) -> Attribute<'new, 'src> {
877    Attribute {
878        name: folder.fold_name(arena, &attribute.name),
879        args: fold_args(folder, arena, &attribute.args),
880        span: attribute.span,
881    }
882}
883
884/// Folds a `catch` clause.
885pub fn fold_catch_clause<'new, 'src, F: Fold<'src> + ?Sized>(
886    folder: &mut F,
887    arena: &'new Bump,
888    catch: &CatchClause<'_, 'src>,
889) -> CatchClause<'new, 'src> {
890    let mut types = ArenaVec::with_capacity_in(catch.types.len(), arena);
891    for ty in catch.types.iter() {
892        types.push(folder.fold_name(arena, ty));
893    }
894    CatchClause {
895        types,
896        var: catch.var,
897        body: arena.alloc(folder.fold_block(arena, catch.body)),
898        span: catch.span,
899    }
900}
901
902/// Folds a `match` arm.
903pub fn fold_match_arm<'new, 'src, F: Fold<'src> + ?Sized>(
904    folder: &mut F,
905    arena: &'new Bump,
906    arm: &MatchArm<'_, 'src>,
907) -> MatchArm<'new, 'src> {
908    let conditions = arm.conditions.as_ref().map(|conds| {
909        let mut new_conds = ArenaVec::with_capacity_in(conds.len(), arena);
910        for c in conds.iter() {
911            new_conds.push(folder.fold_expr(arena, c));
912        }
913        new_conds
914    });
915    MatchArm {
916        conditions,
917        body: folder.fold_expr(arena, &arm.body),
918        span: arm.span,
919    }
920}
921
922/// Folds a `use Trait;` declaration.
923pub fn fold_trait_use<'new, 'src, F: Fold<'src> + ?Sized>(
924    folder: &mut F,
925    arena: &'new Bump,
926    trait_use: &TraitUseDecl<'_, 'src>,
927) -> TraitUseDecl<'new, 'src> {
928    let mut traits = ArenaVec::with_capacity_in(trait_use.traits.len(), arena);
929    for t in trait_use.traits.iter() {
930        traits.push(folder.fold_name(arena, t));
931    }
932    let mut adaptations = ArenaVec::with_capacity_in(trait_use.adaptations.len(), arena);
933    for a in trait_use.adaptations.iter() {
934        adaptations.push(folder.fold_trait_adaptation(arena, a));
935    }
936    TraitUseDecl {
937        traits,
938        adaptations,
939        adaptations_brace_span: trait_use.adaptations_brace_span,
940        doc_comment: trait_use.doc_comment.as_ref().map(fold_comment),
941    }
942}
943
944/// Folds a trait `insteadof`/`as` adaptation.
945pub fn fold_trait_adaptation<'new, 'src, F: Fold<'src> + ?Sized>(
946    folder: &mut F,
947    arena: &'new Bump,
948    adaptation: &TraitAdaptation<'_, 'src>,
949) -> TraitAdaptation<'new, 'src> {
950    let kind = match &adaptation.kind {
951        TraitAdaptationKind::Precedence {
952            trait_name,
953            method,
954            insteadof,
955        } => {
956            let mut new_insteadof = ArenaVec::with_capacity_in(insteadof.len(), arena);
957            for n in insteadof.iter() {
958                new_insteadof.push(folder.fold_name(arena, n));
959            }
960            TraitAdaptationKind::Precedence {
961                trait_name: folder.fold_name(arena, trait_name),
962                method: folder.fold_name(arena, method),
963                insteadof: new_insteadof,
964            }
965        }
966        TraitAdaptationKind::Alias {
967            trait_name,
968            method,
969            new_modifier,
970            is_final,
971            new_name,
972        } => TraitAdaptationKind::Alias {
973            trait_name: trait_name.as_ref().map(|n| folder.fold_name(arena, n)),
974            method: folder.fold_name(arena, method),
975            new_modifier: *new_modifier,
976            is_final: *is_final,
977            new_name: new_name.as_ref().map(|n| folder.fold_name(arena, n)),
978        },
979    };
980    TraitAdaptation {
981        kind,
982        span: adaptation.span,
983    }
984}
985
986/// Folds a name reference.
987pub fn fold_name<'new, 'src, F: Fold<'src> + ?Sized>(
988    _folder: &mut F,
989    arena: &'new Bump,
990    name: &Name<'_, 'src>,
991) -> Name<'new, 'src> {
992    match name {
993        Name::Simple { value, span } => Name::Simple { value, span: *span },
994        Name::Complex { parts, kind, span } => {
995            let mut new_parts = ArenaVec::with_capacity_in(parts.len(), arena);
996            for &part in parts.iter() {
997                new_parts.push(part);
998            }
999            Name::Complex {
1000                parts: new_parts,
1001                kind: *kind,
1002                span: *span,
1003            }
1004        }
1005        Name::Error { span } => Name::Error { span: *span },
1006    }
1007}
1008
1009// =============================================================================
1010// Private helpers — complex declaration types
1011// =============================================================================
1012
1013fn fold_function_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1014    folder: &mut F,
1015    arena: &'new Bump,
1016    func: &FunctionDecl<'_, 'src>,
1017) -> FunctionDecl<'new, 'src> {
1018    FunctionDecl {
1019        name: func.name,
1020        params: fold_params(folder, arena, &func.params),
1021        missing_param_list: func.missing_param_list,
1022        body: arena.alloc(folder.fold_block(arena, func.body)),
1023        return_type: func
1024            .return_type
1025            .as_ref()
1026            .map(|t| folder.fold_type_hint(arena, t)),
1027        by_ref: func.by_ref,
1028        attributes: fold_attrs(folder, arena, &func.attributes),
1029        doc_comment: func.doc_comment.as_ref().map(fold_comment),
1030    }
1031}
1032
1033fn fold_method_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1034    folder: &mut F,
1035    arena: &'new Bump,
1036    method: &MethodDecl<'_, 'src>,
1037) -> MethodDecl<'new, 'src> {
1038    MethodDecl {
1039        name: method.name,
1040        modifiers: method.modifiers,
1041        by_ref: method.by_ref,
1042        params: fold_params(folder, arena, &method.params),
1043        missing_param_list: method.missing_param_list,
1044        return_type: method
1045            .return_type
1046            .as_ref()
1047            .map(|t| folder.fold_type_hint(arena, t)),
1048        body: method
1049            .body
1050            .map(|b| &*arena.alloc(folder.fold_block(arena, b))),
1051        attributes: fold_attrs(folder, arena, &method.attributes),
1052        doc_comment: method.doc_comment.as_ref().map(fold_comment),
1053    }
1054}
1055
1056fn fold_property_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1057    folder: &mut F,
1058    arena: &'new Bump,
1059    prop: &PropertyDecl<'_, 'src>,
1060) -> PropertyDecl<'new, 'src> {
1061    PropertyDecl {
1062        name: prop.name,
1063        modifiers: prop.modifiers,
1064        type_hint: prop
1065            .type_hint
1066            .as_ref()
1067            .map(|t| folder.fold_type_hint(arena, t)),
1068        default: prop.default.as_ref().map(|d| folder.fold_expr(arena, d)),
1069        attributes: fold_attrs(folder, arena, &prop.attributes),
1070        hooks: fold_hooks(folder, arena, &prop.hooks),
1071        empty_hook_list: prop.empty_hook_list,
1072        doc_comment: prop.doc_comment.as_ref().map(fold_comment),
1073    }
1074}
1075
1076fn fold_class_const_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1077    folder: &mut F,
1078    arena: &'new Bump,
1079    cc: &ClassConstDecl<'_, 'src>,
1080) -> ClassConstDecl<'new, 'src> {
1081    ClassConstDecl {
1082        name: cc.name,
1083        modifiers: cc.modifiers,
1084        type_hint: cc
1085            .type_hint
1086            .map(|t| &*arena.alloc(folder.fold_type_hint(arena, t))),
1087        value: folder.fold_expr(arena, &cc.value),
1088        attributes: fold_attrs(folder, arena, &cc.attributes),
1089        doc_comment: cc.doc_comment.as_ref().map(fold_comment),
1090    }
1091}
1092
1093fn fold_class_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1094    folder: &mut F,
1095    arena: &'new Bump,
1096    class: &ClassDecl<'_, 'src>,
1097) -> ClassDecl<'new, 'src> {
1098    let mut members = ArenaVec::with_capacity_in(class.body.members.len(), arena);
1099    for member in class.body.members.iter() {
1100        members.push(folder.fold_class_member(arena, member));
1101    }
1102    ClassDecl {
1103        name: class.name,
1104        modifiers: class.modifiers.clone(),
1105        extends: class.extends.as_ref().map(|n| folder.fold_name(arena, n)),
1106        implements: {
1107            let mut v = ArenaVec::with_capacity_in(class.implements.len(), arena);
1108            for n in class.implements.iter() {
1109                v.push(folder.fold_name(arena, n));
1110            }
1111            v
1112        },
1113        body: ClassBody {
1114            members,
1115            span: class.body.span,
1116        },
1117        attributes: fold_attrs(folder, arena, &class.attributes),
1118        doc_comment: class.doc_comment.as_ref().map(fold_comment),
1119    }
1120}
1121
1122fn fold_interface_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1123    folder: &mut F,
1124    arena: &'new Bump,
1125    iface: &InterfaceDecl<'_, 'src>,
1126) -> InterfaceDecl<'new, 'src> {
1127    let mut extends = ArenaVec::with_capacity_in(iface.extends.len(), arena);
1128    for n in iface.extends.iter() {
1129        extends.push(folder.fold_name(arena, n));
1130    }
1131    let mut members = ArenaVec::with_capacity_in(iface.body.members.len(), arena);
1132    for member in iface.body.members.iter() {
1133        members.push(folder.fold_class_member(arena, member));
1134    }
1135    InterfaceDecl {
1136        name: iface.name,
1137        extends,
1138        body: ClassBody {
1139            members,
1140            span: iface.body.span,
1141        },
1142        attributes: fold_attrs(folder, arena, &iface.attributes),
1143        doc_comment: iface.doc_comment.as_ref().map(fold_comment),
1144    }
1145}
1146
1147fn fold_trait_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1148    folder: &mut F,
1149    arena: &'new Bump,
1150    t: &TraitDecl<'_, 'src>,
1151) -> TraitDecl<'new, 'src> {
1152    let mut members = ArenaVec::with_capacity_in(t.body.members.len(), arena);
1153    for member in t.body.members.iter() {
1154        members.push(folder.fold_class_member(arena, member));
1155    }
1156    TraitDecl {
1157        name: t.name,
1158        body: ClassBody {
1159            members,
1160            span: t.body.span,
1161        },
1162        attributes: fold_attrs(folder, arena, &t.attributes),
1163        doc_comment: t.doc_comment.as_ref().map(fold_comment),
1164    }
1165}
1166
1167fn fold_enum_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1168    folder: &mut F,
1169    arena: &'new Bump,
1170    e: &EnumDecl<'_, 'src>,
1171) -> EnumDecl<'new, 'src> {
1172    let mut members = ArenaVec::with_capacity_in(e.body.members.len(), arena);
1173    for member in e.body.members.iter() {
1174        members.push(folder.fold_enum_member(arena, member));
1175    }
1176    EnumDecl {
1177        name: e.name,
1178        scalar_type: e.scalar_type.as_ref().map(|n| folder.fold_name(arena, n)),
1179        implements: {
1180            let mut v = ArenaVec::with_capacity_in(e.implements.len(), arena);
1181            for n in e.implements.iter() {
1182                v.push(folder.fold_name(arena, n));
1183            }
1184            v
1185        },
1186        body: EnumBody {
1187            members,
1188            span: e.body.span,
1189        },
1190        attributes: fold_attrs(folder, arena, &e.attributes),
1191        doc_comment: e.doc_comment.as_ref().map(fold_comment),
1192    }
1193}
1194
1195// =============================================================================
1196// Private helpers — collection folding
1197// =============================================================================
1198
1199/// Folds each statement of a list in order.
1200pub fn fold_stmts<'new, 'src, F: Fold<'src> + ?Sized>(
1201    folder: &mut F,
1202    arena: &'new Bump,
1203    stmts: &[Stmt<'_, 'src>],
1204) -> ArenaVec<'new, Stmt<'new, 'src>> {
1205    let mut vec = ArenaVec::with_capacity_in(stmts.len(), arena);
1206    for stmt in stmts {
1207        vec.push(folder.fold_stmt(arena, stmt));
1208    }
1209    vec
1210}
1211
1212/// Folds a block of statements.
1213pub fn fold_block<'new, 'src, F: Fold<'src> + ?Sized>(
1214    folder: &mut F,
1215    arena: &'new Bump,
1216    block: &Block<'_, 'src>,
1217) -> Block<'new, 'src> {
1218    Block {
1219        stmts: folder.fold_stmts(arena, &block.stmts),
1220        span: block.span,
1221    }
1222}
1223
1224fn fold_exprs<'new, 'src, F: Fold<'src> + ?Sized>(
1225    folder: &mut F,
1226    arena: &'new Bump,
1227    exprs: &[Expr<'_, 'src>],
1228) -> ArenaVec<'new, Expr<'new, 'src>> {
1229    let mut vec = ArenaVec::with_capacity_in(exprs.len(), arena);
1230    for expr in exprs {
1231        vec.push(folder.fold_expr(arena, expr));
1232    }
1233    vec
1234}
1235
1236fn fold_args<'new, 'src, F: Fold<'src> + ?Sized>(
1237    folder: &mut F,
1238    arena: &'new Bump,
1239    args: &[Arg<'_, 'src>],
1240) -> ArenaVec<'new, Arg<'new, 'src>> {
1241    let mut vec = ArenaVec::with_capacity_in(args.len(), arena);
1242    for arg in args {
1243        vec.push(folder.fold_arg(arena, arg));
1244    }
1245    vec
1246}
1247
1248fn fold_params<'new, 'src, F: Fold<'src> + ?Sized>(
1249    folder: &mut F,
1250    arena: &'new Bump,
1251    params: &[Param<'_, 'src>],
1252) -> ArenaVec<'new, Param<'new, 'src>> {
1253    let mut vec = ArenaVec::with_capacity_in(params.len(), arena);
1254    for param in params {
1255        vec.push(folder.fold_param(arena, param));
1256    }
1257    vec
1258}
1259
1260fn fold_attrs<'new, 'src, F: Fold<'src> + ?Sized>(
1261    folder: &mut F,
1262    arena: &'new Bump,
1263    attrs: &[Attribute<'_, 'src>],
1264) -> ArenaVec<'new, Attribute<'new, 'src>> {
1265    let mut vec = ArenaVec::with_capacity_in(attrs.len(), arena);
1266    for attr in attrs {
1267        vec.push(folder.fold_attribute(arena, attr));
1268    }
1269    vec
1270}
1271
1272fn fold_hooks<'new, 'src, F: Fold<'src> + ?Sized>(
1273    folder: &mut F,
1274    arena: &'new Bump,
1275    hooks: &[PropertyHook<'_, 'src>],
1276) -> ArenaVec<'new, PropertyHook<'new, 'src>> {
1277    let mut vec = ArenaVec::with_capacity_in(hooks.len(), arena);
1278    for hook in hooks {
1279        vec.push(folder.fold_property_hook(arena, hook));
1280    }
1281    vec
1282}
1283
1284fn fold_string_parts<'new, 'src, F: Fold<'src> + ?Sized>(
1285    folder: &mut F,
1286    arena: &'new Bump,
1287    parts: &[StringPart<'_, 'src>],
1288) -> ArenaVec<'new, StringPart<'new, 'src>> {
1289    let mut vec = ArenaVec::with_capacity_in(parts.len(), arena);
1290    for part in parts {
1291        vec.push(match part {
1292            StringPart::Literal(s) => StringPart::Literal(fold_byte_str(*s, arena)),
1293            StringPart::Expr(e) => StringPart::Expr(folder.fold_expr(arena, e)),
1294        });
1295    }
1296    vec
1297}
1298
1299// =============================================================================
1300// Private helpers — leaf types
1301// =============================================================================
1302
1303/// Moves a [`ByteStr`] into the output arena of a fold: a string borrowed from the source stays
1304/// borrowed, anything else is copied into `arena`.
1305pub fn fold_byte_str<'new, 'src>(
1306    value: ByteStr<'_, 'src>,
1307    arena: &'new Bump,
1308) -> ByteStr<'new, 'src> {
1309    match value.into_src_str() {
1310        Some(s) => ByteStr::from_source(s),
1311        None => ByteStr::new_in(arena, value.as_bytes()),
1312    }
1313}
1314
1315/// Moves a [`NameStr`] into the output arena of a fold: a string borrowed from
1316/// the source stays borrowed, an arena string is copied into `arena`. Use it
1317/// when overriding a fold method that rebuilds names, variables or strings.
1318pub fn fold_name_str<'new, 'src>(
1319    name: NameStr<'_, 'src>,
1320    arena: &'new Bump,
1321) -> NameStr<'new, 'src> {
1322    if let Some(s) = name.into_src_str() {
1323        NameStr::from_source(s)
1324    } else {
1325        NameStr::from_arena(arena.alloc_str(name.as_str()))
1326    }
1327}
1328
1329fn fold_comment<'src>(comment: &Comment<'src>) -> Comment<'src> {
1330    Comment {
1331        kind: comment.kind,
1332        text: comment.text,
1333        span: comment.span,
1334    }
1335}