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                key_by_ref: fe.key_by_ref,
307                value: folder.fold_expr(arena, &fe.value),
308                by_ref: fe.by_ref,
309                by_ref_span: fe.by_ref_span,
310                body: arena.alloc(folder.fold_stmt(arena, fe.body)),
311                uses_alternative: fe.uses_alternative,
312            });
313            StmtKind::Foreach(new_fe)
314        }
315        StmtKind::DoWhile(dw) => {
316            let new_dw = arena.alloc(DoWhileStmt {
317                body: arena.alloc(folder.fold_stmt(arena, dw.body)),
318                condition: folder.fold_expr(arena, &dw.condition),
319            });
320            StmtKind::DoWhile(new_dw)
321        }
322        StmtKind::Function(func) => {
323            StmtKind::Function(arena.alloc(fold_function_decl(folder, arena, func)))
324        }
325        StmtKind::Break(expr) => {
326            StmtKind::Break(expr.map(|e| &*arena.alloc(folder.fold_expr(arena, e))))
327        }
328        StmtKind::Continue(expr) => {
329            StmtKind::Continue(expr.map(|e| &*arena.alloc(folder.fold_expr(arena, e))))
330        }
331        StmtKind::Switch(sw) => {
332            let mut cases = ArenaVec::with_capacity_in(sw.body.cases.len(), arena);
333            for case in sw.body.cases.iter() {
334                cases.push(SwitchCase {
335                    value: case.value.as_ref().map(|v| folder.fold_expr(arena, v)),
336                    body: folder.fold_stmts(arena, &case.body),
337                    span: case.span,
338                });
339            }
340            let new_sw = arena.alloc(SwitchStmt {
341                expr: folder.fold_expr(arena, &sw.expr),
342                body: SwitchBody {
343                    cases,
344                    span: sw.body.span,
345                },
346                uses_alternative: sw.uses_alternative,
347            });
348            StmtKind::Switch(new_sw)
349        }
350        StmtKind::Goto(ident) => StmtKind::Goto(*ident),
351        StmtKind::Label(ident) => StmtKind::Label(*ident),
352        StmtKind::Declare(decl) => {
353            let mut directives = ArenaVec::with_capacity_in(decl.directives.len(), arena);
354            for (name, expr) in decl.directives.iter() {
355                directives.push((*name, folder.fold_expr(arena, expr)));
356            }
357            let new_decl = arena.alloc(DeclareStmt {
358                directives,
359                body: decl.body.map(|b| &*arena.alloc(folder.fold_stmt(arena, b))),
360                uses_alternative: decl.uses_alternative,
361            });
362            StmtKind::Declare(new_decl)
363        }
364        StmtKind::Unset(exprs) => StmtKind::Unset(fold_exprs(folder, arena, exprs)),
365        StmtKind::Throw(expr) => StmtKind::Throw(arena.alloc(folder.fold_expr(arena, expr))),
366        StmtKind::TryCatch(tc) => {
367            let mut catches = ArenaVec::with_capacity_in(tc.catches.len(), arena);
368            for catch in tc.catches.iter() {
369                catches.push(folder.fold_catch_clause(arena, catch));
370            }
371            let new_tc = arena.alloc(TryCatchStmt {
372                body: arena.alloc(folder.fold_block(arena, tc.body)),
373                catches,
374                finally: tc
375                    .finally
376                    .map(|f| &*arena.alloc(folder.fold_block(arena, f))),
377                finally_kw_span: tc.finally_kw_span,
378            });
379            StmtKind::TryCatch(new_tc)
380        }
381        StmtKind::Global(exprs) => StmtKind::Global(fold_exprs(folder, arena, exprs)),
382        StmtKind::Class(class) => {
383            StmtKind::Class(arena.alloc(fold_class_decl(folder, arena, class)))
384        }
385        StmtKind::Interface(iface) => {
386            StmtKind::Interface(arena.alloc(fold_interface_decl(folder, arena, iface)))
387        }
388        StmtKind::Trait(t) => StmtKind::Trait(arena.alloc(fold_trait_decl(folder, arena, t))),
389        StmtKind::Enum(e) => StmtKind::Enum(arena.alloc(fold_enum_decl(folder, arena, e))),
390        StmtKind::Namespace(ns) => {
391            let new_ns = arena.alloc(NamespaceDecl {
392                name: ns.name.as_ref().map(|n| folder.fold_name(arena, n)),
393                body: match &ns.body {
394                    NamespaceBody::Braced(block) => {
395                        NamespaceBody::Braced(arena.alloc(folder.fold_block(arena, block)))
396                    }
397                    NamespaceBody::Simple => NamespaceBody::Simple,
398                },
399            });
400            StmtKind::Namespace(new_ns)
401        }
402        StmtKind::Use(use_decl) => {
403            let mut uses = ArenaVec::with_capacity_in(use_decl.uses.len(), arena);
404            for item in use_decl.uses.iter() {
405                uses.push(UseItem {
406                    name: folder.fold_name(arena, &item.name),
407                    alias: item.alias,
408                    kind: item.kind,
409                    span: item.span,
410                });
411            }
412            let new_use = arena.alloc(UseDecl {
413                kind: use_decl.kind,
414                uses,
415            });
416            StmtKind::Use(new_use)
417        }
418        StmtKind::Const(items) => {
419            let mut new_items = ArenaVec::with_capacity_in(items.len(), arena);
420            for item in items.iter() {
421                new_items.push(ConstItem {
422                    name: item.name,
423                    value: folder.fold_expr(arena, &item.value),
424                    attributes: fold_attrs(folder, arena, &item.attributes),
425                    span: item.span,
426                    doc_comment: item.doc_comment.as_ref().map(fold_comment),
427                });
428            }
429            StmtKind::Const(new_items)
430        }
431        StmtKind::StaticVar(vars) => {
432            let mut new_vars = ArenaVec::with_capacity_in(vars.len(), arena);
433            for var in vars.iter() {
434                new_vars.push(StaticVar {
435                    name: var.name,
436                    default: var.default.as_ref().map(|d| folder.fold_expr(arena, d)),
437                    span: var.span,
438                });
439            }
440            StmtKind::StaticVar(new_vars)
441        }
442        StmtKind::HaltCompiler(s) => StmtKind::HaltCompiler(s),
443        StmtKind::Nop => StmtKind::Nop,
444        StmtKind::InlineHtml(s) => StmtKind::InlineHtml(s),
445        StmtKind::Error => StmtKind::Error,
446    };
447    Stmt {
448        kind,
449        span: stmt.span,
450        doc_comment: stmt.doc_comment.map(|c| &*arena.alloc(fold_comment(c))),
451    }
452}
453
454/// Folds an expression.
455pub fn fold_expr<'new, 'src, F: Fold<'src> + ?Sized>(
456    folder: &mut F,
457    arena: &'new Bump,
458    expr: &Expr<'_, 'src>,
459) -> Expr<'new, 'src> {
460    let kind = match &expr.kind {
461        ExprKind::Int(n) => ExprKind::Int(*n),
462        ExprKind::Float(f) => ExprKind::Float(*f),
463        ExprKind::String(s) => ExprKind::String(fold_byte_str(*s, arena)),
464        ExprKind::InterpolatedString(parts) => {
465            ExprKind::InterpolatedString(fold_string_parts(folder, arena, parts))
466        }
467        ExprKind::Heredoc { label, parts } => ExprKind::Heredoc {
468            label,
469            parts: fold_string_parts(folder, arena, parts),
470        },
471        ExprKind::Nowdoc { label, value } => ExprKind::Nowdoc {
472            label,
473            value: arena.alloc_str(value),
474        },
475        ExprKind::ShellExec(parts) => ExprKind::ShellExec(fold_string_parts(folder, arena, parts)),
476        ExprKind::Bool(b) => ExprKind::Bool(*b),
477        ExprKind::Null => ExprKind::Null,
478        ExprKind::Variable(name) => ExprKind::Variable(fold_name_str(*name, arena)),
479        ExprKind::VariableVariable(inner) => {
480            ExprKind::VariableVariable(arena.alloc(folder.fold_expr(arena, inner)))
481        }
482        ExprKind::Identifier(name) => ExprKind::Identifier(fold_name_str(*name, arena)),
483        ExprKind::Name(name) => ExprKind::Name(folder.fold_name(arena, name)),
484        ExprKind::Assign(assign) => ExprKind::Assign(AssignExpr {
485            target: arena.alloc(folder.fold_expr(arena, assign.target)),
486            op: assign.op,
487            value: arena.alloc(folder.fold_expr(arena, assign.value)),
488            by_ref: assign.by_ref,
489        }),
490        ExprKind::Binary(binary) => ExprKind::Binary(BinaryExpr {
491            left: arena.alloc(folder.fold_expr(arena, binary.left)),
492            op: binary.op,
493            right: arena.alloc(folder.fold_expr(arena, binary.right)),
494        }),
495        ExprKind::UnaryPrefix(u) => ExprKind::UnaryPrefix(UnaryPrefixExpr {
496            op: u.op,
497            operand: arena.alloc(folder.fold_expr(arena, u.operand)),
498        }),
499        ExprKind::UnaryPostfix(u) => ExprKind::UnaryPostfix(UnaryPostfixExpr {
500            operand: arena.alloc(folder.fold_expr(arena, u.operand)),
501            op: u.op,
502        }),
503        ExprKind::Ternary(t) => ExprKind::Ternary(TernaryExpr {
504            condition: arena.alloc(folder.fold_expr(arena, t.condition)),
505            then_expr: t
506                .then_expr
507                .map(|e| &*arena.alloc(folder.fold_expr(arena, e))),
508            else_expr: arena.alloc(folder.fold_expr(arena, t.else_expr)),
509        }),
510        ExprKind::NullCoalesce(nc) => ExprKind::NullCoalesce(NullCoalesceExpr {
511            left: arena.alloc(folder.fold_expr(arena, nc.left)),
512            right: arena.alloc(folder.fold_expr(arena, nc.right)),
513        }),
514        ExprKind::FunctionCall(call) => ExprKind::FunctionCall(FunctionCallExpr {
515            name: arena.alloc(folder.fold_expr(arena, call.name)),
516            args: fold_args(folder, arena, &call.args),
517        }),
518        ExprKind::Array(elements) => {
519            let mut new_elements = ArenaVec::with_capacity_in(elements.len(), arena);
520            for elem in elements.iter() {
521                new_elements.push(ArrayElement {
522                    key: elem.key.as_ref().map(|k| folder.fold_expr(arena, k)),
523                    kind: match &elem.kind {
524                        ArrayElementKind::Value(e) => {
525                            ArrayElementKind::Value(folder.fold_expr(arena, e))
526                        }
527                        ArrayElementKind::Spread(e) => {
528                            ArrayElementKind::Spread(folder.fold_expr(arena, e))
529                        }
530                        ArrayElementKind::ByRef(e) => {
531                            ArrayElementKind::ByRef(folder.fold_expr(arena, e))
532                        }
533                    },
534                    span: elem.span,
535                });
536            }
537            ExprKind::Array(new_elements)
538        }
539        ExprKind::ArrayAccess(access) => ExprKind::ArrayAccess(ArrayAccessExpr {
540            array: arena.alloc(folder.fold_expr(arena, access.array)),
541            index: access
542                .index
543                .map(|i| &*arena.alloc(folder.fold_expr(arena, i))),
544        }),
545        ExprKind::Print(e) => ExprKind::Print(arena.alloc(folder.fold_expr(arena, e))),
546        ExprKind::Parenthesized(e) => {
547            ExprKind::Parenthesized(arena.alloc(folder.fold_expr(arena, e)))
548        }
549        ExprKind::Cast(kind, e) => ExprKind::Cast(*kind, arena.alloc(folder.fold_expr(arena, e))),
550        ExprKind::ErrorSuppress(e) => {
551            ExprKind::ErrorSuppress(arena.alloc(folder.fold_expr(arena, e)))
552        }
553        ExprKind::Isset(exprs) => ExprKind::Isset(fold_exprs(folder, arena, exprs)),
554        ExprKind::Empty(e) => ExprKind::Empty(arena.alloc(folder.fold_expr(arena, e))),
555        ExprKind::Include(kind, e) => {
556            ExprKind::Include(*kind, arena.alloc(folder.fold_expr(arena, e)))
557        }
558        ExprKind::Eval(e) => ExprKind::Eval(arena.alloc(folder.fold_expr(arena, e))),
559        ExprKind::Exit(e) => ExprKind::Exit(e.map(|e| &*arena.alloc(folder.fold_expr(arena, e)))),
560        ExprKind::MagicConst(k) => ExprKind::MagicConst(*k),
561        ExprKind::Clone(e) => ExprKind::Clone(arena.alloc(folder.fold_expr(arena, e))),
562        ExprKind::CloneWith(obj, overrides) => ExprKind::CloneWith(
563            arena.alloc(folder.fold_expr(arena, obj)),
564            arena.alloc(folder.fold_expr(arena, overrides)),
565        ),
566        ExprKind::New(new_expr) => ExprKind::New(NewExpr {
567            class: arena.alloc(folder.fold_expr(arena, new_expr.class)),
568            args: fold_args(folder, arena, &new_expr.args),
569        }),
570        ExprKind::PropertyAccess(access) => ExprKind::PropertyAccess(PropertyAccessExpr {
571            object: arena.alloc(folder.fold_expr(arena, access.object)),
572            property: arena.alloc(folder.fold_expr(arena, access.property)),
573        }),
574        ExprKind::NullsafePropertyAccess(access) => {
575            ExprKind::NullsafePropertyAccess(PropertyAccessExpr {
576                object: arena.alloc(folder.fold_expr(arena, access.object)),
577                property: arena.alloc(folder.fold_expr(arena, access.property)),
578            })
579        }
580        ExprKind::MethodCall(call) => ExprKind::MethodCall(arena.alloc(MethodCallExpr {
581            object: arena.alloc(folder.fold_expr(arena, call.object)),
582            method: arena.alloc(folder.fold_expr(arena, call.method)),
583            args: fold_args(folder, arena, &call.args),
584        })),
585        ExprKind::NullsafeMethodCall(call) => {
586            ExprKind::NullsafeMethodCall(arena.alloc(MethodCallExpr {
587                object: arena.alloc(folder.fold_expr(arena, call.object)),
588                method: arena.alloc(folder.fold_expr(arena, call.method)),
589                args: fold_args(folder, arena, &call.args),
590            }))
591        }
592        ExprKind::StaticPropertyAccess(access) => {
593            ExprKind::StaticPropertyAccess(StaticAccessExpr {
594                class: arena.alloc(folder.fold_expr(arena, access.class)),
595                member: arena.alloc(folder.fold_expr(arena, access.member)),
596            })
597        }
598        ExprKind::StaticMethodCall(call) => {
599            ExprKind::StaticMethodCall(arena.alloc(StaticMethodCallExpr {
600                class: arena.alloc(folder.fold_expr(arena, call.class)),
601                method: arena.alloc(folder.fold_expr(arena, call.method)),
602                args: fold_args(folder, arena, &call.args),
603            }))
604        }
605        ExprKind::StaticMethodCallDynamic(call) => {
606            ExprKind::StaticMethodCallDynamic(arena.alloc(StaticMethodCallDynamicExpr {
607                class: arena.alloc(folder.fold_expr(arena, call.class)),
608                method: arena.alloc(folder.fold_expr(arena, call.method)),
609                args: fold_args(folder, arena, &call.args),
610            }))
611        }
612        ExprKind::ClassConstAccess(access) => ExprKind::ClassConstAccess(StaticAccessExpr {
613            class: arena.alloc(folder.fold_expr(arena, access.class)),
614            member: arena.alloc(folder.fold_expr(arena, access.member)),
615        }),
616        ExprKind::ClassConstAccessDynamic { class, member } => ExprKind::ClassConstAccessDynamic {
617            class: arena.alloc(folder.fold_expr(arena, class)),
618            member: arena.alloc(folder.fold_expr(arena, member)),
619        },
620        ExprKind::StaticPropertyAccessDynamic { class, member } => {
621            ExprKind::StaticPropertyAccessDynamic {
622                class: arena.alloc(folder.fold_expr(arena, class)),
623                member: arena.alloc(folder.fold_expr(arena, member)),
624            }
625        }
626        ExprKind::Closure(closure) => {
627            let mut use_vars = ArenaVec::with_capacity_in(closure.use_vars.len(), arena);
628            for var in closure.use_vars.iter() {
629                use_vars.push(folder.fold_closure_use_var(arena, var));
630            }
631            let new_closure = arena.alloc(ClosureExpr {
632                is_static: closure.is_static,
633                by_ref: closure.by_ref,
634                params: fold_params(folder, arena, &closure.params),
635                missing_param_list: closure.missing_param_list,
636                use_vars,
637                return_type: closure
638                    .return_type
639                    .as_ref()
640                    .map(|t| folder.fold_type_hint(arena, t)),
641                body: arena.alloc(folder.fold_block(arena, closure.body)),
642                attributes: fold_attrs(folder, arena, &closure.attributes),
643            });
644            ExprKind::Closure(new_closure)
645        }
646        ExprKind::ArrowFunction(arrow) => {
647            let new_arrow = arena.alloc(ArrowFunctionExpr {
648                is_static: arrow.is_static,
649                by_ref: arrow.by_ref,
650                params: fold_params(folder, arena, &arrow.params),
651                missing_param_list: arrow.missing_param_list,
652                return_type: arrow
653                    .return_type
654                    .as_ref()
655                    .map(|t| folder.fold_type_hint(arena, t)),
656                body: arena.alloc(folder.fold_expr(arena, arrow.body)),
657                attributes: fold_attrs(folder, arena, &arrow.attributes),
658            });
659            ExprKind::ArrowFunction(new_arrow)
660        }
661        ExprKind::Match(match_expr) => ExprKind::Match(MatchExpr {
662            subject: arena.alloc(folder.fold_expr(arena, match_expr.subject)),
663            arms: {
664                let mut arms = ArenaVec::with_capacity_in(match_expr.arms.len(), arena);
665                for arm in match_expr.arms.iter() {
666                    arms.push(folder.fold_match_arm(arena, arm));
667                }
668                arms
669            },
670            brace_span: match_expr.brace_span,
671        }),
672        ExprKind::Throw(e) => ExprKind::Throw(arena.alloc(folder.fold_expr(arena, e))),
673        ExprKind::Yield(y) => ExprKind::Yield(YieldExpr {
674            key: y.key.map(|k| &*arena.alloc(folder.fold_expr(arena, k))),
675            value: y.value.map(|v| &*arena.alloc(folder.fold_expr(arena, v))),
676            is_from: y.is_from,
677        }),
678        ExprKind::AnonymousClass(class) => {
679            ExprKind::AnonymousClass(arena.alloc(fold_class_decl(folder, arena, class)))
680        }
681        ExprKind::CallableCreate(cc) => {
682            let kind = match &cc.kind {
683                CallableCreateKind::Function(name) => {
684                    CallableCreateKind::Function(arena.alloc(folder.fold_expr(arena, name)))
685                }
686                CallableCreateKind::Method { object, method } => CallableCreateKind::Method {
687                    object: arena.alloc(folder.fold_expr(arena, object)),
688                    method: arena.alloc(folder.fold_expr(arena, method)),
689                },
690                CallableCreateKind::NullsafeMethod { object, method } => {
691                    CallableCreateKind::NullsafeMethod {
692                        object: arena.alloc(folder.fold_expr(arena, object)),
693                        method: arena.alloc(folder.fold_expr(arena, method)),
694                    }
695                }
696                CallableCreateKind::StaticMethod { class, method } => {
697                    CallableCreateKind::StaticMethod {
698                        class: arena.alloc(folder.fold_expr(arena, class)),
699                        method: arena.alloc(folder.fold_expr(arena, method)),
700                    }
701                }
702            };
703            ExprKind::CallableCreate(CallableCreateExpr { kind })
704        }
705        ExprKind::Omit => ExprKind::Omit,
706        ExprKind::Error => ExprKind::Error,
707    };
708    Expr {
709        kind,
710        span: expr.span,
711    }
712}
713
714/// Folds a function parameter.
715pub fn fold_param<'new, 'src, F: Fold<'src> + ?Sized>(
716    folder: &mut F,
717    arena: &'new Bump,
718    param: &Param<'_, 'src>,
719) -> Param<'new, 'src> {
720    Param {
721        name: param.name,
722        type_hint: param
723            .type_hint
724            .as_ref()
725            .map(|t| folder.fold_type_hint(arena, t)),
726        default: param.default.as_ref().map(|d| folder.fold_expr(arena, d)),
727        by_ref: param.by_ref,
728        variadic: param.variadic,
729        modifiers: param.modifiers,
730        attributes: fold_attrs(folder, arena, &param.attributes),
731        hooks: fold_hooks(folder, arena, &param.hooks),
732        empty_hook_list: param.empty_hook_list,
733        doc_comment: param.doc_comment,
734        span: param.span,
735    }
736}
737
738/// Folds a call argument.
739pub fn fold_arg<'new, 'src, F: Fold<'src> + ?Sized>(
740    folder: &mut F,
741    arena: &'new Bump,
742    arg: &Arg<'_, 'src>,
743) -> Arg<'new, 'src> {
744    Arg {
745        name: arg.name.as_ref().map(|n| folder.fold_name(arena, n)),
746        kind: match &arg.kind {
747            ArgKind::Value(e) => ArgKind::Value(folder.fold_expr(arena, e)),
748            ArgKind::Spread(e) => ArgKind::Spread(folder.fold_expr(arena, e)),
749            ArgKind::ByRef(e) => ArgKind::ByRef(folder.fold_expr(arena, e)),
750            ArgKind::Placeholder => ArgKind::Placeholder,
751            ArgKind::SpreadPlaceholder => ArgKind::SpreadPlaceholder,
752        },
753        span: arg.span,
754    }
755}
756
757/// Folds a class-like member.
758pub fn fold_class_member<'new, 'src, F: Fold<'src> + ?Sized>(
759    folder: &mut F,
760    arena: &'new Bump,
761    member: &ClassMember<'_, 'src>,
762) -> ClassMember<'new, 'src> {
763    let kind = match &member.kind {
764        ClassMemberKind::Property(prop) => {
765            ClassMemberKind::Property(fold_property_decl(folder, arena, prop))
766        }
767        ClassMemberKind::Method(method) => {
768            ClassMemberKind::Method(fold_method_decl(folder, arena, method))
769        }
770        ClassMemberKind::ClassConst(cc) => {
771            ClassMemberKind::ClassConst(fold_class_const_decl(folder, arena, cc))
772        }
773        ClassMemberKind::TraitUse(tu) => {
774            ClassMemberKind::TraitUse(folder.fold_trait_use(arena, tu))
775        }
776    };
777    ClassMember {
778        kind,
779        span: member.span,
780    }
781}
782
783/// Folds an enum member.
784pub fn fold_enum_member<'new, 'src, F: Fold<'src> + ?Sized>(
785    folder: &mut F,
786    arena: &'new Bump,
787    member: &EnumMember<'_, 'src>,
788) -> EnumMember<'new, 'src> {
789    let kind = match &member.kind {
790        EnumMemberKind::Case(case) => EnumMemberKind::Case(EnumCase {
791            name: case.name,
792            value: case.value.as_ref().map(|v| folder.fold_expr(arena, v)),
793            attributes: fold_attrs(folder, arena, &case.attributes),
794            doc_comment: case.doc_comment.as_ref().map(fold_comment),
795        }),
796        EnumMemberKind::Method(method) => {
797            EnumMemberKind::Method(fold_method_decl(folder, arena, method))
798        }
799        EnumMemberKind::ClassConst(cc) => {
800            EnumMemberKind::ClassConst(fold_class_const_decl(folder, arena, cc))
801        }
802        EnumMemberKind::TraitUse(tu) => EnumMemberKind::TraitUse(folder.fold_trait_use(arena, tu)),
803        EnumMemberKind::Property(prop) => {
804            EnumMemberKind::Property(fold_property_decl(folder, arena, prop))
805        }
806    };
807    EnumMember {
808        kind,
809        span: member.span,
810    }
811}
812
813/// Folds a property hook.
814pub fn fold_property_hook<'new, 'src, F: Fold<'src> + ?Sized>(
815    folder: &mut F,
816    arena: &'new Bump,
817    hook: &PropertyHook<'_, 'src>,
818) -> PropertyHook<'new, 'src> {
819    let body = match &hook.body {
820        PropertyHookBody::Block(block) => {
821            PropertyHookBody::Block(arena.alloc(fold_block(folder, arena, block)))
822        }
823        PropertyHookBody::Expression(expr) => {
824            PropertyHookBody::Expression(folder.fold_expr(arena, expr))
825        }
826        PropertyHookBody::Abstract => PropertyHookBody::Abstract,
827    };
828    PropertyHook {
829        kind: hook.kind,
830        body,
831        modifiers: hook.modifiers,
832        by_ref: hook.by_ref,
833        params: fold_params(folder, arena, &hook.params),
834        empty_param_list: hook.empty_param_list,
835        attributes: fold_attrs(folder, arena, &hook.attributes),
836        name_span: hook.name_span,
837        span: hook.span,
838    }
839}
840
841/// Folds a type hint.
842pub fn fold_type_hint<'new, 'src, F: Fold<'src> + ?Sized>(
843    folder: &mut F,
844    arena: &'new Bump,
845    type_hint: &TypeHint<'_, 'src>,
846) -> TypeHint<'new, 'src> {
847    let kind = match &type_hint.kind {
848        TypeHintKind::Named(name) => TypeHintKind::Named(folder.fold_name(arena, name)),
849        TypeHintKind::Keyword(builtin, span) => TypeHintKind::Keyword(*builtin, *span),
850        TypeHintKind::Nullable(inner) => {
851            TypeHintKind::Nullable(arena.alloc(folder.fold_type_hint(arena, inner)))
852        }
853        TypeHintKind::Union(types) => {
854            let mut new_types = ArenaVec::with_capacity_in(types.len(), arena);
855            for t in types.iter() {
856                new_types.push(folder.fold_type_hint(arena, t));
857            }
858            TypeHintKind::Union(new_types)
859        }
860        TypeHintKind::Intersection(types) => {
861            let mut new_types = ArenaVec::with_capacity_in(types.len(), arena);
862            for t in types.iter() {
863                new_types.push(folder.fold_type_hint(arena, t));
864            }
865            TypeHintKind::Intersection(new_types)
866        }
867    };
868    TypeHint {
869        kind,
870        span: type_hint.span,
871    }
872}
873
874/// Folds an attribute.
875pub fn fold_attribute<'new, 'src, F: Fold<'src> + ?Sized>(
876    folder: &mut F,
877    arena: &'new Bump,
878    attribute: &Attribute<'_, 'src>,
879) -> Attribute<'new, 'src> {
880    Attribute {
881        name: folder.fold_name(arena, &attribute.name),
882        args: fold_args(folder, arena, &attribute.args),
883        span: attribute.span,
884    }
885}
886
887/// Folds a `catch` clause.
888pub fn fold_catch_clause<'new, 'src, F: Fold<'src> + ?Sized>(
889    folder: &mut F,
890    arena: &'new Bump,
891    catch: &CatchClause<'_, 'src>,
892) -> CatchClause<'new, 'src> {
893    let mut types = ArenaVec::with_capacity_in(catch.types.len(), arena);
894    for ty in catch.types.iter() {
895        types.push(folder.fold_name(arena, ty));
896    }
897    CatchClause {
898        types,
899        var: catch.var,
900        body: arena.alloc(folder.fold_block(arena, catch.body)),
901        span: catch.span,
902    }
903}
904
905/// Folds a `match` arm.
906pub fn fold_match_arm<'new, 'src, F: Fold<'src> + ?Sized>(
907    folder: &mut F,
908    arena: &'new Bump,
909    arm: &MatchArm<'_, 'src>,
910) -> MatchArm<'new, 'src> {
911    let conditions = arm.conditions.as_ref().map(|conds| {
912        let mut new_conds = ArenaVec::with_capacity_in(conds.len(), arena);
913        for c in conds.iter() {
914            new_conds.push(folder.fold_expr(arena, c));
915        }
916        new_conds
917    });
918    MatchArm {
919        conditions,
920        body: folder.fold_expr(arena, &arm.body),
921        span: arm.span,
922    }
923}
924
925/// Folds a `use Trait;` declaration.
926pub fn fold_trait_use<'new, 'src, F: Fold<'src> + ?Sized>(
927    folder: &mut F,
928    arena: &'new Bump,
929    trait_use: &TraitUseDecl<'_, 'src>,
930) -> TraitUseDecl<'new, 'src> {
931    let mut traits = ArenaVec::with_capacity_in(trait_use.traits.len(), arena);
932    for t in trait_use.traits.iter() {
933        traits.push(folder.fold_name(arena, t));
934    }
935    let mut adaptations = ArenaVec::with_capacity_in(trait_use.adaptations.len(), arena);
936    for a in trait_use.adaptations.iter() {
937        adaptations.push(folder.fold_trait_adaptation(arena, a));
938    }
939    TraitUseDecl {
940        traits,
941        adaptations,
942        adaptations_brace_span: trait_use.adaptations_brace_span,
943        doc_comment: trait_use.doc_comment.as_ref().map(fold_comment),
944    }
945}
946
947/// Folds a trait `insteadof`/`as` adaptation.
948pub fn fold_trait_adaptation<'new, 'src, F: Fold<'src> + ?Sized>(
949    folder: &mut F,
950    arena: &'new Bump,
951    adaptation: &TraitAdaptation<'_, 'src>,
952) -> TraitAdaptation<'new, 'src> {
953    let kind = match &adaptation.kind {
954        TraitAdaptationKind::Precedence {
955            trait_name,
956            method,
957            insteadof,
958        } => {
959            let mut new_insteadof = ArenaVec::with_capacity_in(insteadof.len(), arena);
960            for n in insteadof.iter() {
961                new_insteadof.push(folder.fold_name(arena, n));
962            }
963            TraitAdaptationKind::Precedence {
964                trait_name: folder.fold_name(arena, trait_name),
965                method: folder.fold_name(arena, method),
966                insteadof: new_insteadof,
967            }
968        }
969        TraitAdaptationKind::Alias {
970            trait_name,
971            method,
972            new_modifier,
973            is_final,
974            invalid_modifier,
975            new_name,
976        } => TraitAdaptationKind::Alias {
977            trait_name: trait_name.as_ref().map(|n| folder.fold_name(arena, n)),
978            method: folder.fold_name(arena, method),
979            new_modifier: *new_modifier,
980            is_final: *is_final,
981            invalid_modifier: *invalid_modifier,
982            new_name: new_name.as_ref().map(|n| folder.fold_name(arena, n)),
983        },
984    };
985    TraitAdaptation {
986        kind,
987        span: adaptation.span,
988    }
989}
990
991/// Folds a name reference.
992pub fn fold_name<'new, 'src, F: Fold<'src> + ?Sized>(
993    _folder: &mut F,
994    arena: &'new Bump,
995    name: &Name<'_, 'src>,
996) -> Name<'new, 'src> {
997    match name {
998        Name::Simple { value, span } => Name::Simple { value, span: *span },
999        Name::Complex { parts, kind, span } => {
1000            let mut new_parts = ArenaVec::with_capacity_in(parts.len(), arena);
1001            for &part in parts.iter() {
1002                new_parts.push(part);
1003            }
1004            Name::Complex {
1005                parts: new_parts,
1006                kind: *kind,
1007                span: *span,
1008            }
1009        }
1010        Name::Error { span } => Name::Error { span: *span },
1011    }
1012}
1013
1014// =============================================================================
1015// Private helpers — complex declaration types
1016// =============================================================================
1017
1018fn fold_function_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1019    folder: &mut F,
1020    arena: &'new Bump,
1021    func: &FunctionDecl<'_, 'src>,
1022) -> FunctionDecl<'new, 'src> {
1023    FunctionDecl {
1024        name: func.name,
1025        params: fold_params(folder, arena, &func.params),
1026        missing_param_list: func.missing_param_list,
1027        body: arena.alloc(folder.fold_block(arena, func.body)),
1028        return_type: func
1029            .return_type
1030            .as_ref()
1031            .map(|t| folder.fold_type_hint(arena, t)),
1032        by_ref: func.by_ref,
1033        attributes: fold_attrs(folder, arena, &func.attributes),
1034        doc_comment: func.doc_comment.as_ref().map(fold_comment),
1035    }
1036}
1037
1038fn fold_method_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1039    folder: &mut F,
1040    arena: &'new Bump,
1041    method: &MethodDecl<'_, 'src>,
1042) -> MethodDecl<'new, 'src> {
1043    MethodDecl {
1044        name: method.name,
1045        modifiers: method.modifiers,
1046        by_ref: method.by_ref,
1047        params: fold_params(folder, arena, &method.params),
1048        missing_param_list: method.missing_param_list,
1049        return_type: method
1050            .return_type
1051            .as_ref()
1052            .map(|t| folder.fold_type_hint(arena, t)),
1053        body: method
1054            .body
1055            .map(|b| &*arena.alloc(folder.fold_block(arena, b))),
1056        attributes: fold_attrs(folder, arena, &method.attributes),
1057        doc_comment: method.doc_comment.as_ref().map(fold_comment),
1058    }
1059}
1060
1061fn fold_property_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1062    folder: &mut F,
1063    arena: &'new Bump,
1064    prop: &PropertyDecl<'_, 'src>,
1065) -> PropertyDecl<'new, 'src> {
1066    PropertyDecl {
1067        name: prop.name,
1068        modifiers: prop.modifiers,
1069        type_hint: prop
1070            .type_hint
1071            .as_ref()
1072            .map(|t| folder.fold_type_hint(arena, t)),
1073        default: prop.default.as_ref().map(|d| folder.fold_expr(arena, d)),
1074        attributes: fold_attrs(folder, arena, &prop.attributes),
1075        hooks: fold_hooks(folder, arena, &prop.hooks),
1076        empty_hook_list: prop.empty_hook_list,
1077        doc_comment: prop.doc_comment.as_ref().map(fold_comment),
1078    }
1079}
1080
1081fn fold_class_const_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1082    folder: &mut F,
1083    arena: &'new Bump,
1084    cc: &ClassConstDecl<'_, 'src>,
1085) -> ClassConstDecl<'new, 'src> {
1086    ClassConstDecl {
1087        name: cc.name,
1088        modifiers: cc.modifiers,
1089        type_hint: cc
1090            .type_hint
1091            .map(|t| &*arena.alloc(folder.fold_type_hint(arena, t))),
1092        value: folder.fold_expr(arena, &cc.value),
1093        attributes: fold_attrs(folder, arena, &cc.attributes),
1094        doc_comment: cc.doc_comment.as_ref().map(fold_comment),
1095    }
1096}
1097
1098fn fold_class_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1099    folder: &mut F,
1100    arena: &'new Bump,
1101    class: &ClassDecl<'_, 'src>,
1102) -> ClassDecl<'new, 'src> {
1103    let mut members = ArenaVec::with_capacity_in(class.body.members.len(), arena);
1104    for member in class.body.members.iter() {
1105        members.push(folder.fold_class_member(arena, member));
1106    }
1107    ClassDecl {
1108        name: class.name,
1109        modifiers: class.modifiers.clone(),
1110        extends: class.extends.as_ref().map(|n| folder.fold_name(arena, n)),
1111        implements: {
1112            let mut v = ArenaVec::with_capacity_in(class.implements.len(), arena);
1113            for n in class.implements.iter() {
1114                v.push(folder.fold_name(arena, n));
1115            }
1116            v
1117        },
1118        body: ClassBody {
1119            members,
1120            span: class.body.span,
1121        },
1122        attributes: fold_attrs(folder, arena, &class.attributes),
1123        doc_comment: class.doc_comment.as_ref().map(fold_comment),
1124    }
1125}
1126
1127fn fold_interface_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1128    folder: &mut F,
1129    arena: &'new Bump,
1130    iface: &InterfaceDecl<'_, 'src>,
1131) -> InterfaceDecl<'new, 'src> {
1132    let mut extends = ArenaVec::with_capacity_in(iface.extends.len(), arena);
1133    for n in iface.extends.iter() {
1134        extends.push(folder.fold_name(arena, n));
1135    }
1136    let mut members = ArenaVec::with_capacity_in(iface.body.members.len(), arena);
1137    for member in iface.body.members.iter() {
1138        members.push(folder.fold_class_member(arena, member));
1139    }
1140    InterfaceDecl {
1141        name: iface.name,
1142        extends,
1143        body: ClassBody {
1144            members,
1145            span: iface.body.span,
1146        },
1147        attributes: fold_attrs(folder, arena, &iface.attributes),
1148        doc_comment: iface.doc_comment.as_ref().map(fold_comment),
1149    }
1150}
1151
1152fn fold_trait_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1153    folder: &mut F,
1154    arena: &'new Bump,
1155    t: &TraitDecl<'_, 'src>,
1156) -> TraitDecl<'new, 'src> {
1157    let mut members = ArenaVec::with_capacity_in(t.body.members.len(), arena);
1158    for member in t.body.members.iter() {
1159        members.push(folder.fold_class_member(arena, member));
1160    }
1161    TraitDecl {
1162        name: t.name,
1163        body: ClassBody {
1164            members,
1165            span: t.body.span,
1166        },
1167        attributes: fold_attrs(folder, arena, &t.attributes),
1168        doc_comment: t.doc_comment.as_ref().map(fold_comment),
1169    }
1170}
1171
1172fn fold_enum_decl<'new, 'src, F: Fold<'src> + ?Sized>(
1173    folder: &mut F,
1174    arena: &'new Bump,
1175    e: &EnumDecl<'_, 'src>,
1176) -> EnumDecl<'new, 'src> {
1177    let mut members = ArenaVec::with_capacity_in(e.body.members.len(), arena);
1178    for member in e.body.members.iter() {
1179        members.push(folder.fold_enum_member(arena, member));
1180    }
1181    EnumDecl {
1182        name: e.name,
1183        scalar_type: e.scalar_type.as_ref().map(|n| folder.fold_name(arena, n)),
1184        implements: {
1185            let mut v = ArenaVec::with_capacity_in(e.implements.len(), arena);
1186            for n in e.implements.iter() {
1187                v.push(folder.fold_name(arena, n));
1188            }
1189            v
1190        },
1191        body: EnumBody {
1192            members,
1193            span: e.body.span,
1194        },
1195        attributes: fold_attrs(folder, arena, &e.attributes),
1196        doc_comment: e.doc_comment.as_ref().map(fold_comment),
1197    }
1198}
1199
1200// =============================================================================
1201// Private helpers — collection folding
1202// =============================================================================
1203
1204/// Folds each statement of a list in order.
1205pub fn fold_stmts<'new, 'src, F: Fold<'src> + ?Sized>(
1206    folder: &mut F,
1207    arena: &'new Bump,
1208    stmts: &[Stmt<'_, 'src>],
1209) -> ArenaVec<'new, Stmt<'new, 'src>> {
1210    let mut vec = ArenaVec::with_capacity_in(stmts.len(), arena);
1211    for stmt in stmts {
1212        vec.push(folder.fold_stmt(arena, stmt));
1213    }
1214    vec
1215}
1216
1217/// Folds a block of statements.
1218pub fn fold_block<'new, 'src, F: Fold<'src> + ?Sized>(
1219    folder: &mut F,
1220    arena: &'new Bump,
1221    block: &Block<'_, 'src>,
1222) -> Block<'new, 'src> {
1223    Block {
1224        stmts: folder.fold_stmts(arena, &block.stmts),
1225        span: block.span,
1226    }
1227}
1228
1229fn fold_exprs<'new, 'src, F: Fold<'src> + ?Sized>(
1230    folder: &mut F,
1231    arena: &'new Bump,
1232    exprs: &[Expr<'_, 'src>],
1233) -> ArenaVec<'new, Expr<'new, 'src>> {
1234    let mut vec = ArenaVec::with_capacity_in(exprs.len(), arena);
1235    for expr in exprs {
1236        vec.push(folder.fold_expr(arena, expr));
1237    }
1238    vec
1239}
1240
1241fn fold_args<'new, 'src, F: Fold<'src> + ?Sized>(
1242    folder: &mut F,
1243    arena: &'new Bump,
1244    args: &[Arg<'_, 'src>],
1245) -> ArenaVec<'new, Arg<'new, 'src>> {
1246    let mut vec = ArenaVec::with_capacity_in(args.len(), arena);
1247    for arg in args {
1248        vec.push(folder.fold_arg(arena, arg));
1249    }
1250    vec
1251}
1252
1253fn fold_params<'new, 'src, F: Fold<'src> + ?Sized>(
1254    folder: &mut F,
1255    arena: &'new Bump,
1256    params: &[Param<'_, 'src>],
1257) -> ArenaVec<'new, Param<'new, 'src>> {
1258    let mut vec = ArenaVec::with_capacity_in(params.len(), arena);
1259    for param in params {
1260        vec.push(folder.fold_param(arena, param));
1261    }
1262    vec
1263}
1264
1265fn fold_attrs<'new, 'src, F: Fold<'src> + ?Sized>(
1266    folder: &mut F,
1267    arena: &'new Bump,
1268    attrs: &[Attribute<'_, 'src>],
1269) -> ArenaVec<'new, Attribute<'new, 'src>> {
1270    let mut vec = ArenaVec::with_capacity_in(attrs.len(), arena);
1271    for attr in attrs {
1272        vec.push(folder.fold_attribute(arena, attr));
1273    }
1274    vec
1275}
1276
1277fn fold_hooks<'new, 'src, F: Fold<'src> + ?Sized>(
1278    folder: &mut F,
1279    arena: &'new Bump,
1280    hooks: &[PropertyHook<'_, 'src>],
1281) -> ArenaVec<'new, PropertyHook<'new, 'src>> {
1282    let mut vec = ArenaVec::with_capacity_in(hooks.len(), arena);
1283    for hook in hooks {
1284        vec.push(folder.fold_property_hook(arena, hook));
1285    }
1286    vec
1287}
1288
1289fn fold_string_parts<'new, 'src, F: Fold<'src> + ?Sized>(
1290    folder: &mut F,
1291    arena: &'new Bump,
1292    parts: &[StringPart<'_, 'src>],
1293) -> ArenaVec<'new, StringPart<'new, 'src>> {
1294    let mut vec = ArenaVec::with_capacity_in(parts.len(), arena);
1295    for part in parts {
1296        vec.push(match part {
1297            StringPart::Literal(s) => StringPart::Literal(fold_byte_str(*s, arena)),
1298            StringPart::Expr(e) => StringPart::Expr(folder.fold_expr(arena, e)),
1299        });
1300    }
1301    vec
1302}
1303
1304// =============================================================================
1305// Private helpers — leaf types
1306// =============================================================================
1307
1308/// Moves a [`ByteStr`] into the output arena of a fold: a string borrowed from the source stays
1309/// borrowed, anything else is copied into `arena`.
1310pub fn fold_byte_str<'new, 'src>(
1311    value: ByteStr<'_, 'src>,
1312    arena: &'new Bump,
1313) -> ByteStr<'new, 'src> {
1314    match value.into_src_str() {
1315        Some(s) => ByteStr::from_source(s),
1316        None => ByteStr::new_in(arena, value.as_bytes()),
1317    }
1318}
1319
1320/// Moves a [`NameStr`] into the output arena of a fold: a string borrowed from
1321/// the source stays borrowed, an arena string is copied into `arena`. Use it
1322/// when overriding a fold method that rebuilds names, variables or strings.
1323pub fn fold_name_str<'new, 'src>(
1324    name: NameStr<'_, 'src>,
1325    arena: &'new Bump,
1326) -> NameStr<'new, 'src> {
1327    if let Some(s) = name.into_src_str() {
1328        NameStr::from_source(s)
1329    } else {
1330        NameStr::from_arena(arena.alloc_str(name.as_str()))
1331    }
1332}
1333
1334fn fold_comment<'src>(comment: &Comment<'src>) -> Comment<'src> {
1335    Comment {
1336        kind: comment.kind,
1337        text: comment.text,
1338        span: comment.span,
1339    }
1340}