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