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