1use indexmap::IndexMap;
2use indexmap::IndexSet;
3use react_compiler_ast::scope::BindingId;
4use react_compiler_ast::scope::ImportBindingKind;
5use react_compiler_ast::scope::ScopeId;
6use react_compiler_ast::scope::ScopeInfo;
7use react_compiler_diagnostics::CompilerDiagnostic;
8use react_compiler_diagnostics::CompilerDiagnosticDetail;
9use react_compiler_diagnostics::CompilerError;
10use react_compiler_diagnostics::CompilerErrorDetail;
11use react_compiler_diagnostics::ErrorCategory;
12use react_compiler_hir::environment::Environment;
13use react_compiler_hir::visitors::each_terminal_successor;
14use react_compiler_hir::visitors::terminal_fallthrough;
15use react_compiler_hir::*;
16
17use crate::identifier_loc_index::IdentifierLocIndex;
18
19pub(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
65pub(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, message: Some("reserved word".to_string()),
77 identifier_name: None,
78 })
79}
80
81enum 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
101impl 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
119pub struct WipBlock {
124 pub id: BlockId,
125 pub instructions: Vec<InstructionId>,
126 pub kind: BlockKind,
127}
128
129fn new_block(id: BlockId, kind: BlockKind) -> WipBlock {
130 WipBlock {
131 id,
132 kind,
133 instructions: Vec::new(),
134 }
135}
136
137pub struct HirBuilder<'a> {
142 completed: IndexMap<BlockId, BasicBlock>,
143 current: WipBlock,
144 entry: BlockId,
145 scopes: Vec<Scope>,
146 context: IndexMap<BindingId, Option<SourceLocation>>,
149 bindings: IndexMap<BindingId, IdentifierId>,
151 used_names: IndexMap<String, BindingId>,
154 env: &'a mut Environment,
155 scope_info: &'a ScopeInfo,
156 exception_handler_stack: Vec<BlockId>,
157 instruction_table: Vec<Instruction>,
159 pub fbt_depth: u32,
162 function_scope: ScopeId,
164 component_scope: ScopeId,
166 context_identifiers: std::collections::HashSet<BindingId>,
170 claimed_synthetic_scopes: std::collections::HashSet<ScopeId>,
173 identifier_locs: &'a IdentifierLocIndex,
175}
176
177impl<'a> HirBuilder<'a> {
178 pub fn new(
191 env: &'a mut Environment,
192 scope_info: &'a ScopeInfo,
193 function_scope: ScopeId,
194 component_scope: ScopeId,
195 context_identifiers: std::collections::HashSet<BindingId>,
196 bindings: Option<IndexMap<BindingId, IdentifierId>>,
197 context: Option<IndexMap<BindingId, Option<SourceLocation>>>,
198 entry_block_kind: Option<BlockKind>,
199 used_names: Option<IndexMap<String, BindingId>>,
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::new(),
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: std::collections::HashSet::new(),
221 identifier_locs,
222 }
223 }
224
225 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 pub fn environment(&self) -> &Environment {
246 self.env
247 }
248
249 pub fn environment_mut(&mut self) -> &mut Environment {
251 self.env
252 }
253
254 pub fn make_type(&mut self) -> Type {
257 let type_id = self.env.make_type();
258 Type::TypeVar { id: type_id }
259 }
260
261 pub fn scope_info(&self) -> &ScopeInfo {
263 self.scope_info
264 }
265
266 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 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 pub fn function_scope(&self) -> ScopeId {
284 self.function_scope
285 }
286
287 pub fn component_scope(&self) -> ScopeId {
289 self.component_scope
290 }
291
292 pub fn context(&self) -> &IndexMap<BindingId, Option<SourceLocation>> {
294 &self.context
295 }
296
297 pub fn context_identifiers(&self) -> &std::collections::HashSet<BindingId> {
299 &self.context_identifiers
300 }
301
302 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 pub fn scope_info_and_env_mut(&mut self) -> (&ScopeInfo, &mut Environment) {
319 (self.scope_info, self.env)
320 }
321
322 pub fn identifier_locs(&self) -> &'a IdentifierLocIndex {
325 self.identifier_locs
326 }
327
328 pub fn bindings(&self) -> &IndexMap<BindingId, IdentifierId> {
330 &self.bindings
331 }
332
333 pub fn used_names(&self) -> &IndexMap<String, BindingId> {
335 &self.used_names
336 }
337
338 pub fn merge_used_names(&mut self, child_used_names: IndexMap<String, BindingId>) {
341 for (name, binding_id) in child_used_names {
342 self.used_names.entry(name).or_insert(binding_id);
343 }
344 }
345
346 pub fn merge_bindings(&mut self, child_bindings: IndexMap<BindingId, IdentifierId>) {
350 for (binding_id, identifier_id) in child_bindings {
351 self.bindings.entry(binding_id).or_insert(identifier_id);
352 }
353 }
354
355 pub fn push(&mut self, instruction: Instruction) {
364 let loc = instruction.loc.clone();
365 let instr_id = InstructionId(self.instruction_table.len() as u32);
366 self.instruction_table.push(instruction);
367 self.current.instructions.push(instr_id);
368
369 if let Some(&handler) = self.exception_handler_stack.last() {
370 let continuation = self.reserve(self.current_block_kind());
371 self.terminate_with_continuation(
372 Terminal::MaybeThrow {
373 continuation: continuation.id,
374 handler: Some(handler),
375 id: EvaluationOrder(0),
376 loc,
377 effects: None,
378 },
379 continuation,
380 );
381 }
382 }
383
384 pub fn terminate(&mut self, terminal: Terminal, next_block_kind: Option<BlockKind>) -> BlockId {
389 let wip = std::mem::replace(
394 &mut self.current,
395 new_block(BlockId(u32::MAX), BlockKind::Block),
396 );
397 let block_id = wip.id;
398
399 self.completed.insert(
400 block_id,
401 BasicBlock {
402 kind: wip.kind,
403 id: block_id,
404 instructions: wip.instructions,
405 terminal,
406 preds: IndexSet::new(),
407 phis: Vec::new(),
408 },
409 );
410
411 if let Some(kind) = next_block_kind {
412 let next_id = self.env.next_block_id();
413 self.current = new_block(next_id, kind);
414 }
415 block_id
416 }
417
418 pub fn terminate_with_continuation(&mut self, terminal: Terminal, continuation: WipBlock) {
421 let wip = std::mem::replace(&mut self.current, continuation);
422 let block_id = wip.id;
423 self.completed.insert(
424 block_id,
425 BasicBlock {
426 kind: wip.kind,
427 id: block_id,
428 instructions: wip.instructions,
429 terminal,
430 preds: IndexSet::new(),
431 phis: Vec::new(),
432 },
433 );
434 }
435
436 pub fn reserve(&mut self, kind: BlockKind) -> WipBlock {
440 let id = self.env.next_block_id();
441 new_block(id, kind)
442 }
443
444 pub fn complete(&mut self, block: WipBlock, terminal: Terminal) {
446 let block_id = block.id;
447 self.completed.insert(
448 block_id,
449 BasicBlock {
450 kind: block.kind,
451 id: block_id,
452 instructions: block.instructions,
453 terminal,
454 preds: IndexSet::new(),
455 phis: Vec::new(),
456 },
457 );
458 }
459
460 pub fn enter_reserved(&mut self, wip: WipBlock, f: impl FnOnce(&mut Self) -> Terminal) {
464 let prev = std::mem::replace(&mut self.current, wip);
465 let terminal = f(self);
466 let completed_wip = std::mem::replace(&mut self.current, prev);
467 self.completed.insert(
468 completed_wip.id,
469 BasicBlock {
470 kind: completed_wip.kind,
471 id: completed_wip.id,
472 instructions: completed_wip.instructions,
473 terminal,
474 preds: IndexSet::new(),
475 phis: Vec::new(),
476 },
477 );
478 }
479
480 pub fn try_enter_reserved(
482 &mut self,
483 wip: WipBlock,
484 f: impl FnOnce(&mut Self) -> Result<Terminal, CompilerDiagnostic>,
485 ) -> Result<(), CompilerDiagnostic> {
486 let prev = std::mem::replace(&mut self.current, wip);
487 let terminal = f(self)?;
488 let completed_wip = std::mem::replace(&mut self.current, prev);
489 self.completed.insert(
490 completed_wip.id,
491 BasicBlock {
492 kind: completed_wip.kind,
493 id: completed_wip.id,
494 instructions: completed_wip.instructions,
495 terminal,
496 preds: IndexSet::new(),
497 phis: Vec::new(),
498 },
499 );
500 Ok(())
501 }
502
503 pub fn enter(
507 &mut self,
508 kind: BlockKind,
509 f: impl FnOnce(&mut Self, BlockId) -> Terminal,
510 ) -> BlockId {
511 let wip = self.reserve(kind);
512 let wip_id = wip.id;
513 self.enter_reserved(wip, |this| f(this, wip_id));
514 wip_id
515 }
516
517 pub fn try_enter(
519 &mut self,
520 kind: BlockKind,
521 f: impl FnOnce(&mut Self, BlockId) -> Result<Terminal, CompilerDiagnostic>,
522 ) -> Result<BlockId, CompilerDiagnostic> {
523 let wip = self.reserve(kind);
524 let wip_id = wip.id;
525 self.try_enter_reserved(wip, |this| f(this, wip_id))?;
526 Ok(wip_id)
527 }
528
529 pub fn enter_try_catch(&mut self, handler: BlockId, f: impl FnOnce(&mut Self)) {
531 self.exception_handler_stack.push(handler);
532 f(self);
533 self.exception_handler_stack.pop();
534 }
535
536 pub fn try_enter_try_catch(
538 &mut self,
539 handler: BlockId,
540 f: impl FnOnce(&mut Self) -> Result<(), CompilerDiagnostic>,
541 ) -> Result<(), CompilerDiagnostic> {
542 self.exception_handler_stack.push(handler);
543 let result = f(self);
544 self.exception_handler_stack.pop();
545 result
546 }
547
548 pub fn resolve_throw_handler(&self) -> Option<BlockId> {
550 self.exception_handler_stack.last().copied()
551 }
552
553 pub fn loop_scope<T>(
555 &mut self,
556 label: Option<String>,
557 continue_block: BlockId,
558 break_block: BlockId,
559 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
560 ) -> Result<T, CompilerDiagnostic> {
561 self.scopes.push(Scope::Loop {
562 label: label.clone(),
563 continue_block,
564 break_block,
565 });
566 let value = f(self)?;
567 let last = self
568 .scopes
569 .pop()
570 .expect("Mismatched loop scope: stack empty");
571 match &last {
572 Scope::Loop {
573 label: l,
574 continue_block: c,
575 break_block: b,
576 } => {
577 assert!(
578 *l == label && *c == continue_block && *b == break_block,
579 "Mismatched loop scope"
580 );
581 }
582 _ => {
583 return Err(CompilerDiagnostic::new(
584 ErrorCategory::Invariant,
585 "Mismatched loop scope: expected Loop, got other",
586 None,
587 ));
588 }
589 }
590 Ok(value)
591 }
592
593 pub fn label_scope<T>(
595 &mut self,
596 label: String,
597 break_block: BlockId,
598 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
599 ) -> Result<T, CompilerDiagnostic> {
600 self.scopes.push(Scope::Label {
601 label: label.clone(),
602 break_block,
603 });
604 let value = f(self)?;
605 let last = self
606 .scopes
607 .pop()
608 .expect("Mismatched label scope: stack empty");
609 match &last {
610 Scope::Label {
611 label: l,
612 break_block: b,
613 } => {
614 assert!(*l == label && *b == break_block, "Mismatched label scope");
615 }
616 _ => {
617 return Err(CompilerDiagnostic::new(
618 ErrorCategory::Invariant,
619 "Mismatched label scope: expected Label, got other",
620 None,
621 ));
622 }
623 }
624 Ok(value)
625 }
626
627 pub fn switch_scope<T>(
629 &mut self,
630 label: Option<String>,
631 break_block: BlockId,
632 f: impl FnOnce(&mut Self) -> Result<T, CompilerDiagnostic>,
633 ) -> Result<T, CompilerDiagnostic> {
634 self.scopes.push(Scope::Switch {
635 label: label.clone(),
636 break_block,
637 });
638 let value = f(self)?;
639 let last = self
640 .scopes
641 .pop()
642 .expect("Mismatched switch scope: stack empty");
643 match &last {
644 Scope::Switch {
645 label: l,
646 break_block: b,
647 } => {
648 assert!(*l == label && *b == break_block, "Mismatched switch scope");
649 }
650 _ => {
651 return Err(CompilerDiagnostic::new(
652 ErrorCategory::Invariant,
653 "Mismatched switch scope: expected Switch, got other",
654 None,
655 ));
656 }
657 }
658 Ok(value)
659 }
660
661 pub fn lookup_break(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
664 for scope in self.scopes.iter().rev() {
665 match scope {
666 Scope::Loop { .. } | Scope::Switch { .. } if label.is_none() => {
667 return Ok(scope.break_block());
668 }
669 _ if label.is_some() && scope.label() == label => {
670 return Ok(scope.break_block());
671 }
672 _ => continue,
673 }
674 }
675 Err(CompilerDiagnostic::new(
676 ErrorCategory::Invariant,
677 "Expected a loop or switch to be in scope for break",
678 None,
679 ))
680 }
681
682 pub fn lookup_continue(&self, label: Option<&str>) -> Result<BlockId, CompilerDiagnostic> {
685 for scope in self.scopes.iter().rev() {
686 match scope {
687 Scope::Loop {
688 label: scope_label,
689 continue_block,
690 ..
691 } => {
692 if label.is_none() || label == scope_label.as_deref() {
693 return Ok(*continue_block);
694 }
695 }
696 _ => {
697 if label.is_some() && scope.label() == label {
698 return Err(CompilerDiagnostic::new(
699 ErrorCategory::Invariant,
700 "Continue may only refer to a labeled loop",
701 None,
702 ));
703 }
704 }
705 }
706 }
707 Err(CompilerDiagnostic::new(
708 ErrorCategory::Invariant,
709 "Expected a loop to be in scope for continue",
710 None,
711 ))
712 }
713
714 pub fn make_temporary(&mut self, loc: Option<SourceLocation>) -> IdentifierId {
716 let id = self.env.next_identifier_id();
717 self.env.identifiers[id.0 as usize].loc = loc;
719 id
720 }
721
722 pub fn set_identifier_loc(&mut self, id: IdentifierId, loc: Option<SourceLocation>) {
724 self.env.identifiers[id.0 as usize].loc = loc;
725 }
726
727 pub fn record_error(&mut self, error: CompilerErrorDetail) -> Result<(), CompilerError> {
730 self.env.record_error(error)
731 }
732
733 pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
735 self.env.record_diagnostic(diagnostic);
736 }
737
738 pub fn has_local_binding(&self, name: &str) -> bool {
742 if let Some(binding) = self
743 .scope_info
744 .find_binding_in_descendants(name, self.component_scope)
745 {
746 return binding.scope != self.scope_info.program_scope;
747 }
748 false
749 }
750
751 pub fn current_block_kind(&self) -> BlockKind {
753 self.current.kind
754 }
755
756 pub fn build(
767 mut self,
768 ) -> Result<
769 (
770 HIR,
771 Vec<Instruction>,
772 IndexMap<String, BindingId>,
773 IndexMap<BindingId, IdentifierId>,
774 ),
775 CompilerError,
776 > {
777 let mut hir = HIR {
778 blocks: std::mem::take(&mut self.completed),
779 entry: self.entry,
780 };
781
782 let mut instructions = std::mem::take(&mut self.instruction_table);
783
784 let rpo_blocks = get_reverse_postordered_blocks(&hir, &instructions);
785
786 for (id, block) in &hir.blocks {
789 if !rpo_blocks.contains_key(id) {
790 let has_function_expr = block.instructions.iter().any(|&instr_id| {
791 matches!(
792 instructions[instr_id.0 as usize].value,
793 InstructionValue::FunctionExpression { .. }
794 )
795 });
796 if has_function_expr {
797 let loc = block
798 .instructions
799 .first()
800 .and_then(|&i| instructions[i.0 as usize].loc.clone())
801 .or_else(|| block.terminal.loc().copied());
802 self.env.record_error(CompilerErrorDetail {
803 category: ErrorCategory::Todo,
804 reason: "Support functions with unreachable code that may contain hoisted declarations".to_string(),
805 description: None,
806 loc,
807 suggestions: None,
808 })?;
809 }
810 }
811 }
812
813 hir.blocks = rpo_blocks;
814
815 remove_unreachable_for_updates(&mut hir);
816 remove_dead_do_while_statements(&mut hir);
817 remove_unnecessary_try_catch(&mut hir);
818 mark_instruction_ids(&mut hir, &mut instructions);
819 mark_predecessors(&mut hir);
820
821 let used_names = self.used_names;
822 let bindings = self.bindings;
823 Ok((hir, instructions, used_names, bindings))
824 }
825
826 pub fn resolve_binding(
838 &mut self,
839 name: &str,
840 binding_id: BindingId,
841 ) -> Result<IdentifierId, CompilerError> {
842 self.resolve_binding_with_loc(name, binding_id, None)
843 }
844
845 pub fn resolve_binding_with_loc(
847 &mut self,
848 name: &str,
849 binding_id: BindingId,
850 loc: Option<SourceLocation>,
851 ) -> Result<IdentifierId, CompilerError> {
852 if name == "fbt" {
858 let should_record_fbt_error =
860 if let Some(&identifier_id) = self.bindings.get(&binding_id) {
861 match &self.env.identifiers[identifier_id.0 as usize].name {
863 Some(IdentifierName::Named(resolved_name)) => resolved_name == "fbt",
864 _ => false,
865 }
866 } else {
867 true
869 };
870 if should_record_fbt_error {
871 let error_loc = self.scope_info.bindings[binding_id.0 as usize]
872 .declaration_node_id
873 .and_then(|nid| self.get_identifier_loc(nid))
874 .or_else(|| loc.clone());
875 self.env.record_error(CompilerErrorDetail {
876 category: ErrorCategory::Todo,
877 reason: "Support local variables named `fbt`".to_string(),
878 description: Some(
879 "Local variables named `fbt` may conflict with the fbt plugin and are not yet supported".to_string(),
880 ),
881 loc: error_loc,
882 suggestions: None,
883 })?;
884 }
885 }
886
887 if let Some(&identifier_id) = self.bindings.get(&binding_id) {
889 return Ok(identifier_id);
890 }
891
892 if is_always_reserved_word(name) {
893 return Err(CompilerError::from(reserved_identifier_diagnostic(name)));
895 }
896
897 let mut candidate = name.to_string();
899 let mut index = 0u32;
900 loop {
901 if let Some(&existing_binding_id) = self.used_names.get(&candidate) {
902 if existing_binding_id == binding_id {
903 break;
905 }
906 candidate = format!("{}_{}", name, index);
908 index += 1;
909 } else {
910 break;
912 }
913 }
914
915 if candidate != name {
917 let binding = &self.scope_info.bindings[binding_id.0 as usize];
918 if let Some(decl_start) = binding.declaration_start {
919 self.env
920 .renames
921 .push(react_compiler_hir::environment::BindingRename {
922 original: name.to_string(),
923 renamed: candidate.clone(),
924 declaration_start: decl_start,
925 });
926 }
927 }
928
929 let id = self.env.next_identifier_id();
931 self.env.identifiers[id.0 as usize].name = Some(IdentifierName::Named(candidate.clone()));
933 let binding = &self.scope_info.bindings[binding_id.0 as usize];
937 let decl_loc = binding
938 .declaration_node_id
939 .and_then(|nid| self.get_identifier_loc(nid));
940 if let Some(ref dl) = decl_loc {
941 self.env.identifiers[id.0 as usize].loc = Some(dl.clone());
942 } else if let Some(ref loc) = loc {
943 self.env.identifiers[id.0 as usize].loc = Some(loc.clone());
944 }
945
946 self.used_names.insert(candidate, binding_id);
947 self.bindings.insert(binding_id, id);
948 Ok(id)
949 }
950
951 pub fn set_identifier_declaration_loc(
954 &mut self,
955 id: IdentifierId,
956 loc: &Option<SourceLocation>,
957 ) {
958 if let Some(loc_val) = loc {
959 self.env.identifiers[id.0 as usize].loc = Some(loc_val.clone());
960 }
961 }
962
963 pub fn resolve_identifier(
971 &mut self,
972 name: &str,
973 _start_offset: u32,
974 loc: Option<SourceLocation>,
975 node_id: Option<u32>,
976 ) -> Result<VariableBinding, CompilerError> {
977 let binding_data = self.scope_info.resolve_reference_for_node(node_id);
978
979 match binding_data {
980 None => {
981 Ok(VariableBinding::Global {
983 name: name.to_string(),
984 })
985 }
986 Some(binding) => {
987 if matches!(
993 binding.declaration_type.as_str(),
994 "TSTypeAliasDeclaration"
995 | "TSInterfaceDeclaration"
996 | "TSEnumDeclaration"
997 | "TSModuleDeclaration"
998 ) {
999 return Ok(VariableBinding::Global {
1000 name: name.to_string(),
1001 });
1002 }
1003 if binding.scope == self.scope_info.program_scope {
1004 Ok(match &binding.import {
1006 Some(import_info) => match import_info.kind {
1007 ImportBindingKind::Default => VariableBinding::ImportDefault {
1008 name: name.to_string(),
1009 module: import_info.source.clone(),
1010 },
1011 ImportBindingKind::Named => VariableBinding::ImportSpecifier {
1012 name: name.to_string(),
1013 module: import_info.source.clone(),
1014 imported: import_info
1015 .imported
1016 .clone()
1017 .unwrap_or_else(|| name.to_string()),
1018 },
1019 ImportBindingKind::Namespace => VariableBinding::ImportNamespace {
1020 name: name.to_string(),
1021 module: import_info.source.clone(),
1022 },
1023 },
1024 None => VariableBinding::ModuleLocal {
1025 name: name.to_string(),
1026 },
1027 })
1028 } else if !self.is_scope_within_compiled_function(binding.scope) {
1029 Ok(VariableBinding::ModuleLocal {
1030 name: name.to_string(),
1031 })
1032 } else {
1033 let binding_id = binding.id;
1034 let binding_kind = crate::convert_binding_kind(&binding.kind);
1035 let identifier_id = self.resolve_binding_with_loc(name, binding_id, loc)?;
1036 Ok(VariableBinding::Identifier {
1037 identifier: identifier_id,
1038 binding_kind,
1039 })
1040 }
1041 }
1042 }
1043 }
1044
1045 pub fn is_context_identifier(
1052 &self,
1053 _name: &str,
1054 _start_offset: u32,
1055 node_id: Option<u32>,
1056 ) -> bool {
1057 let binding = self.scope_info.resolve_reference_for_node(node_id);
1058
1059 match binding {
1060 None => false,
1061 Some(binding_data) => {
1062 if binding_data.scope == self.scope_info.program_scope {
1063 return false;
1064 }
1065 self.context_identifiers.contains(&binding_data.id)
1066 }
1067 }
1068 }
1069
1070 pub fn is_context_binding(&self, binding_id: BindingId) -> bool {
1073 let binding = &self.scope_info.bindings[binding_id.0 as usize];
1074 if binding.scope == self.scope_info.program_scope {
1075 return false;
1076 }
1077 self.context_identifiers.contains(&binding_id)
1078 }
1079
1080 pub fn get_function_declaration_binding(
1097 &self,
1098 function_scope: ScopeId,
1099 name: &str,
1100 ) -> Option<BindingId> {
1101 let resolved_name_matches = |bid: BindingId| -> Option<bool> {
1103 let &identifier_id = self.bindings.get(&bid)?;
1104 match &self.env.identifiers[identifier_id.0 as usize].name {
1105 Some(IdentifierName::Named(n)) => Some(n == name),
1106 _ => Some(false),
1107 }
1108 };
1109 let mut current = Some(function_scope);
1110 while let Some(id) = current {
1111 let scope = &self.scope_info.scopes[id.0 as usize];
1112 let mut found = scope
1113 .bindings
1114 .values()
1115 .copied()
1116 .find(|&bid| resolved_name_matches(bid) == Some(true));
1117 if found.is_none() {
1118 if let Some(&bid) = scope.bindings.get(name) {
1119 if resolved_name_matches(bid) != Some(false) {
1121 found = Some(bid);
1122 }
1123 }
1124 }
1125 if let Some(bid) = found {
1126 let binding_scope = self.scope_info.bindings[bid.0 as usize].scope;
1127 if !self.is_scope_within_compiled_function(binding_scope) {
1128 return None;
1129 }
1130 return Some(bid);
1131 }
1132 current = scope.parent;
1133 }
1134 None
1135 }
1136}
1137
1138pub fn get_reverse_postordered_blocks(
1151 hir: &HIR,
1152 _instructions: &[Instruction],
1153) -> IndexMap<BlockId, BasicBlock> {
1154 let mut visited: IndexSet<BlockId> = IndexSet::new();
1155 let mut used: IndexSet<BlockId> = IndexSet::new();
1156 let mut used_fallthroughs: IndexSet<BlockId> = IndexSet::new();
1157 let mut postorder: Vec<BlockId> = Vec::new();
1158
1159 fn visit(
1160 hir: &HIR,
1161 block_id: BlockId,
1162 is_used: bool,
1163 visited: &mut IndexSet<BlockId>,
1164 used: &mut IndexSet<BlockId>,
1165 used_fallthroughs: &mut IndexSet<BlockId>,
1166 postorder: &mut Vec<BlockId>,
1167 ) {
1168 let was_used = used.contains(&block_id);
1169 let was_visited = visited.contains(&block_id);
1170 visited.insert(block_id);
1171 if is_used {
1172 used.insert(block_id);
1173 }
1174 if was_visited && (was_used || !is_used) {
1175 return;
1176 }
1177
1178 let block = hir
1179 .blocks
1180 .get(&block_id)
1181 .unwrap_or_else(|| panic!("[HIRBuilder] expected block {:?} to exist", block_id));
1182
1183 let mut successors = each_terminal_successor(&block.terminal);
1186 successors.reverse();
1187
1188 let fallthrough = terminal_fallthrough(&block.terminal);
1189
1190 if let Some(ft) = fallthrough {
1193 if is_used {
1194 used_fallthroughs.insert(ft);
1195 }
1196 visit(hir, ft, false, visited, used, used_fallthroughs, postorder);
1197 }
1198 for successor in successors {
1199 visit(
1200 hir,
1201 successor,
1202 is_used,
1203 visited,
1204 used,
1205 used_fallthroughs,
1206 postorder,
1207 );
1208 }
1209
1210 if !was_visited {
1211 postorder.push(block_id);
1212 }
1213 }
1214
1215 visit(
1216 hir,
1217 hir.entry,
1218 true,
1219 &mut visited,
1220 &mut used,
1221 &mut used_fallthroughs,
1222 &mut postorder,
1223 );
1224
1225 let mut blocks = IndexMap::new();
1226 for block_id in postorder.into_iter().rev() {
1227 let block = hir.blocks.get(&block_id).unwrap();
1228 if used.contains(&block_id) {
1229 blocks.insert(block_id, block.clone());
1230 } else if used_fallthroughs.contains(&block_id) {
1231 blocks.insert(
1232 block_id,
1233 BasicBlock {
1234 kind: block.kind,
1235 id: block_id,
1236 instructions: Vec::new(),
1237 terminal: Terminal::Unreachable {
1238 id: block.terminal.evaluation_order(),
1239 loc: block.terminal.loc().copied(),
1240 },
1241 preds: block.preds.clone(),
1242 phis: Vec::new(),
1243 },
1244 );
1245 }
1246 }
1248
1249 blocks
1250}
1251
1252pub fn remove_unreachable_for_updates(hir: &mut HIR) {
1255 let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
1256 for block in hir.blocks.values_mut() {
1257 if let Terminal::For { update, .. } = &mut block.terminal {
1258 if let Some(update_id) = *update {
1259 if !block_ids.contains(&update_id) {
1260 *update = None;
1261 }
1262 }
1263 }
1264 }
1265}
1266
1267pub fn remove_dead_do_while_statements(hir: &mut HIR) {
1270 let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
1271 for block in hir.blocks.values_mut() {
1272 let should_replace = if let Terminal::DoWhile { test, .. } = &block.terminal {
1273 !block_ids.contains(test)
1274 } else {
1275 false
1276 };
1277 if should_replace {
1278 if let Terminal::DoWhile {
1279 loop_block,
1280 id,
1281 loc,
1282 ..
1283 } = std::mem::replace(
1284 &mut block.terminal,
1285 Terminal::Unreachable {
1286 id: EvaluationOrder(0),
1287 loc: None,
1288 },
1289 ) {
1290 block.terminal = Terminal::Goto {
1291 block: loop_block,
1292 variant: GotoVariant::Break,
1293 id,
1294 loc,
1295 };
1296 }
1297 }
1298 }
1299}
1300
1301pub fn remove_unnecessary_try_catch(hir: &mut HIR) {
1307 let block_ids: IndexSet<BlockId> = hir.blocks.keys().copied().collect();
1308
1309 let replacements: Vec<(BlockId, BlockId, BlockId, BlockId, Option<SourceLocation>)> = hir
1311 .blocks
1312 .iter()
1313 .filter_map(|(&block_id, block)| {
1314 if let Terminal::Try {
1315 block: try_block,
1316 handler,
1317 fallthrough,
1318 loc,
1319 ..
1320 } = &block.terminal
1321 {
1322 if !block_ids.contains(handler) {
1323 return Some((block_id, *try_block, *handler, *fallthrough, loc.clone()));
1324 }
1325 }
1326 None
1327 })
1328 .collect();
1329
1330 for (block_id, try_block, handler_id, fallthrough_id, loc) in replacements {
1331 if let Some(block) = hir.blocks.get_mut(&block_id) {
1333 block.terminal = Terminal::Goto {
1334 block: try_block,
1335 id: EvaluationOrder(0),
1336 loc,
1337 variant: GotoVariant::Break,
1338 };
1339 }
1340
1341 if let Some(fallthrough) = hir.blocks.get_mut(&fallthrough_id) {
1343 if fallthrough.preds.len() == 1 && fallthrough.preds.contains(&handler_id) {
1344 hir.blocks.shift_remove(&fallthrough_id);
1346 } else {
1347 fallthrough.preds.shift_remove(&handler_id);
1348 }
1349 }
1350 }
1351}
1352
1353pub fn mark_instruction_ids(hir: &mut HIR, instructions: &mut [Instruction]) {
1355 let mut order: u32 = 0;
1356 for block in hir.blocks.values_mut() {
1357 for &instr_id in &block.instructions {
1358 order += 1;
1359 instructions[instr_id.0 as usize].id = EvaluationOrder(order);
1360 }
1361 order += 1;
1362 block.terminal.set_evaluation_order(EvaluationOrder(order));
1363 }
1364}
1365
1366pub fn mark_predecessors(hir: &mut HIR) {
1374 for block in hir.blocks.values_mut() {
1376 block.preds.clear();
1377 }
1378
1379 let mut visited: IndexSet<BlockId> = IndexSet::new();
1380
1381 fn visit(
1382 hir: &mut HIR,
1383 block_id: BlockId,
1384 prev_block_id: Option<BlockId>,
1385 visited: &mut IndexSet<BlockId>,
1386 ) {
1387 if let Some(prev_id) = prev_block_id {
1389 if let Some(block) = hir.blocks.get_mut(&block_id) {
1390 block.preds.insert(prev_id);
1391 } else {
1392 return;
1393 }
1394 }
1395
1396 if visited.contains(&block_id) {
1397 return;
1398 }
1399 visited.insert(block_id);
1400
1401 let successors = if let Some(block) = hir.blocks.get(&block_id) {
1403 each_terminal_successor(&block.terminal)
1404 } else {
1405 return;
1406 };
1407
1408 for successor in successors {
1409 visit(hir, successor, Some(block_id), visited);
1410 }
1411 }
1412
1413 visit(hir, hir.entry, None, &mut visited);
1414}
1415
1416pub fn create_temporary_place(env: &mut Environment, loc: Option<SourceLocation>) -> Place {
1422 let id = env.next_identifier_id();
1423 env.identifiers[id.0 as usize].loc = loc;
1425 Place {
1426 identifier: id,
1427 reactive: false,
1428 effect: Effect::Unknown,
1429 loc: None,
1430 }
1431}