Skip to main content

php_ast/owned/
fold.rs

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