compiler/crates/react_compiler_lowering/src/hir_builder.rs RUST 1,443 lines View on github.com → Search inside
1use indexmap::{IndexMap, IndexSet};2use react_compiler_ast::scope::BindingId;3use react_compiler_ast::scope::ImportBindingKind;4use react_compiler_ast::scope::ScopeId;5use react_compiler_ast::scope::ScopeInfo;6use react_compiler_diagnostics::CompilerDiagnostic;7use react_compiler_diagnostics::CompilerDiagnosticDetail;8use react_compiler_diagnostics::CompilerError;9use react_compiler_diagnostics::CompilerErrorDetail;10use react_compiler_diagnostics::ErrorCategory;11use react_compiler_hir::environment::Environment;12use react_compiler_hir::visitors::each_terminal_successor;13use react_compiler_hir::visitors::terminal_fallthrough;14use react_compiler_hir::*;15use rustc_hash::FxBuildHasher;1617use crate::identifier_loc_index::IdentifierLocIndex;1819// ---------------------------------------------------------------------------20// Reserved word check (matches TS isReservedWord)21// ---------------------------------------------------------------------------2223pub(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}6465pub(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            name72        )),73    )74    .with_detail(CompilerDiagnosticDetail::Error {75        loc: None, // GeneratedSource in TS76        message: Some("reserved word".to_string()),77        identifier_name: None,78    })79}8081// ---------------------------------------------------------------------------82// Scope types for tracking break/continue targets83// ---------------------------------------------------------------------------8485enum 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}100101impl 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    }109110    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}118119// ---------------------------------------------------------------------------120// WipBlock: a block under construction that does not yet have a terminal121// ---------------------------------------------------------------------------122123pub struct WipBlock {124    pub id: BlockId,125    pub instructions: Vec<InstructionId>,126    pub kind: BlockKind,127}128129fn new_block(id: BlockId, kind: BlockKind) -> WipBlock {130    WipBlock {131        id,132        kind,133        instructions: Vec::new(),134    }135}136137// ---------------------------------------------------------------------------138// HirBuilder: helper struct for constructing a CFG139// ---------------------------------------------------------------------------140141pub 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 parents160    /// 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_scope167    /// 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}176177impl<'a> HirBuilder<'a> {178    // -----------------------------------------------------------------------179    // M2: Core methods180    // -----------------------------------------------------------------------181182    /// Create a new HirBuilder.183    ///184    /// - `env`: the shared environment (counters, arenas, error accumulator)185    /// - `scope_info`: the scope information from the AST186    /// - `function_scope`: the ScopeId of the function being compiled187    /// - `bindings`: optional pre-existing bindings (e.g., from a parent function)188    /// - `context`: optional pre-existing captured context map189    /// - `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    }224225    /// 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 function227    /// or belongs to an ancestor function scope (e.g., a factory function228    /// wrapping a nested component declaration).229    /// Uses component_scope (the outermost compiled function's scope) rather230    /// than function_scope because inner function expressions within the231    /// compiled function have their own function_scope but still consider232    /// 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        false242    }243244    /// Access the environment.245    pub fn environment(&self) -> &Environment {246        self.env247    }248249    /// Access the environment mutably.250    pub fn environment_mut(&mut self) -> &mut Environment {251        self.env252    }253254    /// Create a new unique TypeVar type, allocated from the environment's type arena255    /// 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    }260261    /// Access the scope info.262    pub fn scope_info(&self) -> &ScopeInfo {263        self.scope_info264    }265266    /// 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_locs269            .get(&node_id)270            .map(|entry| entry.loc.clone())271    }272273    /// Check whether a reference at the given byte offset corresponds to a274    /// JSXIdentifier. Scans the node_id-keyed index for an entry whose stored275    /// `start` matches the offset.276    pub fn is_jsx_identifier_at_pos(&self, offset: u32) -> bool {277        self.identifier_locs278            .values()279            .any(|entry| entry.start == offset && entry.is_jsx)280    }281282    /// Access the function scope (the scope of the function being compiled).283    pub fn function_scope(&self) -> ScopeId {284        self.function_scope285    }286287    /// Access the component scope.288    pub fn component_scope(&self) -> ScopeId {289        self.component_scope290    }291292    /// Access the context map.293    pub fn context(&self) -> &IndexMap<BindingId, Option<SourceLocation>, FxBuildHasher> {294        &self.context295    }296297    /// Access the pre-computed context identifiers set.298    pub fn context_identifiers(&self) -> &rustc_hash::FxHashSet<BindingId> {299        &self.context_identifiers300    }301302    /// 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    }306307    pub fn claim_synthetic_scope(&mut self, scope_id: ScopeId) {308        self.claimed_synthetic_scopes.insert(scope_id);309    }310311    pub fn is_synthetic_scope_claimed(&self, scope_id: ScopeId) -> bool {312        self.claimed_synthetic_scopes.contains(&scope_id)313    }314315    /// Access scope_info and environment mutably at the same time.316    /// This is safe because they are disjoint fields, but Rust's borrow checker317    /// 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    }321322    /// 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_locs326    }327328    /// Access the bindings map.329    pub fn bindings(&self) -> &IndexMap<BindingId, IdentifierId, FxBuildHasher> {330        &self.bindings331    }332333    /// Access the used names map.334    pub fn used_names(&self) -> &IndexMap<String, BindingId, FxBuildHasher> {335        &self.used_names336    }337338    /// 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    }348349    /// 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    }360361    /// Push an instruction onto the current block.362    ///363    /// Adds the instruction to the flat instruction table and records364    /// its InstructionId in the current block's instruction list.365    ///366    /// If an exception handler is active, also emits a MaybeThrow terminal367    /// 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);374375        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    }389390    /// 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 when396        // next_block_kind is None, meaning this is the final terminate() call.397        // It will never be read or completed because build() consumes self398        // 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;404405        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        );416417        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_id422    }423424    /// Terminate the current block with the given terminal, and set425    /// 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    }441442    /// Reserve a new block so it can be referenced before construction.443    /// Use `terminate_with_continuation()` to make it current, or `complete()` to444    /// 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    }449450    /// 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    }465466    /// Sets the given wip block as current, executes the closure to populate467    /// it and obtain its terminal, then completes the block and restores the468    /// 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    }485486    /// 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    }508509    /// Create a new block, set it as current, run the closure to populate it510    /// and obtain its terminal, complete the block, and restore the previous511    /// 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_id521    }522523    /// 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    }534535    /// 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    }541542    /// 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        result552    }553554    /// 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    }558559    /// 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 = self574            .scopes575            .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    }598599    /// 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 = self612            .scopes613            .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    }632633    /// 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 = self646            .scopes647            .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    }666667    /// Look up the break target for the given label (or the innermost668    /// 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    }687688    /// Look up the continue target for the given label (or the innermost689    /// 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    }719720    /// 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 identifier724        self.env.identifiers[id.0 as usize].loc = loc;725        id726    }727728    /// 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    }732733    /// 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    }738739    /// Record a diagnostic on the environment.740    pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {741        self.env.record_diagnostic(diagnostic);742    }743744    /// Check if a name has a local binding (non-module-level).745    /// This is used for checking if fbt/fbs JSX tags are local bindings746    /// (which is not supported).747    pub fn has_local_binding(&self, name: &str) -> bool {748        if let Some(binding) = self749            .scope_info750            .find_binding_in_descendants(name, self.component_scope)751        {752            // When component_scope == program_scope (e2e path where scope info753            // 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        false760    }761762    /// Return the kind of the current block.763    pub fn current_block_kind(&self) -> BlockKind {764        self.current.kind765    }766767    /// 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 removal771    /// 2. Check for unreachable blocks containing FunctionExpression instructions772    /// 3. Remove unreachable for-loop updates773    /// 4. Remove dead do-while statements774    /// 5. Remove unnecessary try-catch775    /// 6. Number all instructions and terminals776    /// 7. Mark predecessor blocks777    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        };792793        let mut instructions = std::mem::take(&mut self.instruction_table);794795        let rpo_blocks = get_reverse_postordered_blocks(&hir, &instructions);796797        // 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 = block809                        .instructions810                        .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        }823824        hir.blocks = rpo_blocks;825826        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);831832        let used_names = self.used_names;833        let bindings = self.bindings;834        Ok((hir, instructions, used_names, bindings))835    }836837    // -----------------------------------------------------------------------838    // M3: Binding resolution methods839    // -----------------------------------------------------------------------840841    /// 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    }855856    /// 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 collision865        // 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 the867        // 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 version870            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 record879                    true880                };881            if should_record_fbt_error {882                let error_loc = self.scope_info.bindings[binding_id.0 as usize]883                    .declaration_node_id884                    .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        }897898        // If we've already resolved this binding, return the cached IdentifierId899        if let Some(&identifier_id) = self.bindings.get(&binding_id) {900            return Ok(identifier_id);901        }902903        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        }907908        // 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 name915                    break;916                }917                // Name collision with a different binding, try the next suffix918                candidate = format!("{}_{}", name, index);919                index += 1;920            } else {921                // Name is available922                break;923            }924        }925926        // Record rename if the candidate differs from the original name927        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.env931                    .renames932                    .push(react_compiler_hir::environment::BindingRename {933                        original: name.to_string(),934                        renamed: candidate.clone(),935                        declaration_start: decl_start,936                    });937            }938        }939940        // Allocate identifier in the arena941        let id = self.env.next_identifier_id();942        // Update the name and loc on the allocated identifier943        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 the946        // binding identifier's original loc (the declaration site).947        let binding = &self.scope_info.bindings[binding_id.0 as usize];948        let decl_loc = binding949            .declaration_node_id950            .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        }956957        self.used_names.insert(candidate, binding_id);958        self.bindings.insert(binding_id, id);959        Ok(id)960    }961962    /// 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    }973974    /// 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);989990        match binding_data {991            None => {992                // No binding found: this is a global993                Ok(VariableBinding::Global {994                    name: name.to_string(),995                })996            }997            Some(binding) => {998                // Treat type-only declarations as globals so the compiler999                // doesn't try to create/initialize HIR bindings for them.1000                // TSEnumDeclaration is included because enums inside function1001                // bodies are lowered as UnsupportedNode and their binding1002                // 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 info1016                    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_info1026                                    .imported1027                                    .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    }10551056    /// Check if an identifier reference resolves to a context identifier.1057    ///1058    /// A context identifier is a variable declared in an ancestor scope of the1059    /// current function's scope, but NOT in the program scope itself and NOT1060    /// in the function's own scope. These are "captured" variables from an1061    /// 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);10691070        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    }10801081    /// Like `is_context_identifier`, for callers that already resolved a1082    /// 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    }10901091    /// 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 OWN1093    /// scope, so a body-level local (or parameter) that shadows the function's1094    /// name resolves to that inner binding rather than to the function's1095    /// hoisted binding in the parent scope.1096    ///1097    /// Babel's `scope.rename` re-keys a scope's bindings when the TS builder1098    /// renames a shadowed binding (e.g. `init` -> `init_0`), so a binding only1099    /// matches if its *current* name — the resolved HIR identifier name once1100    /// resolved — still equals `name`. A binding renamed *to* `name` overwrites1101    /// the original key in Babel and takes precedence over an unresolved1102    /// binding with that original name.1103    ///1104    /// Returns None when the walk resolves outside the compiled function1105    /// (degraded scope info); callers should fall back to node-based1106    /// 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 comparison1113        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 = scope1124                .bindings1125                .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        None1146    }1147}11481149// ---------------------------------------------------------------------------1150// Post-build helper functions1151// ---------------------------------------------------------------------------11521153/// Compute a reverse-postorder of blocks reachable from the entry.1154///1155/// Visits successors in reverse order so that when the postorder list is1156/// reversed, sibling edges appear in program order.1157///1158/// Blocks not reachable through successors are removed. Blocks that are1159/// only reachable as fallthroughs (not through real successor edges) are1160/// replaced with empty blocks that have an Unreachable terminal.1161pub 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();11691170    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        }11881189        let block = hir1190            .blocks1191            .get(&block_id)1192            .unwrap_or_else(|| panic!("[HIRBuilder] expected block {:?} to exist", block_id));11931194        // Visit successors in reverse order so that when we reverse the1195        // postorder list, sibling edges come out in program order.1196        let mut successors = each_terminal_successor(&block.terminal);1197        successors.reverse();11981199        let fallthrough = terminal_fallthrough(&block.terminal);12001201        // Visit fallthrough first (marking as not-yet-used) to ensure its1202        // 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        }12201221        if !was_visited {1222            postorder.push(block_id);1223        }1224    }12251226    visit(1227        hir,1228        hir.entry,1229        true,1230        &mut visited,1231        &mut used,1232        &mut used_fallthroughs,1233        &mut postorder,1234    );12351236    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 dropped1258    }12591260    blocks1261}12621263/// For each block with a `For` terminal whose update block is not in the1264/// blocks map, set update to None.1265pub 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}12771278/// For each block with a `DoWhile` terminal whose test block is not in1279/// the blocks map, replace the terminal with a Goto to the loop block.1280pub 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            false1287        };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}13111312/// For each block with a `Try` terminal whose handler block is not in1313/// 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 handler1316/// was the only path to it.1317pub fn remove_unnecessary_try_catch(hir: &mut HIR) {1318    let block_ids: IndexSet<BlockId, FxBuildHasher> = hir.blocks.keys().copied().collect();13191320    // Collect the blocks that need replacement and their associated data1321    let replacements: Vec<(BlockId, BlockId, BlockId, BlockId, Option<SourceLocation>)> = hir1322        .blocks1323        .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.terminal1332            {1333                if !block_ids.contains(handler) {1334                    return Some((block_id, *try_block, *handler, *fallthrough, loc.clone()));1335                }1336            }1337            None1338        })1339        .collect();13401341    for (block_id, try_block, handler_id, fallthrough_id, loc) in replacements {1342        // Replace the terminal1343        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        }13511352        // Clean up fallthrough predecessor info1353        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 block1356                hir.blocks.shift_remove(&fallthrough_id);1357            } else {1358                fallthrough.preds.shift_remove(&handler_id);1359            }1360        }1361    }1362}13631364/// Sequentially number all instructions and terminals starting from 1.1365pub 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}13761377/// DFS from entry, for each successor add the predecessor's id to1378/// 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 via1382/// Goto terminals from within branching blocks, matching the TypeScript1383/// `markPredecessors` behavior.1384pub fn mark_predecessors(hir: &mut HIR) {1385    // Clear all preds first1386    for block in hir.blocks.values_mut() {1387        block.preds.clear();1388    }13891390    let mut visited: IndexSet<BlockId, FxBuildHasher> = IndexSet::default();13911392    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 predecessor1399        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        }14061407        if visited.contains(&block_id) {1408            return;1409        }1410        visited.insert(block_id);14111412        // Get successors before mutating1413        let successors = if let Some(block) = hir.blocks.get(&block_id) {1414            each_terminal_successor(&block.terminal)1415        } else {1416            return;1417        };14181419        for successor in successors {1420            visit(hir, successor, Some(block_id), visited);1421        }1422    }14231424    visit(hir, hir.entry, None, &mut visited);1425}14261427// ---------------------------------------------------------------------------1428// Public helper functions1429// ---------------------------------------------------------------------------14301431/// Create a temporary Place with a fresh identifier allocated in the arena.1432pub 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 identifier1435    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}

