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