main
rs 1,442 lines 53 KB
Raw
1 use indexmap::{IndexMap, IndexSet};
2 use react_compiler_ast::scope::BindingId;
3 use react_compiler_ast::scope::ImportBindingKind;
4 use react_compiler_ast::scope::ScopeId;
5 use react_compiler_ast::scope::ScopeInfo;
6 use react_compiler_diagnostics::CompilerDiagnostic;
7 use react_compiler_diagnostics::CompilerDiagnosticDetail;
8 use react_compiler_diagnostics::CompilerError;
9 use react_compiler_diagnostics::CompilerErrorDetail;
10 use react_compiler_diagnostics::ErrorCategory;
11 use react_compiler_hir::environment::Environment;
12 use react_compiler_hir::visitors::each_terminal_successor;
13 use react_compiler_hir::visitors::terminal_fallthrough;
14 use react_compiler_hir::*;
15 use rustc_hash::FxBuildHasher;
16
17 use crate::identifier_loc_index::IdentifierLocIndex;
18
19 // ---------------------------------------------------------------------------
20 // Reserved word check (matches TS isReservedWord)
21 // ---------------------------------------------------------------------------
22
23 pub(crate) fn is_always_reserved_word(s: &str) -> bool {
24 matches!(
25 s,
26 "break"
27 | "case"
28 | "catch"
29 | "continue"
30 | "debugger"
31 | "default"
32 | "do"
33 | "else"
34 | "finally"
35 | "for"
36 | "function"
37 | "if"
38 | "in"
39 | "instanceof"
40 | "new"
41 | "return"
42 | "switch"
43 | "this"
44 | "throw"
45 | "try"
46 | "typeof"
47 | "var"
48 | "void"
49 | "while"
50 | "with"
51 | "class"
52 | "const"
53 | "enum"
54 | "export"
55 | "extends"
56 | "import"
57 | "super"
58 | "null"
59 | "true"
60 | "false"
61 | "delete"
62 )
63 }
64
65 pub(crate) fn reserved_identifier_diagnostic(name: &str) -> CompilerDiagnostic {
66 CompilerDiagnostic::new(
67 ErrorCategory::Syntax,
68 "Expected a non-reserved identifier name",
69 Some(format!(
70 "`{}` is a reserved word in JavaScript and cannot be used as an identifier name",
71 name
72 )),
73 )
74 .with_detail(CompilerDiagnosticDetail::Error {
75 loc: None, // GeneratedSource in TS
76 message: Some("reserved word".to_string()),
77 identifier_name: None,
78 })
79 }
80
81 // ---------------------------------------------------------------------------
82 // Scope types for tracking break/continue targets
83 // ---------------------------------------------------------------------------
84
85 enum Scope {
86 Loop {
87 label: Option<String>,
88 continue_block: BlockId,
89 break_block: BlockId,
90 },
91 Label {
92 label: String,
93 break_block: BlockId,
94 },
95 Switch {
96 label: Option<String>,
97 break_block: BlockId,
98 },
99 }
100
101 impl Scope {
102 fn label(&self) -> Option<&str> {
103 match self {
104 Scope::Loop { label, .. } => label.as_deref(),
105 Scope::Label { label, .. } => Some(label.as_str()),
106 Scope::Switch { label, .. } => label.as_deref(),
107 }
108 }
109
110 fn break_block(&self) -> BlockId {
111 match self {
112 Scope::Loop { break_block, .. } => *break_block,
113 Scope::Label { break_block, .. } => *break_block,
114 Scope::Switch { break_block, .. } => *break_block,
115 }
116 }
117 }
118
119 // ---------------------------------------------------------------------------
120 // WipBlock: a block under construction that does not yet have a terminal
121 // ---------------------------------------------------------------------------
122
123 pub struct WipBlock {
124 pub id: BlockId,
125 pub instructions: Vec<InstructionId>,
126 pub kind: BlockKind,
127 }
128
129 fn new_block(id: BlockId, kind: BlockKind) -> WipBlock {
130 WipBlock {
131 id,
132 kind,
133 instructions: Vec::new(),
134 }
135 }
136
137 // ---------------------------------------------------------------------------
138 // HirBuilder: helper struct for constructing a CFG
139 // ---------------------------------------------------------------------------
140
141 pub struct HirBuilder<'a> {
142 completed: IndexMap<BlockId, BasicBlock, FxBuildHasher>,
143 current: WipBlock,
144 entry: BlockId,
145 scopes: Vec<Scope>,
146 /// Context identifiers: variables captured from an outer scope.
147 /// Maps the outer scope's BindingId to the source location where it was referenced.
148 context: IndexMap<BindingId, Option<SourceLocation>, FxBuildHasher>,
149 /// Resolved bindings: maps a BindingId to the HIR IdentifierId created for it.
150 bindings: IndexMap<BindingId, IdentifierId, FxBuildHasher>,
151 /// Names already used by bindings, for collision avoidance.
152 /// Maps name string -> how many times it has been used (for appending _0, _1, ...).
153 used_names: IndexMap<String, BindingId, FxBuildHasher>,
154 env: &'a mut Environment,
155 scope_info: &'a ScopeInfo,
156 exception_handler_stack: Vec<BlockId>,
157 /// Flat instruction table being built up.
158 instruction_table: Vec<Instruction>,
159 /// Traversal context: counts the number of `fbt` tag parents
160 /// of the current babel node.
161 pub fbt_depth: u32,
162 /// The scope of the function being compiled (for context identifier checks).
163 function_scope: ScopeId,
164 /// The scope of the outermost component/hook function (for gather_captured_context).
165 component_scope: ScopeId,
166 /// Set of BindingIds for variables declared in scopes between component_scope
167 /// and any inner function scope, that are referenced from an inner function scope.
168 /// These need StoreContext/LoadContext instead of StoreLocal/LoadLocal.
169 context_identifiers: rustc_hash::FxHashSet<BindingId>,
170 /// Set of ScopeIds that have been matched to synthetic blocks/functions.
171 /// Prevents the same scope from being reused for different synthetic nodes.
172 claimed_synthetic_scopes: rustc_hash::FxHashSet<ScopeId>,
173 /// Index mapping identifier byte offsets to source locations and JSX status.
174 identifier_locs: &'a IdentifierLocIndex,
175 }
176
177 impl<'a> HirBuilder<'a> {
178 // -----------------------------------------------------------------------
179 // M2: Core methods
180 // -----------------------------------------------------------------------
181
182 /// Create a new HirBuilder.
183 ///
184 /// - `env`: the shared environment (counters, arenas, error accumulator)
185 /// - `scope_info`: the scope information from the AST
186 /// - `function_scope`: the ScopeId of the function being compiled
187 /// - `bindings`: optional pre-existing bindings (e.g., from a parent function)
188 /// - `context`: optional pre-existing captured context map
189 /// - `entry_block_kind`: the kind of the entry block (defaults to `Block`)
190 pub fn new(
191 env: &'a mut Environment,
192 scope_info: &'a ScopeInfo,
193 function_scope: ScopeId,
194 component_scope: ScopeId,
195 context_identifiers: rustc_hash::FxHashSet<BindingId>,
196 bindings: Option<IndexMap<BindingId, IdentifierId, FxBuildHasher>>,
197 context: Option<IndexMap<BindingId, Option<SourceLocation>, FxBuildHasher>>,
198 entry_block_kind: Option<BlockKind>,
199 used_names: Option<IndexMap<String, BindingId, FxBuildHasher>>,
200 identifier_locs: &'a IdentifierLocIndex,
201 ) -> Self {
202 let entry = env.next_block_id();
203 let kind = entry_block_kind.unwrap_or(BlockKind::Block);
204 HirBuilder {
205 completed: IndexMap::default(),
206 current: new_block(entry, kind),
207 entry,
208 scopes: Vec::new(),
209 context: context.unwrap_or_default(),
210 bindings: bindings.unwrap_or_default(),
211 used_names: used_names.unwrap_or_default(),
212 env,
213 scope_info,
214 exception_handler_stack: Vec::new(),
215 instruction_table: Vec::new(),
216 fbt_depth: 0,
217 function_scope,
218 component_scope,
219 context_identifiers,
220 claimed_synthetic_scopes: rustc_hash::FxHashSet::default(),
221 identifier_locs,
222 }
223 }
224
225 /// Check if a scope is the component scope or a descendant of it.
226 /// Used to determine whether a binding is local to the compiled function
227 /// or belongs to an ancestor function scope (e.g., a factory function
228 /// wrapping a nested component declaration).
229 /// Uses component_scope (the outermost compiled function's scope) rather
230 /// than function_scope because inner function expressions within the
231 /// compiled function have their own function_scope but still consider
232 /// the outer component's variables as local.
233 fn is_scope_within_compiled_function(&self, scope_id: ScopeId) -> bool {
234 let mut current = Some(scope_id);
235 while let Some(id) = current {
236 if id == self.component_scope {
237 return true;
238 }
239 current = self.scope_info.scopes[id.0 as usize].parent;
240 }
241 false
242 }
243
244 /// Access the environment.
245 pub fn environment(&self) -> &Environment {
246 self.env
247 }
248
249 /// Access the environment mutably.
250 pub fn environment_mut(&mut self) -> &mut Environment {
251 self.env
252 }
253
254 /// Create a new unique TypeVar type, allocated from the environment's type arena
255 /// so that TypeIds are consistent with identifier type slots.
256 pub fn make_type(&mut self) -> Type {
257 let type_id = self.env.make_type();
258 Type::TypeVar { id: type_id }
259 }
260
261 /// Access the scope info.
262 pub fn scope_info(&self) -> &ScopeInfo {
263 self.scope_info
264 }
265
266 /// Look up the source location of an identifier by its node_id.
267 pub fn get_identifier_loc(&self, node_id: u32) -> Option<SourceLocation> {
268 self.identifier_locs
269 .get(&node_id)
270 .map(|entry| entry.loc.clone())
271 }
272
273 /// Check whether a reference at the given byte offset corresponds to a
274 /// JSXIdentifier. Scans the node_id-keyed index for an entry whose stored
275 /// `start` matches the offset.
276 pub fn is_jsx_identifier_at_pos(&self, offset: u32) -> bool {
277 self.identifier_locs
278 .values()
279 .any(|entry| entry.start == offset && entry.is_jsx)
280 }
281
282 /// Access the function scope (the scope of the function being compiled).
283 pub fn function_scope(&self) -> ScopeId {
284 self.function_scope
285 }
286
287 /// Access the component scope.
288 pub fn component_scope(&self) -> ScopeId {
289 self.component_scope
290 }
291
292 /// Access the context map.
293 pub fn context(&self) -> &IndexMap<BindingId, Option<SourceLocation>, FxBuildHasher> {
294 &self.context
295 }
296
297 /// Access the pre-computed context identifiers set.
298 pub fn context_identifiers(&self) -> &rustc_hash::FxHashSet<BindingId> {
299 &self.context_identifiers
300 }
301
302 /// Add a binding to the context identifiers set (used by hoisting).
303 pub fn add_context_identifier(&mut self, binding_id: BindingId) {
304 self.context_identifiers.insert(binding_id);
305 }
306
307 pub fn claim_synthetic_scope(&mut self, scope_id: ScopeId) {
308 self.claimed_synthetic_scopes.insert(scope_id);
309 }
310
311 pub fn is_synthetic_scope_claimed(&self, scope_id: ScopeId) -> bool {
312 self.claimed_synthetic_scopes.contains(&scope_id)
313 }
314
315 /// Access scope_info and environment mutably at the same time.
316 /// This is safe because they are disjoint fields, but Rust's borrow checker
317 /// can't prove this through method calls alone.
318 pub fn scope_info_and_env_mut(&mut self) -> (&ScopeInfo, &mut Environment) {
319 (self.scope_info, self.env)
320 }
321
322 /// Access the identifier location index.
323 /// Returns the 'a reference to avoid conflicts with mutable borrows on self.
324 pub fn identifier_locs(&self) -> &'a IdentifierLocIndex {
325 self.identifier_locs
326 }
327
328 /// Access the bindings map.
329 pub fn bindings(&self) -> &IndexMap<BindingId, IdentifierId, FxBuildHasher> {
330 &self.bindings
331 }
332
333 /// Access the used names map.
334 pub fn used_names(&self) -> &IndexMap<String, BindingId, FxBuildHasher> {
335 &self.used_names
336 }
337
338 /// Merge used names from a child builder back into this builder.
339 /// This ensures name deduplication works across function scopes.
340 pub fn merge_used_names(
341 &mut self,
342 child_used_names: IndexMap<String, BindingId, FxBuildHasher>,
343 ) {
344 for (name, binding_id) in child_used_names {
345 self.used_names.entry(name).or_insert(binding_id);
346 }
347 }
348
349 /// Merge bindings (binding_id -> IdentifierId) from a child builder back into this builder.
350 /// This matches TS behavior where parent and child share the same #bindings map by reference,
351 /// so bindings resolved by the child are automatically visible to the parent.
352 pub fn merge_bindings(
353 &mut self,
354 child_bindings: IndexMap<BindingId, IdentifierId, FxBuildHasher>,
355 ) {
356 for (binding_id, identifier_id) in child_bindings {
357 self.bindings.entry(binding_id).or_insert(identifier_id);
358 }
359 }
360
361 /// Push an instruction onto the current block.
362 ///
363 /// Adds the instruction to the flat instruction table and records
364 /// its InstructionId in the current block's instruction list.
365 ///
366 /// If an exception handler is active, also emits a MaybeThrow terminal
367 /// after the instruction to model potential control flow to the handler,
368 /// then continues in a new block.
369 pub fn push(&mut self, instruction: Instruction) {
370 let loc = instruction.loc.clone();
371 let instr_id = InstructionId(self.instruction_table.len() as u32);
372 self.instruction_table.push(instruction);
373 self.current.instructions.push(instr_id);
374
375 if let Some(&handler) = self.exception_handler_stack.last() {
376 let continuation = self.reserve(self.current_block_kind());
377 self.terminate_with_continuation(
378 Terminal::MaybeThrow {
379 continuation: continuation.id,
380 handler: Some(handler),
381 id: EvaluationOrder(0),
382 loc,
383 effects: None,
384 },
385 continuation,
386 );
387 }
388 }
389
390 /// Terminate the current block with the given terminal and start a new block.
391 ///
392 /// If `next_block_kind` is `Some`, a new current block is created with that kind.
393 /// Returns the BlockId of the completed block.
394 pub fn terminate(&mut self, terminal: Terminal, next_block_kind: Option<BlockKind>) -> BlockId {
395 // The placeholder block created here (BlockId(u32::MAX)) is only used when
396 // next_block_kind is None, meaning this is the final terminate() call.
397 // It will never be read or completed because build() consumes self
398 // immediately after, and no further operations should occur on the builder.
399 let wip = std::mem::replace(
400 &mut self.current,
401 new_block(BlockId(u32::MAX), BlockKind::Block),
402 );
403 let block_id = wip.id;
404
405 self.completed.insert(
406 block_id,
407 BasicBlock {
408 kind: wip.kind,
409 id: block_id,
410 instructions: wip.instructions,
411 terminal,
412 preds: IndexSet::default(),
413 phis: Vec::new(),
414 },
415 );
416
417 if let Some(kind) = next_block_kind {
418 let next_id = self.env.next_block_id();
419 self.current = new_block(next_id, kind);
420 }
421 block_id
422 }
423
424 /// Terminate the current block with the given terminal, and set
425 /// a previously reserved block as the new current block.
426 pub fn terminate_with_continuation(&mut self, terminal: Terminal, continuation: WipBlock) {
427 let wip = std::mem::replace(&mut self.current, continuation);
428 let block_id = wip.id;
429 self.completed.insert(
430 block_id,
431 BasicBlock {
432 kind: wip.kind,
433 id: block_id,
434 instructions: wip.instructions,
435 terminal,
436 preds: IndexSet::default(),
437 phis: Vec::new(),
438 },
439 );
440 }
441
442 /// Reserve a new block so it can be referenced before construction.
443 /// Use `terminate_with_continuation()` to make it current, or `complete()` to
444 /// save it directly.
445 pub fn reserve(&mut self, kind: BlockKind) -> WipBlock {
446 let id = self.env.next_block_id();
447 new_block(id, kind)
448 }
449
450 /// Save a previously reserved block as completed with the given terminal.
451 pub fn complete(&mut self, block: WipBlock, terminal: Terminal) {
452 let block_id = block.id;
453 self.completed.insert(
454 block_id,
455 BasicBlock {
456 kind: block.kind,
457 id: block_id,
458 instructions: block.instructions,
459 terminal,
460 preds: IndexSet::default(),
461 phis: Vec::new(),
462 },
463 );
464 }
465
466 /// Sets the given wip block as current, executes the closure to populate
467 /// it and obtain its terminal, then completes the block and restores the
468 /// previous current block.
469 pub fn enter_reserved(&mut self, wip: WipBlock, f: impl FnOnce(&mut Self) -> Terminal) {
470 let prev = std::mem::replace(&mut self.current, wip);
471 let terminal = f(self);
472 let completed_wip = std::mem::replace(&mut self.current, prev);
473 self.completed.insert(
474 completed_wip.id,
475 BasicBlock {
476 kind: completed_wip.kind,
477 id: completed_wip.id,
478 instructions: completed_wip.instructions,
479 terminal,
480 preds: IndexSet::default(),
481 phis: Vec::new(),
482 },
483 );
484 }
485
486 /// Like `enter_reserved`, but the closure returns a `Result<Terminal, CompilerDiagnostic>`.
487 pub fn try_enter_reserved(
488 &mut self,
489 wip: WipBlock,
490 f: impl FnOnce(&mut Self) -> Result<Terminal, CompilerDiagnostic>,
491 ) -> Result<(), CompilerDiagnostic> {
492 let prev = std::mem::replace(&mut self.current, wip);
493 let terminal = f(self)?;
494 let completed_wip = std::mem::replace(&mut self.current, prev);
495 self.completed.insert(
496 completed_wip.id,
497 BasicBlock {
498 kind: completed_wip.kind,
499 id: completed_wip.id,
500 instructions: completed_wip.instructions,
501 terminal,
502 preds: IndexSet::default(),
503 phis: Vec::new(),
504 },
505 );
506 Ok(())
507 }
508
509 /// Create a new block, set it as current, run the closure to populate it
510 /// and obtain its terminal, complete the block, and restore the previous
511 /// current block. Returns the new block's BlockId.
512 pub fn enter(
513 &mut self,
514 kind: BlockKind,
515 f: impl FnOnce(&mut Self, BlockId) -> Terminal,
516 ) -> BlockId {
517 let wip = self.reserve(kind);
518 let wip_id = wip.id;
519 self.enter_reserved(wip, |this| f(this, wip_id));
520 wip_id
521 }
522
523 /// Like `enter`, but the closure returns a `Result<Terminal, CompilerDiagnostic>`.
524 pub fn try_enter(
525 &mut self,
526 kind: BlockKind,
527 f: impl FnOnce(&mut Self, BlockId) -> Result<Terminal, CompilerDiagnostic>,
528 ) -> Result<BlockId, CompilerDiagnostic> {
529 let wip = self.reserve(kind);
530 let wip_id = wip.id;
531 self.try_enter_reserved(wip, |this| f(this, wip_id))?;
532 Ok(wip_id)
533 }
534
535 /// Push an exception handler, run the closure, then pop the handler.
536 pub fn enter_try_catch(&mut self, handler: BlockId, f: impl FnOnce(&mut Self)) {
537 self.exception_handler_stack.push(handler);
538 f(self);
539 self.exception_handler_stack.pop();
540 }
541
542 /// Like `enter_try_catch`, but the closure returns a `Result`.
543 pub fn try_enter_try_catch(
544 &mut self,
545 handler: BlockId,
546 f: impl FnOnce(&mut Self) -> Result<(), CompilerDiagnostic>,
547 ) -> Result<(), CompilerDiagnostic> {
548 self.exception_handler_stack.push(handler);
549 let result = f(self);
550 self.exception_handler_stack.pop();
551 result
552 }
553
554 /// Return the top of the exception handler stack, or None.
555 pub fn resolve_throw_handler(&self) -> Option<BlockId> {
556 self.exception_handler_stack.last().copied()
557 }
558
559 /// Push a Loop scope, run the closure, pop and verify.
560 pub fn loop_scope<T>(
561 &mut self,
562 label: Option<String>,
563 continue_block: BlockId,
564 break_block: BlockId,
565 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
566 ) -> Result<T, CompilerDiagnostic> {
567 self.scopes.push(Scope::Loop {
568 label: label.clone(),
569 continue_block,
570 break_block,
571 });
572 let value = f(self)?;
573 let last = self
574 .scopes
575 .pop()
576 .expect("Mismatched loop scope: stack empty");
577 match &last {
578 Scope::Loop {
579 label: l,
580 continue_block: c,
581 break_block: b,
582 } => {
583 assert!(
584 *l == label && *c == continue_block && *b == break_block,
585 "Mismatched loop scope"
586 );
587 }
588 _ => {
589 return Err(CompilerDiagnostic::new(
590 ErrorCategory::Invariant,
591 "Mismatched loop scope: expected Loop, got other",
592 None,
593 ));
594 }
595 }
596 Ok(value)
597 }
598
599 /// Push a Label scope, run the closure, pop and verify.
600 pub fn label_scope<T>(
601 &mut self,
602 label: String,
603 break_block: BlockId,
604 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
605 ) -> Result<T, CompilerDiagnostic> {
606 self.scopes.push(Scope::Label {
607 label: label.clone(),
608 break_block,
609 });
610 let value = f(self)?;
611 let last = self
612 .scopes
613 .pop()
614 .expect("Mismatched label scope: stack empty");
615 match &last {
616 Scope::Label {
617 label: l,
618 break_block: b,
619 } => {
620 assert!(*l == label && *b == break_block, "Mismatched label scope");
621 }
622 _ => {
623 return Err(CompilerDiagnostic::new(
624 ErrorCategory::Invariant,
625 "Mismatched label scope: expected Label, got other",
626 None,
627 ));
628 }
629 }
630 Ok(value)
631 }
632
633 /// Push a Switch scope, run the closure, pop and verify.
634 pub fn switch_scope<T>(
635 &mut self,
636 label: Option<String>,
637 break_block: BlockId,
638 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
639 ) -> Result<T, CompilerDiagnostic> {
640 self.scopes.push(Scope::Switch {
641 label: label.clone(),
642 break_block,
643 });
644 let value = f(self)?;
645 let last = self
646 .scopes
647 .pop()
648 .expect("Mismatched switch scope: stack empty");
649 match &last {
650 Scope::Switch {
651 label: l,
652 break_block: b,
653 } => {
654 assert!(*l == label && *b == break_block, "Mismatched switch scope");
655 }
656 _ => {
657 return Err(CompilerDiagnostic::new(
658 ErrorCategory::Invariant,
659 "Mismatched switch scope: expected Switch, got other",
660 None,
661 ));
662 }
663 }
664 Ok(value)
665 }
666
667 /// Look up the break target for the given label (or the innermost
668 /// loop/switch if label is None).
669 pub fn lookup_break(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
670 for scope in self.scopes.iter().rev() {
671 match scope {
672 Scope::Loop { .. } | Scope::Switch { .. } if label.is_none() => {
673 return Ok(scope.break_block());
674 }
675 _ if label.is_some() && scope.label() == label => {
676 return Ok(scope.break_block());
677 }
678 _ => continue,
679 }
680 }
681 Err(CompilerDiagnostic::new(
682 ErrorCategory::Invariant,
683 "Expected a loop or switch to be in scope for break",
684 None,
685 ))
686 }
687
688 /// Look up the continue target for the given label (or the innermost
689 /// loop if label is None). Only loops support continue.
690 pub fn lookup_continue(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
691 for scope in self.scopes.iter().rev() {
692 match scope {
693 Scope::Loop {
694 label: scope_label,
695 continue_block,
696 ..
697 } => {
698 if label.is_none() || label == scope_label.as_deref() {
699 return Ok(*continue_block);
700 }
701 }
702 _ => {
703 if label.is_some() && scope.label() == label {
704 return Err(CompilerDiagnostic::new(
705 ErrorCategory::Invariant,
706 "Continue may only refer to a labeled loop",
707 None,
708 ));
709 }
710 }
711 }
712 }
713 Err(CompilerDiagnostic::new(
714 ErrorCategory::Invariant,
715 "Expected a loop to be in scope for continue",
716 None,
717 ))
718 }
719
720 /// Create a temporary identifier with a fresh id, returning its IdentifierId.
721 pub fn make_temporary(&mut self, loc: Option<SourceLocation>) -> IdentifierId {
722 let id = self.env.next_identifier_id();
723 // Update the loc on the allocated identifier
724 self.env.identifiers[id.0 as usize].loc = loc;
725 id
726 }
727
728 /// Set the source location for an identifier.
729 pub fn set_identifier_loc(&mut self, id: IdentifierId, loc: Option<SourceLocation>) {
730 self.env.identifiers[id.0 as usize].loc = loc;
731 }
732
733 /// Record an error on the environment.
734 /// Returns `Err` for Invariant errors (matching TS throw behavior).
735 pub fn record_error(&mut self, error: CompilerErrorDetail) -> Result<(), CompilerError> {
736 self.env.record_error(error)
737 }
738
739 /// Record a diagnostic on the environment.
740 pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
741 self.env.record_diagnostic(diagnostic);
742 }
743
744 /// Check if a name has a local binding (non-module-level).
745 /// This is used for checking if fbt/fbs JSX tags are local bindings
746 /// (which is not supported).
747 pub fn has_local_binding(&self, name: &str) -> bool {
748 if let Some(binding) = self
749 .scope_info
750 .find_binding_in_descendants(name, self.component_scope)
751 {
752 // When component_scope == program_scope (e2e path where scope info
753 // is extracted from the function itself), any binding found is local.
754 if self.component_scope == self.scope_info.program_scope {
755 return true;
756 }
757 return binding.scope != self.scope_info.program_scope;
758 }
759 false
760 }
761
762 /// Return the kind of the current block.
763 pub fn current_block_kind(&self) -> BlockKind {
764 self.current.kind
765 }
766
767 /// Construct the final HIR and instruction table from the completed blocks.
768 ///
769 /// Performs these post-build passes:
770 /// 1. Reverse-postorder sort + unreachable block removal
771 /// 2. Check for unreachable blocks containing FunctionExpression instructions
772 /// 3. Remove unreachable for-loop updates
773 /// 4. Remove dead do-while statements
774 /// 5. Remove unnecessary try-catch
775 /// 6. Number all instructions and terminals
776 /// 7. Mark predecessor blocks
777 pub fn build(
778 mut self,
779 ) -> Result<
780 (
781 HIR,
782 Vec<Instruction>,
783 IndexMap<String, BindingId, FxBuildHasher>,
784 IndexMap<BindingId, IdentifierId, FxBuildHasher>,
785 ),
786 CompilerError,
787 > {
788 let mut hir = HIR {
789 blocks: std::mem::take(&mut self.completed),
790 entry: self.entry,
791 };
792
793 let mut instructions = std::mem::take(&mut self.instruction_table);
794
795 let rpo_blocks = get_reverse_postordered_blocks(&hir, &instructions);
796
797 // Check for unreachable blocks that contain FunctionExpression instructions.
798 // These could contain hoisted declarations that we can't safely remove.
799 for (id, block) in &hir.blocks {
800 if !rpo_blocks.contains_key(id) {
801 let has_function_expr = block.instructions.iter().any(|&instr_id| {
802 matches!(
803 instructions[instr_id.0 as usize].value,
804 InstructionValue::FunctionExpression { .. }
805 )
806 });
807 if has_function_expr {
808 let loc = block
809 .instructions
810 .first()
811 .and_then(|&i| instructions[i.0 as usize].loc.clone())
812 .or_else(|| block.terminal.loc().copied());
813 self.env.record_error(CompilerErrorDetail {
814 category: ErrorCategory::Todo,
815 reason: "Support functions with unreachable code that may contain hoisted declarations".to_string(),
816 description: None,
817 loc,
818 suggestions: None,
819 })?;
820 }
821 }
822 }
823
824 hir.blocks = rpo_blocks;
825
826 remove_unreachable_for_updates(&mut hir);
827 remove_dead_do_while_statements(&mut hir);
828 remove_unnecessary_try_catch(&mut hir);
829 mark_instruction_ids(&mut hir, &mut instructions);
830 mark_predecessors(&mut hir);
831
832 let used_names = self.used_names;
833 let bindings = self.bindings;
834 Ok((hir, instructions, used_names, bindings))
835 }
836
837 // -----------------------------------------------------------------------
838 // M3: Binding resolution methods
839 // -----------------------------------------------------------------------
840
841 /// Map a BindingId to an HIR IdentifierId.
842 ///
843 /// On first encounter, creates a new Identifier with the given name and a fresh id.
844 /// On subsequent encounters, returns the cached IdentifierId.
845 /// Handles name collisions by appending `_0`, `_1`, etc.
846 ///
847 /// Records errors for variables named 'fbt' or 'this'.
848 pub fn resolve_binding(
849 &mut self,
850 name: &str,
851 binding_id: BindingId,
852 ) -> Result<IdentifierId, CompilerError> {
853 self.resolve_binding_with_loc(name, binding_id, None)
854 }
855
856 /// Map a BindingId to an HIR IdentifierId, with an optional source location.
857 pub fn resolve_binding_with_loc(
858 &mut self,
859 name: &str,
860 binding_id: BindingId,
861 loc: Option<SourceLocation>,
862 ) -> Result<IdentifierId, CompilerError> {
863 // Check for unsupported names BEFORE the cache check.
864 // In TS, resolveBinding records fbt errors when node.name === 'fbt'. After a name collision
865 // causes a rename (e.g., "fbt" -> "fbt_0"), TS's scope.rename changes the AST node's name,
866 // preventing subsequent fbt error recording. We simulate this by checking whether the
867 // resolved name for this binding is still "fbt" (not renamed to "fbt_0" etc.).
868 if name == "fbt" {
869 // Check if this binding was previously resolved to a renamed version
870 let should_record_fbt_error =
871 if let Some(&identifier_id) = self.bindings.get(&binding_id) {
872 // Already resolved - check if the resolved name is still "fbt"
873 match &self.env.identifiers[identifier_id.0 as usize].name {
874 Some(IdentifierName::Named(resolved_name)) => resolved_name == "fbt",
875 _ => false,
876 }
877 } else {
878 // First resolution - always record
879 true
880 };
881 if should_record_fbt_error {
882 let error_loc = self.scope_info.bindings[binding_id.0 as usize]
883 .declaration_node_id
884 .and_then(|nid| self.get_identifier_loc(nid))
885 .or_else(|| loc.clone());
886 self.env.record_error(CompilerErrorDetail {
887 category: ErrorCategory::Todo,
888 reason: "Support local variables named `fbt`".to_string(),
889 description: Some(
890 "Local variables named `fbt` may conflict with the fbt plugin and are not yet supported".to_string(),
891 ),
892 loc: error_loc,
893 suggestions: None,
894 })?;
895 }
896 }
897
898 // If we've already resolved this binding, return the cached IdentifierId
899 if let Some(&identifier_id) = self.bindings.get(&binding_id) {
900 return Ok(identifier_id);
901 }
902
903 if is_always_reserved_word(name) {
904 // Match TS behavior: makeIdentifierName throws for reserved words.
905 return Err(CompilerError::from(reserved_identifier_diagnostic(name)));
906 }
907
908 // Find a unique name: start with the original name, then try name_0, name_1, ...
909 let mut candidate = name.to_string();
910 let mut index = 0u32;
911 loop {
912 if let Some(&existing_binding_id) = self.used_names.get(&candidate) {
913 if existing_binding_id == binding_id {
914 // Same binding, use this name
915 break;
916 }
917 // Name collision with a different binding, try the next suffix
918 candidate = format!("{}_{}", name, index);
919 index += 1;
920 } else {
921 // Name is available
922 break;
923 }
924 }
925
926 // Record rename if the candidate differs from the original name
927 if candidate != name {
928 let binding = &self.scope_info.bindings[binding_id.0 as usize];
929 if let Some(decl_start) = binding.declaration_start {
930 self.env
931 .renames
932 .push(react_compiler_hir::environment::BindingRename {
933 original: name.to_string(),
934 renamed: candidate.clone(),
935 declaration_start: decl_start,
936 });
937 }
938 }
939
940 // Allocate identifier in the arena
941 let id = self.env.next_identifier_id();
942 // Update the name and loc on the allocated identifier
943 self.env.identifiers[id.0 as usize].name = Some(IdentifierName::Named(candidate.clone()));
944 // Prefer the binding's declaration loc over the reference loc.
945 // This matches TS behavior where Babel's resolveBinding returns the
946 // binding identifier's original loc (the declaration site).
947 let binding = &self.scope_info.bindings[binding_id.0 as usize];
948 let decl_loc = binding
949 .declaration_node_id
950 .and_then(|nid| self.get_identifier_loc(nid));
951 if let Some(ref dl) = decl_loc {
952 self.env.identifiers[id.0 as usize].loc = Some(dl.clone());
953 } else if let Some(ref loc) = loc {
954 self.env.identifiers[id.0 as usize].loc = Some(loc.clone());
955 }
956
957 self.used_names.insert(candidate, binding_id);
958 self.bindings.insert(binding_id, id);
959 Ok(id)
960 }
961
962 /// Set the loc on an identifier to the declaration-site loc.
963 /// This overrides any previously-set loc (which may have come from a reference site).
964 pub fn set_identifier_declaration_loc(
965 &mut self,
966 id: IdentifierId,
967 loc: &Option<SourceLocation>,
968 ) {
969 if let Some(loc_val) = loc {
970 self.env.identifiers[id.0 as usize].loc = Some(loc_val.clone());
971 }
972 }
973
974 /// Resolve an identifier reference to a VariableBinding.
975 ///
976 /// Uses ScopeInfo to determine whether the reference is:
977 /// - Global (no binding found)
978 /// - ImportDefault, ImportSpecifier, ImportNamespace (program-scope import binding)
979 /// - ModuleLocal (program-scope non-import binding)
980 /// - Identifier (local binding, resolved via resolve_binding)
981 pub fn resolve_identifier(
982 &mut self,
983 name: &str,
984 _start_offset: u32,
985 loc: Option<SourceLocation>,
986 node_id: Option<u32>,
987 ) -> Result<VariableBinding, CompilerError> {
988 let binding_data = self.scope_info.resolve_reference_for_node(node_id);
989
990 match binding_data {
991 None => {
992 // No binding found: this is a global
993 Ok(VariableBinding::Global {
994 name: name.to_string(),
995 })
996 }
997 Some(binding) => {
998 // Treat type-only declarations as globals so the compiler
999 // doesn't try to create/initialize HIR bindings for them.
1000 // TSEnumDeclaration is included because enums inside function
1001 // bodies are lowered as UnsupportedNode and their binding
1002 // is never initialized in HIR.
1003 if matches!(
1004 binding.declaration_type.as_str(),
1005 "TSTypeAliasDeclaration"
1006 | "TSInterfaceDeclaration"
1007 | "TSEnumDeclaration"
1008 | "TSModuleDeclaration"
1009 ) {
1010 return Ok(VariableBinding::Global {
1011 name: name.to_string(),
1012 });
1013 }
1014 if binding.scope == self.scope_info.program_scope {
1015 // Module-level binding: check import info
1016 Ok(match &binding.import {
1017 Some(import_info) => match import_info.kind {
1018 ImportBindingKind::Default => VariableBinding::ImportDefault {
1019 name: name.to_string(),
1020 module: import_info.source.clone(),
1021 },
1022 ImportBindingKind::Named => VariableBinding::ImportSpecifier {
1023 name: name.to_string(),
1024 module: import_info.source.clone(),
1025 imported: import_info
1026 .imported
1027 .clone()
1028 .unwrap_or_else(|| name.to_string()),
1029 },
1030 ImportBindingKind::Namespace => VariableBinding::ImportNamespace {
1031 name: name.to_string(),
1032 module: import_info.source.clone(),
1033 },
1034 },
1035 None => VariableBinding::ModuleLocal {
1036 name: name.to_string(),
1037 },
1038 })
1039 } else if !self.is_scope_within_compiled_function(binding.scope) {
1040 Ok(VariableBinding::ModuleLocal {
1041 name: name.to_string(),
1042 })
1043 } else {
1044 let binding_id = binding.id;
1045 let binding_kind = crate::convert_binding_kind(&binding.kind);
1046 let identifier_id = self.resolve_binding_with_loc(name, binding_id, loc)?;
1047 Ok(VariableBinding::Identifier {
1048 identifier: identifier_id,
1049 binding_kind,
1050 })
1051 }
1052 }
1053 }
1054 }
1055
1056 /// Check if an identifier reference resolves to a context identifier.
1057 ///
1058 /// A context identifier is a variable declared in an ancestor scope of the
1059 /// current function's scope, but NOT in the program scope itself and NOT
1060 /// in the function's own scope. These are "captured" variables from an
1061 /// enclosing function.
1062 pub fn is_context_identifier(
1063 &self,
1064 _name: &str,
1065 _start_offset: u32,
1066 node_id: Option<u32>,
1067 ) -> bool {
1068 let binding = self.scope_info.resolve_reference_for_node(node_id);
1069
1070 match binding {
1071 None => false,
1072 Some(binding_data) => {
1073 if binding_data.scope == self.scope_info.program_scope {
1074 return false;
1075 }
1076 self.context_identifiers.contains(&binding_data.id)
1077 }
1078 }
1079 }
1080
1081 /// Like `is_context_identifier`, for callers that already resolved a
1082 /// BindingId instead of going through a reference node.
1083 pub fn is_context_binding(&self, binding_id: BindingId) -> bool {
1084 let binding = &self.scope_info.bindings[binding_id.0 as usize];
1085 if binding.scope == self.scope_info.program_scope {
1086 return false;
1087 }
1088 self.context_identifiers.contains(&binding_id)
1089 }
1090
1091 /// Resolve the binding for a function declaration's id the way TS does:
1092 /// Babel's `path.scope.getBinding(name)` starts at the function's OWN
1093 /// scope, so a body-level local (or parameter) that shadows the function's
1094 /// name resolves to that inner binding rather than to the function's
1095 /// hoisted binding in the parent scope.
1096 ///
1097 /// Babel's `scope.rename` re-keys a scope's bindings when the TS builder
1098 /// renames a shadowed binding (e.g. `init` -> `init_0`), so a binding only
1099 /// matches if its *current* name — the resolved HIR identifier name once
1100 /// resolved — still equals `name`. A binding renamed *to* `name` overwrites
1101 /// the original key in Babel and takes precedence over an unresolved
1102 /// binding with that original name.
1103 ///
1104 /// Returns None when the walk resolves outside the compiled function
1105 /// (degraded scope info); callers should fall back to node-based
1106 /// resolution in that case.
1107 pub fn get_function_declaration_binding(
1108 &self,
1109 function_scope: ScopeId,
1110 name: &str,
1111 ) -> Option<BindingId> {
1112 // None = unresolved binding; Some(matches) = resolved, current name comparison
1113 let resolved_name_matches = |bid: BindingId| -> Option<bool> {
1114 let &identifier_id = self.bindings.get(&bid)?;
1115 match &self.env.identifiers[identifier_id.0 as usize].name {
1116 Some(IdentifierName::Named(n)) => Some(n == name),
1117 _ => Some(false),
1118 }
1119 };
1120 let mut current = Some(function_scope);
1121 while let Some(id) = current {
1122 let scope = &self.scope_info.scopes[id.0 as usize];
1123 let mut found = scope
1124 .bindings
1125 .values()
1126 .copied()
1127 .find(|&bid| resolved_name_matches(bid) == Some(true));
1128 if found.is_none() {
1129 if let Some(&bid) = scope.bindings.get(name) {
1130 // Skip bindings that were renamed away from `name`.
1131 if resolved_name_matches(bid) != Some(false) {
1132 found = Some(bid);
1133 }
1134 }
1135 }
1136 if let Some(bid) = found {
1137 let binding_scope = self.scope_info.bindings[bid.0 as usize].scope;
1138 if !self.is_scope_within_compiled_function(binding_scope) {
1139 return None;
1140 }
1141 return Some(bid);
1142 }
1143 current = scope.parent;
1144 }
1145 None
1146 }
1147 }
1148
1149 // ---------------------------------------------------------------------------
1150 // Post-build helper functions
1151 // ---------------------------------------------------------------------------
1152
1153 /// Compute a reverse-postorder of blocks reachable from the entry.
1154 ///
1155 /// Visits successors in reverse order so that when the postorder list is
1156 /// reversed, sibling edges appear in program order.
1157 ///
1158 /// Blocks not reachable through successors are removed. Blocks that are
1159 /// only reachable as fallthroughs (not through real successor edges) are
1160 /// replaced with empty blocks that have an Unreachable terminal.
1161 pub fn get_reverse_postordered_blocks(
1162 hir: &HIR,
1163 _instructions: &[Instruction],
1164 ) -> IndexMap<BlockId, BasicBlock, FxBuildHasher> {
1165 let mut visited: IndexSet<BlockId, FxBuildHasher> = IndexSet::default();
1166 let mut used: IndexSet<BlockId, FxBuildHasher> = IndexSet::default();
1167 let mut used_fallthroughs: IndexSet<BlockId, FxBuildHasher> = IndexSet::default();
1168 let mut postorder: Vec<BlockId> = Vec::new();
1169
1170 fn visit(
1171 hir: &HIR,
1172 block_id: BlockId,
1173 is_used: bool,
1174 visited: &mut IndexSet<BlockId, FxBuildHasher>,
1175 used: &mut IndexSet<BlockId, FxBuildHasher>,
1176 used_fallthroughs: &mut IndexSet<BlockId, FxBuildHasher>,
1177 postorder: &mut Vec<BlockId>,
1178 ) {
1179 let was_used = used.contains(&block_id);
1180 let was_visited = visited.contains(&block_id);
1181 visited.insert(block_id);
1182 if is_used {
1183 used.insert(block_id);
1184 }
1185 if was_visited && (was_used || !is_used) {
1186 return;
1187 }
1188
1189 let block = hir
1190 .blocks
1191 .get(&block_id)
1192 .unwrap_or_else(|| panic!("[HIRBuilder] expected block {:?} to exist", block_id));
1193
1194 // Visit successors in reverse order so that when we reverse the
1195 // postorder list, sibling edges come out in program order.
1196 let mut successors = each_terminal_successor(&block.terminal);
1197 successors.reverse();
1198
1199 let fallthrough = terminal_fallthrough(&block.terminal);
1200
1201 // Visit fallthrough first (marking as not-yet-used) to ensure its
1202 // block ID is emitted in the correct position.
1203 if let Some(ft) = fallthrough {
1204 if is_used {
1205 used_fallthroughs.insert(ft);
1206 }
1207 visit(hir, ft, false, visited, used, used_fallthroughs, postorder);
1208 }
1209 for successor in successors {
1210 visit(
1211 hir,
1212 successor,
1213 is_used,
1214 visited,
1215 used,
1216 used_fallthroughs,
1217 postorder,
1218 );
1219 }
1220
1221 if !was_visited {
1222 postorder.push(block_id);
1223 }
1224 }
1225
1226 visit(
1227 hir,
1228 hir.entry,
1229 true,
1230 &mut visited,
1231 &mut used,
1232 &mut used_fallthroughs,
1233 &mut postorder,
1234 );
1235
1236 let mut blocks = IndexMap::default();
1237 for block_id in postorder.into_iter().rev() {
1238 let block = hir.blocks.get(&block_id).unwrap();
1239 if used.contains(&block_id) {
1240 blocks.insert(block_id, block.clone());
1241 } else if used_fallthroughs.contains(&block_id) {
1242 blocks.insert(
1243 block_id,
1244 BasicBlock {
1245 kind: block.kind,
1246 id: block_id,
1247 instructions: Vec::new(),
1248 terminal: Terminal::Unreachable {
1249 id: block.terminal.evaluation_order(),
1250 loc: block.terminal.loc().copied(),
1251 },
1252 preds: block.preds.clone(),
1253 phis: Vec::new(),
1254 },
1255 );
1256 }
1257 // otherwise this block is unreachable and is dropped
1258 }
1259
1260 blocks
1261 }
1262
1263 /// For each block with a `For` terminal whose update block is not in the
1264 /// blocks map, set update to None.
1265 pub fn remove_unreachable_for_updates(hir: &mut HIR) {
1266 let block_ids: IndexSet<BlockId, FxBuildHasher> = hir.blocks.keys().copied().collect();
1267 for block in hir.blocks.values_mut() {
1268 if let Terminal::For { update, .. } = &mut block.terminal {
1269 if let Some(update_id) = *update {
1270 if !block_ids.contains(&update_id) {
1271 *update = None;
1272 }
1273 }
1274 }
1275 }
1276 }
1277
1278 /// For each block with a `DoWhile` terminal whose test block is not in
1279 /// the blocks map, replace the terminal with a Goto to the loop block.
1280 pub fn remove_dead_do_while_statements(hir: &mut HIR) {
1281 let block_ids: IndexSet<BlockId, FxBuildHasher> = hir.blocks.keys().copied().collect();
1282 for block in hir.blocks.values_mut() {
1283 let should_replace = if let Terminal::DoWhile { test, .. } = &block.terminal {
1284 !block_ids.contains(test)
1285 } else {
1286 false
1287 };
1288 if should_replace {
1289 if let Terminal::DoWhile {
1290 loop_block,
1291 id,
1292 loc,
1293 ..
1294 } = std::mem::replace(
1295 &mut block.terminal,
1296 Terminal::Unreachable {
1297 id: EvaluationOrder(0),
1298 loc: None,
1299 },
1300 ) {
1301 block.terminal = Terminal::Goto {
1302 block: loop_block,
1303 variant: GotoVariant::Break,
1304 id,
1305 loc,
1306 };
1307 }
1308 }
1309 }
1310 }
1311
1312 /// For each block with a `Try` terminal whose handler block is not in
1313 /// the blocks map, replace the terminal with a Goto to the try block.
1314 ///
1315 /// Also cleans up the fallthrough block's predecessors if the handler
1316 /// was the only path to it.
1317 pub fn remove_unnecessary_try_catch(hir: &mut HIR) {
1318 let block_ids: IndexSet<BlockId, FxBuildHasher> = hir.blocks.keys().copied().collect();
1319
1320 // Collect the blocks that need replacement and their associated data
1321 let replacements: Vec<(BlockId, BlockId, BlockId, BlockId, Option<SourceLocation>)> = hir
1322 .blocks
1323 .iter()
1324 .filter_map(|(&block_id, block)| {
1325 if let Terminal::Try {
1326 block: try_block,
1327 handler,
1328 fallthrough,
1329 loc,
1330 ..
1331 } = &block.terminal
1332 {
1333 if !block_ids.contains(handler) {
1334 return Some((block_id, *try_block, *handler, *fallthrough, loc.clone()));
1335 }
1336 }
1337 None
1338 })
1339 .collect();
1340
1341 for (block_id, try_block, handler_id, fallthrough_id, loc) in replacements {
1342 // Replace the terminal
1343 if let Some(block) = hir.blocks.get_mut(&block_id) {
1344 block.terminal = Terminal::Goto {
1345 block: try_block,
1346 id: EvaluationOrder(0),
1347 loc,
1348 variant: GotoVariant::Break,
1349 };
1350 }
1351
1352 // Clean up fallthrough predecessor info
1353 if let Some(fallthrough) = hir.blocks.get_mut(&fallthrough_id) {
1354 if fallthrough.preds.len() == 1 && fallthrough.preds.contains(&handler_id) {
1355 // The handler was the only predecessor: remove the fallthrough block
1356 hir.blocks.shift_remove(&fallthrough_id);
1357 } else {
1358 fallthrough.preds.shift_remove(&handler_id);
1359 }
1360 }
1361 }
1362 }
1363
1364 /// Sequentially number all instructions and terminals starting from 1.
1365 pub fn mark_instruction_ids(hir: &mut HIR, instructions: &mut [Instruction]) {
1366 let mut order: u32 = 0;
1367 for block in hir.blocks.values_mut() {
1368 for &instr_id in &block.instructions {
1369 order += 1;
1370 instructions[instr_id.0 as usize].id = EvaluationOrder(order);
1371 }
1372 order += 1;
1373 block.terminal.set_evaluation_order(EvaluationOrder(order));
1374 }
1375 }
1376
1377 /// DFS from entry, for each successor add the predecessor's id to
1378 /// the successor's preds set.
1379 ///
1380 /// Note: This only visits direct successors (via `each_terminal_successor`),
1381 /// not fallthrough blocks. Fallthrough blocks are reached indirectly via
1382 /// Goto terminals from within branching blocks, matching the TypeScript
1383 /// `markPredecessors` behavior.
1384 pub fn mark_predecessors(hir: &mut HIR) {
1385 // Clear all preds first
1386 for block in hir.blocks.values_mut() {
1387 block.preds.clear();
1388 }
1389
1390 let mut visited: IndexSet<BlockId, FxBuildHasher> = IndexSet::default();
1391
1392 fn visit(
1393 hir: &mut HIR,
1394 block_id: BlockId,
1395 prev_block_id: Option<BlockId>,
1396 visited: &mut IndexSet<BlockId, FxBuildHasher>,
1397 ) {
1398 // Add predecessor
1399 if let Some(prev_id) = prev_block_id {
1400 if let Some(block) = hir.blocks.get_mut(&block_id) {
1401 block.preds.insert(prev_id);
1402 } else {
1403 return;
1404 }
1405 }
1406
1407 if visited.contains(&block_id) {
1408 return;
1409 }
1410 visited.insert(block_id);
1411
1412 // Get successors before mutating
1413 let successors = if let Some(block) = hir.blocks.get(&block_id) {
1414 each_terminal_successor(&block.terminal)
1415 } else {
1416 return;
1417 };
1418
1419 for successor in successors {
1420 visit(hir, successor, Some(block_id), visited);
1421 }
1422 }
1423
1424 visit(hir, hir.entry, None, &mut visited);
1425 }
1426
1427 // ---------------------------------------------------------------------------
1428 // Public helper functions
1429 // ---------------------------------------------------------------------------
1430
1431 /// Create a temporary Place with a fresh identifier allocated in the arena.
1432 pub fn create_temporary_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place {
1433 let id = env.next_identifier_id();
1434 // Update the loc on the allocated identifier
1435 env.identifiers[id.0 as usize].loc = loc;
1436 Place {
1437 identifier: id,
1438 reactive: false,
1439 effect: Effect::Unknown,
1440 loc: None,
1441 }
1442 }