Skip to main content

php_ast/owned/
fold.rs

1//! Owned AST transformation via the [`OwnedFold`] trait.
2//!
3//! [`OwnedFold`] is the transformation counterpart of [`super::visitor::OwnedVisitor`].
4//! Where `OwnedVisitor` reads nodes in place, `OwnedFold` rebuilds them: it takes a node by
5//! value and returns the new node, so unchanged children and strings are moved, not cloned.
6//! Override only the node types you want to change; all others are rebuilt by the default
7//! implementations.
8//!
9//! # Example
10//!
11//! ```
12//! use php_ast::owned::fold::{OwnedFold, fold_owned_expr};
13//! use php_ast::owned::{Expr, ExprKind};
14//!
15//! struct NegateInts;
16//!
17//! impl OwnedFold for NegateInts {
18//!     fn fold_expr(&mut self, expr: Expr) -> Expr {
19//!         if let ExprKind::Int(n) = &expr.kind {
20//!             return Expr { kind: ExprKind::Int(-n), span: expr.span };
21//!         }
22//!         fold_owned_expr(self, expr)
23//!     }
24//! }
25//! ```
26
27use super::*;
28
29// =============================================================================
30// OwnedFold trait
31// =============================================================================
32
33/// Trait for transforming owned PHP AST nodes.
34///
35/// Every method takes the node by value and returns its replacement. The defaults call the
36/// corresponding free `fold_owned_*` function, which folds the children and rebuilds the node.
37/// Override only the node types you want to change; the rest recurse automatically.
38pub trait OwnedFold {
39    /// Transforms a program node; recurses by default.
40    fn fold_program(&mut self, program: Program) -> Program {
41        fold_owned_program(self, program)
42    }
43
44    /// Transforms a statement node; recurses by default.
45    fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
46        fold_owned_stmt(self, stmt)
47    }
48
49    /// Transforms a statement list (program, block and `case` bodies); folds each statement by
50    /// default. Override to delete, split or insert statements.
51    fn fold_stmts(&mut self, stmts: Box<[Stmt]>) -> Box<[Stmt]> {
52        fold_owned_stmts(self, stmts)
53    }
54
55    /// Transforms a block node; recurses by default.
56    fn fold_block(&mut self, block: Block) -> Block {
57        fold_owned_block(self, block)
58    }
59
60    /// Transforms an expression node; recurses by default.
61    fn fold_expr(&mut self, expr: Expr) -> Expr {
62        fold_owned_expr(self, expr)
63    }
64
65    /// Transforms a param node; recurses by default.
66    fn fold_param(&mut self, param: Param) -> Param {
67        fold_owned_param(self, param)
68    }
69
70    /// Transforms an arg node; recurses by default.
71    fn fold_arg(&mut self, arg: Arg) -> Arg {
72        fold_owned_arg(self, arg)
73    }
74
75    /// Transforms a class member node; recurses by default.
76    fn fold_class_member(&mut self, member: ClassMember) -> ClassMember {
77        fold_owned_class_member(self, member)
78    }
79
80    /// Transforms an enum member node; recurses by default.
81    fn fold_enum_member(&mut self, member: EnumMember) -> EnumMember {
82        fold_owned_enum_member(self, member)
83    }
84
85    /// Transforms a property hook node; recurses by default.
86    fn fold_property_hook(&mut self, hook: PropertyHook) -> PropertyHook {
87        fold_owned_property_hook(self, hook)
88    }
89
90    /// Transforms a type hint node; recurses by default.
91    fn fold_type_hint(&mut self, type_hint: TypeHint) -> TypeHint {
92        fold_owned_type_hint(self, type_hint)
93    }
94
95    /// Transforms an attribute node; recurses by default.
96    fn fold_attribute(&mut self, attribute: Attribute) -> Attribute {
97        fold_owned_attribute(self, attribute)
98    }
99
100    /// Transforms a catch clause node; recurses by default.
101    fn fold_catch_clause(&mut self, catch: CatchClause) -> CatchClause {
102        fold_owned_catch_clause(self, catch)
103    }
104
105    /// Transforms a match arm node; recurses by default.
106    fn fold_match_arm(&mut self, arm: MatchArm) -> MatchArm {
107        fold_owned_match_arm(self, arm)
108    }
109
110    /// Transforms a closure use var node; recurses by default.
111    fn fold_closure_use_var(&mut self, var: ClosureUseVar) -> ClosureUseVar {
112        fold_owned_closure_use_var(self, var)
113    }
114
115    /// Transforms a `use Trait;` declaration; recurses by default.
116    fn fold_trait_use(&mut self, trait_use: TraitUseDecl) -> TraitUseDecl {
117        fold_owned_trait_use(self, trait_use)
118    }
119
120    /// Transforms a trait `insteadof`/`as` adaptation; recurses by default.
121    fn fold_trait_adaptation(&mut self, adaptation: TraitAdaptation) -> TraitAdaptation {
122        fold_owned_trait_adaptation(self, adaptation)
123    }
124
125    /// Transforms a name node; recurses by default.
126    fn fold_name(&mut self, name: Name) -> Name {
127        fold_owned_name(self, name)
128    }
129}
130
131// =============================================================================
132// Collection and box helpers
133// =============================================================================
134
135fn map_slice<T, U>(items: Box<[T]>, f: impl FnMut(T) -> U) -> Box<[U]> {
136    items.into_vec().into_iter().map(f).collect()
137}
138
139fn fold_boxed_expr<F: OwnedFold + ?Sized>(folder: &mut F, mut boxed: Box<Expr>) -> Box<Expr> {
140    let span = boxed.span;
141    let taken = std::mem::replace(
142        &mut *boxed,
143        Expr {
144            kind: ExprKind::Error,
145            span,
146        },
147    );
148    *boxed = folder.fold_expr(taken);
149    boxed
150}
151
152fn fold_opt_boxed_expr<F: OwnedFold + ?Sized>(
153    folder: &mut F,
154    boxed: Option<Box<Expr>>,
155) -> Option<Box<Expr>> {
156    boxed.map(|e| fold_boxed_expr(folder, e))
157}
158
159fn fold_boxed_stmt<F: OwnedFold + ?Sized>(folder: &mut F, mut boxed: Box<Stmt>) -> Box<Stmt> {
160    let span = boxed.span;
161    let taken = std::mem::replace(
162        &mut *boxed,
163        Stmt {
164            kind: StmtKind::Error,
165            span,
166            doc_comment: None,
167        },
168    );
169    *boxed = folder.fold_stmt(taken);
170    boxed
171}
172
173fn fold_boxed_block<F: OwnedFold + ?Sized>(folder: &mut F, mut boxed: Box<Block>) -> Box<Block> {
174    let span = boxed.span;
175    let taken = std::mem::replace(
176        &mut *boxed,
177        Block {
178            stmts: Box::new([]),
179            span,
180        },
181    );
182    *boxed = folder.fold_block(taken);
183    boxed
184}
185
186fn fold_owned_exprs<F: OwnedFold + ?Sized>(folder: &mut F, exprs: Box<[Expr]>) -> Box<[Expr]> {
187    map_slice(exprs, |e| folder.fold_expr(e))
188}
189
190fn fold_owned_args<F: OwnedFold + ?Sized>(folder: &mut F, args: Box<[Arg]>) -> Box<[Arg]> {
191    map_slice(args, |a| folder.fold_arg(a))
192}
193
194fn fold_owned_attrs<F: OwnedFold + ?Sized>(
195    folder: &mut F,
196    attrs: Box<[Attribute]>,
197) -> Box<[Attribute]> {
198    map_slice(attrs, |a| folder.fold_attribute(a))
199}
200
201fn fold_owned_params<F: OwnedFold + ?Sized>(folder: &mut F, params: Box<[Param]>) -> Box<[Param]> {
202    map_slice(params, |p| folder.fold_param(p))
203}
204
205fn fold_owned_hooks<F: OwnedFold + ?Sized>(
206    folder: &mut F,
207    hooks: Box<[PropertyHook]>,
208) -> Box<[PropertyHook]> {
209    map_slice(hooks, |h| folder.fold_property_hook(h))
210}
211
212fn fold_owned_members<F: OwnedFold + ?Sized>(
213    folder: &mut F,
214    members: Box<[ClassMember]>,
215) -> Box<[ClassMember]> {
216    map_slice(members, |m| folder.fold_class_member(m))
217}
218
219fn fold_owned_names<F: OwnedFold + ?Sized>(folder: &mut F, names: Box<[Name]>) -> Box<[Name]> {
220    map_slice(names, |n| folder.fold_name(n))
221}
222
223fn fold_owned_string_parts<F: OwnedFold + ?Sized>(
224    folder: &mut F,
225    parts: Box<[StringPart]>,
226) -> Box<[StringPart]> {
227    map_slice(parts, |p| match p {
228        StringPart::Literal(s) => StringPart::Literal(s),
229        StringPart::Expr(e) => StringPart::Expr(folder.fold_expr(e)),
230    })
231}
232
233fn fold_owned_type_hints<F: OwnedFold + ?Sized>(
234    folder: &mut F,
235    types: Box<[TypeHint]>,
236) -> Box<[TypeHint]> {
237    map_slice(types, |t| folder.fold_type_hint(t))
238}
239
240// =============================================================================
241// Free fold functions
242// =============================================================================
243
244/// Folds a program, recursing into its children.
245pub fn fold_owned_program<F: OwnedFold + ?Sized>(folder: &mut F, program: Program) -> Program {
246    Program {
247        stmts: folder.fold_stmts(program.stmts),
248        span: program.span,
249    }
250}
251
252/// Folds a statement, recursing into its children.
253pub fn fold_owned_stmt<F: OwnedFold + ?Sized>(folder: &mut F, stmt: Stmt) -> Stmt {
254    Stmt {
255        kind: fold_owned_stmt_kind(folder, stmt.kind),
256        span: stmt.span,
257        doc_comment: stmt.doc_comment,
258    }
259}
260
261/// Folds each statement of a list in order.
262pub fn fold_owned_stmts<F: OwnedFold + ?Sized>(folder: &mut F, stmts: Box<[Stmt]>) -> Box<[Stmt]> {
263    map_slice(stmts, |s| folder.fold_stmt(s))
264}
265
266/// Folds a block, recursing into its children.
267pub fn fold_owned_block<F: OwnedFold + ?Sized>(folder: &mut F, block: Block) -> Block {
268    Block {
269        stmts: folder.fold_stmts(block.stmts),
270        span: block.span,
271    }
272}
273
274fn fold_owned_stmt_kind<F: OwnedFold + ?Sized>(folder: &mut F, k: StmtKind) -> StmtKind {
275    match k {
276        StmtKind::Expression(e) => StmtKind::Expression(fold_boxed_expr(folder, e)),
277        StmtKind::Echo(exprs) => StmtKind::Echo(fold_owned_exprs(folder, exprs)),
278        StmtKind::Return(e) => StmtKind::Return(fold_opt_boxed_expr(folder, e)),
279        StmtKind::Block(block) => StmtKind::Block(fold_boxed_block(folder, block)),
280        StmtKind::If(s) => {
281            let s = *s;
282            StmtKind::If(Box::new(IfStmt {
283                condition: folder.fold_expr(s.condition),
284                then_branch: fold_boxed_stmt(folder, s.then_branch),
285                elseif_branches: map_slice(s.elseif_branches, |b| ElseIfBranch {
286                    condition: folder.fold_expr(b.condition),
287                    body: folder.fold_stmt(b.body),
288                    span: b.span,
289                }),
290                else_branch: s.else_branch.map(|b| fold_boxed_stmt(folder, b)),
291                else_kw_span: s.else_kw_span,
292                uses_alternative: s.uses_alternative,
293            }))
294        }
295        StmtKind::While(s) => {
296            let s = *s;
297            StmtKind::While(Box::new(WhileStmt {
298                condition: folder.fold_expr(s.condition),
299                body: fold_boxed_stmt(folder, s.body),
300                uses_alternative: s.uses_alternative,
301            }))
302        }
303        StmtKind::For(s) => {
304            let s = *s;
305            StmtKind::For(Box::new(ForStmt {
306                init: fold_owned_exprs(folder, s.init),
307                condition: fold_owned_exprs(folder, s.condition),
308                update: fold_owned_exprs(folder, s.update),
309                body: fold_boxed_stmt(folder, s.body),
310                uses_alternative: s.uses_alternative,
311            }))
312        }
313        StmtKind::Foreach(s) => {
314            let s = *s;
315            StmtKind::Foreach(Box::new(ForeachStmt {
316                expr: folder.fold_expr(s.expr),
317                key: s.key.map(|k| folder.fold_expr(k)),
318                value: folder.fold_expr(s.value),
319                by_ref: s.by_ref,
320                body: fold_boxed_stmt(folder, s.body),
321                uses_alternative: s.uses_alternative,
322            }))
323        }
324        StmtKind::DoWhile(s) => {
325            let s = *s;
326            StmtKind::DoWhile(Box::new(DoWhileStmt {
327                body: fold_boxed_stmt(folder, s.body),
328                condition: folder.fold_expr(s.condition),
329            }))
330        }
331        StmtKind::Function(f) => {
332            let f = *f;
333            StmtKind::Function(Box::new(FunctionDecl {
334                name: f.name,
335                params: fold_owned_params(folder, f.params),
336                body: fold_boxed_block(folder, f.body),
337                return_type: f.return_type.map(|t| folder.fold_type_hint(t)),
338                by_ref: f.by_ref,
339                attributes: fold_owned_attrs(folder, f.attributes),
340                doc_comment: f.doc_comment,
341            }))
342        }
343        StmtKind::Break(e) => StmtKind::Break(fold_opt_boxed_expr(folder, e)),
344        StmtKind::Continue(e) => StmtKind::Continue(fold_opt_boxed_expr(folder, e)),
345        StmtKind::Switch(s) => {
346            let s = *s;
347            StmtKind::Switch(Box::new(SwitchStmt {
348                expr: folder.fold_expr(s.expr),
349                body: SwitchBody {
350                    cases: map_slice(s.body.cases, |c| SwitchCase {
351                        value: c.value.map(|v| folder.fold_expr(v)),
352                        body: folder.fold_stmts(c.body),
353                        span: c.span,
354                    }),
355                    span: s.body.span,
356                },
357                uses_alternative: s.uses_alternative,
358            }))
359        }
360        StmtKind::Goto(ident) => StmtKind::Goto(ident),
361        StmtKind::Label(ident) => StmtKind::Label(ident),
362        StmtKind::Declare(d) => {
363            let d = *d;
364            StmtKind::Declare(Box::new(DeclareStmt {
365                directives: map_slice(d.directives, |(k, v)| (k, folder.fold_expr(v))),
366                body: d.body.map(|b| fold_boxed_stmt(folder, b)),
367                uses_alternative: d.uses_alternative,
368            }))
369        }
370        StmtKind::Unset(exprs) => StmtKind::Unset(fold_owned_exprs(folder, exprs)),
371        StmtKind::Throw(e) => StmtKind::Throw(fold_boxed_expr(folder, e)),
372        StmtKind::TryCatch(t) => {
373            let t = *t;
374            StmtKind::TryCatch(Box::new(TryCatchStmt {
375                body: fold_boxed_block(folder, t.body),
376                catches: map_slice(t.catches, |c| folder.fold_catch_clause(c)),
377                finally: t.finally.map(|f| fold_boxed_block(folder, f)),
378                finally_kw_span: t.finally_kw_span,
379            }))
380        }
381        StmtKind::Global(exprs) => StmtKind::Global(fold_owned_exprs(folder, exprs)),
382        StmtKind::Class(cls) => StmtKind::Class(Box::new(fold_owned_class_decl(folder, *cls))),
383        StmtKind::Interface(iface) => {
384            StmtKind::Interface(Box::new(fold_owned_interface_decl(folder, *iface)))
385        }
386        StmtKind::Trait(tr) => StmtKind::Trait(Box::new(fold_owned_trait_decl(folder, *tr))),
387        StmtKind::Enum(en) => StmtKind::Enum(Box::new(fold_owned_enum_decl(folder, *en))),
388        StmtKind::Namespace(ns) => {
389            let ns = *ns;
390            StmtKind::Namespace(Box::new(NamespaceDecl {
391                name: ns.name.map(|n| folder.fold_name(n)),
392                body: match ns.body {
393                    NamespaceBody::Braced(block) => {
394                        NamespaceBody::Braced(fold_boxed_block(folder, block))
395                    }
396                    NamespaceBody::Simple => NamespaceBody::Simple,
397                },
398            }))
399        }
400        StmtKind::Use(u) => {
401            let u = *u;
402            StmtKind::Use(Box::new(UseDecl {
403                kind: u.kind,
404                uses: map_slice(u.uses, |item| UseItem {
405                    name: folder.fold_name(item.name),
406                    alias: item.alias,
407                    kind: item.kind,
408                    span: item.span,
409                }),
410            }))
411        }
412        StmtKind::Const(items) => StmtKind::Const(map_slice(items, |item| ConstItem {
413            name: item.name,
414            value: folder.fold_expr(item.value),
415            attributes: fold_owned_attrs(folder, item.attributes),
416            span: item.span,
417            doc_comment: item.doc_comment,
418        })),
419        StmtKind::StaticVar(vars) => StmtKind::StaticVar(map_slice(vars, |v| StaticVar {
420            name: v.name,
421            default: v.default.map(|e| folder.fold_expr(e)),
422            span: v.span,
423        })),
424        StmtKind::HaltCompiler(s) => StmtKind::HaltCompiler(s),
425        StmtKind::Nop => StmtKind::Nop,
426        StmtKind::InlineHtml(s) => StmtKind::InlineHtml(s),
427        StmtKind::Error => StmtKind::Error,
428    }
429}
430
431/// Folds an expression, recursing into its children.
432pub fn fold_owned_expr<F: OwnedFold + ?Sized>(folder: &mut F, expr: Expr) -> Expr {
433    let span = expr.span;
434    Expr {
435        kind: fold_owned_expr_kind(folder, expr.into_kind()),
436        span,
437    }
438}
439
440fn fold_owned_expr_kind<F: OwnedFold + ?Sized>(folder: &mut F, k: ExprKind) -> ExprKind {
441    match k {
442        ExprKind::Int(v) => ExprKind::Int(v),
443        ExprKind::Float(v) => ExprKind::Float(v),
444        ExprKind::String(s) => ExprKind::String(s),
445        ExprKind::InterpolatedString(parts) => {
446            ExprKind::InterpolatedString(fold_owned_string_parts(folder, parts))
447        }
448        ExprKind::Heredoc { label, parts } => ExprKind::Heredoc {
449            label,
450            parts: fold_owned_string_parts(folder, parts),
451        },
452        ExprKind::Nowdoc { label, value } => ExprKind::Nowdoc { label, value },
453        ExprKind::ShellExec(parts) => ExprKind::ShellExec(fold_owned_string_parts(folder, parts)),
454        ExprKind::Bool(v) => ExprKind::Bool(v),
455        ExprKind::Null => ExprKind::Null,
456        ExprKind::Variable(s) => ExprKind::Variable(s),
457        ExprKind::VariableVariable(inner) => {
458            ExprKind::VariableVariable(fold_boxed_expr(folder, inner))
459        }
460        ExprKind::Identifier(s) => ExprKind::Identifier(s),
461        ExprKind::Name(name) => ExprKind::Name(folder.fold_name(name)),
462        ExprKind::Assign(a) => ExprKind::Assign(AssignExpr {
463            target: fold_boxed_expr(folder, a.target),
464            op: a.op,
465            value: fold_boxed_expr(folder, a.value),
466            by_ref: a.by_ref,
467        }),
468        ExprKind::Binary(b) => ExprKind::Binary(BinaryExpr {
469            left: fold_boxed_expr(folder, b.left),
470            op: b.op,
471            right: fold_boxed_expr(folder, b.right),
472        }),
473        ExprKind::UnaryPrefix(u) => ExprKind::UnaryPrefix(UnaryPrefixExpr {
474            op: u.op,
475            operand: fold_boxed_expr(folder, u.operand),
476        }),
477        ExprKind::UnaryPostfix(u) => ExprKind::UnaryPostfix(UnaryPostfixExpr {
478            operand: fold_boxed_expr(folder, u.operand),
479            op: u.op,
480        }),
481        ExprKind::Ternary(t) => ExprKind::Ternary(TernaryExpr {
482            condition: fold_boxed_expr(folder, t.condition),
483            then_expr: fold_opt_boxed_expr(folder, t.then_expr),
484            else_expr: fold_boxed_expr(folder, t.else_expr),
485        }),
486        ExprKind::NullCoalesce(n) => ExprKind::NullCoalesce(NullCoalesceExpr {
487            left: fold_boxed_expr(folder, n.left),
488            right: fold_boxed_expr(folder, n.right),
489        }),
490        ExprKind::FunctionCall(f) => ExprKind::FunctionCall(FunctionCallExpr {
491            name: fold_boxed_expr(folder, f.name),
492            args: fold_owned_args(folder, f.args),
493        }),
494        ExprKind::Array(elems) => ExprKind::Array(map_slice(elems, |e| ArrayElement {
495            key: e.key.map(|k| folder.fold_expr(k)),
496            kind: match e.kind {
497                ArrayElementKind::Value(v) => ArrayElementKind::Value(folder.fold_expr(v)),
498                ArrayElementKind::Spread(v) => ArrayElementKind::Spread(folder.fold_expr(v)),
499                ArrayElementKind::ByRef(v) => ArrayElementKind::ByRef(folder.fold_expr(v)),
500            },
501            span: e.span,
502        })),
503        ExprKind::ArrayAccess(a) => ExprKind::ArrayAccess(ArrayAccessExpr {
504            array: fold_boxed_expr(folder, a.array),
505            index: fold_opt_boxed_expr(folder, a.index),
506        }),
507        ExprKind::Print(e) => ExprKind::Print(fold_boxed_expr(folder, e)),
508        ExprKind::Parenthesized(e) => ExprKind::Parenthesized(fold_boxed_expr(folder, e)),
509        ExprKind::Cast(kind, e) => ExprKind::Cast(kind, fold_boxed_expr(folder, e)),
510        ExprKind::ErrorSuppress(e) => ExprKind::ErrorSuppress(fold_boxed_expr(folder, e)),
511        ExprKind::Isset(exprs) => ExprKind::Isset(fold_owned_exprs(folder, exprs)),
512        ExprKind::Empty(e) => ExprKind::Empty(fold_boxed_expr(folder, e)),
513        ExprKind::Include(kind, e) => ExprKind::Include(kind, fold_boxed_expr(folder, e)),
514        ExprKind::Eval(e) => ExprKind::Eval(fold_boxed_expr(folder, e)),
515        ExprKind::Exit(e) => ExprKind::Exit(fold_opt_boxed_expr(folder, e)),
516        ExprKind::MagicConst(m) => ExprKind::MagicConst(m),
517        ExprKind::Clone(e) => ExprKind::Clone(fold_boxed_expr(folder, e)),
518        ExprKind::CloneWith(obj, props) => {
519            ExprKind::CloneWith(fold_boxed_expr(folder, obj), fold_boxed_expr(folder, props))
520        }
521        ExprKind::New(n) => ExprKind::New(NewExpr {
522            class: fold_boxed_expr(folder, n.class),
523            args: fold_owned_args(folder, n.args),
524        }),
525        ExprKind::PropertyAccess(p) => ExprKind::PropertyAccess(PropertyAccessExpr {
526            object: fold_boxed_expr(folder, p.object),
527            property: fold_boxed_expr(folder, p.property),
528        }),
529        ExprKind::NullsafePropertyAccess(p) => {
530            ExprKind::NullsafePropertyAccess(PropertyAccessExpr {
531                object: fold_boxed_expr(folder, p.object),
532                property: fold_boxed_expr(folder, p.property),
533            })
534        }
535        ExprKind::MethodCall(m) => {
536            let m = *m;
537            ExprKind::MethodCall(Box::new(MethodCallExpr {
538                object: fold_boxed_expr(folder, m.object),
539                method: fold_boxed_expr(folder, m.method),
540                args: fold_owned_args(folder, m.args),
541            }))
542        }
543        ExprKind::NullsafeMethodCall(m) => {
544            let m = *m;
545            ExprKind::NullsafeMethodCall(Box::new(MethodCallExpr {
546                object: fold_boxed_expr(folder, m.object),
547                method: fold_boxed_expr(folder, m.method),
548                args: fold_owned_args(folder, m.args),
549            }))
550        }
551        ExprKind::StaticPropertyAccess(s) => ExprKind::StaticPropertyAccess(StaticAccessExpr {
552            class: fold_boxed_expr(folder, s.class),
553            member: fold_boxed_expr(folder, s.member),
554        }),
555        ExprKind::StaticMethodCall(s) => {
556            let s = *s;
557            ExprKind::StaticMethodCall(Box::new(StaticMethodCallExpr {
558                class: fold_boxed_expr(folder, s.class),
559                method: fold_boxed_expr(folder, s.method),
560                args: fold_owned_args(folder, s.args),
561            }))
562        }
563        ExprKind::StaticMethodCallDynamic(s) => {
564            let s = *s;
565            ExprKind::StaticMethodCallDynamic(Box::new(StaticMethodCallDynamicExpr {
566                class: fold_boxed_expr(folder, s.class),
567                method: fold_boxed_expr(folder, s.method),
568                args: fold_owned_args(folder, s.args),
569            }))
570        }
571        ExprKind::ClassConstAccess(s) => ExprKind::ClassConstAccess(StaticAccessExpr {
572            class: fold_boxed_expr(folder, s.class),
573            member: fold_boxed_expr(folder, s.member),
574        }),
575        ExprKind::ClassConstAccessDynamic { class, member } => ExprKind::ClassConstAccessDynamic {
576            class: fold_boxed_expr(folder, class),
577            member: fold_boxed_expr(folder, member),
578        },
579        ExprKind::StaticPropertyAccessDynamic { class, member } => {
580            ExprKind::StaticPropertyAccessDynamic {
581                class: fold_boxed_expr(folder, class),
582                member: fold_boxed_expr(folder, member),
583            }
584        }
585        ExprKind::Closure(c) => {
586            let c = *c;
587            ExprKind::Closure(Box::new(ClosureExpr {
588                is_static: c.is_static,
589                by_ref: c.by_ref,
590                params: fold_owned_params(folder, c.params),
591                use_vars: map_slice(c.use_vars, |v| folder.fold_closure_use_var(v)),
592                return_type: c.return_type.map(|t| folder.fold_type_hint(t)),
593                body: fold_boxed_block(folder, c.body),
594                attributes: fold_owned_attrs(folder, c.attributes),
595            }))
596        }
597        ExprKind::ArrowFunction(f) => {
598            let f = *f;
599            ExprKind::ArrowFunction(Box::new(ArrowFunctionExpr {
600                is_static: f.is_static,
601                by_ref: f.by_ref,
602                params: fold_owned_params(folder, f.params),
603                return_type: f.return_type.map(|t| folder.fold_type_hint(t)),
604                body: fold_boxed_expr(folder, f.body),
605                attributes: fold_owned_attrs(folder, f.attributes),
606            }))
607        }
608        ExprKind::Match(m) => ExprKind::Match(MatchExpr {
609            subject: fold_boxed_expr(folder, m.subject),
610            arms: map_slice(m.arms, |arm| folder.fold_match_arm(arm)),
611            brace_span: m.brace_span,
612        }),
613        ExprKind::Throw(e) => ExprKind::Throw(fold_boxed_expr(folder, e)),
614        ExprKind::Yield(y) => ExprKind::Yield(YieldExpr {
615            key: fold_opt_boxed_expr(folder, y.key),
616            value: fold_opt_boxed_expr(folder, y.value),
617            is_from: y.is_from,
618        }),
619        ExprKind::AnonymousClass(cls) => {
620            ExprKind::AnonymousClass(Box::new(fold_owned_class_decl(folder, *cls)))
621        }
622        ExprKind::CallableCreate(c) => ExprKind::CallableCreate(CallableCreateExpr {
623            kind: match c.kind {
624                CallableCreateKind::Function(e) => {
625                    CallableCreateKind::Function(fold_boxed_expr(folder, e))
626                }
627                CallableCreateKind::Method { object, method } => CallableCreateKind::Method {
628                    object: fold_boxed_expr(folder, object),
629                    method: fold_boxed_expr(folder, method),
630                },
631                CallableCreateKind::NullsafeMethod { object, method } => {
632                    CallableCreateKind::NullsafeMethod {
633                        object: fold_boxed_expr(folder, object),
634                        method: fold_boxed_expr(folder, method),
635                    }
636                }
637                CallableCreateKind::StaticMethod { class, method } => {
638                    CallableCreateKind::StaticMethod {
639                        class: fold_boxed_expr(folder, class),
640                        method: fold_boxed_expr(folder, method),
641                    }
642                }
643            },
644        }),
645        ExprKind::Omit => ExprKind::Omit,
646        ExprKind::Error => ExprKind::Error,
647    }
648}
649
650/// Folds a param, recursing into its children.
651pub fn fold_owned_param<F: OwnedFold + ?Sized>(folder: &mut F, p: Param) -> Param {
652    Param {
653        name: p.name,
654        type_hint: p.type_hint.map(|t| folder.fold_type_hint(t)),
655        default: p.default.map(|e| folder.fold_expr(e)),
656        by_ref: p.by_ref,
657        variadic: p.variadic,
658        modifiers: p.modifiers,
659        attributes: fold_owned_attrs(folder, p.attributes),
660        hooks: fold_owned_hooks(folder, p.hooks),
661        doc_comment: p.doc_comment,
662        span: p.span,
663    }
664}
665
666/// Folds an argument, recursing into its children.
667pub fn fold_owned_arg<F: OwnedFold + ?Sized>(folder: &mut F, arg: Arg) -> Arg {
668    Arg {
669        name: arg.name.map(|n| folder.fold_name(n)),
670        kind: match arg.kind {
671            ArgKind::Value(e) => ArgKind::Value(folder.fold_expr(e)),
672            ArgKind::Spread(e) => ArgKind::Spread(folder.fold_expr(e)),
673            ArgKind::ByRef(e) => ArgKind::ByRef(folder.fold_expr(e)),
674            ArgKind::Placeholder => ArgKind::Placeholder,
675            ArgKind::SpreadPlaceholder => ArgKind::SpreadPlaceholder,
676        },
677        span: arg.span,
678    }
679}
680
681/// Folds a closure use var, recursing into its children.
682pub fn fold_owned_closure_use_var<F: OwnedFold + ?Sized>(
683    _folder: &mut F,
684    var: ClosureUseVar,
685) -> ClosureUseVar {
686    var
687}
688
689/// Folds a name, recursing into its children.
690pub fn fold_owned_name<F: OwnedFold + ?Sized>(_folder: &mut F, name: Name) -> Name {
691    name
692}
693
694/// Folds a method declaration's parts.
695fn fold_owned_method<F: OwnedFold + ?Sized>(folder: &mut F, m: MethodDecl) -> MethodDecl {
696    MethodDecl {
697        name: m.name,
698        modifiers: m.modifiers,
699        by_ref: m.by_ref,
700        params: fold_owned_params(folder, m.params),
701        return_type: m.return_type.map(|t| folder.fold_type_hint(t)),
702        body: m.body.map(|b| fold_boxed_block(folder, b)),
703        attributes: fold_owned_attrs(folder, m.attributes),
704        doc_comment: m.doc_comment,
705    }
706}
707
708/// Folds a class member, recursing into its children.
709pub fn fold_owned_class_member<F: OwnedFold + ?Sized>(
710    folder: &mut F,
711    member: ClassMember,
712) -> ClassMember {
713    ClassMember {
714        kind: match member.kind {
715            ClassMemberKind::Property(p) => ClassMemberKind::Property(PropertyDecl {
716                name: p.name,
717                modifiers: p.modifiers,
718                type_hint: p.type_hint.map(|t| folder.fold_type_hint(t)),
719                default: p.default.map(|e| folder.fold_expr(e)),
720                attributes: fold_owned_attrs(folder, p.attributes),
721                hooks: fold_owned_hooks(folder, p.hooks),
722                doc_comment: p.doc_comment,
723            }),
724            ClassMemberKind::Method(m) => ClassMemberKind::Method(fold_owned_method(folder, m)),
725            ClassMemberKind::ClassConst(c) => {
726                ClassMemberKind::ClassConst(fold_owned_class_const(folder, c))
727            }
728            ClassMemberKind::TraitUse(t) => ClassMemberKind::TraitUse(folder.fold_trait_use(t)),
729        },
730        span: member.span,
731    }
732}
733
734/// Folds an enum member, recursing into its children.
735pub fn fold_owned_enum_member<F: OwnedFold + ?Sized>(
736    folder: &mut F,
737    member: EnumMember,
738) -> EnumMember {
739    EnumMember {
740        kind: match member.kind {
741            EnumMemberKind::Case(c) => EnumMemberKind::Case(EnumCase {
742                name: c.name,
743                value: c.value.map(|e| folder.fold_expr(e)),
744                attributes: fold_owned_attrs(folder, c.attributes),
745                doc_comment: c.doc_comment,
746            }),
747            EnumMemberKind::Method(m) => EnumMemberKind::Method(fold_owned_method(folder, m)),
748            EnumMemberKind::ClassConst(c) => {
749                EnumMemberKind::ClassConst(fold_owned_class_const(folder, c))
750            }
751            EnumMemberKind::TraitUse(t) => EnumMemberKind::TraitUse(folder.fold_trait_use(t)),
752        },
753        span: member.span,
754    }
755}
756
757/// Folds a property hook, recursing into its children.
758pub fn fold_owned_property_hook<F: OwnedFold + ?Sized>(
759    folder: &mut F,
760    hook: PropertyHook,
761) -> PropertyHook {
762    PropertyHook {
763        kind: hook.kind,
764        body: match hook.body {
765            PropertyHookBody::Block(block) => {
766                PropertyHookBody::Block(fold_boxed_block(folder, block))
767            }
768            PropertyHookBody::Expression(e) => PropertyHookBody::Expression(folder.fold_expr(e)),
769            PropertyHookBody::Abstract => PropertyHookBody::Abstract,
770        },
771        modifiers: hook.modifiers,
772        by_ref: hook.by_ref,
773        params: fold_owned_params(folder, hook.params),
774        attributes: fold_owned_attrs(folder, hook.attributes),
775        span: hook.span,
776    }
777}
778
779/// Folds a type hint, recursing into its children.
780pub fn fold_owned_type_hint<F: OwnedFold + ?Sized>(
781    folder: &mut F,
782    type_hint: TypeHint,
783) -> TypeHint {
784    TypeHint {
785        kind: match type_hint.kind {
786            TypeHintKind::Named(n) => TypeHintKind::Named(folder.fold_name(n)),
787            TypeHintKind::Keyword(b, span) => TypeHintKind::Keyword(b, span),
788            TypeHintKind::Nullable(inner) => {
789                TypeHintKind::Nullable(Box::new(folder.fold_type_hint(*inner)))
790            }
791            TypeHintKind::Union(types) => TypeHintKind::Union(fold_owned_type_hints(folder, types)),
792            TypeHintKind::Intersection(types) => {
793                TypeHintKind::Intersection(fold_owned_type_hints(folder, types))
794            }
795        },
796        span: type_hint.span,
797    }
798}
799
800/// Folds an attribute, recursing into its children.
801pub fn fold_owned_attribute<F: OwnedFold + ?Sized>(
802    folder: &mut F,
803    attribute: Attribute,
804) -> Attribute {
805    Attribute {
806        name: folder.fold_name(attribute.name),
807        args: fold_owned_args(folder, attribute.args),
808        span: attribute.span,
809    }
810}
811
812/// Folds a catch clause, recursing into its children.
813pub fn fold_owned_catch_clause<F: OwnedFold + ?Sized>(
814    folder: &mut F,
815    catch: CatchClause,
816) -> CatchClause {
817    CatchClause {
818        types: fold_owned_names(folder, catch.types),
819        var: catch.var,
820        body: fold_boxed_block(folder, catch.body),
821        span: catch.span,
822    }
823}
824
825/// Folds a match arm, recursing into its children.
826pub fn fold_owned_match_arm<F: OwnedFold + ?Sized>(folder: &mut F, arm: MatchArm) -> MatchArm {
827    MatchArm {
828        conditions: arm.conditions.map(|conds| fold_owned_exprs(folder, conds)),
829        body: folder.fold_expr(arm.body),
830        span: arm.span,
831    }
832}
833
834fn fold_owned_class_const<F: OwnedFold + ?Sized>(
835    folder: &mut F,
836    c: ClassConstDecl,
837) -> ClassConstDecl {
838    ClassConstDecl {
839        name: c.name,
840        modifiers: c.modifiers,
841        type_hint: c.type_hint.map(|th| Box::new(folder.fold_type_hint(*th))),
842        value: folder.fold_expr(c.value),
843        attributes: fold_owned_attrs(folder, c.attributes),
844        doc_comment: c.doc_comment,
845    }
846}
847
848/// Folds a `use Trait;` declaration inside a class or enum body.
849pub fn fold_owned_trait_use<F: OwnedFold + ?Sized>(
850    folder: &mut F,
851    t: TraitUseDecl,
852) -> TraitUseDecl {
853    TraitUseDecl {
854        traits: fold_owned_names(folder, t.traits),
855        adaptations: map_slice(t.adaptations, |a| folder.fold_trait_adaptation(a)),
856        adaptations_brace_span: t.adaptations_brace_span,
857        doc_comment: t.doc_comment,
858    }
859}
860
861/// Folds a trait `insteadof`/`as` adaptation.
862pub fn fold_owned_trait_adaptation<F: OwnedFold + ?Sized>(
863    folder: &mut F,
864    a: TraitAdaptation,
865) -> TraitAdaptation {
866    TraitAdaptation {
867        kind: match a.kind {
868            TraitAdaptationKind::Precedence {
869                trait_name,
870                method,
871                insteadof,
872            } => TraitAdaptationKind::Precedence {
873                trait_name: folder.fold_name(trait_name),
874                method: folder.fold_name(method),
875                insteadof: fold_owned_names(folder, insteadof),
876            },
877            TraitAdaptationKind::Alias {
878                trait_name,
879                method,
880                new_modifier,
881                is_final,
882                new_name,
883            } => TraitAdaptationKind::Alias {
884                trait_name: trait_name.map(|n| folder.fold_name(n)),
885                method: folder.fold_name(method),
886                new_modifier,
887                is_final,
888                new_name: new_name.map(|n| folder.fold_name(n)),
889            },
890        },
891        span: a.span,
892    }
893}
894
895fn fold_owned_class_decl<F: OwnedFold + ?Sized>(folder: &mut F, cls: ClassDecl) -> ClassDecl {
896    ClassDecl {
897        name: cls.name,
898        modifiers: cls.modifiers,
899        extends: cls.extends.map(|n| folder.fold_name(n)),
900        implements: fold_owned_names(folder, cls.implements),
901        body: ClassBody {
902            members: fold_owned_members(folder, cls.body.members),
903            span: cls.body.span,
904        },
905        attributes: fold_owned_attrs(folder, cls.attributes),
906        doc_comment: cls.doc_comment,
907    }
908}
909
910fn fold_owned_interface_decl<F: OwnedFold + ?Sized>(
911    folder: &mut F,
912    iface: InterfaceDecl,
913) -> InterfaceDecl {
914    InterfaceDecl {
915        name: iface.name,
916        extends: fold_owned_names(folder, iface.extends),
917        body: ClassBody {
918            members: fold_owned_members(folder, iface.body.members),
919            span: iface.body.span,
920        },
921        attributes: fold_owned_attrs(folder, iface.attributes),
922        doc_comment: iface.doc_comment,
923    }
924}
925
926fn fold_owned_trait_decl<F: OwnedFold + ?Sized>(folder: &mut F, tr: TraitDecl) -> TraitDecl {
927    TraitDecl {
928        name: tr.name,
929        body: ClassBody {
930            members: fold_owned_members(folder, tr.body.members),
931            span: tr.body.span,
932        },
933        attributes: fold_owned_attrs(folder, tr.attributes),
934        doc_comment: tr.doc_comment,
935    }
936}
937
938fn fold_owned_enum_decl<F: OwnedFold + ?Sized>(folder: &mut F, en: EnumDecl) -> EnumDecl {
939    EnumDecl {
940        name: en.name,
941        scalar_type: en.scalar_type.map(|n| folder.fold_name(n)),
942        implements: fold_owned_names(folder, en.implements),
943        body: EnumBody {
944            members: map_slice(en.body.members, |m| folder.fold_enum_member(m)),
945            span: en.body.span,
946        },
947        attributes: fold_owned_attrs(folder, en.attributes),
948        doc_comment: en.doc_comment,
949    }
950}
951
952// =============================================================================
953// Tests
954// =============================================================================
955
956#[cfg(test)]
957mod tests {
958    use super::*;
959    use crate::ast::AssignOp;
960    use crate::Span;
961
962    fn dummy_var(name: &str) -> Expr {
963        Expr {
964            kind: ExprKind::Variable(Box::from(name)),
965            span: Span::DUMMY,
966        }
967    }
968
969    fn dummy_int(n: i64) -> Expr {
970        Expr {
971            kind: ExprKind::Int(n),
972            span: Span::DUMMY,
973        }
974    }
975
976    fn assign(target: Expr, value: Expr) -> Expr {
977        Expr {
978            kind: ExprKind::Assign(AssignExpr {
979                target: Box::new(target),
980                op: AssignOp::Assign,
981                value: Box::new(value),
982                by_ref: false,
983            }),
984            span: Span::DUMMY,
985        }
986    }
987
988    fn expr_stmt(e: Expr) -> Stmt {
989        Stmt {
990            kind: StmtKind::Expression(Box::new(e)),
991            span: Span::DUMMY,
992            doc_comment: None,
993        }
994    }
995
996    fn empty_block() -> Block {
997        Block {
998            stmts: Box::from([]),
999            span: Span::DUMMY,
1000        }
1001    }
1002
1003    fn program(stmts: impl IntoIterator<Item = Stmt>) -> Program {
1004        Program {
1005            stmts: stmts.into_iter().collect(),
1006            span: Span::DUMMY,
1007        }
1008    }
1009
1010    /// Identity fold on `$x = $y;` must produce structurally equal output.
1011    #[test]
1012    fn identity_fold_roundtrip() {
1013        let p = program([expr_stmt(assign(dummy_var("x"), dummy_var("y")))]);
1014        struct Identity;
1015        impl OwnedFold for Identity {}
1016        let before = serde_json::to_string(&p).unwrap();
1017        let folded = Identity.fold_program(p);
1018        assert_eq!(serde_json::to_string(&folded).unwrap(), before);
1019    }
1020
1021    /// NegateInts fold on `$x = 42;` must produce `$x = -42;`.
1022    #[test]
1023    fn negate_ints() {
1024        let p = program([expr_stmt(assign(dummy_var("x"), dummy_int(42)))]);
1025        struct NegateInts;
1026        impl OwnedFold for NegateInts {
1027            fn fold_expr(&mut self, expr: Expr) -> Expr {
1028                if let ExprKind::Int(n) = &expr.kind {
1029                    return Expr {
1030                        kind: ExprKind::Int(-n),
1031                        span: expr.span,
1032                    };
1033                }
1034                fold_owned_expr(self, expr)
1035            }
1036        }
1037        let folded = NegateInts.fold_program(p);
1038        let stmt = &folded.stmts[0];
1039        if let StmtKind::Expression(expr) = &stmt.kind {
1040            if let ExprKind::Assign(a) = &expr.kind {
1041                if let ExprKind::Int(n) = &a.value.kind {
1042                    assert_eq!(*n, -42);
1043                    return;
1044                }
1045            }
1046        }
1047        panic!("expected negated int assignment");
1048    }
1049
1050    /// Fold that rewrites variable names: all occurrences of `$x` become `$renamed`.
1051    #[test]
1052    fn rename_variable() {
1053        // $x = $x;
1054        let p = program([expr_stmt(assign(dummy_var("x"), dummy_var("x")))]);
1055        struct Rename;
1056        impl OwnedFold for Rename {
1057            fn fold_expr(&mut self, expr: Expr) -> Expr {
1058                if let ExprKind::Variable(name) = &expr.kind {
1059                    if name.as_ref() == "x" {
1060                        return Expr {
1061                            kind: ExprKind::Variable(Box::from("renamed")),
1062                            span: expr.span,
1063                        };
1064                    }
1065                }
1066                fold_owned_expr(self, expr)
1067            }
1068        }
1069        let folded = Rename.fold_program(p);
1070        let stmt = &folded.stmts[0];
1071        if let StmtKind::Expression(expr) = &stmt.kind {
1072            if let ExprKind::Assign(a) = &expr.kind {
1073                assert!(matches!(&a.target.kind, ExprKind::Variable(n) if n.as_ref() == "renamed"));
1074                assert!(matches!(&a.value.kind, ExprKind::Variable(n) if n.as_ref() == "renamed"));
1075                return;
1076            }
1077        }
1078        panic!("expected renamed variable assignment");
1079    }
1080
1081    /// Rename fold must reach into closure `use` capture lists.
1082    #[test]
1083    fn rename_variable_in_closure_use() {
1084        // function() use ($x) {}
1085        let closure = Expr {
1086            kind: ExprKind::Closure(Box::new(ClosureExpr {
1087                is_static: false,
1088                by_ref: false,
1089                params: Box::from([]),
1090                use_vars: Box::from([ClosureUseVar {
1091                    name: Ident::new("x", Span::DUMMY),
1092                    by_ref: false,
1093                    span: Span::DUMMY,
1094                }]),
1095                return_type: None,
1096                body: Box::new(empty_block()),
1097                attributes: Box::from([]),
1098            })),
1099            span: Span::DUMMY,
1100        };
1101        let p = program([expr_stmt(closure)]);
1102
1103        struct Rename;
1104        impl OwnedFold for Rename {
1105            fn fold_closure_use_var(&mut self, var: ClosureUseVar) -> ClosureUseVar {
1106                ClosureUseVar {
1107                    name: if var.name == "x" {
1108                        Ident::new("renamed", var.name.span)
1109                    } else {
1110                        var.name
1111                    },
1112                    by_ref: var.by_ref,
1113                    span: var.span,
1114                }
1115            }
1116        }
1117
1118        let folded = Rename.fold_program(p);
1119        if let StmtKind::Expression(expr) = &folded.stmts[0].kind {
1120            if let ExprKind::Closure(c) = &expr.kind {
1121                assert_eq!(c.use_vars[0].name, "renamed");
1122                return;
1123            }
1124        }
1125        panic!("expected closure with renamed use-var");
1126    }
1127
1128    /// `fold_stmts` sees the program, block and `case` statement lists, and may drop statements.
1129    #[test]
1130    fn fold_stmts_hook_can_delete_statements() {
1131        struct DropNops;
1132        impl OwnedFold for DropNops {
1133            fn fold_stmts(&mut self, stmts: Box<[Stmt]>) -> Box<[Stmt]> {
1134                let kept: Vec<Stmt> = stmts
1135                    .into_vec()
1136                    .into_iter()
1137                    .filter(|s| !matches!(s.kind, StmtKind::Nop))
1138                    .map(|s| self.fold_stmt(s))
1139                    .collect();
1140                kept.into_boxed_slice()
1141            }
1142        }
1143        let nop = || Stmt {
1144            kind: StmtKind::Nop,
1145            span: Span::DUMMY,
1146            doc_comment: None,
1147        };
1148        let nested = Stmt {
1149            kind: StmtKind::Block(Box::new(Block {
1150                stmts: Box::from([nop(), expr_stmt(dummy_int(1))]),
1151                span: Span::DUMMY,
1152            })),
1153            span: Span::DUMMY,
1154            doc_comment: None,
1155        };
1156        let switch = Stmt {
1157            kind: StmtKind::Switch(Box::new(SwitchStmt {
1158                expr: dummy_int(0),
1159                body: SwitchBody {
1160                    cases: Box::from([SwitchCase {
1161                        value: None,
1162                        body: Box::from([nop()]),
1163                        span: Span::DUMMY,
1164                    }]),
1165                    span: Span::DUMMY,
1166                },
1167                uses_alternative: false,
1168            })),
1169            span: Span::DUMMY,
1170            doc_comment: None,
1171        };
1172        let folded = DropNops.fold_program(program([nop(), nested, switch]));
1173        assert_eq!(folded.stmts.len(), 2);
1174        let StmtKind::Block(block) = &folded.stmts[0].kind else {
1175            panic!("expected Block")
1176        };
1177        assert_eq!(block.stmts.len(), 1);
1178        let StmtKind::Switch(switch) = &folded.stmts[1].kind else {
1179            panic!("expected Switch")
1180        };
1181        assert!(switch.body.cases[0].body.is_empty());
1182    }
1183}