Skip to main content

php_ast/owned/
fold.rs

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