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                by_ref_span: s.by_ref_span,
321                body: fold_boxed_stmt(folder, s.body),
322                uses_alternative: s.uses_alternative,
323            }))
324        }
325        StmtKind::DoWhile(s) => {
326            let s = *s;
327            StmtKind::DoWhile(Box::new(DoWhileStmt {
328                body: fold_boxed_stmt(folder, s.body),
329                condition: folder.fold_expr(s.condition),
330            }))
331        }
332        StmtKind::Function(f) => {
333            let f = *f;
334            StmtKind::Function(Box::new(FunctionDecl {
335                name: f.name,
336                params: fold_owned_params(folder, f.params),
337                missing_param_list: f.missing_param_list,
338                body: fold_boxed_block(folder, f.body),
339                return_type: f.return_type.map(|t| folder.fold_type_hint(t)),
340                by_ref: f.by_ref,
341                attributes: fold_owned_attrs(folder, f.attributes),
342                doc_comment: f.doc_comment,
343            }))
344        }
345        StmtKind::Break(e) => StmtKind::Break(fold_opt_boxed_expr(folder, e)),
346        StmtKind::Continue(e) => StmtKind::Continue(fold_opt_boxed_expr(folder, e)),
347        StmtKind::Switch(s) => {
348            let s = *s;
349            StmtKind::Switch(Box::new(SwitchStmt {
350                expr: folder.fold_expr(s.expr),
351                body: SwitchBody {
352                    cases: map_slice(s.body.cases, |c| SwitchCase {
353                        value: c.value.map(|v| folder.fold_expr(v)),
354                        body: folder.fold_stmts(c.body),
355                        span: c.span,
356                    }),
357                    span: s.body.span,
358                },
359                uses_alternative: s.uses_alternative,
360            }))
361        }
362        StmtKind::Goto(ident) => StmtKind::Goto(ident),
363        StmtKind::Label(ident) => StmtKind::Label(ident),
364        StmtKind::Declare(d) => {
365            let d = *d;
366            StmtKind::Declare(Box::new(DeclareStmt {
367                directives: map_slice(d.directives, |(k, v)| (k, folder.fold_expr(v))),
368                body: d.body.map(|b| fold_boxed_stmt(folder, b)),
369                uses_alternative: d.uses_alternative,
370            }))
371        }
372        StmtKind::Unset(exprs) => StmtKind::Unset(fold_owned_exprs(folder, exprs)),
373        StmtKind::Throw(e) => StmtKind::Throw(fold_boxed_expr(folder, e)),
374        StmtKind::TryCatch(t) => {
375            let t = *t;
376            StmtKind::TryCatch(Box::new(TryCatchStmt {
377                body: fold_boxed_block(folder, t.body),
378                catches: map_slice(t.catches, |c| folder.fold_catch_clause(c)),
379                finally: t.finally.map(|f| fold_boxed_block(folder, f)),
380                finally_kw_span: t.finally_kw_span,
381            }))
382        }
383        StmtKind::Global(exprs) => StmtKind::Global(fold_owned_exprs(folder, exprs)),
384        StmtKind::Class(cls) => StmtKind::Class(Box::new(fold_owned_class_decl(folder, *cls))),
385        StmtKind::Interface(iface) => {
386            StmtKind::Interface(Box::new(fold_owned_interface_decl(folder, *iface)))
387        }
388        StmtKind::Trait(tr) => StmtKind::Trait(Box::new(fold_owned_trait_decl(folder, *tr))),
389        StmtKind::Enum(en) => StmtKind::Enum(Box::new(fold_owned_enum_decl(folder, *en))),
390        StmtKind::Namespace(ns) => {
391            let ns = *ns;
392            StmtKind::Namespace(Box::new(NamespaceDecl {
393                name: ns.name.map(|n| folder.fold_name(n)),
394                body: match ns.body {
395                    NamespaceBody::Braced(block) => {
396                        NamespaceBody::Braced(fold_boxed_block(folder, block))
397                    }
398                    NamespaceBody::Simple => NamespaceBody::Simple,
399                },
400            }))
401        }
402        StmtKind::Use(u) => {
403            let u = *u;
404            StmtKind::Use(Box::new(UseDecl {
405                kind: u.kind,
406                uses: map_slice(u.uses, |item| UseItem {
407                    name: folder.fold_name(item.name),
408                    alias: item.alias,
409                    kind: item.kind,
410                    span: item.span,
411                }),
412            }))
413        }
414        StmtKind::Const(items) => StmtKind::Const(map_slice(items, |item| ConstItem {
415            name: item.name,
416            value: folder.fold_expr(item.value),
417            attributes: fold_owned_attrs(folder, item.attributes),
418            span: item.span,
419            doc_comment: item.doc_comment,
420        })),
421        StmtKind::StaticVar(vars) => StmtKind::StaticVar(map_slice(vars, |v| StaticVar {
422            name: v.name,
423            default: v.default.map(|e| folder.fold_expr(e)),
424            span: v.span,
425        })),
426        StmtKind::HaltCompiler(s) => StmtKind::HaltCompiler(s),
427        StmtKind::Nop => StmtKind::Nop,
428        StmtKind::InlineHtml(s) => StmtKind::InlineHtml(s),
429        StmtKind::Error => StmtKind::Error,
430    }
431}
432
433/// Folds an expression, recursing into its children.
434pub fn fold_owned_expr<F: OwnedFold + ?Sized>(folder: &mut F, expr: Expr) -> Expr {
435    let span = expr.span;
436    Expr {
437        kind: fold_owned_expr_kind(folder, expr.into_kind()),
438        span,
439    }
440}
441
442fn fold_owned_expr_kind<F: OwnedFold + ?Sized>(folder: &mut F, k: ExprKind) -> ExprKind {
443    match k {
444        ExprKind::Int(v) => ExprKind::Int(v),
445        ExprKind::Float(v) => ExprKind::Float(v),
446        ExprKind::String(s) => ExprKind::String(s),
447        ExprKind::InterpolatedString(parts) => {
448            ExprKind::InterpolatedString(fold_owned_string_parts(folder, parts))
449        }
450        ExprKind::Heredoc { label, parts } => ExprKind::Heredoc {
451            label,
452            parts: fold_owned_string_parts(folder, parts),
453        },
454        ExprKind::Nowdoc { label, value } => ExprKind::Nowdoc { label, value },
455        ExprKind::ShellExec(parts) => ExprKind::ShellExec(fold_owned_string_parts(folder, parts)),
456        ExprKind::Bool(v) => ExprKind::Bool(v),
457        ExprKind::Null => ExprKind::Null,
458        ExprKind::Variable(s) => ExprKind::Variable(s),
459        ExprKind::VariableVariable(inner) => {
460            ExprKind::VariableVariable(fold_boxed_expr(folder, inner))
461        }
462        ExprKind::Identifier(s) => ExprKind::Identifier(s),
463        ExprKind::Name(name) => ExprKind::Name(folder.fold_name(name)),
464        ExprKind::Assign(a) => ExprKind::Assign(AssignExpr {
465            target: fold_boxed_expr(folder, a.target),
466            op: a.op,
467            value: fold_boxed_expr(folder, a.value),
468            by_ref: a.by_ref,
469        }),
470        ExprKind::Binary(b) => ExprKind::Binary(BinaryExpr {
471            left: fold_boxed_expr(folder, b.left),
472            op: b.op,
473            right: fold_boxed_expr(folder, b.right),
474        }),
475        ExprKind::UnaryPrefix(u) => ExprKind::UnaryPrefix(UnaryPrefixExpr {
476            op: u.op,
477            operand: fold_boxed_expr(folder, u.operand),
478        }),
479        ExprKind::UnaryPostfix(u) => ExprKind::UnaryPostfix(UnaryPostfixExpr {
480            operand: fold_boxed_expr(folder, u.operand),
481            op: u.op,
482        }),
483        ExprKind::Ternary(t) => ExprKind::Ternary(TernaryExpr {
484            condition: fold_boxed_expr(folder, t.condition),
485            then_expr: fold_opt_boxed_expr(folder, t.then_expr),
486            else_expr: fold_boxed_expr(folder, t.else_expr),
487        }),
488        ExprKind::NullCoalesce(n) => ExprKind::NullCoalesce(NullCoalesceExpr {
489            left: fold_boxed_expr(folder, n.left),
490            right: fold_boxed_expr(folder, n.right),
491        }),
492        ExprKind::FunctionCall(f) => ExprKind::FunctionCall(FunctionCallExpr {
493            name: fold_boxed_expr(folder, f.name),
494            args: fold_owned_args(folder, f.args),
495        }),
496        ExprKind::Array(elems) => ExprKind::Array(map_slice(elems, |e| ArrayElement {
497            key: e.key.map(|k| folder.fold_expr(k)),
498            kind: match e.kind {
499                ArrayElementKind::Value(v) => ArrayElementKind::Value(folder.fold_expr(v)),
500                ArrayElementKind::Spread(v) => ArrayElementKind::Spread(folder.fold_expr(v)),
501                ArrayElementKind::ByRef(v) => ArrayElementKind::ByRef(folder.fold_expr(v)),
502            },
503            span: e.span,
504        })),
505        ExprKind::ArrayAccess(a) => ExprKind::ArrayAccess(ArrayAccessExpr {
506            array: fold_boxed_expr(folder, a.array),
507            index: fold_opt_boxed_expr(folder, a.index),
508        }),
509        ExprKind::Print(e) => ExprKind::Print(fold_boxed_expr(folder, e)),
510        ExprKind::Parenthesized(e) => ExprKind::Parenthesized(fold_boxed_expr(folder, e)),
511        ExprKind::Cast(kind, e) => ExprKind::Cast(kind, fold_boxed_expr(folder, e)),
512        ExprKind::ErrorSuppress(e) => ExprKind::ErrorSuppress(fold_boxed_expr(folder, e)),
513        ExprKind::Isset(exprs) => ExprKind::Isset(fold_owned_exprs(folder, exprs)),
514        ExprKind::Empty(e) => ExprKind::Empty(fold_boxed_expr(folder, e)),
515        ExprKind::Include(kind, e) => ExprKind::Include(kind, fold_boxed_expr(folder, e)),
516        ExprKind::Eval(e) => ExprKind::Eval(fold_boxed_expr(folder, e)),
517        ExprKind::Exit(e) => ExprKind::Exit(fold_opt_boxed_expr(folder, e)),
518        ExprKind::MagicConst(m) => ExprKind::MagicConst(m),
519        ExprKind::Clone(e) => ExprKind::Clone(fold_boxed_expr(folder, e)),
520        ExprKind::CloneWith(obj, props) => {
521            ExprKind::CloneWith(fold_boxed_expr(folder, obj), fold_boxed_expr(folder, props))
522        }
523        ExprKind::New(n) => ExprKind::New(NewExpr {
524            class: fold_boxed_expr(folder, n.class),
525            args: fold_owned_args(folder, n.args),
526        }),
527        ExprKind::PropertyAccess(p) => ExprKind::PropertyAccess(PropertyAccessExpr {
528            object: fold_boxed_expr(folder, p.object),
529            property: fold_boxed_expr(folder, p.property),
530        }),
531        ExprKind::NullsafePropertyAccess(p) => {
532            ExprKind::NullsafePropertyAccess(PropertyAccessExpr {
533                object: fold_boxed_expr(folder, p.object),
534                property: fold_boxed_expr(folder, p.property),
535            })
536        }
537        ExprKind::MethodCall(m) => {
538            let m = *m;
539            ExprKind::MethodCall(Box::new(MethodCallExpr {
540                object: fold_boxed_expr(folder, m.object),
541                method: fold_boxed_expr(folder, m.method),
542                args: fold_owned_args(folder, m.args),
543            }))
544        }
545        ExprKind::NullsafeMethodCall(m) => {
546            let m = *m;
547            ExprKind::NullsafeMethodCall(Box::new(MethodCallExpr {
548                object: fold_boxed_expr(folder, m.object),
549                method: fold_boxed_expr(folder, m.method),
550                args: fold_owned_args(folder, m.args),
551            }))
552        }
553        ExprKind::StaticPropertyAccess(s) => ExprKind::StaticPropertyAccess(StaticAccessExpr {
554            class: fold_boxed_expr(folder, s.class),
555            member: fold_boxed_expr(folder, s.member),
556        }),
557        ExprKind::StaticMethodCall(s) => {
558            let s = *s;
559            ExprKind::StaticMethodCall(Box::new(StaticMethodCallExpr {
560                class: fold_boxed_expr(folder, s.class),
561                method: fold_boxed_expr(folder, s.method),
562                args: fold_owned_args(folder, s.args),
563            }))
564        }
565        ExprKind::StaticMethodCallDynamic(s) => {
566            let s = *s;
567            ExprKind::StaticMethodCallDynamic(Box::new(StaticMethodCallDynamicExpr {
568                class: fold_boxed_expr(folder, s.class),
569                method: fold_boxed_expr(folder, s.method),
570                args: fold_owned_args(folder, s.args),
571            }))
572        }
573        ExprKind::ClassConstAccess(s) => ExprKind::ClassConstAccess(StaticAccessExpr {
574            class: fold_boxed_expr(folder, s.class),
575            member: fold_boxed_expr(folder, s.member),
576        }),
577        ExprKind::ClassConstAccessDynamic { class, member } => ExprKind::ClassConstAccessDynamic {
578            class: fold_boxed_expr(folder, class),
579            member: fold_boxed_expr(folder, member),
580        },
581        ExprKind::StaticPropertyAccessDynamic { class, member } => {
582            ExprKind::StaticPropertyAccessDynamic {
583                class: fold_boxed_expr(folder, class),
584                member: fold_boxed_expr(folder, member),
585            }
586        }
587        ExprKind::Closure(c) => {
588            let c = *c;
589            ExprKind::Closure(Box::new(ClosureExpr {
590                is_static: c.is_static,
591                by_ref: c.by_ref,
592                params: fold_owned_params(folder, c.params),
593                missing_param_list: c.missing_param_list,
594                use_vars: map_slice(c.use_vars, |v| folder.fold_closure_use_var(v)),
595                return_type: c.return_type.map(|t| folder.fold_type_hint(t)),
596                body: fold_boxed_block(folder, c.body),
597                attributes: fold_owned_attrs(folder, c.attributes),
598            }))
599        }
600        ExprKind::ArrowFunction(f) => {
601            let f = *f;
602            ExprKind::ArrowFunction(Box::new(ArrowFunctionExpr {
603                is_static: f.is_static,
604                by_ref: f.by_ref,
605                params: fold_owned_params(folder, f.params),
606                missing_param_list: f.missing_param_list,
607                return_type: f.return_type.map(|t| folder.fold_type_hint(t)),
608                body: fold_boxed_expr(folder, f.body),
609                attributes: fold_owned_attrs(folder, f.attributes),
610            }))
611        }
612        ExprKind::Match(m) => ExprKind::Match(MatchExpr {
613            subject: fold_boxed_expr(folder, m.subject),
614            arms: map_slice(m.arms, |arm| folder.fold_match_arm(arm)),
615            brace_span: m.brace_span,
616        }),
617        ExprKind::Throw(e) => ExprKind::Throw(fold_boxed_expr(folder, e)),
618        ExprKind::Yield(y) => ExprKind::Yield(YieldExpr {
619            key: fold_opt_boxed_expr(folder, y.key),
620            value: fold_opt_boxed_expr(folder, y.value),
621            is_from: y.is_from,
622        }),
623        ExprKind::AnonymousClass(cls) => {
624            ExprKind::AnonymousClass(Box::new(fold_owned_class_decl(folder, *cls)))
625        }
626        ExprKind::CallableCreate(c) => ExprKind::CallableCreate(CallableCreateExpr {
627            kind: match c.kind {
628                CallableCreateKind::Function(e) => {
629                    CallableCreateKind::Function(fold_boxed_expr(folder, e))
630                }
631                CallableCreateKind::Method { object, method } => CallableCreateKind::Method {
632                    object: fold_boxed_expr(folder, object),
633                    method: fold_boxed_expr(folder, method),
634                },
635                CallableCreateKind::NullsafeMethod { object, method } => {
636                    CallableCreateKind::NullsafeMethod {
637                        object: fold_boxed_expr(folder, object),
638                        method: fold_boxed_expr(folder, method),
639                    }
640                }
641                CallableCreateKind::StaticMethod { class, method } => {
642                    CallableCreateKind::StaticMethod {
643                        class: fold_boxed_expr(folder, class),
644                        method: fold_boxed_expr(folder, method),
645                    }
646                }
647            },
648        }),
649        ExprKind::Omit => ExprKind::Omit,
650        ExprKind::Error => ExprKind::Error,
651    }
652}
653
654/// Folds a param, recursing into its children.
655pub fn fold_owned_param<F: OwnedFold + ?Sized>(folder: &mut F, p: Param) -> Param {
656    Param {
657        name: p.name,
658        type_hint: p.type_hint.map(|t| folder.fold_type_hint(t)),
659        default: p.default.map(|e| folder.fold_expr(e)),
660        by_ref: p.by_ref,
661        variadic: p.variadic,
662        modifiers: p.modifiers,
663        attributes: fold_owned_attrs(folder, p.attributes),
664        hooks: fold_owned_hooks(folder, p.hooks),
665        empty_hook_list: p.empty_hook_list,
666        doc_comment: p.doc_comment,
667        span: p.span,
668    }
669}
670
671/// Folds an argument, recursing into its children.
672pub fn fold_owned_arg<F: OwnedFold + ?Sized>(folder: &mut F, arg: Arg) -> Arg {
673    Arg {
674        name: arg.name.map(|n| folder.fold_name(n)),
675        kind: match arg.kind {
676            ArgKind::Value(e) => ArgKind::Value(folder.fold_expr(e)),
677            ArgKind::Spread(e) => ArgKind::Spread(folder.fold_expr(e)),
678            ArgKind::ByRef(e) => ArgKind::ByRef(folder.fold_expr(e)),
679            ArgKind::Placeholder => ArgKind::Placeholder,
680            ArgKind::SpreadPlaceholder => ArgKind::SpreadPlaceholder,
681        },
682        span: arg.span,
683    }
684}
685
686/// Folds a closure use var, recursing into its children.
687pub fn fold_owned_closure_use_var<F: OwnedFold + ?Sized>(
688    _folder: &mut F,
689    var: ClosureUseVar,
690) -> ClosureUseVar {
691    var
692}
693
694/// Folds a name, recursing into its children.
695pub fn fold_owned_name<F: OwnedFold + ?Sized>(_folder: &mut F, name: Name) -> Name {
696    name
697}
698
699/// Folds a method declaration's parts.
700fn fold_owned_method<F: OwnedFold + ?Sized>(folder: &mut F, m: MethodDecl) -> MethodDecl {
701    MethodDecl {
702        name: m.name,
703        modifiers: m.modifiers,
704        by_ref: m.by_ref,
705        params: fold_owned_params(folder, m.params),
706        missing_param_list: m.missing_param_list,
707        return_type: m.return_type.map(|t| folder.fold_type_hint(t)),
708        body: m.body.map(|b| fold_boxed_block(folder, b)),
709        attributes: fold_owned_attrs(folder, m.attributes),
710        doc_comment: m.doc_comment,
711    }
712}
713
714fn fold_owned_property<F: OwnedFold + ?Sized>(folder: &mut F, p: PropertyDecl) -> PropertyDecl {
715    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        empty_hook_list: p.empty_hook_list,
723        doc_comment: p.doc_comment,
724    }
725}
726
727/// Folds a class member, recursing into its children.
728pub fn fold_owned_class_member<F: OwnedFold + ?Sized>(
729    folder: &mut F,
730    member: ClassMember,
731) -> ClassMember {
732    ClassMember {
733        kind: match member.kind {
734            ClassMemberKind::Property(p) => {
735                ClassMemberKind::Property(fold_owned_property(folder, p))
736            }
737            ClassMemberKind::Method(m) => ClassMemberKind::Method(fold_owned_method(folder, m)),
738            ClassMemberKind::ClassConst(c) => {
739                ClassMemberKind::ClassConst(fold_owned_class_const(folder, c))
740            }
741            ClassMemberKind::TraitUse(t) => ClassMemberKind::TraitUse(folder.fold_trait_use(t)),
742        },
743        span: member.span,
744    }
745}
746
747/// Folds an enum member, recursing into its children.
748pub fn fold_owned_enum_member<F: OwnedFold + ?Sized>(
749    folder: &mut F,
750    member: EnumMember,
751) -> EnumMember {
752    EnumMember {
753        kind: match member.kind {
754            EnumMemberKind::Case(c) => EnumMemberKind::Case(EnumCase {
755                name: c.name,
756                value: c.value.map(|e| folder.fold_expr(e)),
757                attributes: fold_owned_attrs(folder, c.attributes),
758                doc_comment: c.doc_comment,
759            }),
760            EnumMemberKind::Method(m) => EnumMemberKind::Method(fold_owned_method(folder, m)),
761            EnumMemberKind::ClassConst(c) => {
762                EnumMemberKind::ClassConst(fold_owned_class_const(folder, c))
763            }
764            EnumMemberKind::TraitUse(t) => EnumMemberKind::TraitUse(folder.fold_trait_use(t)),
765            EnumMemberKind::Property(p) => EnumMemberKind::Property(fold_owned_property(folder, p)),
766        },
767        span: member.span,
768    }
769}
770
771/// Folds a property hook, recursing into its children.
772pub fn fold_owned_property_hook<F: OwnedFold + ?Sized>(
773    folder: &mut F,
774    hook: PropertyHook,
775) -> PropertyHook {
776    PropertyHook {
777        kind: hook.kind,
778        body: match hook.body {
779            PropertyHookBody::Block(block) => {
780                PropertyHookBody::Block(fold_boxed_block(folder, block))
781            }
782            PropertyHookBody::Expression(e) => PropertyHookBody::Expression(folder.fold_expr(e)),
783            PropertyHookBody::Abstract => PropertyHookBody::Abstract,
784        },
785        modifiers: hook.modifiers,
786        by_ref: hook.by_ref,
787        params: fold_owned_params(folder, hook.params),
788        attributes: fold_owned_attrs(folder, hook.attributes),
789        span: hook.span,
790    }
791}
792
793/// Folds a type hint, recursing into its children.
794pub fn fold_owned_type_hint<F: OwnedFold + ?Sized>(
795    folder: &mut F,
796    type_hint: TypeHint,
797) -> TypeHint {
798    TypeHint {
799        kind: match type_hint.kind {
800            TypeHintKind::Named(n) => TypeHintKind::Named(folder.fold_name(n)),
801            TypeHintKind::Keyword(b, span) => TypeHintKind::Keyword(b, span),
802            TypeHintKind::Nullable(inner) => {
803                TypeHintKind::Nullable(Box::new(folder.fold_type_hint(*inner)))
804            }
805            TypeHintKind::Union(types) => TypeHintKind::Union(fold_owned_type_hints(folder, types)),
806            TypeHintKind::Intersection(types) => {
807                TypeHintKind::Intersection(fold_owned_type_hints(folder, types))
808            }
809        },
810        span: type_hint.span,
811    }
812}
813
814/// Folds an attribute, recursing into its children.
815pub fn fold_owned_attribute<F: OwnedFold + ?Sized>(
816    folder: &mut F,
817    attribute: Attribute,
818) -> Attribute {
819    Attribute {
820        name: folder.fold_name(attribute.name),
821        args: fold_owned_args(folder, attribute.args),
822        span: attribute.span,
823    }
824}
825
826/// Folds a catch clause, recursing into its children.
827pub fn fold_owned_catch_clause<F: OwnedFold + ?Sized>(
828    folder: &mut F,
829    catch: CatchClause,
830) -> CatchClause {
831    CatchClause {
832        types: fold_owned_names(folder, catch.types),
833        var: catch.var,
834        body: fold_boxed_block(folder, catch.body),
835        span: catch.span,
836    }
837}
838
839/// Folds a match arm, recursing into its children.
840pub fn fold_owned_match_arm<F: OwnedFold + ?Sized>(folder: &mut F, arm: MatchArm) -> MatchArm {
841    MatchArm {
842        conditions: arm.conditions.map(|conds| fold_owned_exprs(folder, conds)),
843        body: folder.fold_expr(arm.body),
844        span: arm.span,
845    }
846}
847
848fn fold_owned_class_const<F: OwnedFold + ?Sized>(
849    folder: &mut F,
850    c: ClassConstDecl,
851) -> ClassConstDecl {
852    ClassConstDecl {
853        name: c.name,
854        modifiers: c.modifiers,
855        type_hint: c.type_hint.map(|th| Box::new(folder.fold_type_hint(*th))),
856        value: folder.fold_expr(c.value),
857        attributes: fold_owned_attrs(folder, c.attributes),
858        doc_comment: c.doc_comment,
859    }
860}
861
862/// Folds a `use Trait;` declaration inside a class or enum body.
863pub fn fold_owned_trait_use<F: OwnedFold + ?Sized>(
864    folder: &mut F,
865    t: TraitUseDecl,
866) -> TraitUseDecl {
867    TraitUseDecl {
868        traits: fold_owned_names(folder, t.traits),
869        adaptations: map_slice(t.adaptations, |a| folder.fold_trait_adaptation(a)),
870        adaptations_brace_span: t.adaptations_brace_span,
871        doc_comment: t.doc_comment,
872    }
873}
874
875/// Folds a trait `insteadof`/`as` adaptation.
876pub fn fold_owned_trait_adaptation<F: OwnedFold + ?Sized>(
877    folder: &mut F,
878    a: TraitAdaptation,
879) -> TraitAdaptation {
880    TraitAdaptation {
881        kind: match a.kind {
882            TraitAdaptationKind::Precedence {
883                trait_name,
884                method,
885                insteadof,
886            } => TraitAdaptationKind::Precedence {
887                trait_name: folder.fold_name(trait_name),
888                method: folder.fold_name(method),
889                insteadof: fold_owned_names(folder, insteadof),
890            },
891            TraitAdaptationKind::Alias {
892                trait_name,
893                method,
894                new_modifier,
895                is_final,
896                new_name,
897            } => TraitAdaptationKind::Alias {
898                trait_name: trait_name.map(|n| folder.fold_name(n)),
899                method: folder.fold_name(method),
900                new_modifier,
901                is_final,
902                new_name: new_name.map(|n| folder.fold_name(n)),
903            },
904        },
905        span: a.span,
906    }
907}
908
909fn fold_owned_class_decl<F: OwnedFold + ?Sized>(folder: &mut F, cls: ClassDecl) -> ClassDecl {
910    ClassDecl {
911        name: cls.name,
912        modifiers: cls.modifiers,
913        extends: cls.extends.map(|n| folder.fold_name(n)),
914        implements: fold_owned_names(folder, cls.implements),
915        body: ClassBody {
916            members: fold_owned_members(folder, cls.body.members),
917            span: cls.body.span,
918        },
919        attributes: fold_owned_attrs(folder, cls.attributes),
920        doc_comment: cls.doc_comment,
921    }
922}
923
924fn fold_owned_interface_decl<F: OwnedFold + ?Sized>(
925    folder: &mut F,
926    iface: InterfaceDecl,
927) -> InterfaceDecl {
928    InterfaceDecl {
929        name: iface.name,
930        extends: fold_owned_names(folder, iface.extends),
931        body: ClassBody {
932            members: fold_owned_members(folder, iface.body.members),
933            span: iface.body.span,
934        },
935        attributes: fold_owned_attrs(folder, iface.attributes),
936        doc_comment: iface.doc_comment,
937    }
938}
939
940fn fold_owned_trait_decl<F: OwnedFold + ?Sized>(folder: &mut F, tr: TraitDecl) -> TraitDecl {
941    TraitDecl {
942        name: tr.name,
943        body: ClassBody {
944            members: fold_owned_members(folder, tr.body.members),
945            span: tr.body.span,
946        },
947        attributes: fold_owned_attrs(folder, tr.attributes),
948        doc_comment: tr.doc_comment,
949    }
950}
951
952fn fold_owned_enum_decl<F: OwnedFold + ?Sized>(folder: &mut F, en: EnumDecl) -> EnumDecl {
953    EnumDecl {
954        name: en.name,
955        scalar_type: en.scalar_type.map(|n| folder.fold_name(n)),
956        implements: fold_owned_names(folder, en.implements),
957        body: EnumBody {
958            members: map_slice(en.body.members, |m| folder.fold_enum_member(m)),
959            span: en.body.span,
960        },
961        attributes: fold_owned_attrs(folder, en.attributes),
962        doc_comment: en.doc_comment,
963    }
964}
965
966// =============================================================================
967// Tests
968// =============================================================================
969
970#[cfg(test)]
971mod tests {
972    use super::*;
973    use crate::ast::AssignOp;
974    use crate::Span;
975
976    fn dummy_var(name: &str) -> Expr {
977        Expr {
978            kind: ExprKind::Variable(Box::from(name)),
979            span: Span::DUMMY,
980        }
981    }
982
983    fn dummy_int(n: i64) -> Expr {
984        Expr {
985            kind: ExprKind::Int(n),
986            span: Span::DUMMY,
987        }
988    }
989
990    fn assign(target: Expr, value: Expr) -> Expr {
991        Expr {
992            kind: ExprKind::Assign(AssignExpr {
993                target: Box::new(target),
994                op: AssignOp::Assign,
995                value: Box::new(value),
996                by_ref: false,
997            }),
998            span: Span::DUMMY,
999        }
1000    }
1001
1002    fn expr_stmt(e: Expr) -> Stmt {
1003        Stmt {
1004            kind: StmtKind::Expression(Box::new(e)),
1005            span: Span::DUMMY,
1006            doc_comment: None,
1007        }
1008    }
1009
1010    fn empty_block() -> Block {
1011        Block {
1012            stmts: Box::from([]),
1013            span: Span::DUMMY,
1014        }
1015    }
1016
1017    fn program(stmts: impl IntoIterator<Item = Stmt>) -> Program {
1018        Program {
1019            stmts: stmts.into_iter().collect(),
1020            span: Span::DUMMY,
1021        }
1022    }
1023
1024    /// Identity fold on `$x = $y;` must produce structurally equal output.
1025    #[test]
1026    fn identity_fold_roundtrip() {
1027        let p = program([expr_stmt(assign(dummy_var("x"), dummy_var("y")))]);
1028        struct Identity;
1029        impl OwnedFold for Identity {}
1030        let before = serde_json::to_string(&p).unwrap();
1031        let folded = Identity.fold_program(p);
1032        assert_eq!(serde_json::to_string(&folded).unwrap(), before);
1033    }
1034
1035    /// NegateInts fold on `$x = 42;` must produce `$x = -42;`.
1036    #[test]
1037    fn negate_ints() {
1038        let p = program([expr_stmt(assign(dummy_var("x"), dummy_int(42)))]);
1039        struct NegateInts;
1040        impl OwnedFold for NegateInts {
1041            fn fold_expr(&mut self, expr: Expr) -> Expr {
1042                if let ExprKind::Int(n) = &expr.kind {
1043                    return Expr {
1044                        kind: ExprKind::Int(-n),
1045                        span: expr.span,
1046                    };
1047                }
1048                fold_owned_expr(self, expr)
1049            }
1050        }
1051        let folded = NegateInts.fold_program(p);
1052        let stmt = &folded.stmts[0];
1053        if let StmtKind::Expression(expr) = &stmt.kind {
1054            if let ExprKind::Assign(a) = &expr.kind {
1055                if let ExprKind::Int(n) = &a.value.kind {
1056                    assert_eq!(*n, -42);
1057                    return;
1058                }
1059            }
1060        }
1061        panic!("expected negated int assignment");
1062    }
1063
1064    /// Fold that rewrites variable names: all occurrences of `$x` become `$renamed`.
1065    #[test]
1066    fn rename_variable() {
1067        // $x = $x;
1068        let p = program([expr_stmt(assign(dummy_var("x"), dummy_var("x")))]);
1069        struct Rename;
1070        impl OwnedFold for Rename {
1071            fn fold_expr(&mut self, expr: Expr) -> Expr {
1072                if let ExprKind::Variable(name) = &expr.kind {
1073                    if name.as_ref() == "x" {
1074                        return Expr {
1075                            kind: ExprKind::Variable(Box::from("renamed")),
1076                            span: expr.span,
1077                        };
1078                    }
1079                }
1080                fold_owned_expr(self, expr)
1081            }
1082        }
1083        let folded = Rename.fold_program(p);
1084        let stmt = &folded.stmts[0];
1085        if let StmtKind::Expression(expr) = &stmt.kind {
1086            if let ExprKind::Assign(a) = &expr.kind {
1087                assert!(matches!(&a.target.kind, ExprKind::Variable(n) if n.as_ref() == "renamed"));
1088                assert!(matches!(&a.value.kind, ExprKind::Variable(n) if n.as_ref() == "renamed"));
1089                return;
1090            }
1091        }
1092        panic!("expected renamed variable assignment");
1093    }
1094
1095    /// Rename fold must reach into closure `use` capture lists.
1096    #[test]
1097    fn rename_variable_in_closure_use() {
1098        // function() use ($x) {}
1099        let closure = Expr {
1100            kind: ExprKind::Closure(Box::new(ClosureExpr {
1101                is_static: false,
1102                by_ref: false,
1103                params: Box::from([]),
1104                missing_param_list: false,
1105                use_vars: Box::from([ClosureUseVar {
1106                    name: Ident::new("x", Span::DUMMY),
1107                    by_ref: false,
1108                    span: Span::DUMMY,
1109                }]),
1110                return_type: None,
1111                body: Box::new(empty_block()),
1112                attributes: Box::from([]),
1113            })),
1114            span: Span::DUMMY,
1115        };
1116        let p = program([expr_stmt(closure)]);
1117
1118        struct Rename;
1119        impl OwnedFold for Rename {
1120            fn fold_closure_use_var(&mut self, var: ClosureUseVar) -> ClosureUseVar {
1121                ClosureUseVar {
1122                    name: if var.name == "x" {
1123                        Ident::new("renamed", var.name.span)
1124                    } else {
1125                        var.name
1126                    },
1127                    by_ref: var.by_ref,
1128                    span: var.span,
1129                }
1130            }
1131        }
1132
1133        let folded = Rename.fold_program(p);
1134        if let StmtKind::Expression(expr) = &folded.stmts[0].kind {
1135            if let ExprKind::Closure(c) = &expr.kind {
1136                assert_eq!(c.use_vars[0].name, "renamed");
1137                return;
1138            }
1139        }
1140        panic!("expected closure with renamed use-var");
1141    }
1142
1143    /// `fold_stmts` sees the program, block and `case` statement lists, and may drop statements.
1144    #[test]
1145    fn fold_stmts_hook_can_delete_statements() {
1146        struct DropNops;
1147        impl OwnedFold for DropNops {
1148            fn fold_stmts(&mut self, stmts: Box<[Stmt]>) -> Box<[Stmt]> {
1149                let kept: Vec<Stmt> = stmts
1150                    .into_vec()
1151                    .into_iter()
1152                    .filter(|s| !matches!(s.kind, StmtKind::Nop))
1153                    .map(|s| self.fold_stmt(s))
1154                    .collect();
1155                kept.into_boxed_slice()
1156            }
1157        }
1158        let nop = || Stmt {
1159            kind: StmtKind::Nop,
1160            span: Span::DUMMY,
1161            doc_comment: None,
1162        };
1163        let nested = Stmt {
1164            kind: StmtKind::Block(Box::new(Block {
1165                stmts: Box::from([nop(), expr_stmt(dummy_int(1))]),
1166                span: Span::DUMMY,
1167            })),
1168            span: Span::DUMMY,
1169            doc_comment: None,
1170        };
1171        let switch = Stmt {
1172            kind: StmtKind::Switch(Box::new(SwitchStmt {
1173                expr: dummy_int(0),
1174                body: SwitchBody {
1175                    cases: Box::from([SwitchCase {
1176                        value: None,
1177                        body: Box::from([nop()]),
1178                        span: Span::DUMMY,
1179                    }]),
1180                    span: Span::DUMMY,
1181                },
1182                uses_alternative: false,
1183            })),
1184            span: Span::DUMMY,
1185            doc_comment: None,
1186        };
1187        let folded = DropNops.fold_program(program([nop(), nested, switch]));
1188        assert_eq!(folded.stmts.len(), 2);
1189        let StmtKind::Block(block) = &folded.stmts[0].kind else {
1190            panic!("expected Block")
1191        };
1192        assert_eq!(block.stmts.len(), 1);
1193        let StmtKind::Switch(switch) = &folded.stmts[1].kind else {
1194            panic!("expected Switch")
1195        };
1196        assert!(switch.body.cases[0].body.is_empty());
1197    }
1198}