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