compiler/crates/react_compiler_reactive_scopes/src/build_reactive_function.rs RUST 1,675 lines View on github.com → Search inside
1// Copyright (c) Meta Platforms, Inc. and affiliates.2//3// This source code is licensed under the MIT license found in the4// LICENSE file in the root directory of this source tree.56//! Converts the HIR CFG into a tree-structured ReactiveFunction.7//!8//! Corresponds to `src/ReactiveScopes/BuildReactiveFunction.ts`.910use rustc_hash::FxHashSet;1112use react_compiler_diagnostics::{13    CompilerDiagnostic, CompilerDiagnosticDetail, ErrorCategory, SourceLocation,14};15use react_compiler_hir::environment::Environment;16use react_compiler_hir::{17    BasicBlock, BlockId, EvaluationOrder, GotoVariant, HirFunction, InstructionValue, Place,18    PrunedReactiveScopeBlock, ReactiveBlock, ReactiveFunction, ReactiveInstruction, ReactiveLabel,19    ReactiveScopeBlock, ReactiveStatement, ReactiveSwitchCase, ReactiveTerminal,20    ReactiveTerminalStatement, ReactiveTerminalTargetKind, ReactiveValue, Terminal,21};2223/// Convert the HIR CFG into a tree-structured ReactiveFunction.24pub fn build_reactive_function(25    hir: &HirFunction,26    env: &Environment,27) -> Result<ReactiveFunction, CompilerDiagnostic> {28    let mut ctx = Context::new(hir);29    let mut driver = Driver {30        cx: &mut ctx,31        hir,32        env,33    };3435    let entry_block_id = hir.body.entry;36    let mut body = Vec::new();37    driver.visit_block(entry_block_id, &mut body)?;3839    Ok(ReactiveFunction {40        loc: hir.loc,41        id: hir.id.clone(),42        name_hint: hir.name_hint.clone(),43        params: hir.params.clone(),44        generator: hir.generator,45        is_async: hir.is_async,46        body,47        directives: hir.directives.clone(),48    })49}5051// =============================================================================52// ControlFlowTarget53// =============================================================================5455#[derive(Debug)]56enum ControlFlowTarget {57    If {58        block: BlockId,59        id: u32,60    },61    Switch {62        block: BlockId,63        id: u32,64    },65    Case {66        block: BlockId,67        id: u32,68    },69    Loop {70        block: BlockId,71        #[allow(dead_code)]72        owns_block: bool,73        continue_block: BlockId,74        loop_block: Option<BlockId>,75        owns_loop: bool,76        id: u32,77    },78}7980impl ControlFlowTarget {81    fn block(&self) -> BlockId {82        match self {83            ControlFlowTarget::If { block, .. }84            | ControlFlowTarget::Switch { block, .. }85            | ControlFlowTarget::Case { block, .. }86            | ControlFlowTarget::Loop { block, .. } => *block,87        }88    }8990    fn id(&self) -> u32 {91        match self {92            ControlFlowTarget::If { id, .. }93            | ControlFlowTarget::Switch { id, .. }94            | ControlFlowTarget::Case { id, .. }95            | ControlFlowTarget::Loop { id, .. } => *id,96        }97    }9899    fn is_loop(&self) -> bool {100        matches!(self, ControlFlowTarget::Loop { .. })101    }102}103104// =============================================================================105// Context106// =============================================================================107108struct Context<'a> {109    ir: &'a HirFunction,110    next_schedule_id: u32,111    emitted: FxHashSet<BlockId>,112    scope_fallthroughs: FxHashSet<BlockId>,113    scheduled: FxHashSet<BlockId>,114    catch_handlers: FxHashSet<BlockId>,115    control_flow_stack: Vec<ControlFlowTarget>,116}117118impl<'a> Context<'a> {119    fn new(ir: &'a HirFunction) -> Self {120        Self {121            ir,122            next_schedule_id: 0,123            emitted: FxHashSet::default(),124            scope_fallthroughs: FxHashSet::default(),125            scheduled: FxHashSet::default(),126            catch_handlers: FxHashSet::default(),127            control_flow_stack: Vec::new(),128        }129    }130131    fn block(&self, id: BlockId) -> &BasicBlock {132        &self.ir.body.blocks[&id]133    }134135    fn schedule_catch_handler(&mut self, block: BlockId) {136        self.catch_handlers.insert(block);137    }138139    fn reachable(&self, id: BlockId) -> bool {140        let block = self.block(id);141        !matches!(block.terminal, Terminal::Unreachable { .. })142    }143144    fn schedule(&mut self, block: BlockId, target_type: &str) -> Result<u32, CompilerDiagnostic> {145        let id = self.next_schedule_id;146        self.next_schedule_id += 1;147        if self.scheduled.contains(&block) {148            return Err(CompilerDiagnostic::new(149                ErrorCategory::Invariant,150                format!("Break block is already scheduled: bb{}", block.0),151                None,152            ));153        }154        self.scheduled.insert(block);155        let target = match target_type {156            "if" => ControlFlowTarget::If { block, id },157            "switch" => ControlFlowTarget::Switch { block, id },158            "case" => ControlFlowTarget::Case { block, id },159            _ => {160                return Err(CompilerDiagnostic::new(161                    ErrorCategory::Invariant,162                    format!("Unknown target type: {}", target_type),163                    None,164                ));165            }166        };167        self.control_flow_stack.push(target);168        Ok(id)169    }170171    fn schedule_loop(172        &mut self,173        fallthrough_block: BlockId,174        continue_block: BlockId,175        loop_block: Option<BlockId>,176    ) -> Result<u32, CompilerDiagnostic> {177        let id = self.next_schedule_id;178        self.next_schedule_id += 1;179        let owns_block = !self.scheduled.contains(&fallthrough_block);180        self.scheduled.insert(fallthrough_block);181        if self.scheduled.contains(&continue_block) {182            return Err(CompilerDiagnostic::new(183                ErrorCategory::Invariant,184                format!(185                    "Continue block is already scheduled: bb{}",186                    continue_block.0187                ),188                None,189            ));190        }191        self.scheduled.insert(continue_block);192        let mut owns_loop = false;193        if let Some(lb) = loop_block {194            owns_loop = !self.scheduled.contains(&lb);195            self.scheduled.insert(lb);196        }197198        self.control_flow_stack.push(ControlFlowTarget::Loop {199            block: fallthrough_block,200            owns_block,201            continue_block,202            loop_block,203            owns_loop,204            id,205        });206        Ok(id)207    }208209    fn unschedule(&mut self, schedule_id: u32) -> Result<(), CompilerDiagnostic> {210        let last = self211            .control_flow_stack212            .pop()213            .expect("Can only unschedule the last target");214        if last.id() != schedule_id {215            return Err(CompilerDiagnostic::new(216                ErrorCategory::Invariant,217                "Can only unschedule the last target".to_string(),218                None,219            ));220        }221        match &last {222            ControlFlowTarget::Loop {223                block,224                continue_block,225                loop_block,226                owns_loop,227                ..228            } => {229                // TS: always removes block from scheduled for loops230                // (ownsBlock is boolean, so `!== null` is always true)231                self.scheduled.remove(block);232                self.scheduled.remove(continue_block);233                if *owns_loop {234                    if let Some(lb) = loop_block {235                        self.scheduled.remove(lb);236                    }237                }238            }239            _ => {240                self.scheduled.remove(&last.block());241            }242        }243        Ok(())244    }245246    fn unschedule_all(&mut self, schedule_ids: &[u32]) -> Result<(), CompilerDiagnostic> {247        for &id in schedule_ids.iter().rev() {248            self.unschedule(id)?;249        }250        Ok(())251    }252253    fn is_scheduled(&self, block: BlockId) -> bool {254        self.scheduled.contains(&block) || self.catch_handlers.contains(&block)255    }256257    fn get_break_target(258        &self,259        block: BlockId,260    ) -> Result<(BlockId, ReactiveTerminalTargetKind), CompilerDiagnostic> {261        let mut has_preceding_loop = false;262        for i in (0..self.control_flow_stack.len()).rev() {263            let target = &self.control_flow_stack[i];264            if target.block() == block {265                let kind = if target.is_loop() {266                    if has_preceding_loop {267                        ReactiveTerminalTargetKind::Labeled268                    } else {269                        ReactiveTerminalTargetKind::Unlabeled270                    }271                } else if i == self.control_flow_stack.len() - 1 {272                    ReactiveTerminalTargetKind::Implicit273                } else {274                    ReactiveTerminalTargetKind::Labeled275                };276                return Ok((target.block(), kind));277            }278            has_preceding_loop = has_preceding_loop || target.is_loop();279        }280        Err(CompilerDiagnostic::new(281            ErrorCategory::Invariant,282            format!("Expected a break target for bb{}", block.0),283            None,284        ))285    }286287    fn get_continue_target(&self, block: BlockId) -> Option<(BlockId, ReactiveTerminalTargetKind)> {288        let mut has_preceding_loop = false;289        for i in (0..self.control_flow_stack.len()).rev() {290            let target = &self.control_flow_stack[i];291            if let ControlFlowTarget::Loop {292                block: fallthrough_block,293                continue_block,294                ..295            } = target296            {297                if *continue_block == block {298                    let kind = if has_preceding_loop {299                        ReactiveTerminalTargetKind::Labeled300                    } else if i == self.control_flow_stack.len() - 1 {301                        ReactiveTerminalTargetKind::Implicit302                    } else {303                        ReactiveTerminalTargetKind::Unlabeled304                    };305                    return Some((*fallthrough_block, kind));306                }307            }308            has_preceding_loop = has_preceding_loop || target.is_loop();309        }310        None311    }312}313314// =============================================================================315// Driver316// =============================================================================317318struct Driver<'a, 'b> {319    cx: &'b mut Context<'a>,320    hir: &'a HirFunction,321    #[allow(dead_code)]322    env: &'a Environment,323}324325impl<'a, 'b> Driver<'a, 'b> {326    fn traverse_block(&mut self, block_id: BlockId) -> Result<ReactiveBlock, CompilerDiagnostic> {327        let mut block_value = Vec::new();328        self.visit_block(block_id, &mut block_value)?;329        Ok(block_value)330    }331332    fn visit_block(333        &mut self,334        mut block_id: BlockId,335        block_value: &mut ReactiveBlock,336    ) -> Result<(), CompilerDiagnostic> {337        // Use a loop to avoid deep recursion for fallthrough chains.338        // Each terminal that would tail-call visit_block(fallthrough, block_value)339        // instead sets next_block and continues the loop.340        loop {341            // Extract data from block before any mutable operations342            let block = &self.hir.body.blocks[&block_id];343            let block_id_val = block.id;344            let instructions: Vec<_> = block.instructions.clone();345            let terminal = block.terminal.clone();346347            if !self.cx.emitted.insert(block_id_val) {348                return Err(CompilerDiagnostic::new(349                    ErrorCategory::Invariant,350                    format!("Block bb{} was already emitted", block_id_val.0),351                    None,352                ));353            }354355            // Emit instructions356            for instr_id in &instructions {357                let instr = &self.hir.instructions[instr_id.0 as usize];358                block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {359                    id: instr.id,360                    lvalue: Some(instr.lvalue.clone()),361                    value: ReactiveValue::Instruction(instr.value.clone()),362                    effects: instr.effects.clone(),363                    loc: instr.loc,364                }));365            }366367            // Process terminal368            let mut schedule_ids: Vec<u32> = Vec::new();369            let mut next_block: Option<BlockId> = None;370371            match &terminal {372                Terminal::If {373                    test,374                    consequent,375                    alternate,376                    fallthrough,377                    id,378                    loc,379                } => {380                    // TS: reachable(fallthrough) && !isScheduled(fallthrough)381                    let fallthrough_id =382                        if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {383                            Some(*fallthrough)384                        } else {385                            None386                        };387                    // TS: alternate !== fallthrough ? alternate : null388                    let alternate_id = if *alternate != *fallthrough {389                        Some(*alternate)390                    } else {391                        None392                    };393394                    if let Some(ft) = fallthrough_id {395                        schedule_ids.push(self.cx.schedule(ft, "if")?);396                    }397398                    let consequent_block = if self.cx.is_scheduled(*consequent) {399                        return Err(CompilerDiagnostic::new(400                            ErrorCategory::Invariant,401                            format!(402                                "Unexpected 'if' where consequent is already scheduled (bb{})",403                                consequent.0404                            ),405                            None,406                        ));407                    } else {408                        self.traverse_block(*consequent)?409                    };410411                    let alternate_block = if let Some(alt) = alternate_id {412                        if self.cx.is_scheduled(alt) {413                            return Err(CompilerDiagnostic::new(414                                ErrorCategory::Invariant,415                                format!(416                                    "Unexpected 'if' where the alternate is already scheduled (bb{})",417                                    alt.0418                                ),419                                None,420                            ));421                        } else {422                            Some(self.traverse_block(alt)?)423                        }424                    } else {425                        None426                    };427428                    self.cx.unschedule_all(&schedule_ids)?;429                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {430                        terminal: ReactiveTerminal::If {431                            test: test.clone(),432                            consequent: consequent_block,433                            alternate: alternate_block,434                            id: *id,435                            loc: *loc,436                        },437                        label: fallthrough_id.map(|ft| ReactiveLabel {438                            id: ft,439                            implicit: false,440                        }),441                    }));442443                    next_block = fallthrough_id;444                }445446                Terminal::Switch {447                    test,448                    cases,449                    fallthrough,450                    id,451                    loc,452                } => {453                    // TS: reachable(fallthrough) && !isScheduled(fallthrough)454                    let fallthrough_id =455                        if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {456                            Some(*fallthrough)457                        } else {458                            None459                        };460                    if let Some(ft) = fallthrough_id {461                        schedule_ids.push(self.cx.schedule(ft, "switch")?);462                    }463464                    // TS processes cases in reverse order, then reverses the result.465                    // This ensures that later cases are scheduled when earlier cases466                    // are traversed, matching fallthrough semantics.467                    let mut reactive_cases = Vec::new();468                    for case in cases.iter().rev() {469                        let case_block_id = case.block;470471                        if self.cx.is_scheduled(case_block_id) {472                            // TS: asserts case.block === fallthrough, then skips (return)473                            if case_block_id != *fallthrough {474                                return Err(CompilerDiagnostic::new(475                                ErrorCategory::Invariant,476                                "Unexpected 'switch' where a case is already scheduled and block is not the fallthrough".to_string(),477                                None,478                            ));479                            }480                            continue;481                        }482483                        let consequent = self.traverse_block(case_block_id)?;484                        let case_schedule_id = self.cx.schedule(case_block_id, "case")?;485                        schedule_ids.push(case_schedule_id);486487                        reactive_cases.push(ReactiveSwitchCase {488                            test: case.test.clone(),489                            block: Some(consequent),490                        });491                    }492                    reactive_cases.reverse();493494                    self.cx.unschedule_all(&schedule_ids)?;495                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {496                        terminal: ReactiveTerminal::Switch {497                            test: test.clone(),498                            cases: reactive_cases,499                            id: *id,500                            loc: *loc,501                        },502                        label: fallthrough_id.map(|ft| ReactiveLabel {503                            id: ft,504                            implicit: false,505                        }),506                    }));507508                    next_block = fallthrough_id;509                }510511                Terminal::DoWhile {512                    loop_block,513                    test,514                    fallthrough,515                    id,516                    loc,517                } => {518                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {519                        Some(*fallthrough)520                    } else {521                        None522                    };523                    let loop_id =524                        if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {525                            Some(*loop_block)526                        } else {527                            None528                        };529530                    schedule_ids.push(self.cx.schedule_loop(531                        *fallthrough,532                        *test,533                        Some(*loop_block),534                    )?);535536                    let loop_body = if let Some(lid) = loop_id {537                        self.traverse_block(lid)?538                    } else {539                        return Err(CompilerDiagnostic::new(540                            ErrorCategory::Invariant,541                            "Unexpected 'do-while' where the loop is already scheduled",542                            None,543                        ));544                    };545                    let test_result = self.visit_value_block(*test, *loc, None)?;546547                    self.cx.unschedule_all(&schedule_ids)?;548                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {549                        terminal: ReactiveTerminal::DoWhile {550                            loop_block: loop_body,551                            test: test_result.value,552                            id: *id,553                            loc: *loc,554                        },555                        label: fallthrough_id.map(|ft| ReactiveLabel {556                            id: ft,557                            implicit: false,558                        }),559                    }));560561                    next_block = fallthrough_id;562                }563564                Terminal::While {565                    test,566                    loop_block,567                    fallthrough,568                    id,569                    loc,570                } => {571                    // TS: reachable(fallthrough) && !isScheduled(fallthrough)572                    let fallthrough_id =573                        if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {574                            Some(*fallthrough)575                        } else {576                            None577                        };578                    let loop_id =579                        if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {580                            Some(*loop_block)581                        } else {582                            None583                        };584585                    schedule_ids.push(self.cx.schedule_loop(586                        *fallthrough,587                        *test,588                        Some(*loop_block),589                    )?);590591                    let test_result = self.visit_value_block(*test, *loc, None)?;592593                    let loop_body = if let Some(lid) = loop_id {594                        self.traverse_block(lid)?595                    } else {596                        return Err(CompilerDiagnostic::new(597                            ErrorCategory::Invariant,598                            "Unexpected 'while' where the loop is already scheduled",599                            None,600                        ));601                    };602603                    self.cx.unschedule_all(&schedule_ids)?;604                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {605                        terminal: ReactiveTerminal::While {606                            test: test_result.value,607                            loop_block: loop_body,608                            id: *id,609                            loc: *loc,610                        },611                        label: fallthrough_id.map(|ft| ReactiveLabel {612                            id: ft,613                            implicit: false,614                        }),615                    }));616617                    next_block = fallthrough_id;618                }619620                Terminal::For {621                    init,622                    test,623                    update,624                    loop_block,625                    fallthrough,626                    id,627                    loc,628                } => {629                    let loop_id =630                        if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {631                            Some(*loop_block)632                        } else {633                            None634                        };635636                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {637                        Some(*fallthrough)638                    } else {639                        None640                    };641642                    // Continue block is update (if present) or test643                    let continue_block = update.unwrap_or(*test);644                    schedule_ids.push(self.cx.schedule_loop(645                        *fallthrough,646                        continue_block,647                        Some(*loop_block),648                    )?);649650                    let init_result = self.visit_value_block(*init, *loc, None)?;651                    let init_value = self.value_block_result_to_sequence(init_result, *loc);652653                    let test_result = self.visit_value_block(*test, *loc, None)?;654655                    let update_result = match update {656                        Some(u) => Some(self.visit_value_block(*u, *loc, None)?),657                        None => None,658                    };659660                    let loop_body = if let Some(lid) = loop_id {661                        self.traverse_block(lid)?662                    } else {663                        return Err(CompilerDiagnostic::new(664                            ErrorCategory::Invariant,665                            "Unexpected 'for' where the loop is already scheduled",666                            None,667                        ));668                    };669670                    self.cx.unschedule_all(&schedule_ids)?;671                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {672                        terminal: ReactiveTerminal::For {673                            init: init_value,674                            test: test_result.value,675                            update: update_result.map(|r| r.value),676                            loop_block: loop_body,677                            id: *id,678                            loc: *loc,679                        },680                        label: fallthrough_id.map(|ft| ReactiveLabel {681                            id: ft,682                            implicit: false,683                        }),684                    }));685686                    next_block = fallthrough_id;687                }688689                Terminal::ForOf {690                    init,691                    test,692                    loop_block,693                    fallthrough,694                    id,695                    loc,696                } => {697                    let loop_id =698                        if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {699                            Some(*loop_block)700                        } else {701                            None702                        };703704                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {705                        Some(*fallthrough)706                    } else {707                        None708                    };709710                    // TS: scheduleLoop(fallthrough, init, loop)711                    schedule_ids.push(self.cx.schedule_loop(712                        *fallthrough,713                        *init,714                        Some(*loop_block),715                    )?);716717                    let init_result = self.visit_value_block(*init, *loc, None)?;718                    let init_value = self.value_block_result_to_sequence(init_result, *loc);719720                    let test_result = self.visit_value_block(*test, *loc, None)?;721                    let test_value = self.value_block_result_to_sequence(test_result, *loc);722723                    let loop_body = if let Some(lid) = loop_id {724                        self.traverse_block(lid)?725                    } else {726                        return Err(CompilerDiagnostic::new(727                            ErrorCategory::Invariant,728                            "Unexpected 'for-of' where the loop is already scheduled",729                            None,730                        ));731                    };732733                    self.cx.unschedule_all(&schedule_ids)?;734                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {735                        terminal: ReactiveTerminal::ForOf {736                            init: init_value,737                            test: test_value,738                            loop_block: loop_body,739                            id: *id,740                            loc: *loc,741                        },742                        label: fallthrough_id.map(|ft| ReactiveLabel {743                            id: ft,744                            implicit: false,745                        }),746                    }));747748                    next_block = fallthrough_id;749                }750751                Terminal::ForIn {752                    init,753                    loop_block,754                    fallthrough,755                    id,756                    loc,757                } => {758                    let loop_id =759                        if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {760                            Some(*loop_block)761                        } else {762                            None763                        };764765                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {766                        Some(*fallthrough)767                    } else {768                        None769                    };770771                    schedule_ids.push(self.cx.schedule_loop(772                        *fallthrough,773                        *init,774                        Some(*loop_block),775                    )?);776777                    let init_result = self.visit_value_block(*init, *loc, None)?;778                    let init_value = self.value_block_result_to_sequence(init_result, *loc);779780                    let loop_body = if let Some(lid) = loop_id {781                        self.traverse_block(lid)?782                    } else {783                        return Err(CompilerDiagnostic::new(784                            ErrorCategory::Invariant,785                            "Unexpected 'for-in' where the loop is already scheduled",786                            None,787                        ));788                    };789790                    self.cx.unschedule_all(&schedule_ids)?;791                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {792                        terminal: ReactiveTerminal::ForIn {793                            init: init_value,794                            loop_block: loop_body,795                            id: *id,796                            loc: *loc,797                        },798                        label: fallthrough_id.map(|ft| ReactiveLabel {799                            id: ft,800                            implicit: false,801                        }),802                    }));803804                    next_block = fallthrough_id;805                }806807                Terminal::Label {808                    block: label_block,809                    fallthrough,810                    id,811                    loc,812                } => {813                    // TS: reachable(fallthrough) && !isScheduled(fallthrough)814                    let fallthrough_id =815                        if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {816                            Some(*fallthrough)817                        } else {818                            None819                        };820                    if let Some(ft) = fallthrough_id {821                        schedule_ids.push(self.cx.schedule(ft, "if")?);822                    }823824                    if self.cx.is_scheduled(*label_block) {825                        return Err(CompilerDiagnostic::new(826                            ErrorCategory::Invariant,827                            "Unexpected 'label' where the block is already scheduled".to_string(),828                            None,829                        ));830                    }831                    let label_body = self.traverse_block(*label_block)?;832833                    self.cx.unschedule_all(&schedule_ids)?;834                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {835                        terminal: ReactiveTerminal::Label {836                            block: label_body,837                            id: *id,838                            loc: *loc,839                        },840                        label: fallthrough_id.map(|ft| ReactiveLabel {841                            id: ft,842                            implicit: false,843                        }),844                    }));845846                    next_block = fallthrough_id;847                }848849                Terminal::Sequence { .. }850                | Terminal::Optional { .. }851                | Terminal::Ternary { .. }852                | Terminal::Logical { .. } => {853                    let fallthrough = match &terminal {854                        Terminal::Sequence { fallthrough, .. }855                        | Terminal::Optional { fallthrough, .. }856                        | Terminal::Ternary { fallthrough, .. }857                        | Terminal::Logical { fallthrough, .. } => *fallthrough,858                        _ => unreachable!(),859                    };860                    let fallthrough_id = if !self.cx.is_scheduled(fallthrough) {861                        Some(fallthrough)862                    } else {863                        None864                    };865                    if let Some(ft) = fallthrough_id {866                        schedule_ids.push(self.cx.schedule(ft, "if")?);867                    }868869                    let result = self.visit_value_block_terminal(&terminal)?;870                    self.cx.unschedule_all(&schedule_ids)?;871                    block_value.push(ReactiveStatement::Instruction(ReactiveInstruction {872                        id: result.id,873                        lvalue: Some(result.place),874                        value: result.value,875                        effects: None,876                        loc: *terminal_loc(&terminal),877                    }));878879                    next_block = fallthrough_id;880                }881882                Terminal::Goto {883                    block: goto_block,884                    variant,885                    id,886                    loc,887                } => {888                    match variant {889                        GotoVariant::Break => {890                            if let Some(stmt) = self.visit_break(*goto_block, *id, *loc)? {891                                block_value.push(stmt);892                            }893                        }894                        GotoVariant::Continue => {895                            let stmt = self.visit_continue(*goto_block, *id, *loc)?;896                            block_value.push(stmt);897                        }898                        GotoVariant::Try => {899                            // noop900                        }901                    }902                }903904                Terminal::MaybeThrow { continuation, .. } => {905                    if !self.cx.is_scheduled(*continuation) {906                        next_block = Some(*continuation);907                    }908                }909910                Terminal::Try {911                    block: try_block,912                    handler_binding,913                    handler,914                    fallthrough,915                    id,916                    loc,917                } => {918                    let fallthrough_id =919                        if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {920                            Some(*fallthrough)921                        } else {922                            None923                        };924                    if let Some(ft) = fallthrough_id {925                        schedule_ids.push(self.cx.schedule(ft, "if")?);926                    }927                    self.cx.schedule_catch_handler(*handler);928929                    let try_body = self.traverse_block(*try_block)?;930                    let handler_body = self.traverse_block(*handler)?;931932                    self.cx.unschedule_all(&schedule_ids)?;933                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {934                        terminal: ReactiveTerminal::Try {935                            block: try_body,936                            handler_binding: handler_binding.clone(),937                            handler: handler_body,938                            id: *id,939                            loc: *loc,940                        },941                        label: fallthrough_id.map(|ft| ReactiveLabel {942                            id: ft,943                            implicit: false,944                        }),945                    }));946947                    next_block = fallthrough_id;948                }949950                Terminal::Scope {951                    fallthrough,952                    block: scope_block,953                    scope,954                    ..955                } => {956                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {957                        Some(*fallthrough)958                    } else {959                        None960                    };961                    if let Some(ft) = fallthrough_id {962                        schedule_ids.push(self.cx.schedule(ft, "if")?);963                        self.cx.scope_fallthroughs.insert(ft);964                    }965966                    if self.cx.is_scheduled(*scope_block) {967                        return Err(CompilerDiagnostic::new(968                            ErrorCategory::Invariant,969                            "Unexpected 'scope' where the block is already scheduled".to_string(),970                            None,971                        ));972                    }973                    let scope_body = self.traverse_block(*scope_block)?;974975                    self.cx.unschedule_all(&schedule_ids)?;976                    block_value.push(ReactiveStatement::Scope(ReactiveScopeBlock {977                        scope: *scope,978                        instructions: scope_body,979                    }));980981                    next_block = fallthrough_id;982                }983984                Terminal::PrunedScope {985                    fallthrough,986                    block: scope_block,987                    scope,988                    ..989                } => {990                    let fallthrough_id = if !self.cx.is_scheduled(*fallthrough) {991                        Some(*fallthrough)992                    } else {993                        None994                    };995                    if let Some(ft) = fallthrough_id {996                        schedule_ids.push(self.cx.schedule(ft, "if")?);997                        self.cx.scope_fallthroughs.insert(ft);998                    }9991000                    if self.cx.is_scheduled(*scope_block) {1001                        return Err(CompilerDiagnostic::new(1002                            ErrorCategory::Invariant,1003                            "Unexpected 'scope' where the block is already scheduled".to_string(),1004                            None,1005                        ));1006                    }1007                    let scope_body = self.traverse_block(*scope_block)?;10081009                    self.cx.unschedule_all(&schedule_ids)?;1010                    block_value.push(ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock {1011                        scope: *scope,1012                        instructions: scope_body,1013                    }));10141015                    next_block = fallthrough_id;1016                }10171018                Terminal::Return { value, id, loc, .. } => {1019                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {1020                        terminal: ReactiveTerminal::Return {1021                            value: value.clone(),1022                            id: *id,1023                            loc: *loc,1024                        },1025                        label: None,1026                    }));1027                }10281029                Terminal::Throw { value, id, loc } => {1030                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {1031                        terminal: ReactiveTerminal::Throw {1032                            value: value.clone(),1033                            id: *id,1034                            loc: *loc,1035                        },1036                        label: None,1037                    }));1038                }10391040                Terminal::Unreachable { .. } => {1041                    // noop1042                }10431044                Terminal::Unsupported { .. } => {1045                    return Err(CompilerDiagnostic::new(1046                        ErrorCategory::Invariant,1047                        "Unexpected unsupported terminal",1048                        None,1049                    ));1050                }10511052                Terminal::Branch {1053                    test,1054                    consequent,1055                    alternate,1056                    id,1057                    loc,1058                    ..1059                } => {1060                    let consequent_block = if self.cx.is_scheduled(*consequent) {1061                        if let Some(stmt) = self.visit_break(*consequent, *id, *loc)? {1062                            vec![stmt]1063                        } else {1064                            Vec::new()1065                        }1066                    } else {1067                        self.traverse_block(*consequent)?1068                    };10691070                    if self.cx.is_scheduled(*alternate) {1071                        return Err(CompilerDiagnostic::new(1072                            ErrorCategory::Invariant,1073                            "Unexpected 'branch' where the alternate is already scheduled"1074                                .to_string(),1075                            None,1076                        ));1077                    }1078                    let alternate_block = self.traverse_block(*alternate)?;10791080                    block_value.push(ReactiveStatement::Terminal(ReactiveTerminalStatement {1081                        terminal: ReactiveTerminal::If {1082                            test: test.clone(),1083                            consequent: consequent_block,1084                            alternate: Some(alternate_block),1085                            id: *id,1086                            loc: *loc,1087                        },1088                        label: None,1089                    }));1090                }1091            }1092            match next_block {1093                Some(nb) => block_id = nb,1094                None => return Ok(()),1095            }1096        } // end loop1097    }10981099    // =========================================================================1100    // Value block processing1101    // =========================================================================11021103    fn visit_value_block(1104        &mut self,1105        block_id: BlockId,1106        loc: Option<SourceLocation>,1107        fallthrough: Option<BlockId>,1108    ) -> Result<ValueBlockResult, CompilerDiagnostic> {1109        let block = &self.hir.body.blocks[&block_id];1110        let block_id_val = block.id;1111        let terminal = block.terminal.clone();1112        let instructions: Vec<_> = block.instructions.clone();11131114        // If we've reached the fallthrough, stop1115        if let Some(ft) = fallthrough {1116            if block_id == ft {1117                return Err(CompilerDiagnostic::new(1118                    ErrorCategory::Invariant,1119                    format!(1120                        "Did not expect to reach the fallthrough of a value block (bb{})",1121                        block_id.01122                    ),1123                    None,1124                ));1125            }1126        }11271128        match &terminal {1129            Terminal::Branch {1130                test, id: term_id, ..1131            } => {1132                if instructions.is_empty() {1133                    Ok(ValueBlockResult {1134                        block: block_id_val,1135                        place: test.clone(),1136                        value: ReactiveValue::Instruction(InstructionValue::LoadLocal {1137                            place: test.clone(),1138                            loc: test.loc,1139                        }),1140                        id: *term_id,1141                    })1142                } else {1143                    Ok(self.extract_value_block_result(&instructions, block_id_val, loc))1144                }1145            }1146            Terminal::Goto { .. } => {1147                if instructions.is_empty() {1148                    return Err(CompilerDiagnostic::new(1149                        ErrorCategory::Invariant,1150                        "Unexpected empty block with `goto` terminal",1151                        Some(format!("Block bb{} is empty", block_id.0)),1152                    )1153                    .with_detail(CompilerDiagnosticDetail::Error {1154                        loc,1155                        message: Some("Unexpected empty block with `goto` terminal".to_string()),1156                        identifier_name: None,1157                    }));1158                }1159                Ok(self.extract_value_block_result(&instructions, block_id_val, loc))1160            }1161            Terminal::MaybeThrow { continuation, .. } => {1162                let continuation_id = *continuation;1163                let continuation_block = self.cx.block(continuation_id);1164                let cont_instructions_empty = continuation_block.instructions.is_empty();1165                let cont_is_goto = matches!(continuation_block.terminal, Terminal::Goto { .. });1166                let cont_block_id = continuation_block.id;11671168                if cont_instructions_empty && cont_is_goto {1169                    Ok(self.extract_value_block_result(&instructions, cont_block_id, loc))1170                } else {1171                    let continuation = self.visit_value_block(continuation_id, loc, fallthrough)?;1172                    Ok(self.wrap_with_sequence(&instructions, continuation, loc))1173                }1174            }1175            _ => {1176                // Value block ended in a value terminal, recurse to get the value1177                // of that terminal and stitch them together in a sequence.1178                // TS: visitValueBlock(init.fallthrough, loc) — does NOT propagate fallthrough1179                let init = self.visit_value_block_terminal(&terminal)?;1180                let init_fallthrough = init.fallthrough;1181                let init_instr = ReactiveInstruction {1182                    id: init.id,1183                    lvalue: Some(init.place),1184                    value: init.value,1185                    effects: None,1186                    loc,1187                };1188                let final_result = self.visit_value_block(init_fallthrough, loc, None)?;11891190                // Combine block instructions + init instruction, then wrap1191                let mut all_instrs: Vec<ReactiveInstruction> = instructions1192                    .iter()1193                    .map(|iid| {1194                        let instr = &self.hir.instructions[iid.0 as usize];1195                        ReactiveInstruction {1196                            id: instr.id,1197                            lvalue: Some(instr.lvalue.clone()),1198                            value: ReactiveValue::Instruction(instr.value.clone()),1199                            effects: instr.effects.clone(),1200                            loc: instr.loc,1201                        }1202                    })1203                    .collect();1204                all_instrs.push(init_instr);12051206                if all_instrs.is_empty() {1207                    Ok(final_result)1208                } else {1209                    Ok(ValueBlockResult {1210                        block: final_result.block,1211                        place: final_result.place.clone(),1212                        value: ReactiveValue::SequenceExpression {1213                            instructions: all_instrs,1214                            id: final_result.id,1215                            value: Box::new(final_result.value),1216                            loc,1217                        },1218                        id: final_result.id,1219                    })1220                }1221            }1222        }1223    }12241225    fn visit_test_block(1226        &mut self,1227        test_block_id: BlockId,1228        loc: Option<SourceLocation>,1229        terminal_kind: &str,1230    ) -> Result<TestBlockResult, CompilerDiagnostic> {1231        let test = self.visit_value_block(test_block_id, loc, None)?;1232        let test_block = &self.hir.body.blocks[&test.block];1233        match &test_block.terminal {1234            Terminal::Branch {1235                consequent,1236                alternate,1237                loc: branch_loc,1238                ..1239            } => Ok(TestBlockResult {1240                test,1241                consequent: *consequent,1242                alternate: *alternate,1243                branch_loc: *branch_loc,1244            }),1245            other => Err(CompilerDiagnostic::new(1246                ErrorCategory::Invariant,1247                format!(1248                    "Expected a branch terminal for {} test block, got {:?}",1249                    terminal_kind,1250                    std::mem::discriminant(other)1251                ),1252                None,1253            )),1254        }1255    }12561257    fn visit_value_block_terminal(1258        &mut self,1259        terminal: &Terminal,1260    ) -> Result<ValueTerminalResult, CompilerDiagnostic> {1261        match terminal {1262            Terminal::Sequence {1263                block,1264                fallthrough,1265                id,1266                loc,1267            } => {1268                let block_result = self.visit_value_block(*block, *loc, Some(*fallthrough))?;1269                Ok(ValueTerminalResult {1270                    value: block_result.value,1271                    place: block_result.place,1272                    fallthrough: *fallthrough,1273                    id: *id,1274                })1275            }1276            Terminal::Optional {1277                optional,1278                test,1279                fallthrough,1280                id,1281                loc,1282            } => {1283                let test_result = self.visit_test_block(*test, *loc, "optional")?;1284                let consequent =1285                    self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;1286                let call = ReactiveValue::SequenceExpression {1287                    instructions: vec![ReactiveInstruction {1288                        id: test_result.test.id,1289                        lvalue: Some(test_result.test.place.clone()),1290                        value: test_result.test.value,1291                        effects: None,1292                        loc: test_result.branch_loc,1293                    }],1294                    id: consequent.id,1295                    value: Box::new(consequent.value),1296                    loc: *loc,1297                };1298                Ok(ValueTerminalResult {1299                    place: consequent.place,1300                    value: ReactiveValue::OptionalExpression {1301                        optional: *optional,1302                        value: Box::new(call),1303                        id: *id,1304                        loc: *loc,1305                    },1306                    fallthrough: *fallthrough,1307                    id: *id,1308                })1309            }1310            Terminal::Logical {1311                operator,1312                test,1313                fallthrough,1314                id,1315                loc,1316            } => {1317                let test_result = self.visit_test_block(*test, *loc, "logical")?;1318                let left_final =1319                    self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;1320                let left = ReactiveValue::SequenceExpression {1321                    instructions: vec![ReactiveInstruction {1322                        id: test_result.test.id,1323                        lvalue: Some(test_result.test.place.clone()),1324                        value: test_result.test.value,1325                        effects: None,1326                        loc: *loc,1327                    }],1328                    id: left_final.id,1329                    value: Box::new(left_final.value),1330                    loc: *loc,1331                };1332                let right =1333                    self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?;1334                Ok(ValueTerminalResult {1335                    place: left_final.place,1336                    value: ReactiveValue::LogicalExpression {1337                        operator: *operator,1338                        left: Box::new(left),1339                        right: Box::new(right.value),1340                        loc: *loc,1341                    },1342                    fallthrough: *fallthrough,1343                    id: *id,1344                })1345            }1346            Terminal::Ternary {1347                test,1348                fallthrough,1349                id,1350                loc,1351            } => {1352                let test_result = self.visit_test_block(*test, *loc, "ternary")?;1353                let consequent =1354                    self.visit_value_block(test_result.consequent, *loc, Some(*fallthrough))?;1355                let alternate =1356                    self.visit_value_block(test_result.alternate, *loc, Some(*fallthrough))?;1357                Ok(ValueTerminalResult {1358                    place: consequent.place,1359                    value: ReactiveValue::ConditionalExpression {1360                        test: Box::new(test_result.test.value),1361                        consequent: Box::new(consequent.value),1362                        alternate: Box::new(alternate.value),1363                        loc: *loc,1364                    },1365                    fallthrough: *fallthrough,1366                    id: *id,1367                })1368            }1369            Terminal::MaybeThrow { .. } => Err(CompilerDiagnostic::new(1370                ErrorCategory::Invariant,1371                "Unexpected maybe-throw in visit_value_block_terminal",1372                None,1373            )),1374            Terminal::Label { .. } => Err(CompilerDiagnostic::new(1375                ErrorCategory::Todo,1376                "Support labeled statements combined with value blocks is not yet implemented",1377                None,1378            )),1379            _ => Err(CompilerDiagnostic::new(1380                ErrorCategory::Invariant,1381                "Unsupported terminal kind in value block",1382                None,1383            )),1384        }1385    }13861387    fn extract_value_block_result(1388        &self,1389        instructions: &[react_compiler_hir::InstructionId],1390        block_id: BlockId,1391        loc: Option<SourceLocation>,1392    ) -> ValueBlockResult {1393        let last_id = instructions1394            .last()1395            .expect("Expected non-empty instructions");1396        let last_instr = &self.hir.instructions[last_id.0 as usize];13971398        let remaining: Vec<ReactiveInstruction> = instructions[..instructions.len() - 1]1399            .iter()1400            .map(|iid| {1401                let instr = &self.hir.instructions[iid.0 as usize];1402                ReactiveInstruction {1403                    id: instr.id,1404                    lvalue: Some(instr.lvalue.clone()),1405                    value: ReactiveValue::Instruction(instr.value.clone()),1406                    effects: instr.effects.clone(),1407                    loc: instr.loc,1408                }1409            })1410            .collect();14111412        // If the last instruction is a StoreLocal to a temporary (unnamed identifier),1413        // convert it to a LoadLocal of the value being stored, matching the TS behavior.1414        let (value, place) = match &last_instr.value {1415            InstructionValue::StoreLocal {1416                lvalue,1417                value: store_value,1418                ..1419            } => {1420                let ident = &self.env.identifiers[lvalue.place.identifier.0 as usize];1421                if ident.name.is_none() {1422                    (1423                        ReactiveValue::Instruction(InstructionValue::LoadLocal {1424                            place: store_value.clone(),1425                            loc: store_value.loc,1426                        }),1427                        lvalue.place.clone(),1428                    )1429                } else {1430                    (1431                        ReactiveValue::Instruction(last_instr.value.clone()),1432                        last_instr.lvalue.clone(),1433                    )1434                }1435            }1436            _ => (1437                ReactiveValue::Instruction(last_instr.value.clone()),1438                last_instr.lvalue.clone(),1439            ),1440        };1441        let id = last_instr.id;14421443        if remaining.is_empty() {1444            ValueBlockResult {1445                block: block_id,1446                place,1447                value,1448                id,1449            }1450        } else {1451            ValueBlockResult {1452                block: block_id,1453                place: place.clone(),1454                value: ReactiveValue::SequenceExpression {1455                    instructions: remaining,1456                    id,1457                    value: Box::new(value),1458                    loc,1459                },1460                id,1461            }1462        }1463    }14641465    fn wrap_with_sequence(1466        &self,1467        instructions: &[react_compiler_hir::InstructionId],1468        continuation: ValueBlockResult,1469        loc: Option<SourceLocation>,1470    ) -> ValueBlockResult {1471        if instructions.is_empty() {1472            return continuation;1473        }14741475        let reactive_instrs: Vec<ReactiveInstruction> = instructions1476            .iter()1477            .map(|iid| {1478                let instr = &self.hir.instructions[iid.0 as usize];1479                ReactiveInstruction {1480                    id: instr.id,1481                    lvalue: Some(instr.lvalue.clone()),1482                    value: ReactiveValue::Instruction(instr.value.clone()),1483                    effects: instr.effects.clone(),1484                    loc: instr.loc,1485                }1486            })1487            .collect();14881489        ValueBlockResult {1490            block: continuation.block,1491            place: continuation.place.clone(),1492            value: ReactiveValue::SequenceExpression {1493                instructions: reactive_instrs,1494                id: continuation.id,1495                value: Box::new(continuation.value),1496                loc,1497            },1498            id: continuation.id,1499        }1500    }15011502    /// Converts the result of visit_value_block into a SequenceExpression that includes1503    /// the instruction with its lvalue. This is needed for for/for-of/for-in init/test1504    /// blocks where the instruction's lvalue assignment must be preserved.1505    ///1506    /// This also flattens nested SequenceExpressions that can occur from MaybeThrow1507    /// handling in try-catch blocks.1508    ///1509    /// TS: valueBlockResultToSequence()1510    fn value_block_result_to_sequence(1511        &self,1512        result: ValueBlockResult,1513        loc: Option<SourceLocation>,1514    ) -> ReactiveValue {1515        // Collect all instructions from potentially nested SequenceExpressions1516        let mut instructions: Vec<ReactiveInstruction> = Vec::new();1517        let mut inner_value = result.value;15181519        // Flatten nested SequenceExpressions1520        loop {1521            match inner_value {1522                ReactiveValue::SequenceExpression {1523                    instructions: seq_instrs,1524                    value,1525                    ..1526                } => {1527                    instructions.extend(seq_instrs);1528                    inner_value = *value;1529                }1530                _ => break,1531            }1532        }15331534        // Only add the final instruction if the innermost value is not just a LoadLocal1535        // of the same place we're storing to (which would be a no-op).1536        let is_load_of_same_place = match &inner_value {1537            ReactiveValue::Instruction(InstructionValue::LoadLocal { place, .. }) => {1538                place.identifier == result.place.identifier1539            }1540            _ => false,1541        };15421543        if !is_load_of_same_place {1544            instructions.push(ReactiveInstruction {1545                id: result.id,1546                lvalue: Some(result.place),1547                value: inner_value,1548                effects: None,1549                loc,1550            });1551        }15521553        ReactiveValue::SequenceExpression {1554            instructions,1555            id: result.id,1556            value: Box::new(ReactiveValue::Instruction(InstructionValue::Primitive {1557                value: react_compiler_hir::PrimitiveValue::Undefined,1558                loc,1559            })),1560            loc,1561        }1562    }15631564    fn visit_break(1565        &self,1566        block: BlockId,1567        id: EvaluationOrder,1568        loc: Option<SourceLocation>,1569    ) -> Result<Option<ReactiveStatement>, CompilerDiagnostic> {1570        let (target_block, target_kind) = self.cx.get_break_target(block)?;1571        if self.cx.scope_fallthroughs.contains(&target_block) {1572            if target_kind != ReactiveTerminalTargetKind::Implicit {1573                return Err(CompilerDiagnostic::new(1574                    ErrorCategory::Invariant,1575                    "Expected reactive scope to implicitly break to fallthrough".to_string(),1576                    None,1577                ));1578            }1579            return Ok(None);1580        }1581        Ok(Some(ReactiveStatement::Terminal(1582            ReactiveTerminalStatement {1583                terminal: ReactiveTerminal::Break {1584                    target: target_block,1585                    id,1586                    target_kind,1587                    loc,1588                },1589                label: None,1590            },1591        )))1592    }15931594    fn visit_continue(1595        &self,1596        block: BlockId,1597        id: EvaluationOrder,1598        loc: Option<SourceLocation>,1599    ) -> Result<ReactiveStatement, CompilerDiagnostic> {1600        let (target_block, target_kind) = match self.cx.get_continue_target(block) {1601            Some(result) => result,1602            None => {1603                return Err(CompilerDiagnostic::new(1604                    ErrorCategory::Invariant,1605                    format!("Expected continue target to be scheduled for bb{}", block.0),1606                    None,1607                ));1608            }1609        };16101611        Ok(ReactiveStatement::Terminal(ReactiveTerminalStatement {1612            terminal: ReactiveTerminal::Continue {1613                target: target_block,1614                id,1615                target_kind,1616                loc,1617            },1618            label: None,1619        }))1620    }1621}16221623// =============================================================================1624// Helper types1625// =============================================================================16261627struct ValueBlockResult {1628    block: BlockId,1629    place: Place,1630    value: ReactiveValue,1631    id: EvaluationOrder,1632}16331634struct TestBlockResult {1635    test: ValueBlockResult,1636    consequent: BlockId,1637    alternate: BlockId,1638    branch_loc: Option<SourceLocation>,1639}16401641struct ValueTerminalResult {1642    value: ReactiveValue,1643    place: Place,1644    fallthrough: BlockId,1645    id: EvaluationOrder,1646}16471648/// Helper to get loc from a terminal1649fn terminal_loc(terminal: &Terminal) -> &Option<SourceLocation> {1650    match terminal {1651        Terminal::If { loc, .. }1652        | Terminal::Branch { loc, .. }1653        | Terminal::Logical { loc, .. }1654        | Terminal::Ternary { loc, .. }1655        | Terminal::Optional { loc, .. }1656        | Terminal::Throw { loc, .. }1657        | Terminal::Return { loc, .. }1658        | Terminal::Goto { loc, .. }1659        | Terminal::Switch { loc, .. }1660        | Terminal::DoWhile { loc, .. }1661        | Terminal::While { loc, .. }1662        | Terminal::For { loc, .. }1663        | Terminal::ForOf { loc, .. }1664        | Terminal::ForIn { loc, .. }1665        | Terminal::Label { loc, .. }1666        | Terminal::Sequence { loc, .. }1667        | Terminal::Unreachable { loc, .. }1668        | Terminal::Unsupported { loc, .. }1669        | Terminal::MaybeThrow { loc, .. }1670        | Terminal::Scope { loc, .. }1671        | Terminal::PrunedScope { loc, .. }1672        | Terminal::Try { loc, .. } => loc,1673    }1674}

Findings

✓ No findings reported for this file.

Get this view in your editor

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