Skip to main content

react_compiler_hir/
visitors.rs

1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7use rustc_hash::FxHashMap;
8
9use crate::environment::Environment;
10use crate::{
11    ArrayElement, ArrayPatternElement, BasicBlock, BlockId, HirFunction, IdentifierId, Instruction,
12    InstructionKind, InstructionValue, JsxAttribute, JsxTag, ManualMemoDependencyRoot,
13    ObjectPropertyKey, ObjectPropertyOrSpread, Pattern, Place, PlaceOrSpread, ScopeId, Terminal,
14};
15
16// =============================================================================
17// Iterator functions (return Vec instead of generators)
18// =============================================================================
19
20/// Yields `instr.lvalue` plus the value's lvalues.
21/// Equivalent to TS `eachInstructionLValue`.
22pub fn each_instruction_lvalue(instr: &Instruction) -> Vec<Place> {
23    let mut result = Vec::new();
24    result.push(instr.lvalue.clone());
25    result.extend(each_instruction_value_lvalue(&instr.value));
26    result
27}
28
29/// Yields lvalues from DeclareLocal/StoreLocal/DeclareContext/StoreContext/Destructure/PostfixUpdate/PrefixUpdate.
30/// Equivalent to TS `eachInstructionValueLValue`.
31pub fn each_instruction_value_lvalue(value: &InstructionValue) -> Vec<Place> {
32    let mut result = Vec::new();
33    match value {
34        InstructionValue::DeclareContext { lvalue, .. }
35        | InstructionValue::StoreContext { lvalue, .. }
36        | InstructionValue::DeclareLocal { lvalue, .. }
37        | InstructionValue::StoreLocal { lvalue, .. } => {
38            result.push(lvalue.place.clone());
39        }
40        InstructionValue::Destructure { lvalue, .. } => {
41            result.extend(each_pattern_operand(&lvalue.pattern));
42        }
43        InstructionValue::PostfixUpdate { lvalue, .. }
44        | InstructionValue::PrefixUpdate { lvalue, .. } => {
45            result.push(lvalue.clone());
46        }
47        // All other variants have no lvalues
48        InstructionValue::LoadLocal { .. }
49        | InstructionValue::LoadContext { .. }
50        | InstructionValue::Primitive { .. }
51        | InstructionValue::JSXText { .. }
52        | InstructionValue::BinaryExpression { .. }
53        | InstructionValue::NewExpression { .. }
54        | InstructionValue::CallExpression { .. }
55        | InstructionValue::MethodCall { .. }
56        | InstructionValue::UnaryExpression { .. }
57        | InstructionValue::TypeCastExpression { .. }
58        | InstructionValue::JsxExpression { .. }
59        | InstructionValue::ObjectExpression { .. }
60        | InstructionValue::ObjectMethod { .. }
61        | InstructionValue::ArrayExpression { .. }
62        | InstructionValue::JsxFragment { .. }
63        | InstructionValue::RegExpLiteral { .. }
64        | InstructionValue::MetaProperty { .. }
65        | InstructionValue::PropertyStore { .. }
66        | InstructionValue::PropertyLoad { .. }
67        | InstructionValue::PropertyDelete { .. }
68        | InstructionValue::ComputedStore { .. }
69        | InstructionValue::ComputedLoad { .. }
70        | InstructionValue::ComputedDelete { .. }
71        | InstructionValue::LoadGlobal { .. }
72        | InstructionValue::StoreGlobal { .. }
73        | InstructionValue::FunctionExpression { .. }
74        | InstructionValue::TaggedTemplateExpression { .. }
75        | InstructionValue::TemplateLiteral { .. }
76        | InstructionValue::Await { .. }
77        | InstructionValue::GetIterator { .. }
78        | InstructionValue::IteratorNext { .. }
79        | InstructionValue::NextPropertyOf { .. }
80        | InstructionValue::Debugger { .. }
81        | InstructionValue::StartMemoize { .. }
82        | InstructionValue::FinishMemoize { .. }
83        | InstructionValue::UnsupportedNode { .. } => {}
84    }
85    result
86}
87
88/// Yields lvalues with their InstructionKind.
89/// Equivalent to TS `eachInstructionLValueWithKind`.
90pub fn each_instruction_lvalue_with_kind(
91    value: &InstructionValue,
92) -> Vec<(Place, InstructionKind)> {
93    let mut result = Vec::new();
94    match value {
95        InstructionValue::DeclareContext { lvalue, .. }
96        | InstructionValue::StoreContext { lvalue, .. }
97        | InstructionValue::DeclareLocal { lvalue, .. }
98        | InstructionValue::StoreLocal { lvalue, .. } => {
99            result.push((lvalue.place.clone(), lvalue.kind));
100        }
101        InstructionValue::Destructure { lvalue, .. } => {
102            let kind = lvalue.kind;
103            for place in each_pattern_operand(&lvalue.pattern) {
104                result.push((place, kind));
105            }
106        }
107        InstructionValue::PostfixUpdate { lvalue, .. }
108        | InstructionValue::PrefixUpdate { lvalue, .. } => {
109            result.push((lvalue.clone(), InstructionKind::Reassign));
110        }
111        // All other variants have no lvalues with kind
112        InstructionValue::LoadLocal { .. }
113        | InstructionValue::LoadContext { .. }
114        | InstructionValue::Primitive { .. }
115        | InstructionValue::JSXText { .. }
116        | InstructionValue::BinaryExpression { .. }
117        | InstructionValue::NewExpression { .. }
118        | InstructionValue::CallExpression { .. }
119        | InstructionValue::MethodCall { .. }
120        | InstructionValue::UnaryExpression { .. }
121        | InstructionValue::TypeCastExpression { .. }
122        | InstructionValue::JsxExpression { .. }
123        | InstructionValue::ObjectExpression { .. }
124        | InstructionValue::ObjectMethod { .. }
125        | InstructionValue::ArrayExpression { .. }
126        | InstructionValue::JsxFragment { .. }
127        | InstructionValue::RegExpLiteral { .. }
128        | InstructionValue::MetaProperty { .. }
129        | InstructionValue::PropertyStore { .. }
130        | InstructionValue::PropertyLoad { .. }
131        | InstructionValue::PropertyDelete { .. }
132        | InstructionValue::ComputedStore { .. }
133        | InstructionValue::ComputedLoad { .. }
134        | InstructionValue::ComputedDelete { .. }
135        | InstructionValue::LoadGlobal { .. }
136        | InstructionValue::StoreGlobal { .. }
137        | InstructionValue::FunctionExpression { .. }
138        | InstructionValue::TaggedTemplateExpression { .. }
139        | InstructionValue::TemplateLiteral { .. }
140        | InstructionValue::Await { .. }
141        | InstructionValue::GetIterator { .. }
142        | InstructionValue::IteratorNext { .. }
143        | InstructionValue::NextPropertyOf { .. }
144        | InstructionValue::Debugger { .. }
145        | InstructionValue::StartMemoize { .. }
146        | InstructionValue::FinishMemoize { .. }
147        | InstructionValue::UnsupportedNode { .. } => {}
148    }
149    result
150}
151
152/// Delegates to each_instruction_value_operand.
153/// Equivalent to TS `eachInstructionOperand`.
154pub fn each_instruction_operand(instr: &Instruction, env: &Environment) -> Vec<Place> {
155    each_instruction_value_operand(&instr.value, env)
156}
157
158/// Like `each_instruction_operand` but takes `functions` directly instead of `env`.
159/// Useful when borrow splitting prevents passing the full `Environment`.
160pub fn each_instruction_operand_with_functions(
161    instr: &Instruction,
162    functions: &[HirFunction],
163) -> Vec<Place> {
164    each_instruction_value_operand_with_functions(&instr.value, functions)
165}
166
167/// Yields operand places from an InstructionValue.
168/// Equivalent to TS `eachInstructionValueOperand`.
169pub fn each_instruction_value_operand(value: &InstructionValue, env: &Environment) -> Vec<Place> {
170    each_instruction_value_operand_with_functions(value, &env.functions)
171}
172
173/// Like `each_instruction_value_operand` but takes `functions` directly instead of `env`.
174/// Useful when borrow splitting prevents passing the full `Environment`.
175pub fn each_instruction_value_operand_with_functions(
176    value: &InstructionValue,
177    functions: &[HirFunction],
178) -> Vec<Place> {
179    let mut result = Vec::new();
180    match value {
181        InstructionValue::NewExpression { callee, args, .. }
182        | InstructionValue::CallExpression { callee, args, .. } => {
183            result.push(callee.clone());
184            result.extend(each_call_argument(args));
185        }
186        InstructionValue::BinaryExpression { left, right, .. } => {
187            result.push(left.clone());
188            result.push(right.clone());
189        }
190        InstructionValue::MethodCall {
191            receiver,
192            property,
193            args,
194            ..
195        } => {
196            result.push(receiver.clone());
197            result.push(property.clone());
198            result.extend(each_call_argument(args));
199        }
200        InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {
201            // no operands
202        }
203        InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
204            result.push(place.clone());
205        }
206        InstructionValue::StoreLocal { value: val, .. } => {
207            result.push(val.clone());
208        }
209        InstructionValue::StoreContext {
210            lvalue, value: val, ..
211        } => {
212            result.push(lvalue.place.clone());
213            result.push(val.clone());
214        }
215        InstructionValue::StoreGlobal { value: val, .. } => {
216            result.push(val.clone());
217        }
218        InstructionValue::Destructure { value: val, .. } => {
219            result.push(val.clone());
220        }
221        InstructionValue::PropertyLoad { object, .. } => {
222            result.push(object.clone());
223        }
224        InstructionValue::PropertyDelete { object, .. } => {
225            result.push(object.clone());
226        }
227        InstructionValue::PropertyStore {
228            object, value: val, ..
229        } => {
230            result.push(object.clone());
231            result.push(val.clone());
232        }
233        InstructionValue::ComputedLoad {
234            object, property, ..
235        } => {
236            result.push(object.clone());
237            result.push(property.clone());
238        }
239        InstructionValue::ComputedDelete {
240            object, property, ..
241        } => {
242            result.push(object.clone());
243            result.push(property.clone());
244        }
245        InstructionValue::ComputedStore {
246            object,
247            property,
248            value: val,
249            ..
250        } => {
251            result.push(object.clone());
252            result.push(property.clone());
253            result.push(val.clone());
254        }
255        InstructionValue::UnaryExpression { value: val, .. } => {
256            result.push(val.clone());
257        }
258        InstructionValue::JsxExpression {
259            tag,
260            props,
261            children,
262            ..
263        } => {
264            if let JsxTag::Place(place) = tag {
265                result.push(place.clone());
266            }
267            for attribute in props {
268                match attribute {
269                    JsxAttribute::Attribute { place, .. } => {
270                        result.push(place.clone());
271                    }
272                    JsxAttribute::SpreadAttribute { argument, .. } => {
273                        result.push(argument.clone());
274                    }
275                }
276            }
277            if let Some(children) = children {
278                for child in children {
279                    result.push(child.clone());
280                }
281            }
282        }
283        InstructionValue::JsxFragment { children, .. } => {
284            for child in children {
285                result.push(child.clone());
286            }
287        }
288        InstructionValue::ObjectExpression { properties, .. } => {
289            for property in properties {
290                match property {
291                    ObjectPropertyOrSpread::Property(prop) => {
292                        if let ObjectPropertyKey::Computed { name } = &prop.key {
293                            result.push(name.clone());
294                        }
295                        result.push(prop.place.clone());
296                    }
297                    ObjectPropertyOrSpread::Spread(spread) => {
298                        result.push(spread.place.clone());
299                    }
300                }
301            }
302        }
303        InstructionValue::ArrayExpression { elements, .. } => {
304            for element in elements {
305                match element {
306                    ArrayElement::Place(place) => {
307                        result.push(place.clone());
308                    }
309                    ArrayElement::Spread(spread) => {
310                        result.push(spread.place.clone());
311                    }
312                    ArrayElement::Hole => {}
313                }
314            }
315        }
316        InstructionValue::ObjectMethod { lowered_func, .. }
317        | InstructionValue::FunctionExpression { lowered_func, .. } => {
318            let func = &functions[lowered_func.func.0 as usize];
319            for ctx_place in &func.context {
320                result.push(ctx_place.clone());
321            }
322        }
323        InstructionValue::TaggedTemplateExpression { tag, .. } => {
324            result.push(tag.clone());
325        }
326        InstructionValue::TypeCastExpression { value: val, .. } => {
327            result.push(val.clone());
328        }
329        InstructionValue::TemplateLiteral { subexprs, .. } => {
330            for subexpr in subexprs {
331                result.push(subexpr.clone());
332            }
333        }
334        InstructionValue::Await { value: val, .. } => {
335            result.push(val.clone());
336        }
337        InstructionValue::GetIterator { collection, .. } => {
338            result.push(collection.clone());
339        }
340        InstructionValue::IteratorNext {
341            iterator,
342            collection,
343            ..
344        } => {
345            result.push(iterator.clone());
346            result.push(collection.clone());
347        }
348        InstructionValue::NextPropertyOf { value: val, .. } => {
349            result.push(val.clone());
350        }
351        InstructionValue::PostfixUpdate { value: val, .. }
352        | InstructionValue::PrefixUpdate { value: val, .. } => {
353            result.push(val.clone());
354        }
355        InstructionValue::StartMemoize { deps, .. } => {
356            if let Some(deps) = deps {
357                for dep in deps {
358                    if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &dep.root {
359                        result.push(value.clone());
360                    }
361                }
362            }
363        }
364        InstructionValue::FinishMemoize { decl, .. } => {
365            result.push(decl.clone());
366        }
367        InstructionValue::Debugger { .. }
368        | InstructionValue::RegExpLiteral { .. }
369        | InstructionValue::MetaProperty { .. }
370        | InstructionValue::LoadGlobal { .. }
371        | InstructionValue::UnsupportedNode { .. }
372        | InstructionValue::Primitive { .. }
373        | InstructionValue::JSXText { .. } => {
374            // no operands
375        }
376    }
377    result
378}
379
380/// Yields each arg's place.
381/// Equivalent to TS `eachCallArgument`.
382pub fn each_call_argument(args: &[PlaceOrSpread]) -> Vec<Place> {
383    let mut result = Vec::new();
384    for arg in args {
385        match arg {
386            PlaceOrSpread::Place(place) => {
387                result.push(place.clone());
388            }
389            PlaceOrSpread::Spread(spread) => {
390                result.push(spread.place.clone());
391            }
392        }
393    }
394    result
395}
396
397/// Yields places from array/object patterns.
398/// Equivalent to TS `eachPatternOperand`.
399pub fn each_pattern_operand(pattern: &Pattern) -> Vec<Place> {
400    let mut result = Vec::new();
401    match pattern {
402        Pattern::Array(arr) => {
403            for item in &arr.items {
404                match item {
405                    ArrayPatternElement::Place(place) => {
406                        result.push(place.clone());
407                    }
408                    ArrayPatternElement::Spread(spread) => {
409                        result.push(spread.place.clone());
410                    }
411                    ArrayPatternElement::Hole => {}
412                }
413            }
414        }
415        Pattern::Object(obj) => {
416            for property in &obj.properties {
417                match property {
418                    ObjectPropertyOrSpread::Property(prop) => {
419                        result.push(prop.place.clone());
420                    }
421                    ObjectPropertyOrSpread::Spread(spread) => {
422                        result.push(spread.place.clone());
423                    }
424                }
425            }
426        }
427    }
428    result
429}
430
431/// Returns true if the pattern contains a spread element.
432/// Equivalent to TS `doesPatternContainSpreadElement`.
433pub fn does_pattern_contain_spread_element(pattern: &Pattern) -> bool {
434    match pattern {
435        Pattern::Array(arr) => {
436            for item in &arr.items {
437                if matches!(item, ArrayPatternElement::Spread(_)) {
438                    return true;
439                }
440            }
441        }
442        Pattern::Object(obj) => {
443            for property in &obj.properties {
444                if matches!(property, ObjectPropertyOrSpread::Spread(_)) {
445                    return true;
446                }
447            }
448        }
449    }
450    false
451}
452
453/// Yields successor block IDs (NOT fallthroughs, this is intentional).
454/// Equivalent to TS `eachTerminalSuccessor`.
455pub fn each_terminal_successor(terminal: &Terminal) -> Vec<BlockId> {
456    let mut result = Vec::new();
457    match terminal {
458        Terminal::Goto { block, .. } => {
459            result.push(*block);
460        }
461        Terminal::If {
462            consequent,
463            alternate,
464            ..
465        } => {
466            result.push(*consequent);
467            result.push(*alternate);
468        }
469        Terminal::Branch {
470            consequent,
471            alternate,
472            ..
473        } => {
474            result.push(*consequent);
475            result.push(*alternate);
476        }
477        Terminal::Switch { cases, .. } => {
478            for case in cases {
479                result.push(case.block);
480            }
481        }
482        Terminal::Optional { test, .. }
483        | Terminal::Ternary { test, .. }
484        | Terminal::Logical { test, .. } => {
485            result.push(*test);
486        }
487        Terminal::Return { .. } => {}
488        Terminal::Throw { .. } => {}
489        Terminal::DoWhile { loop_block, .. } => {
490            result.push(*loop_block);
491        }
492        Terminal::While { test, .. } => {
493            result.push(*test);
494        }
495        Terminal::For { init, .. } => {
496            result.push(*init);
497        }
498        Terminal::ForOf { init, .. } => {
499            result.push(*init);
500        }
501        Terminal::ForIn { init, .. } => {
502            result.push(*init);
503        }
504        Terminal::Label { block, .. } => {
505            result.push(*block);
506        }
507        Terminal::Sequence { block, .. } => {
508            result.push(*block);
509        }
510        Terminal::MaybeThrow {
511            continuation,
512            handler,
513            ..
514        } => {
515            result.push(*continuation);
516            if let Some(handler) = handler {
517                result.push(*handler);
518            }
519        }
520        Terminal::Try { block, .. } => {
521            result.push(*block);
522        }
523        Terminal::Scope { block, .. } | Terminal::PrunedScope { block, .. } => {
524            result.push(*block);
525        }
526        Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {}
527    }
528    result
529}
530
531/// Yields places used by terminal.
532/// Equivalent to TS `eachTerminalOperand`.
533pub fn each_terminal_operand(terminal: &Terminal) -> Vec<Place> {
534    let mut result = Vec::new();
535    match terminal {
536        Terminal::If { test, .. } => {
537            result.push(test.clone());
538        }
539        Terminal::Branch { test, .. } => {
540            result.push(test.clone());
541        }
542        Terminal::Switch { test, cases, .. } => {
543            result.push(test.clone());
544            for case in cases {
545                if let Some(test) = &case.test {
546                    result.push(test.clone());
547                }
548            }
549        }
550        Terminal::Return { value, .. } | Terminal::Throw { value, .. } => {
551            result.push(value.clone());
552        }
553        Terminal::Try {
554            handler_binding, ..
555        } => {
556            if let Some(binding) = handler_binding {
557                result.push(binding.clone());
558            }
559        }
560        Terminal::MaybeThrow { .. }
561        | Terminal::Sequence { .. }
562        | Terminal::Label { .. }
563        | Terminal::Optional { .. }
564        | Terminal::Ternary { .. }
565        | Terminal::Logical { .. }
566        | Terminal::DoWhile { .. }
567        | Terminal::While { .. }
568        | Terminal::For { .. }
569        | Terminal::ForOf { .. }
570        | Terminal::ForIn { .. }
571        | Terminal::Goto { .. }
572        | Terminal::Unreachable { .. }
573        | Terminal::Unsupported { .. }
574        | Terminal::Scope { .. }
575        | Terminal::PrunedScope { .. } => {
576            // no-op
577        }
578    }
579    result
580}
581
582// =============================================================================
583// Mapping functions (mutate in place)
584// =============================================================================
585
586/// Maps the instruction's lvalue and value's lvalues.
587/// Equivalent to TS `mapInstructionLValues`.
588pub fn map_instruction_lvalues(instr: &mut Instruction, f: &mut impl FnMut(Place) -> Place) {
589    match &mut instr.value {
590        InstructionValue::DeclareLocal { lvalue, .. }
591        | InstructionValue::StoreLocal { lvalue, .. }
592        | InstructionValue::DeclareContext { lvalue, .. }
593        | InstructionValue::StoreContext { lvalue, .. } => {
594            lvalue.place = f(lvalue.place.clone());
595        }
596        InstructionValue::Destructure { lvalue, .. } => {
597            map_pattern_operands(&mut lvalue.pattern, f);
598        }
599        InstructionValue::PostfixUpdate { lvalue, .. }
600        | InstructionValue::PrefixUpdate { lvalue, .. } => {
601            *lvalue = f(lvalue.clone());
602        }
603        _ => {}
604    }
605    instr.lvalue = f(instr.lvalue.clone());
606}
607
608/// Maps operands of an instruction.
609/// Equivalent to TS `mapInstructionOperands`.
610pub fn map_instruction_operands(
611    instr: &mut Instruction,
612    env: &mut Environment,
613    f: &mut impl FnMut(Place) -> Place,
614) {
615    map_instruction_value_operands(&mut instr.value, env, f);
616}
617
618/// Maps operand places in an InstructionValue.
619/// Equivalent to TS `mapInstructionValueOperands`.
620pub fn map_instruction_value_operands(
621    value: &mut InstructionValue,
622    env: &mut Environment,
623    f: &mut impl FnMut(Place) -> Place,
624) {
625    match value {
626        InstructionValue::BinaryExpression { left, right, .. } => {
627            *left = f(left.clone());
628            *right = f(right.clone());
629        }
630        InstructionValue::PropertyLoad { object, .. } => {
631            *object = f(object.clone());
632        }
633        InstructionValue::PropertyDelete { object, .. } => {
634            *object = f(object.clone());
635        }
636        InstructionValue::PropertyStore {
637            object, value: val, ..
638        } => {
639            *object = f(object.clone());
640            *val = f(val.clone());
641        }
642        InstructionValue::ComputedLoad {
643            object, property, ..
644        } => {
645            *object = f(object.clone());
646            *property = f(property.clone());
647        }
648        InstructionValue::ComputedDelete {
649            object, property, ..
650        } => {
651            *object = f(object.clone());
652            *property = f(property.clone());
653        }
654        InstructionValue::ComputedStore {
655            object,
656            property,
657            value: val,
658            ..
659        } => {
660            *object = f(object.clone());
661            *property = f(property.clone());
662            *val = f(val.clone());
663        }
664        InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {
665            // no operands
666        }
667        InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
668            *place = f(place.clone());
669        }
670        InstructionValue::StoreLocal { value: val, .. } => {
671            *val = f(val.clone());
672        }
673        InstructionValue::StoreContext {
674            lvalue, value: val, ..
675        } => {
676            lvalue.place = f(lvalue.place.clone());
677            *val = f(val.clone());
678        }
679        InstructionValue::StoreGlobal { value: val, .. } => {
680            *val = f(val.clone());
681        }
682        InstructionValue::Destructure { value: val, .. } => {
683            *val = f(val.clone());
684        }
685        InstructionValue::NewExpression { callee, args, .. }
686        | InstructionValue::CallExpression { callee, args, .. } => {
687            *callee = f(callee.clone());
688            map_call_arguments(args, f);
689        }
690        InstructionValue::MethodCall {
691            receiver,
692            property,
693            args,
694            ..
695        } => {
696            *receiver = f(receiver.clone());
697            *property = f(property.clone());
698            map_call_arguments(args, f);
699        }
700        InstructionValue::UnaryExpression { value: val, .. } => {
701            *val = f(val.clone());
702        }
703        InstructionValue::JsxExpression {
704            tag,
705            props,
706            children,
707            ..
708        } => {
709            if let JsxTag::Place(place) = tag {
710                *place = f(place.clone());
711            }
712            for attribute in props.iter_mut() {
713                match attribute {
714                    JsxAttribute::Attribute { place, .. } => {
715                        *place = f(place.clone());
716                    }
717                    JsxAttribute::SpreadAttribute { argument, .. } => {
718                        *argument = f(argument.clone());
719                    }
720                }
721            }
722            if let Some(children) = children {
723                *children = children.iter().map(|p| f(p.clone())).collect();
724            }
725        }
726        InstructionValue::ObjectExpression { properties, .. } => {
727            for property in properties.iter_mut() {
728                match property {
729                    ObjectPropertyOrSpread::Property(prop) => {
730                        if let ObjectPropertyKey::Computed { name } = &mut prop.key {
731                            *name = f(name.clone());
732                        }
733                        prop.place = f(prop.place.clone());
734                    }
735                    ObjectPropertyOrSpread::Spread(spread) => {
736                        spread.place = f(spread.place.clone());
737                    }
738                }
739            }
740        }
741        InstructionValue::ArrayExpression { elements, .. } => {
742            *elements = elements
743                .iter()
744                .map(|element| match element {
745                    ArrayElement::Place(place) => ArrayElement::Place(f(place.clone())),
746                    ArrayElement::Spread(spread) => {
747                        let mut spread = spread.clone();
748                        spread.place = f(spread.place.clone());
749                        ArrayElement::Spread(spread)
750                    }
751                    ArrayElement::Hole => ArrayElement::Hole,
752                })
753                .collect();
754        }
755        InstructionValue::JsxFragment { children, .. } => {
756            *children = children.iter().map(|e| f(e.clone())).collect();
757        }
758        InstructionValue::ObjectMethod { lowered_func, .. }
759        | InstructionValue::FunctionExpression { lowered_func, .. } => {
760            let func = &mut env.functions[lowered_func.func.0 as usize];
761            func.context = func.context.iter().map(|d| f(d.clone())).collect();
762        }
763        InstructionValue::TaggedTemplateExpression { tag, .. } => {
764            *tag = f(tag.clone());
765        }
766        InstructionValue::TypeCastExpression { value: val, .. } => {
767            *val = f(val.clone());
768        }
769        InstructionValue::TemplateLiteral { subexprs, .. } => {
770            *subexprs = subexprs.iter().map(|s| f(s.clone())).collect();
771        }
772        InstructionValue::Await { value: val, .. } => {
773            *val = f(val.clone());
774        }
775        InstructionValue::GetIterator { collection, .. } => {
776            *collection = f(collection.clone());
777        }
778        InstructionValue::IteratorNext {
779            iterator,
780            collection,
781            ..
782        } => {
783            *iterator = f(iterator.clone());
784            *collection = f(collection.clone());
785        }
786        InstructionValue::NextPropertyOf { value: val, .. } => {
787            *val = f(val.clone());
788        }
789        InstructionValue::PostfixUpdate { value: val, .. }
790        | InstructionValue::PrefixUpdate { value: val, .. } => {
791            *val = f(val.clone());
792        }
793        InstructionValue::StartMemoize { deps, .. } => {
794            if let Some(deps) = deps {
795                for dep in deps.iter_mut() {
796                    if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &mut dep.root {
797                        *value = f(value.clone());
798                    }
799                }
800            }
801        }
802        InstructionValue::FinishMemoize { decl, .. } => {
803            *decl = f(decl.clone());
804        }
805        InstructionValue::Debugger { .. }
806        | InstructionValue::RegExpLiteral { .. }
807        | InstructionValue::MetaProperty { .. }
808        | InstructionValue::LoadGlobal { .. }
809        | InstructionValue::UnsupportedNode { .. }
810        | InstructionValue::Primitive { .. }
811        | InstructionValue::JSXText { .. } => {
812            // no operands
813        }
814    }
815}
816
817/// Maps call arguments in place.
818/// Equivalent to TS `mapCallArguments`.
819pub fn map_call_arguments(args: &mut Vec<PlaceOrSpread>, f: &mut impl FnMut(Place) -> Place) {
820    for arg in args.iter_mut() {
821        match arg {
822            PlaceOrSpread::Place(place) => {
823                *place = f(place.clone());
824            }
825            PlaceOrSpread::Spread(spread) => {
826                spread.place = f(spread.place.clone());
827            }
828        }
829    }
830}
831
832/// Maps pattern operands in place.
833/// Equivalent to TS `mapPatternOperands`.
834pub fn map_pattern_operands(pattern: &mut Pattern, f: &mut impl FnMut(Place) -> Place) {
835    match pattern {
836        Pattern::Array(arr) => {
837            arr.items = arr
838                .items
839                .iter()
840                .map(|item| match item {
841                    ArrayPatternElement::Place(place) => {
842                        ArrayPatternElement::Place(f(place.clone()))
843                    }
844                    ArrayPatternElement::Spread(spread) => {
845                        let mut spread = spread.clone();
846                        spread.place = f(spread.place.clone());
847                        ArrayPatternElement::Spread(spread)
848                    }
849                    ArrayPatternElement::Hole => ArrayPatternElement::Hole,
850                })
851                .collect();
852        }
853        Pattern::Object(obj) => {
854            for property in obj.properties.iter_mut() {
855                match property {
856                    ObjectPropertyOrSpread::Property(prop) => {
857                        prop.place = f(prop.place.clone());
858                    }
859                    ObjectPropertyOrSpread::Spread(spread) => {
860                        spread.place = f(spread.place.clone());
861                    }
862                }
863            }
864        }
865    }
866}
867
868/// Maps a terminal node's block assignments in place.
869/// Equivalent to TS `mapTerminalSuccessors` — but mutates in place instead of returning a new terminal.
870pub fn map_terminal_successors(terminal: &mut Terminal, f: &mut impl FnMut(BlockId) -> BlockId) {
871    match terminal {
872        Terminal::Goto { block, .. } => {
873            *block = f(*block);
874        }
875        Terminal::If {
876            consequent,
877            alternate,
878            fallthrough,
879            ..
880        } => {
881            *consequent = f(*consequent);
882            *alternate = f(*alternate);
883            *fallthrough = f(*fallthrough);
884        }
885        Terminal::Branch {
886            consequent,
887            alternate,
888            fallthrough,
889            ..
890        } => {
891            *consequent = f(*consequent);
892            *alternate = f(*alternate);
893            *fallthrough = f(*fallthrough);
894        }
895        Terminal::Switch {
896            cases, fallthrough, ..
897        } => {
898            for case in cases.iter_mut() {
899                case.block = f(case.block);
900            }
901            *fallthrough = f(*fallthrough);
902        }
903        Terminal::Logical {
904            test, fallthrough, ..
905        } => {
906            *test = f(*test);
907            *fallthrough = f(*fallthrough);
908        }
909        Terminal::Ternary {
910            test, fallthrough, ..
911        } => {
912            *test = f(*test);
913            *fallthrough = f(*fallthrough);
914        }
915        Terminal::Optional {
916            test, fallthrough, ..
917        } => {
918            *test = f(*test);
919            *fallthrough = f(*fallthrough);
920        }
921        Terminal::Return { .. } => {}
922        Terminal::Throw { .. } => {}
923        Terminal::DoWhile {
924            loop_block,
925            test,
926            fallthrough,
927            ..
928        } => {
929            *loop_block = f(*loop_block);
930            *test = f(*test);
931            *fallthrough = f(*fallthrough);
932        }
933        Terminal::While {
934            test,
935            loop_block,
936            fallthrough,
937            ..
938        } => {
939            *test = f(*test);
940            *loop_block = f(*loop_block);
941            *fallthrough = f(*fallthrough);
942        }
943        Terminal::For {
944            init,
945            test,
946            update,
947            loop_block,
948            fallthrough,
949            ..
950        } => {
951            *init = f(*init);
952            *test = f(*test);
953            if let Some(update) = update {
954                *update = f(*update);
955            }
956            *loop_block = f(*loop_block);
957            *fallthrough = f(*fallthrough);
958        }
959        Terminal::ForOf {
960            init,
961            test,
962            loop_block,
963            fallthrough,
964            ..
965        } => {
966            *init = f(*init);
967            *test = f(*test);
968            *loop_block = f(*loop_block);
969            *fallthrough = f(*fallthrough);
970        }
971        Terminal::ForIn {
972            init,
973            loop_block,
974            fallthrough,
975            ..
976        } => {
977            *init = f(*init);
978            *loop_block = f(*loop_block);
979            *fallthrough = f(*fallthrough);
980        }
981        Terminal::Label {
982            block, fallthrough, ..
983        } => {
984            *block = f(*block);
985            *fallthrough = f(*fallthrough);
986        }
987        Terminal::Sequence {
988            block, fallthrough, ..
989        } => {
990            *block = f(*block);
991            *fallthrough = f(*fallthrough);
992        }
993        Terminal::MaybeThrow {
994            continuation,
995            handler,
996            ..
997        } => {
998            *continuation = f(*continuation);
999            if let Some(handler) = handler {
1000                *handler = f(*handler);
1001            }
1002        }
1003        Terminal::Try {
1004            block,
1005            handler,
1006            fallthrough,
1007            ..
1008        } => {
1009            *block = f(*block);
1010            *handler = f(*handler);
1011            *fallthrough = f(*fallthrough);
1012        }
1013        Terminal::Scope {
1014            block, fallthrough, ..
1015        }
1016        | Terminal::PrunedScope {
1017            block, fallthrough, ..
1018        } => {
1019            *block = f(*block);
1020            *fallthrough = f(*fallthrough);
1021        }
1022        Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {}
1023    }
1024}
1025
1026/// Maps a terminal node's operand places in place.
1027/// Equivalent to TS `mapTerminalOperands`.
1028pub fn map_terminal_operands(terminal: &mut Terminal, f: &mut impl FnMut(Place) -> Place) {
1029    match terminal {
1030        Terminal::If { test, .. } => {
1031            *test = f(test.clone());
1032        }
1033        Terminal::Branch { test, .. } => {
1034            *test = f(test.clone());
1035        }
1036        Terminal::Switch { test, cases, .. } => {
1037            *test = f(test.clone());
1038            for case in cases.iter_mut() {
1039                if let Some(t) = &mut case.test {
1040                    *t = f(t.clone());
1041                }
1042            }
1043        }
1044        Terminal::Return { value, .. } | Terminal::Throw { value, .. } => {
1045            *value = f(value.clone());
1046        }
1047        Terminal::Try {
1048            handler_binding, ..
1049        } => {
1050            if let Some(binding) = handler_binding {
1051                *binding = f(binding.clone());
1052            }
1053        }
1054        Terminal::MaybeThrow { .. }
1055        | Terminal::Sequence { .. }
1056        | Terminal::Label { .. }
1057        | Terminal::Optional { .. }
1058        | Terminal::Ternary { .. }
1059        | Terminal::Logical { .. }
1060        | Terminal::DoWhile { .. }
1061        | Terminal::While { .. }
1062        | Terminal::For { .. }
1063        | Terminal::ForOf { .. }
1064        | Terminal::ForIn { .. }
1065        | Terminal::Goto { .. }
1066        | Terminal::Unreachable { .. }
1067        | Terminal::Unsupported { .. }
1068        | Terminal::Scope { .. }
1069        | Terminal::PrunedScope { .. } => {
1070            // no-op
1071        }
1072    }
1073}
1074
1075/// Yields ALL block IDs referenced by a terminal (successors + fallthroughs + internal blocks).
1076/// Unlike `each_terminal_successor` which yields only standard control flow successors,
1077/// this function yields every block ID that `map_terminal_successors` would visit.
1078pub fn each_terminal_all_successors(terminal: &Terminal) -> Vec<BlockId> {
1079    let mut result = Vec::new();
1080    match terminal {
1081        Terminal::Goto { block, .. } => {
1082            result.push(*block);
1083        }
1084        Terminal::If {
1085            consequent,
1086            alternate,
1087            fallthrough,
1088            ..
1089        } => {
1090            result.push(*consequent);
1091            result.push(*alternate);
1092            result.push(*fallthrough);
1093        }
1094        Terminal::Branch {
1095            consequent,
1096            alternate,
1097            fallthrough,
1098            ..
1099        } => {
1100            result.push(*consequent);
1101            result.push(*alternate);
1102            result.push(*fallthrough);
1103        }
1104        Terminal::Switch {
1105            cases, fallthrough, ..
1106        } => {
1107            for case in cases {
1108                result.push(case.block);
1109            }
1110            result.push(*fallthrough);
1111        }
1112        Terminal::Logical {
1113            test, fallthrough, ..
1114        }
1115        | Terminal::Ternary {
1116            test, fallthrough, ..
1117        }
1118        | Terminal::Optional {
1119            test, fallthrough, ..
1120        } => {
1121            result.push(*test);
1122            result.push(*fallthrough);
1123        }
1124        Terminal::Return { .. } | Terminal::Throw { .. } => {}
1125        Terminal::DoWhile {
1126            loop_block,
1127            test,
1128            fallthrough,
1129            ..
1130        } => {
1131            result.push(*loop_block);
1132            result.push(*test);
1133            result.push(*fallthrough);
1134        }
1135        Terminal::While {
1136            test,
1137            loop_block,
1138            fallthrough,
1139            ..
1140        } => {
1141            result.push(*test);
1142            result.push(*loop_block);
1143            result.push(*fallthrough);
1144        }
1145        Terminal::For {
1146            init,
1147            test,
1148            update,
1149            loop_block,
1150            fallthrough,
1151            ..
1152        } => {
1153            result.push(*init);
1154            result.push(*test);
1155            if let Some(update) = update {
1156                result.push(*update);
1157            }
1158            result.push(*loop_block);
1159            result.push(*fallthrough);
1160        }
1161        Terminal::ForOf {
1162            init,
1163            test,
1164            loop_block,
1165            fallthrough,
1166            ..
1167        } => {
1168            result.push(*init);
1169            result.push(*test);
1170            result.push(*loop_block);
1171            result.push(*fallthrough);
1172        }
1173        Terminal::ForIn {
1174            init,
1175            loop_block,
1176            fallthrough,
1177            ..
1178        } => {
1179            result.push(*init);
1180            result.push(*loop_block);
1181            result.push(*fallthrough);
1182        }
1183        Terminal::Label {
1184            block, fallthrough, ..
1185        }
1186        | Terminal::Sequence {
1187            block, fallthrough, ..
1188        } => {
1189            result.push(*block);
1190            result.push(*fallthrough);
1191        }
1192        Terminal::MaybeThrow {
1193            continuation,
1194            handler,
1195            ..
1196        } => {
1197            result.push(*continuation);
1198            if let Some(handler) = handler {
1199                result.push(*handler);
1200            }
1201        }
1202        Terminal::Try {
1203            block,
1204            handler,
1205            fallthrough,
1206            ..
1207        } => {
1208            result.push(*block);
1209            result.push(*handler);
1210            result.push(*fallthrough);
1211        }
1212        Terminal::Scope {
1213            block, fallthrough, ..
1214        }
1215        | Terminal::PrunedScope {
1216            block, fallthrough, ..
1217        } => {
1218            result.push(*block);
1219            result.push(*fallthrough);
1220        }
1221        Terminal::Unreachable { .. } | Terminal::Unsupported { .. } => {}
1222    }
1223    result
1224}
1225
1226// =============================================================================
1227// Terminal fallthrough functions
1228// =============================================================================
1229
1230/// Returns the fallthrough block ID for terminals that have one.
1231/// Equivalent to TS `terminalFallthrough`.
1232pub fn terminal_fallthrough(terminal: &Terminal) -> Option<BlockId> {
1233    match terminal {
1234        // These terminals do NOT have a fallthrough
1235        Terminal::MaybeThrow { .. }
1236        | Terminal::Goto { .. }
1237        | Terminal::Return { .. }
1238        | Terminal::Throw { .. }
1239        | Terminal::Unreachable { .. }
1240        | Terminal::Unsupported { .. } => None,
1241
1242        // These terminals DO have a fallthrough
1243        Terminal::Branch { fallthrough, .. }
1244        | Terminal::Try { fallthrough, .. }
1245        | Terminal::DoWhile { fallthrough, .. }
1246        | Terminal::ForOf { fallthrough, .. }
1247        | Terminal::ForIn { fallthrough, .. }
1248        | Terminal::For { fallthrough, .. }
1249        | Terminal::If { fallthrough, .. }
1250        | Terminal::Label { fallthrough, .. }
1251        | Terminal::Logical { fallthrough, .. }
1252        | Terminal::Optional { fallthrough, .. }
1253        | Terminal::Sequence { fallthrough, .. }
1254        | Terminal::Switch { fallthrough, .. }
1255        | Terminal::Ternary { fallthrough, .. }
1256        | Terminal::While { fallthrough, .. }
1257        | Terminal::Scope { fallthrough, .. }
1258        | Terminal::PrunedScope { fallthrough, .. } => Some(*fallthrough),
1259    }
1260}
1261
1262/// Returns true if the terminal has a fallthrough block.
1263/// Equivalent to TS `terminalHasFallthrough`.
1264pub fn terminal_has_fallthrough(terminal: &Terminal) -> bool {
1265    terminal_fallthrough(terminal).is_some()
1266}
1267
1268// =============================================================================
1269// ScopeBlockTraversal
1270// =============================================================================
1271
1272/// Block info entry for ScopeBlockTraversal.
1273#[derive(Debug, Clone)]
1274pub enum ScopeBlockInfo {
1275    Begin {
1276        scope: ScopeId,
1277        pruned: bool,
1278        fallthrough: BlockId,
1279    },
1280    End {
1281        scope: ScopeId,
1282        pruned: bool,
1283    },
1284}
1285
1286/// Helper struct for traversing scope blocks in HIR-form.
1287/// Equivalent to TS `ScopeBlockTraversal` class.
1288pub struct ScopeBlockTraversal {
1289    /// Live stack of active scopes
1290    active_scopes: Vec<ScopeId>,
1291    /// Map from block ID to scope block info
1292    pub block_infos: FxHashMap<BlockId, ScopeBlockInfo>,
1293}
1294
1295impl ScopeBlockTraversal {
1296    pub fn new() -> Self {
1297        ScopeBlockTraversal {
1298            active_scopes: Vec::new(),
1299            block_infos: FxHashMap::default(),
1300        }
1301    }
1302
1303    /// Record scope information for a block's terminal.
1304    /// Equivalent to TS `recordScopes`.
1305    pub fn record_scopes(&mut self, block: &BasicBlock) {
1306        if let Some(block_info) = self.block_infos.get(&block.id) {
1307            match block_info {
1308                ScopeBlockInfo::Begin { scope, .. } => {
1309                    self.active_scopes.push(*scope);
1310                }
1311                ScopeBlockInfo::End { scope, .. } => {
1312                    let top = self.active_scopes.last();
1313                    assert_eq!(
1314                        Some(scope),
1315                        top,
1316                        "Expected traversed block fallthrough to match top-most active scope"
1317                    );
1318                    self.active_scopes.pop();
1319                }
1320            }
1321        }
1322
1323        match &block.terminal {
1324            Terminal::Scope {
1325                block: scope_block,
1326                fallthrough,
1327                scope,
1328                ..
1329            } => {
1330                assert!(
1331                    !self.block_infos.contains_key(scope_block)
1332                        && !self.block_infos.contains_key(fallthrough),
1333                    "Expected unique scope blocks and fallthroughs"
1334                );
1335                self.block_infos.insert(
1336                    *scope_block,
1337                    ScopeBlockInfo::Begin {
1338                        scope: *scope,
1339                        pruned: false,
1340                        fallthrough: *fallthrough,
1341                    },
1342                );
1343                self.block_infos.insert(
1344                    *fallthrough,
1345                    ScopeBlockInfo::End {
1346                        scope: *scope,
1347                        pruned: false,
1348                    },
1349                );
1350            }
1351            Terminal::PrunedScope {
1352                block: scope_block,
1353                fallthrough,
1354                scope,
1355                ..
1356            } => {
1357                assert!(
1358                    !self.block_infos.contains_key(scope_block)
1359                        && !self.block_infos.contains_key(fallthrough),
1360                    "Expected unique scope blocks and fallthroughs"
1361                );
1362                self.block_infos.insert(
1363                    *scope_block,
1364                    ScopeBlockInfo::Begin {
1365                        scope: *scope,
1366                        pruned: true,
1367                        fallthrough: *fallthrough,
1368                    },
1369                );
1370                self.block_infos.insert(
1371                    *fallthrough,
1372                    ScopeBlockInfo::End {
1373                        scope: *scope,
1374                        pruned: true,
1375                    },
1376                );
1377            }
1378            _ => {}
1379        }
1380    }
1381
1382    /// Returns true if the given scope is currently 'active', i.e. if the scope start
1383    /// block but not the scope fallthrough has been recorded.
1384    pub fn is_scope_active(&self, scope_id: ScopeId) -> bool {
1385        self.active_scopes.contains(&scope_id)
1386    }
1387
1388    /// The current, innermost active scope.
1389    pub fn current_scope(&self) -> Option<ScopeId> {
1390        self.active_scopes.last().copied()
1391    }
1392}
1393
1394impl Default for ScopeBlockTraversal {
1395    fn default() -> Self {
1396        Self::new()
1397    }
1398}
1399
1400// =============================================================================
1401// Convenience wrappers: extract IdentifierIds from Places
1402// =============================================================================
1403
1404/// Collect all lvalue IdentifierIds from an instruction.
1405/// Convenience wrapper around `each_instruction_lvalue` that maps to ids.
1406pub fn each_instruction_lvalue_ids(instr: &Instruction) -> Vec<IdentifierId> {
1407    each_instruction_lvalue(instr)
1408        .into_iter()
1409        .map(|p| p.identifier)
1410        .collect()
1411}
1412
1413/// Collect all operand IdentifierIds from an instruction.
1414/// Convenience wrapper around `each_instruction_operand` that maps to ids.
1415pub fn each_instruction_operand_ids(instr: &Instruction, env: &Environment) -> Vec<IdentifierId> {
1416    each_instruction_operand(instr, env)
1417        .into_iter()
1418        .map(|p| p.identifier)
1419        .collect()
1420}
1421
1422/// Collect all operand IdentifierIds from an instruction value.
1423/// Convenience wrapper around `each_instruction_value_operand` that maps to ids.
1424pub fn each_instruction_value_operand_ids(
1425    value: &InstructionValue,
1426    env: &Environment,
1427) -> Vec<IdentifierId> {
1428    each_instruction_value_operand(value, env)
1429        .into_iter()
1430        .map(|p| p.identifier)
1431        .collect()
1432}
1433
1434/// Collect all operand IdentifierIds from a terminal.
1435/// Convenience wrapper around `each_terminal_operand` that maps to ids.
1436pub fn each_terminal_operand_ids(terminal: &Terminal) -> Vec<IdentifierId> {
1437    each_terminal_operand(terminal)
1438        .into_iter()
1439        .map(|p| p.identifier)
1440        .collect()
1441}
1442
1443/// Collect all IdentifierIds from a pattern.
1444/// Convenience wrapper around `each_pattern_operand` that maps to ids.
1445pub fn each_pattern_operand_ids(pattern: &Pattern) -> Vec<IdentifierId> {
1446    each_pattern_operand(pattern)
1447        .into_iter()
1448        .map(|p| p.identifier)
1449        .collect()
1450}
1451
1452// =============================================================================
1453// In-place mutation variants (f(&mut Place) callbacks)
1454// =============================================================================
1455//
1456// These variants use `f(&mut Place)` instead of `f(Place) -> Place`, which is
1457// more natural for Rust in-place mutation patterns. They do NOT handle
1458// FunctionExpression/ObjectMethod context (since that requires env access).
1459// Callers that need to process inner function context should handle it
1460// separately, e.g.:
1461//
1462//   for_each_instruction_value_operand_mut(&mut instr.value, &mut |place| { ... });
1463//   if let InstructionValue::FunctionExpression { lowered_func, .. }
1464//       | InstructionValue::ObjectMethod { lowered_func, .. } = &mut instr.value {
1465//       let func = &mut env.functions[lowered_func.func.0 as usize];
1466//       for ctx in func.context.iter_mut() { ... }
1467//   }
1468//
1469
1470/// In-place mutation of all operand places in an InstructionValue.
1471/// Does NOT handle FunctionExpression/ObjectMethod context — callers handle those separately.
1472pub fn for_each_instruction_value_operand_mut(
1473    value: &mut InstructionValue,
1474    f: &mut impl FnMut(&mut Place),
1475) {
1476    match value {
1477        InstructionValue::BinaryExpression { left, right, .. } => {
1478            f(left);
1479            f(right);
1480        }
1481        InstructionValue::PropertyLoad { object, .. }
1482        | InstructionValue::PropertyDelete { object, .. } => {
1483            f(object);
1484        }
1485        InstructionValue::PropertyStore {
1486            object, value: val, ..
1487        } => {
1488            f(object);
1489            f(val);
1490        }
1491        InstructionValue::ComputedLoad {
1492            object, property, ..
1493        }
1494        | InstructionValue::ComputedDelete {
1495            object, property, ..
1496        } => {
1497            f(object);
1498            f(property);
1499        }
1500        InstructionValue::ComputedStore {
1501            object,
1502            property,
1503            value: val,
1504            ..
1505        } => {
1506            f(object);
1507            f(property);
1508            f(val);
1509        }
1510        InstructionValue::DeclareContext { .. } | InstructionValue::DeclareLocal { .. } => {}
1511        InstructionValue::LoadLocal { place, .. } | InstructionValue::LoadContext { place, .. } => {
1512            f(place);
1513        }
1514        InstructionValue::StoreLocal { value: val, .. } => {
1515            f(val);
1516        }
1517        InstructionValue::StoreContext {
1518            lvalue, value: val, ..
1519        } => {
1520            f(&mut lvalue.place);
1521            f(val);
1522        }
1523        InstructionValue::StoreGlobal { value: val, .. } => {
1524            f(val);
1525        }
1526        InstructionValue::Destructure { value: val, .. } => {
1527            f(val);
1528        }
1529        InstructionValue::NewExpression { callee, args, .. }
1530        | InstructionValue::CallExpression { callee, args, .. } => {
1531            f(callee);
1532            for_each_call_argument_mut(args, f);
1533        }
1534        InstructionValue::MethodCall {
1535            receiver,
1536            property,
1537            args,
1538            ..
1539        } => {
1540            f(receiver);
1541            f(property);
1542            for_each_call_argument_mut(args, f);
1543        }
1544        InstructionValue::UnaryExpression { value: val, .. } => {
1545            f(val);
1546        }
1547        InstructionValue::JsxExpression {
1548            tag,
1549            props,
1550            children,
1551            ..
1552        } => {
1553            if let JsxTag::Place(place) = tag {
1554                f(place);
1555            }
1556            for attribute in props.iter_mut() {
1557                match attribute {
1558                    JsxAttribute::Attribute { place, .. } => f(place),
1559                    JsxAttribute::SpreadAttribute { argument, .. } => f(argument),
1560                }
1561            }
1562            if let Some(children) = children {
1563                for child in children.iter_mut() {
1564                    f(child);
1565                }
1566            }
1567        }
1568        InstructionValue::ObjectExpression { properties, .. } => {
1569            for property in properties.iter_mut() {
1570                match property {
1571                    ObjectPropertyOrSpread::Property(prop) => {
1572                        if let ObjectPropertyKey::Computed { name } = &mut prop.key {
1573                            f(name);
1574                        }
1575                        f(&mut prop.place);
1576                    }
1577                    ObjectPropertyOrSpread::Spread(spread) => {
1578                        f(&mut spread.place);
1579                    }
1580                }
1581            }
1582        }
1583        InstructionValue::ArrayExpression { elements, .. } => {
1584            for elem in elements.iter_mut() {
1585                match elem {
1586                    ArrayElement::Place(p) => f(p),
1587                    ArrayElement::Spread(s) => f(&mut s.place),
1588                    ArrayElement::Hole => {}
1589                }
1590            }
1591        }
1592        InstructionValue::JsxFragment { children, .. } => {
1593            for child in children.iter_mut() {
1594                f(child);
1595            }
1596        }
1597        InstructionValue::FunctionExpression { .. } | InstructionValue::ObjectMethod { .. } => {
1598            // Context places require env access — callers handle separately.
1599        }
1600        InstructionValue::TaggedTemplateExpression { tag, .. } => {
1601            f(tag);
1602        }
1603        InstructionValue::TypeCastExpression { value: val, .. } => {
1604            f(val);
1605        }
1606        InstructionValue::TemplateLiteral { subexprs, .. } => {
1607            for expr in subexprs.iter_mut() {
1608                f(expr);
1609            }
1610        }
1611        InstructionValue::Await { value: val, .. } => {
1612            f(val);
1613        }
1614        InstructionValue::GetIterator { collection, .. } => {
1615            f(collection);
1616        }
1617        InstructionValue::IteratorNext {
1618            iterator,
1619            collection,
1620            ..
1621        } => {
1622            f(iterator);
1623            f(collection);
1624        }
1625        InstructionValue::NextPropertyOf { value: val, .. } => {
1626            f(val);
1627        }
1628        InstructionValue::PostfixUpdate { value: val, .. }
1629        | InstructionValue::PrefixUpdate { value: val, .. } => {
1630            f(val);
1631        }
1632        InstructionValue::StartMemoize { deps, .. } => {
1633            if let Some(deps) = deps {
1634                for dep in deps.iter_mut() {
1635                    if let ManualMemoDependencyRoot::NamedLocal { value, .. } = &mut dep.root {
1636                        f(value);
1637                    }
1638                }
1639            }
1640        }
1641        InstructionValue::FinishMemoize { decl, .. } => {
1642            f(decl);
1643        }
1644        InstructionValue::Debugger { .. }
1645        | InstructionValue::RegExpLiteral { .. }
1646        | InstructionValue::MetaProperty { .. }
1647        | InstructionValue::LoadGlobal { .. }
1648        | InstructionValue::UnsupportedNode { .. }
1649        | InstructionValue::Primitive { .. }
1650        | InstructionValue::JSXText { .. } => {}
1651    }
1652}
1653
1654/// In-place mutation of call arguments.
1655pub fn for_each_call_argument_mut(args: &mut [PlaceOrSpread], f: &mut impl FnMut(&mut Place)) {
1656    for arg in args.iter_mut() {
1657        match arg {
1658            PlaceOrSpread::Place(place) => f(place),
1659            PlaceOrSpread::Spread(spread) => f(&mut spread.place),
1660        }
1661    }
1662}
1663
1664/// In-place mutation of an InstructionValue's lvalues (DeclareLocal, StoreLocal, DeclareContext,
1665/// StoreContext, Destructure, PostfixUpdate, PrefixUpdate). Does NOT include the instruction's
1666/// top-level lvalue — use `for_each_instruction_lvalue_mut` for that.
1667pub fn for_each_instruction_value_lvalue_mut(
1668    value: &mut InstructionValue,
1669    f: &mut impl FnMut(&mut Place),
1670) {
1671    match value {
1672        InstructionValue::DeclareContext { lvalue, .. }
1673        | InstructionValue::StoreContext { lvalue, .. }
1674        | InstructionValue::DeclareLocal { lvalue, .. }
1675        | InstructionValue::StoreLocal { lvalue, .. } => {
1676            f(&mut lvalue.place);
1677        }
1678        InstructionValue::Destructure { lvalue, .. } => {
1679            for_each_pattern_operand_mut(&mut lvalue.pattern, f);
1680        }
1681        InstructionValue::PostfixUpdate { lvalue, .. }
1682        | InstructionValue::PrefixUpdate { lvalue, .. } => {
1683            f(lvalue);
1684        }
1685        _ => {}
1686    }
1687}
1688
1689/// In-place mutation of the instruction's lvalue and value's lvalues.
1690/// Matches the same variants as TS `mapInstructionLValues` (skips DeclareContext/StoreContext).
1691pub fn for_each_instruction_lvalue_mut(instr: &mut Instruction, f: &mut impl FnMut(&mut Place)) {
1692    match &mut instr.value {
1693        InstructionValue::DeclareLocal { lvalue, .. }
1694        | InstructionValue::StoreLocal { lvalue, .. } => {
1695            f(&mut lvalue.place);
1696        }
1697        InstructionValue::Destructure { lvalue, .. } => {
1698            for_each_pattern_operand_mut(&mut lvalue.pattern, f);
1699        }
1700        InstructionValue::PostfixUpdate { lvalue, .. }
1701        | InstructionValue::PrefixUpdate { lvalue, .. } => {
1702            f(lvalue);
1703        }
1704        _ => {}
1705    }
1706    f(&mut instr.lvalue);
1707}
1708
1709/// In-place mutation of pattern operands.
1710pub fn for_each_pattern_operand_mut(pattern: &mut Pattern, f: &mut impl FnMut(&mut Place)) {
1711    match pattern {
1712        Pattern::Array(arr) => {
1713            for item in arr.items.iter_mut() {
1714                match item {
1715                    ArrayPatternElement::Place(p) => f(p),
1716                    ArrayPatternElement::Spread(s) => f(&mut s.place),
1717                    ArrayPatternElement::Hole => {}
1718                }
1719            }
1720        }
1721        Pattern::Object(obj) => {
1722            for property in obj.properties.iter_mut() {
1723                match property {
1724                    ObjectPropertyOrSpread::Property(prop) => f(&mut prop.place),
1725                    ObjectPropertyOrSpread::Spread(spread) => f(&mut spread.place),
1726                }
1727            }
1728        }
1729    }
1730}
1731
1732/// In-place mutation of terminal operand places.
1733pub fn for_each_terminal_operand_mut(terminal: &mut Terminal, f: &mut impl FnMut(&mut Place)) {
1734    match terminal {
1735        Terminal::If { test, .. } | Terminal::Branch { test, .. } => {
1736            f(test);
1737        }
1738        Terminal::Switch { test, cases, .. } => {
1739            f(test);
1740            for case in cases.iter_mut() {
1741                if let Some(t) = &mut case.test {
1742                    f(t);
1743                }
1744            }
1745        }
1746        Terminal::Return { value, .. } | Terminal::Throw { value, .. } => {
1747            f(value);
1748        }
1749        Terminal::Try {
1750            handler_binding, ..
1751        } => {
1752            if let Some(binding) = handler_binding {
1753                f(binding);
1754            }
1755        }
1756        Terminal::MaybeThrow { .. }
1757        | Terminal::Sequence { .. }
1758        | Terminal::Label { .. }
1759        | Terminal::Optional { .. }
1760        | Terminal::Ternary { .. }
1761        | Terminal::Logical { .. }
1762        | Terminal::DoWhile { .. }
1763        | Terminal::While { .. }
1764        | Terminal::For { .. }
1765        | Terminal::ForOf { .. }
1766        | Terminal::ForIn { .. }
1767        | Terminal::Goto { .. }
1768        | Terminal::Unreachable { .. }
1769        | Terminal::Unsupported { .. }
1770        | Terminal::Scope { .. }
1771        | Terminal::PrunedScope { .. } => {}
1772    }
1773}