Code quality findings 34

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
current = self.scope_info.scopes[id.0 as usize].parent;
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("Mismatched loop scope: stack empty");
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("Mismatched label scope: stack empty");
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("Mismatched switch scope: stack empty");
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].loc = loc;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].loc = loc;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
instructions[instr_id.0 as usize].value,
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
.and_then(|&i| instructions[i.0 as usize].loc.clone())
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
match &self.env.identifiers[identifier_id.0 as usize].name {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let error_loc = self.scope_info.bindings[binding_id.0 as usize]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let binding = &self.scope_info.bindings[binding_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].name = Some(IdentifierName::Named(candidate.clone()));
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let binding = &self.scope_info.bindings[binding_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].loc = Some(dl.clone());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].loc = Some(loc.clone());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
self.env.identifiers[id.0 as usize].loc = Some(loc_val.clone());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let binding = &self.scope_info.bindings[binding_id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
match &self.env.identifiers[identifier_id.0 as usize].name {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let scope = &self.scope_info.scopes[id.0 as usize];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let binding_scope = self.scope_info.bindings[bid.0 as usize].scope;
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let block = hir.blocks.get(&block_id).unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
pub fn mark_instruction_ids(hir: &mut HIR, instructions: &mut [Instruction]) {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
instructions[instr_id.0 as usize].id = EvaluationOrder(order);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
env.identifiers[id.0 as usize].loc = loc;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_hir::*;
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &last {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &last {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match scope {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match scope {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &self.env.identifiers[identifier_id.0 as usize].name {
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
let mut candidate = name.to_string();
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match &self.env.identifiers[identifier_id.0 as usize].name {
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
blocks.insert(block_id, block.clone());
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
return Some((block_id, *try_block, *handler, *fallthrough, loc.clone()));

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.