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