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