compiler/crates/react_compiler_reactive_scopes/src/codegen_reactive_function.rs RUST 4,342 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,342.
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//! Code generation pass: converts a `ReactiveFunction` tree back into a Babel-compatible7//! AST with memoization (useMemoCache) wired in.8//!9//! This is the final pass in the compilation pipeline.10//!11//! Corresponds to `src/ReactiveScopes/CodegenReactiveFunction.ts` in the TS compiler.1213use rustc_hash::{FxHashMap, FxHashSet};1415use react_compiler_ast::common::BaseNode;16use react_compiler_ast::common::Position as AstPosition;17use react_compiler_ast::common::RawNode;18use react_compiler_ast::common::SourceLocation as AstSourceLocation;19use react_compiler_ast::expressions::ArrowFunctionBody;20use react_compiler_ast::expressions::Expression;21use react_compiler_ast::expressions::Identifier as AstIdentifier;22use react_compiler_ast::expressions::{self as ast_expr};23use react_compiler_ast::jsx::JSXAttribute as AstJSXAttribute;24use react_compiler_ast::jsx::JSXAttributeItem;25use react_compiler_ast::jsx::JSXAttributeName;26use react_compiler_ast::jsx::JSXAttributeValue;27use react_compiler_ast::jsx::JSXChild;28use react_compiler_ast::jsx::JSXClosingElement;29use react_compiler_ast::jsx::JSXClosingFragment;30use react_compiler_ast::jsx::JSXElement;31use react_compiler_ast::jsx::JSXElementName;32use react_compiler_ast::jsx::JSXExpressionContainer;33use react_compiler_ast::jsx::JSXExpressionContainerExpr;34use react_compiler_ast::jsx::JSXFragment;35use react_compiler_ast::jsx::JSXIdentifier;36use react_compiler_ast::jsx::JSXMemberExprObject;37use react_compiler_ast::jsx::JSXMemberExpression;38use react_compiler_ast::jsx::JSXNamespacedName;39use react_compiler_ast::jsx::JSXOpeningElement;40use react_compiler_ast::jsx::JSXOpeningFragment;41use react_compiler_ast::jsx::JSXSpreadAttribute;42use react_compiler_ast::jsx::JSXText;43use react_compiler_ast::literals::BooleanLiteral;44use react_compiler_ast::literals::NullLiteral;45use react_compiler_ast::literals::NumericLiteral;46use react_compiler_ast::literals::RegExpLiteral as AstRegExpLiteral;47use react_compiler_ast::literals::StringLiteral;48use react_compiler_ast::literals::TemplateElement;49use react_compiler_ast::literals::TemplateElementValue;50use react_compiler_ast::operators::AssignmentOperator;51use react_compiler_ast::operators::BinaryOperator as AstBinaryOperator;52use react_compiler_ast::operators::LogicalOperator as AstLogicalOperator;53use react_compiler_ast::operators::UnaryOperator as AstUnaryOperator;54use react_compiler_ast::operators::UpdateOperator as AstUpdateOperator;55use react_compiler_ast::patterns::ArrayPattern as AstArrayPattern;56use react_compiler_ast::patterns::ObjectPatternProp;57use react_compiler_ast::patterns::ObjectPatternProperty;58use react_compiler_ast::patterns::PatternLike;59use react_compiler_ast::patterns::RestElement;60use react_compiler_ast::statements::BlockStatement;61use react_compiler_ast::statements::BreakStatement;62use react_compiler_ast::statements::CatchClause;63use react_compiler_ast::statements::ContinueStatement;64use react_compiler_ast::statements::DebuggerStatement;65use react_compiler_ast::statements::Directive;66use react_compiler_ast::statements::DirectiveLiteral;67use react_compiler_ast::statements::DoWhileStatement;68use react_compiler_ast::statements::EmptyStatement;69use react_compiler_ast::statements::ExpressionStatement;70use react_compiler_ast::statements::ForInStatement;71use react_compiler_ast::statements::ForInit;72use react_compiler_ast::statements::ForOfStatement;73use react_compiler_ast::statements::ForStatement;74use react_compiler_ast::statements::FunctionDeclaration;75use react_compiler_ast::statements::IfStatement;76use react_compiler_ast::statements::LabeledStatement;77use react_compiler_ast::statements::ReturnStatement;78use react_compiler_ast::statements::Statement;79use react_compiler_ast::statements::SwitchCase;80use react_compiler_ast::statements::SwitchStatement;81use react_compiler_ast::statements::ThrowStatement;82use react_compiler_ast::statements::TryStatement;83use react_compiler_ast::statements::UnknownStatement;84use react_compiler_ast::statements::VariableDeclaration;85use react_compiler_ast::statements::VariableDeclarationKind;86use react_compiler_ast::statements::VariableDeclarator;87use react_compiler_ast::statements::WhileStatement;88use react_compiler_ast::statements::is_known_statement_type;89use react_compiler_diagnostics::CompilerDiagnostic;90use react_compiler_diagnostics::CompilerDiagnosticDetail;91use react_compiler_diagnostics::CompilerError;92use react_compiler_diagnostics::CompilerErrorDetail;93use react_compiler_diagnostics::ErrorCategory;94use react_compiler_diagnostics::SourceLocation as DiagSourceLocation;95use react_compiler_hir::ArrayElement;96use react_compiler_hir::ArrayPattern;97use react_compiler_hir::BlockId;98use react_compiler_hir::DeclarationId;99use react_compiler_hir::FunctionExpressionType;100use react_compiler_hir::IdentifierId;101use react_compiler_hir::InstructionKind;102use react_compiler_hir::InstructionValue;103use react_compiler_hir::JsxAttribute;104use react_compiler_hir::JsxTag;105use react_compiler_hir::LogicalOperator;106use react_compiler_hir::ObjectPattern;107use react_compiler_hir::ObjectPropertyKey;108use react_compiler_hir::ObjectPropertyOrSpread;109use react_compiler_hir::ObjectPropertyType;110use react_compiler_hir::ParamPattern;111use react_compiler_hir::Pattern;112use react_compiler_hir::Place;113use react_compiler_hir::PlaceOrSpread;114use react_compiler_hir::PrimitiveValue;115use react_compiler_hir::PropertyLiteral;116use react_compiler_hir::ScopeId;117use react_compiler_hir::SpreadPattern;118use react_compiler_hir::environment::Environment;119use react_compiler_hir::reactive::PrunedReactiveScopeBlock;120use react_compiler_hir::reactive::ReactiveBlock;121use react_compiler_hir::reactive::ReactiveFunction;122use react_compiler_hir::reactive::ReactiveInstruction;123use react_compiler_hir::reactive::ReactiveScopeBlock;124use react_compiler_hir::reactive::ReactiveStatement;125use react_compiler_hir::reactive::ReactiveTerminal;126use react_compiler_hir::reactive::ReactiveTerminalTargetKind;127use react_compiler_hir::reactive::ReactiveValue;128129use crate::build_reactive_function::build_reactive_function;130use crate::prune_hoisted_contexts::prune_hoisted_contexts;131use crate::prune_unused_labels::prune_unused_labels;132use crate::prune_unused_lvalues::prune_unused_lvalues;133use crate::rename_variables::rename_variables;134use crate::visitors::ReactiveFunctionVisitor;135use crate::visitors::visit_reactive_function;136137// =============================================================================138// Public API139// =============================================================================140141pub const MEMO_CACHE_SENTINEL: &str = "react.memo_cache_sentinel";142pub const EARLY_RETURN_SENTINEL: &str = "react.early_return_sentinel";143144/// FBT tags whose children get special codegen treatment.145const SINGLE_CHILD_FBT_TAGS: &[&str] = &["fbt:param", "fbs:param"];146147/// Result of code generation for a single function.148pub struct CodegenFunction {149    pub loc: Option<DiagSourceLocation>,150    pub id: Option<AstIdentifier>,151    pub name_hint: Option<String>,152    pub params: Vec<PatternLike>,153    pub body: BlockStatement,154    pub generator: bool,155    pub is_async: bool,156    pub memo_slots_used: u32,157    pub memo_blocks: u32,158    pub memo_values: u32,159    pub pruned_memo_blocks: u32,160    pub pruned_memo_values: u32,161    pub outlined: Vec<OutlinedFunction>,162}163164impl std::fmt::Debug for CodegenFunction {165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {166        f.debug_struct("CodegenFunction")167            .field("memo_slots_used", &self.memo_slots_used)168            .field("memo_blocks", &self.memo_blocks)169            .field("memo_values", &self.memo_values)170            .field("pruned_memo_blocks", &self.pruned_memo_blocks)171            .field("pruned_memo_values", &self.pruned_memo_values)172            .finish()173    }174}175176/// An outlined function extracted during compilation.177pub struct OutlinedFunction {178    pub func: CodegenFunction,179    pub fn_type: Option<react_compiler_hir::ReactFunctionType>,180}181182/// Top-level entry point: generates code for a reactive function.183/// Computes the Fast Refresh source hash used to bust the memo cache when the184/// source file changes. Matches the TS compiler's185/// `createHmac('sha256', code).digest('hex')`: an HMAC-SHA256 keyed by the186/// source code, hashing empty data.187fn source_file_hash(code: &str) -> String {188    hmac_sha256::HMAC::mac(b"", code.as_bytes())189        .iter()190        .map(|b| format!("{b:02x}"))191        .collect()192}193194pub fn codegen_function(195    func: &ReactiveFunction,196    env: &mut Environment,197    unique_identifiers: FxHashSet<String>,198    fbt_operands: FxHashSet<IdentifierId>,199) -> Result<CodegenFunction, CompilerError> {200    let fn_name = func.id.as_deref().unwrap_or("[[ anonymous ]]");201    let mut cx = Context::new(env, fn_name.to_string(), unique_identifiers, fbt_operands);202203    // Fast Refresh: compute source hash and reserve a cache slot if enabled204    let fast_refresh_state: Option<(u32, String)> =205        if cx.env.config.enable_reset_cache_on_source_file_changes == Some(true) {206            if let Some(ref code) = cx.env.code {207                let hash = source_file_hash(code);208                let cache_index = cx.alloc_cache_index(); // Reserve slot 0 for the hash check209                Some((cache_index, hash))210            } else {211                None212            }213        } else {214            None215        };216217    let mut compiled = codegen_reactive_function(&mut cx, func)?;218219    // enableEmitHookGuards: wrap entire function body in try/finally with220    // $dispatcherGuard(PushHookGuard=0) / $dispatcherGuard(PopHookGuard=1).221    // Per-hook-call wrapping is done inline during codegen (CallExpression/MethodCall).222    if cx.env.hook_guard_name.is_some()223        && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client224    {225        let guard_name = cx.env.hook_guard_name.as_ref().unwrap().clone();226        let body_stmts = std::mem::replace(&mut compiled.body.body, Vec::new());227        compiled.body.body = vec![create_function_body_hook_guard(228            &guard_name,229            body_stmts,230            0,231            1,232        )];233    }234235    let cache_count = compiled.memo_slots_used;236    if cache_count != 0 {237        let mut preface: Vec<Statement> = Vec::new();238        let cache_name = cx.synthesize_name("$");239240        // const $ = useMemoCache(N)241        preface.push(Statement::VariableDeclaration(VariableDeclaration {242            base: BaseNode::typed("VariableDeclaration"),243            declarations: vec![VariableDeclarator {244                base: BaseNode::typed("VariableDeclarator"),245                id: PatternLike::Identifier(make_identifier(&cache_name)),246                init: Some(Box::new(Expression::CallExpression(247                    ast_expr::CallExpression {248                        base: BaseNode::typed("CallExpression"),249                        callee: Box::new(Expression::Identifier(make_identifier("useMemoCache"))),250                        arguments: vec![Expression::NumericLiteral(NumericLiteral {251                            base: BaseNode::typed("NumericLiteral"),252                            value: cache_count as f64,253                            extra: None,254                        })],255                        type_parameters: None,256                        type_arguments: None,257                        optional: None,258                    },259                ))),260                definite: None,261            }],262            kind: VariableDeclarationKind::Const,263            declare: None,264        }));265266        // Fast Refresh: emit cache invalidation check after useMemoCache267        if let Some((cache_index, ref hash)) = fast_refresh_state {268            let index_var = cx.synthesize_name("$i");269            // if ($[cacheIndex] !== "hash") { for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } $[cacheIndex] = "hash"; }270            preface.push(Statement::IfStatement(IfStatement {271                base: BaseNode::typed("IfStatement"),272                test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression {273                    base: BaseNode::typed("BinaryExpression"),274                    operator: AstBinaryOperator::StrictNeq,275                    left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {276                        base: BaseNode::typed("MemberExpression"),277                        object: Box::new(Expression::Identifier(make_identifier(&cache_name))),278                        property: Box::new(Expression::NumericLiteral(NumericLiteral {279                            base: BaseNode::typed("NumericLiteral"),280                            value: cache_index as f64,281                            extra: None,282                        })),283                        computed: true,284                    })),285                    right: Box::new(Expression::StringLiteral(StringLiteral {286                        base: BaseNode::typed("StringLiteral"),287                        value: hash.clone().into(),288                    })),289                })),290                consequent: Box::new(Statement::BlockStatement(BlockStatement {291                    base: BaseNode::typed("BlockStatement"),292                    body: vec![293                        // for (let $i = 0; $i < N; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); }294                        Statement::ForStatement(ForStatement {295                            base: BaseNode::typed("ForStatement"),296                            init: Some(Box::new(ForInit::VariableDeclaration(297                                VariableDeclaration {298                                    base: BaseNode::typed("VariableDeclaration"),299                                    declarations: vec![VariableDeclarator {300                                        base: BaseNode::typed("VariableDeclarator"),301                                        id: PatternLike::Identifier(make_identifier(&index_var)),302                                        init: Some(Box::new(Expression::NumericLiteral(303                                            NumericLiteral {304                                                base: BaseNode::typed("NumericLiteral"),305                                                value: 0.0,306                                                extra: None,307                                            },308                                        ))),309                                        definite: None,310                                    }],311                                    kind: VariableDeclarationKind::Let,312                                    declare: None,313                                },314                            ))),315                            test: Some(Box::new(Expression::BinaryExpression(316                                ast_expr::BinaryExpression {317                                    base: BaseNode::typed("BinaryExpression"),318                                    operator: AstBinaryOperator::Lt,319                                    left: Box::new(Expression::Identifier(make_identifier(320                                        &index_var,321                                    ))),322                                    right: Box::new(Expression::NumericLiteral(NumericLiteral {323                                        base: BaseNode::typed("NumericLiteral"),324                                        value: cache_count as f64,325                                        extra: None,326                                    })),327                                },328                            ))),329                            update: Some(Box::new(Expression::AssignmentExpression(330                                ast_expr::AssignmentExpression {331                                    base: BaseNode::typed("AssignmentExpression"),332                                    operator: AssignmentOperator::AddAssign,333                                    left: Box::new(PatternLike::Identifier(make_identifier(334                                        &index_var,335                                    ))),336                                    right: Box::new(Expression::NumericLiteral(NumericLiteral {337                                        base: BaseNode::typed("NumericLiteral"),338                                        value: 1.0,339                                        extra: None,340                                    })),341                                },342                            ))),343                            body: Box::new(Statement::BlockStatement(BlockStatement {344                                base: BaseNode::typed("BlockStatement"),345                                body: vec![Statement::ExpressionStatement(ExpressionStatement {346                                    base: BaseNode::typed("ExpressionStatement"),347                                    expression: Box::new(Expression::AssignmentExpression(348                                        ast_expr::AssignmentExpression {349                                            base: BaseNode::typed("AssignmentExpression"),350                                            operator: AssignmentOperator::Assign,351                                            left: Box::new(PatternLike::MemberExpression(352                                                ast_expr::MemberExpression {353                                                    base: BaseNode::typed("MemberExpression"),354                                                    object: Box::new(Expression::Identifier(355                                                        make_identifier(&cache_name),356                                                    )),357                                                    property: Box::new(Expression::Identifier(358                                                        make_identifier(&index_var),359                                                    )),360                                                    computed: true,361                                                },362                                            )),363                                            right: Box::new(Expression::CallExpression(364                                                ast_expr::CallExpression {365                                                    base: BaseNode::typed("CallExpression"),366                                                    callee: Box::new(Expression::MemberExpression(367                                                        ast_expr::MemberExpression {368                                                            base: BaseNode::typed(369                                                                "MemberExpression",370                                                            ),371                                                            object: Box::new(372                                                                Expression::Identifier(373                                                                    make_identifier("Symbol"),374                                                                ),375                                                            ),376                                                            property: Box::new(377                                                                Expression::Identifier(378                                                                    make_identifier("for"),379                                                                ),380                                                            ),381                                                            computed: false,382                                                        },383                                                    )),384                                                    arguments: vec![Expression::StringLiteral(385                                                        StringLiteral {386                                                            base: BaseNode::typed("StringLiteral"),387                                                            value: MEMO_CACHE_SENTINEL388                                                                .to_string()389                                                                .into(),390                                                        },391                                                    )],392                                                    type_parameters: None,393                                                    type_arguments: None,394                                                    optional: None,395                                                },396                                            )),397                                        },398                                    )),399                                })],400                                directives: Vec::new(),401                            })),402                        }),403                        // $[cacheIndex] = "hash"404                        Statement::ExpressionStatement(ExpressionStatement {405                            base: BaseNode::typed("ExpressionStatement"),406                            expression: Box::new(Expression::AssignmentExpression(407                                ast_expr::AssignmentExpression {408                                    base: BaseNode::typed("AssignmentExpression"),409                                    operator: AssignmentOperator::Assign,410                                    left: Box::new(PatternLike::MemberExpression(411                                        ast_expr::MemberExpression {412                                            base: BaseNode::typed("MemberExpression"),413                                            object: Box::new(Expression::Identifier(414                                                make_identifier(&cache_name),415                                            )),416                                            property: Box::new(Expression::NumericLiteral(417                                                NumericLiteral {418                                                    base: BaseNode::typed("NumericLiteral"),419                                                    value: cache_index as f64,420                                                    extra: None,421                                                },422                                            )),423                                            computed: true,424                                        },425                                    )),426                                    right: Box::new(Expression::StringLiteral(StringLiteral {427                                        base: BaseNode::typed("StringLiteral"),428                                        value: hash.clone().into(),429                                    })),430                                },431                            )),432                        }),433                    ],434                    directives: Vec::new(),435                })),436                alternate: None,437            }));438        }439440        // Insert preface at the beginning of the body441        let mut new_body = preface;442        new_body.append(&mut compiled.body.body);443        compiled.body.body = new_body;444    }445446    // Instrument forget: emit instrumentation call at the top of the function body447    let emit_instrument_forget = cx.env.config.enable_emit_instrument_forget.clone();448    if let Some(ref instrument_config) = emit_instrument_forget {449        if func.id.is_some()450            && cx.env.output_mode == react_compiler_hir::environment::OutputMode::Client451        {452            // Use pre-resolved import names from environment (set by program-level code)453            let instrument_fn_local = cx454                .env455                .instrument_fn_name456                .clone()457                .unwrap_or_else(|| instrument_config.fn_.import_specifier_name.clone());458            let instrument_gating_local = cx.env.instrument_gating_name.clone();459460            // Build the gating condition461            let gating_expr: Option<Expression> =462                instrument_gating_local.map(|name| Expression::Identifier(make_identifier(&name)));463            let global_gating_expr: Option<Expression> = instrument_config464                .global_gating465                .as_ref()466                .map(|g| Expression::Identifier(make_identifier(g)));467468            let if_test = match (gating_expr, global_gating_expr) {469                (Some(gating), Some(global)) => {470                    Expression::LogicalExpression(ast_expr::LogicalExpression {471                        base: BaseNode::typed("LogicalExpression"),472                        operator: AstLogicalOperator::And,473                        left: Box::new(global),474                        right: Box::new(gating),475                    })476                }477                (Some(gating), None) => gating,478                (None, Some(global)) => global,479                (None, None) => unreachable!(480                    "InstrumentationConfig requires at least one of gating or globalGating"481                ),482            };483484            let fn_name_str = func.id.as_deref().unwrap_or("");485            let filename_str = cx.env.filename.as_deref().unwrap_or("");486487            let instrument_call = Statement::IfStatement(IfStatement {488                base: BaseNode::typed("IfStatement"),489                test: Box::new(if_test),490                consequent: Box::new(Statement::ExpressionStatement(ExpressionStatement {491                    base: BaseNode::typed("ExpressionStatement"),492                    expression: Box::new(Expression::CallExpression(ast_expr::CallExpression {493                        base: BaseNode::typed("CallExpression"),494                        callee: Box::new(Expression::Identifier(make_identifier(495                            &instrument_fn_local,496                        ))),497                        arguments: vec![498                            Expression::StringLiteral(StringLiteral {499                                base: BaseNode::typed("StringLiteral"),500                                value: fn_name_str.to_string().into(),501                            }),502                            Expression::StringLiteral(StringLiteral {503                                base: BaseNode::typed("StringLiteral"),504                                value: filename_str.to_string().into(),505                            }),506                        ],507                        type_parameters: None,508                        type_arguments: None,509                        optional: None,510                    })),511                })),512                alternate: None,513            });514            compiled.body.body.insert(0, instrument_call);515        }516    }517518    // Process outlined functions.519    // Use clone (not take) to match TS behavior: getOutlinedFunctions() returns520    // a reference, so outlined functions persist on the environment and are also521    // available to the parent function's codegen. The inner function codegen522    // processes them here, and the parent/top-level codegen processes them again.523    let outlined_entries = cx.env.get_outlined_functions().to_vec();524    let mut outlined: Vec<OutlinedFunction> = Vec::new();525    for entry in outlined_entries {526        let reactive_fn = build_reactive_function(&entry.func, cx.env)?;527        let mut reactive_fn_mut = reactive_fn;528        prune_unused_labels(&mut reactive_fn_mut, cx.env)?;529        prune_unused_lvalues(&mut reactive_fn_mut, cx.env);530        prune_hoisted_contexts(&mut reactive_fn_mut, cx.env)?;531532        let identifiers = rename_variables(&mut reactive_fn_mut, cx.env);533        let mut outlined_cx = Context::new(534            cx.env,535            reactive_fn_mut536                .id537                .as_deref()538                .unwrap_or("[[ anonymous ]]")539                .to_string(),540            identifiers,541            cx.fbt_operands.clone(),542        );543        let codegen = codegen_reactive_function(&mut outlined_cx, &reactive_fn_mut)?;544        outlined.push(OutlinedFunction {545            func: codegen,546            fn_type: entry.fn_type,547        });548    }549    compiled.outlined = outlined;550551    Ok(compiled)552}553554// =============================================================================555// Context556// =============================================================================557558type Temporaries = FxHashMap<DeclarationId, Option<ExpressionOrJsxText>>;559560#[derive(Clone)]561enum ExpressionOrJsxText {562    Expression(Expression),563    JsxText(JSXText),564}565566struct Context<'env> {567    env: &'env mut Environment,568    #[allow(dead_code)]569    fn_name: String,570    next_cache_index: u32,571    declarations: FxHashSet<DeclarationId>,572    temp: Temporaries,573    object_methods: FxHashMap<574        IdentifierId,575        (576            InstructionValue,577            Option<react_compiler_diagnostics::SourceLocation>,578        ),579    >,580    unique_identifiers: FxHashSet<String>,581    fbt_operands: FxHashSet<IdentifierId>,582    synthesized_names: FxHashMap<String, String>,583}584585impl<'env> Context<'env> {586    fn new(587        env: &'env mut Environment,588        fn_name: String,589        unique_identifiers: FxHashSet<String>,590        fbt_operands: FxHashSet<IdentifierId>,591    ) -> Self {592        Context {593            env,594            fn_name,595            next_cache_index: 0,596            declarations: FxHashSet::default(),597            temp: FxHashMap::default(),598            object_methods: FxHashMap::default(),599            unique_identifiers,600            fbt_operands,601            synthesized_names: FxHashMap::default(),602        }603    }604605    fn alloc_cache_index(&mut self) -> u32 {606        let idx = self.next_cache_index;607        self.next_cache_index += 1;608        idx609    }610611    fn declare(&mut self, identifier_id: IdentifierId) {612        let ident = &self.env.identifiers[identifier_id.0 as usize];613        self.declarations.insert(ident.declaration_id);614    }615616    fn has_declared(&self, identifier_id: IdentifierId) -> bool {617        let ident = &self.env.identifiers[identifier_id.0 as usize];618        self.declarations.contains(&ident.declaration_id)619    }620621    fn synthesize_name(&mut self, name: &str) -> String {622        if let Some(prev) = self.synthesized_names.get(name) {623            return prev.clone();624        }625        let mut validated = name.to_string();626        let mut index = 0u32;627        while self.unique_identifiers.contains(&validated) {628            validated = format!("{name}{index}");629            index += 1;630        }631        self.unique_identifiers.insert(validated.clone());632        self.synthesized_names633            .insert(name.to_string(), validated.clone());634        validated635    }636637    fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> {638        self.env.record_error(detail)639    }640}641642// =============================================================================643// Core codegen functions644// =============================================================================645646fn codegen_reactive_function(647    cx: &mut Context,648    func: &ReactiveFunction,649) -> Result<CodegenFunction, CompilerError> {650    // Register parameters651    for param in &func.params {652        let place = match param {653            ParamPattern::Place(p) => p,654            ParamPattern::Spread(sp) => &sp.place,655        };656        let ident = &cx.env.identifiers[place.identifier.0 as usize];657        cx.temp.insert(ident.declaration_id, None);658        cx.declare(place.identifier);659    }660661    let params: Vec<PatternLike> = func662        .params663        .iter()664        .map(|p| convert_parameter(p, cx.env))665        .collect::<Result<_, _>>()?;666    let mut body = codegen_block(cx, &func.body)?;667668    // Add directives669    body.directives = func670        .directives671        .iter()672        .map(|d| Directive {673            base: BaseNode::typed("Directive"),674            value: DirectiveLiteral {675                base: BaseNode::typed("DirectiveLiteral"),676                value: d.clone(),677            },678        })679        .collect();680681    // Remove trailing `return undefined`682    if let Some(last) = body.body.last() {683        if matches!(last, Statement::ReturnStatement(ret) if ret.argument.is_none()) {684            body.body.pop();685        }686    }687688    // Count memo blocks689    let (memo_blocks, memo_values, pruned_memo_blocks, pruned_memo_values) =690        count_memo_blocks(func, cx.env);691692    Ok(CodegenFunction {693        loc: func.loc,694        id: func.id.as_ref().map(|name| make_identifier(name)),695        name_hint: func.name_hint.clone(),696        params,697        body,698        generator: func.generator,699        is_async: func.is_async,700        memo_slots_used: cx.next_cache_index,701        memo_blocks,702        memo_values,703        pruned_memo_blocks,704        pruned_memo_values,705        outlined: Vec::new(),706    })707}708709fn convert_parameter(710    param: &ParamPattern,711    env: &Environment,712) -> Result<PatternLike, CompilerError> {713    match param {714        ParamPattern::Place(place) => Ok(PatternLike::Identifier(convert_identifier(715            place.identifier,716            env,717        )?)),718        ParamPattern::Spread(spread) => Ok(PatternLike::RestElement(RestElement {719            base: BaseNode::typed("RestElement"),720            argument: Box::new(PatternLike::Identifier(convert_identifier(721                spread.place.identifier,722                env,723            )?)),724            type_annotation: None,725            decorators: None,726        })),727    }728}729730// =============================================================================731// Block codegen732// =============================================================================733734fn codegen_block(cx: &mut Context, block: &ReactiveBlock) -> Result<BlockStatement, CompilerError> {735    let temp_snapshot: Temporaries = cx.temp.clone();736    let result = codegen_block_no_reset(cx, block)?;737    cx.temp = temp_snapshot;738    Ok(result)739}740741fn codegen_block_no_reset(742    cx: &mut Context,743    block: &ReactiveBlock,744) -> Result<BlockStatement, CompilerError> {745    let mut statements: Vec<Statement> = Vec::new();746    for item in block {747        match item {748            ReactiveStatement::Instruction(instr) => {749                if let Some(stmt) = codegen_instruction_nullable(cx, instr)? {750                    statements.push(stmt);751                }752            }753            ReactiveStatement::PrunedScope(PrunedReactiveScopeBlock { instructions, .. }) => {754                let scope_block = codegen_block_no_reset(cx, instructions)?;755                statements.extend(scope_block.body);756            }757            ReactiveStatement::Scope(ReactiveScopeBlock {758                scope,759                instructions,760            }) => {761                let temp_snapshot = cx.temp.clone();762                codegen_reactive_scope(cx, &mut statements, *scope, instructions)?;763                cx.temp = temp_snapshot;764            }765            ReactiveStatement::Terminal(term_stmt) => {766                let stmt = codegen_terminal(cx, &term_stmt.terminal)?;767                let Some(stmt) = stmt else {768                    continue;769                };770                if let Some(ref label) = term_stmt.label {771                    if !label.implicit {772                        let inner = if let Statement::BlockStatement(bs) = &stmt {773                            if bs.body.len() == 1 {774                                bs.body[0].clone()775                            } else {776                                stmt777                            }778                        } else {779                            stmt780                        };781                        statements.push(Statement::LabeledStatement(LabeledStatement {782                            base: BaseNode::typed("LabeledStatement"),783                            label: make_identifier(&codegen_label(label.id)),784                            body: Box::new(inner),785                        }));786                    } else if let Statement::BlockStatement(bs) = stmt {787                        statements.extend(bs.body);788                    } else {789                        statements.push(stmt);790                    }791                } else if let Statement::BlockStatement(bs) = stmt {792                    statements.extend(bs.body);793                } else {794                    statements.push(stmt);795                }796            }797        }798    }799    Ok(BlockStatement {800        base: BaseNode::typed("BlockStatement"),801        body: statements,802        directives: Vec::new(),803    })804}805806// =============================================================================807// Reactive scope codegen (memoization)808// =============================================================================809810fn codegen_reactive_scope(811    cx: &mut Context,812    statements: &mut Vec<Statement>,813    scope_id: ScopeId,814    block: &ReactiveBlock,815) -> Result<(), CompilerError> {816    // Clone scope data upfront to avoid holding a borrow on cx.env817    let scope_deps = cx.env.scopes[scope_id.0 as usize].dependencies.clone();818    let scope_decls = cx.env.scopes[scope_id.0 as usize].declarations.clone();819    let scope_reassignments = cx.env.scopes[scope_id.0 as usize].reassignments.clone();820821    let mut cache_store_stmts: Vec<Statement> = Vec::new();822    let mut cache_load_stmts: Vec<Statement> = Vec::new();823    let mut cache_loads: Vec<(AstIdentifier, u32, Expression)> = Vec::new();824    let mut change_exprs: Vec<Expression> = Vec::new();825826    // Sort dependencies827    let mut deps = scope_deps;828    deps.sort_by(|a, b| compare_scope_dependency(a, b, cx.env));829830    for dep in &deps {831        let index = cx.alloc_cache_index();832        let cache_name = cx.synthesize_name("$");833        let comparison = Expression::BinaryExpression(ast_expr::BinaryExpression {834            base: BaseNode::typed("BinaryExpression"),835            operator: AstBinaryOperator::StrictNeq,836            left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {837                base: BaseNode::typed("MemberExpression"),838                object: Box::new(Expression::Identifier(make_identifier(&cache_name))),839                property: Box::new(Expression::NumericLiteral(NumericLiteral {840                    base: BaseNode::typed("NumericLiteral"),841                    value: index as f64,842                    extra: None,843                })),844                computed: true,845            })),846            right: Box::new(codegen_dependency(cx, dep)?),847        });848        change_exprs.push(comparison);849850        // Store dependency value into cache851        let dep_value = codegen_dependency(cx, dep)?;852        cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement {853            base: BaseNode::typed("ExpressionStatement"),854            expression: Box::new(Expression::AssignmentExpression(855                ast_expr::AssignmentExpression {856                    base: BaseNode::typed("AssignmentExpression"),857                    operator: AssignmentOperator::Assign,858                    left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {859                        base: BaseNode::typed("MemberExpression"),860                        object: Box::new(Expression::Identifier(make_identifier(&cache_name))),861                        property: Box::new(Expression::NumericLiteral(NumericLiteral {862                            base: BaseNode::typed("NumericLiteral"),863                            value: index as f64,864                            extra: None,865                        })),866                        computed: true,867                    })),868                    right: Box::new(dep_value),869                },870            )),871        }));872    }873874    let mut first_output_index: Option<u32> = None;875876    // Sort declarations877    let mut decls = scope_decls;878    decls.sort_by(|(_id_a, a), (_id_b, b)| compare_scope_declaration(a, b, cx.env));879880    for (_ident_id, decl) in &decls {881        let index = cx.alloc_cache_index();882        if first_output_index.is_none() {883            first_output_index = Some(index);884        }885886        let ident = &cx.env.identifiers[decl.identifier.0 as usize];887        invariant(888            ident.name.is_some(),889            &format!(890                "Expected scope declaration identifier to be named, id={}",891                decl.identifier.0892            ),893            None,894        )?;895896        let name = convert_identifier(decl.identifier, cx.env)?;897        if !cx.has_declared(decl.identifier) {898            statements.push(Statement::VariableDeclaration(VariableDeclaration {899                base: BaseNode::typed("VariableDeclaration"),900                declarations: vec![make_var_declarator(901                    PatternLike::Identifier(name.clone()),902                    None,903                )],904                kind: VariableDeclarationKind::Let,905                declare: None,906            }));907        }908        cache_loads.push((name.clone(), index, Expression::Identifier(name.clone())));909        cx.declare(decl.identifier);910    }911912    for reassignment_id in scope_reassignments {913        let index = cx.alloc_cache_index();914        if first_output_index.is_none() {915            first_output_index = Some(index);916        }917        let name = convert_identifier(reassignment_id, cx.env)?;918        cache_loads.push((name.clone(), index, Expression::Identifier(name)));919    }920921    // Build test condition922    let test_condition = if change_exprs.is_empty() {923        let first_idx = first_output_index.ok_or_else(|| {924            invariant_err("Expected scope to have at least one declaration", None)925        })?;926        let cache_name = cx.synthesize_name("$");927        Expression::BinaryExpression(ast_expr::BinaryExpression {928            base: BaseNode::typed("BinaryExpression"),929            operator: AstBinaryOperator::StrictEq,930            left: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {931                base: BaseNode::typed("MemberExpression"),932                object: Box::new(Expression::Identifier(make_identifier(&cache_name))),933                property: Box::new(Expression::NumericLiteral(NumericLiteral {934                    base: BaseNode::typed("NumericLiteral"),935                    value: first_idx as f64,936                    extra: None,937                })),938                computed: true,939            })),940            right: Box::new(symbol_for(MEMO_CACHE_SENTINEL)),941        })942    } else {943        change_exprs944            .into_iter()945            .reduce(|acc, expr| {946                Expression::LogicalExpression(ast_expr::LogicalExpression {947                    base: BaseNode::typed("LogicalExpression"),948                    operator: AstLogicalOperator::Or,949                    left: Box::new(acc),950                    right: Box::new(expr),951                })952            })953            .unwrap()954    };955956    let mut computation_block = codegen_block(cx, block)?;957958    // Build cache store and load statements for declarations959    for (name, index, value) in &cache_loads {960        let cache_name = cx.synthesize_name("$");961        cache_store_stmts.push(Statement::ExpressionStatement(ExpressionStatement {962            base: BaseNode::typed("ExpressionStatement"),963            expression: Box::new(Expression::AssignmentExpression(964                ast_expr::AssignmentExpression {965                    base: BaseNode::typed("AssignmentExpression"),966                    operator: AssignmentOperator::Assign,967                    left: Box::new(PatternLike::MemberExpression(ast_expr::MemberExpression {968                        base: BaseNode::typed("MemberExpression"),969                        object: Box::new(Expression::Identifier(make_identifier(&cache_name))),970                        property: Box::new(Expression::NumericLiteral(NumericLiteral {971                            base: BaseNode::typed("NumericLiteral"),972                            value: *index as f64,973                            extra: None,974                        })),975                        computed: true,976                    })),977                    right: Box::new(value.clone()),978                },979            )),980        }));981        cache_load_stmts.push(Statement::ExpressionStatement(ExpressionStatement {982            base: BaseNode::typed("ExpressionStatement"),983            expression: Box::new(Expression::AssignmentExpression(984                ast_expr::AssignmentExpression {985                    base: BaseNode::typed("AssignmentExpression"),986                    operator: AssignmentOperator::Assign,987                    left: Box::new(PatternLike::Identifier(name.clone())),988                    right: Box::new(Expression::MemberExpression(ast_expr::MemberExpression {989                        base: BaseNode::typed("MemberExpression"),990                        object: Box::new(Expression::Identifier(make_identifier(&cache_name))),991                        property: Box::new(Expression::NumericLiteral(NumericLiteral {992                            base: BaseNode::typed("NumericLiteral"),993                            value: *index as f64,994                            extra: None,995                        })),996                        computed: true,997                    })),998                },999            )),1000        }));1001    }10021003    computation_block.body.extend(cache_store_stmts);10041005    let memo_stmt = Statement::IfStatement(IfStatement {1006        base: BaseNode::typed("IfStatement"),1007        test: Box::new(test_condition),1008        consequent: Box::new(Statement::BlockStatement(computation_block)),1009        alternate: Some(Box::new(Statement::BlockStatement(BlockStatement {1010            base: BaseNode::typed("BlockStatement"),1011            body: cache_load_stmts,1012            directives: Vec::new(),1013        }))),1014    });1015    statements.push(memo_stmt);10161017    // Handle early return1018    let early_return_value = cx.env.scopes[scope_id.0 as usize]1019        .early_return_value1020        .clone();1021    if let Some(ref early_return) = early_return_value {1022        let early_ident = &cx.env.identifiers[early_return.value.0 as usize];1023        let name = match &early_ident.name {1024            Some(react_compiler_hir::IdentifierName::Named(n)) => n.clone(),1025            Some(react_compiler_hir::IdentifierName::Promoted(n)) => n.clone(),1026            None => {1027                return Err(invariant_err(1028                    "Expected early return value to be promoted to a named variable",1029                    early_return.loc,1030                ));1031            }1032        };1033        statements.push(Statement::IfStatement(IfStatement {1034            base: BaseNode::typed("IfStatement"),1035            test: Box::new(Expression::BinaryExpression(ast_expr::BinaryExpression {1036                base: BaseNode::typed("BinaryExpression"),1037                operator: AstBinaryOperator::StrictNeq,1038                left: Box::new(Expression::Identifier(make_identifier(&name))),1039                right: Box::new(symbol_for(EARLY_RETURN_SENTINEL)),1040            })),1041            consequent: Box::new(Statement::BlockStatement(BlockStatement {1042                base: BaseNode::typed("BlockStatement"),1043                body: vec![Statement::ReturnStatement(ReturnStatement {1044                    base: BaseNode::typed("ReturnStatement"),1045                    argument: Some(Box::new(Expression::Identifier(make_identifier(&name)))),1046                })],1047                directives: Vec::new(),1048            })),1049            alternate: None,1050        }));1051    }10521053    Ok(())1054}10551056// =============================================================================1057// Terminal codegen1058// =============================================================================10591060fn codegen_terminal(1061    cx: &mut Context,1062    terminal: &ReactiveTerminal,1063) -> Result<Option<Statement>, CompilerError> {1064    match terminal {1065        ReactiveTerminal::Break {1066            target,1067            target_kind,1068            loc,1069            ..1070        } => {1071            if *target_kind == ReactiveTerminalTargetKind::Implicit {1072                return Ok(None);1073            }1074            Ok(Some(Statement::BreakStatement(BreakStatement {1075                base: base_node_with_loc("BreakStatement", *loc),1076                label: if *target_kind == ReactiveTerminalTargetKind::Labeled {1077                    Some(make_identifier(&codegen_label(*target)))1078                } else {1079                    None1080                },1081            })))1082        }1083        ReactiveTerminal::Continue {1084            target,1085            target_kind,1086            loc,1087            ..1088        } => {1089            if *target_kind == ReactiveTerminalTargetKind::Implicit {1090                return Ok(None);1091            }1092            Ok(Some(Statement::ContinueStatement(ContinueStatement {1093                base: base_node_with_loc("ContinueStatement", *loc),1094                label: if *target_kind == ReactiveTerminalTargetKind::Labeled {1095                    Some(make_identifier(&codegen_label(*target)))1096                } else {1097                    None1098                },1099            })))1100        }1101        ReactiveTerminal::Return { value, loc, .. } => {1102            let expr = codegen_place_to_expression(cx, value)?;1103            if let Expression::Identifier(ref ident) = expr {1104                if ident.name == "undefined" {1105                    return Ok(Some(Statement::ReturnStatement(ReturnStatement {1106                        base: base_node_with_loc("ReturnStatement", *loc),1107                        argument: None,1108                    })));1109                }1110            }1111            Ok(Some(Statement::ReturnStatement(ReturnStatement {1112                base: base_node_with_loc("ReturnStatement", *loc),1113                argument: Some(Box::new(expr)),1114            })))1115        }1116        ReactiveTerminal::Throw { value, loc, .. } => {1117            let expr = codegen_place_to_expression(cx, value)?;1118            Ok(Some(Statement::ThrowStatement(ThrowStatement {1119                base: base_node_with_loc("ThrowStatement", *loc),1120                argument: Box::new(expr),1121            })))1122        }1123        ReactiveTerminal::If {1124            test,1125            consequent,1126            alternate,1127            loc,1128            ..1129        } => {1130            let test_expr = codegen_place_to_expression(cx, test)?;1131            let consequent_block = codegen_block(cx, consequent)?;1132            let alternate_stmt = if let Some(alt) = alternate {1133                let block = codegen_block(cx, alt)?;1134                if block.body.is_empty() {1135                    None1136                } else {1137                    Some(Box::new(Statement::BlockStatement(block)))1138                }1139            } else {1140                None1141            };1142            Ok(Some(Statement::IfStatement(IfStatement {1143                base: base_node_with_loc("IfStatement", *loc),1144                test: Box::new(test_expr),1145                consequent: Box::new(Statement::BlockStatement(consequent_block)),1146                alternate: alternate_stmt,1147            })))1148        }1149        ReactiveTerminal::Switch {1150            test, cases, loc, ..1151        } => {1152            let test_expr = codegen_place_to_expression(cx, test)?;1153            let switch_cases: Vec<SwitchCase> = cases1154                .iter()1155                .map(|case| {1156                    let test = case1157                        .test1158                        .as_ref()1159                        .map(|t| codegen_place_to_expression(cx, t))1160                        .transpose()?;1161                    let block = case1162                        .block1163                        .as_ref()1164                        .map(|b| codegen_block(cx, b))1165                        .transpose()?;1166                    let consequent = match block {1167                        Some(b) if b.body.is_empty() => Vec::new(),1168                        Some(b) => vec![Statement::BlockStatement(b)],1169                        None => Vec::new(),1170                    };1171                    Ok(SwitchCase {1172                        base: BaseNode::typed("SwitchCase"),1173                        test: test.map(Box::new),1174                        consequent,1175                    })1176                })1177                .collect::<Result<_, CompilerError>>()?;1178            Ok(Some(Statement::SwitchStatement(SwitchStatement {1179                base: base_node_with_loc("SwitchStatement", *loc),1180                discriminant: Box::new(test_expr),1181                cases: switch_cases,1182            })))1183        }1184        ReactiveTerminal::DoWhile {1185            loop_block,1186            test,1187            loc,1188            ..1189        } => {1190            let test_expr = codegen_instruction_value_to_expression(cx, test)?;1191            let body = codegen_block(cx, loop_block)?;1192            Ok(Some(Statement::DoWhileStatement(DoWhileStatement {1193                base: base_node_with_loc("DoWhileStatement", *loc),1194                test: Box::new(test_expr),1195                body: Box::new(Statement::BlockStatement(body)),1196            })))1197        }1198        ReactiveTerminal::While {1199            test,1200            loop_block,1201            loc,1202            ..1203        } => {1204            let test_expr = codegen_instruction_value_to_expression(cx, test)?;1205            let body = codegen_block(cx, loop_block)?;1206            Ok(Some(Statement::WhileStatement(WhileStatement {1207                base: base_node_with_loc("WhileStatement", *loc),1208                test: Box::new(test_expr),1209                body: Box::new(Statement::BlockStatement(body)),1210            })))1211        }1212        ReactiveTerminal::For {1213            init,1214            test,1215            update,1216            loop_block,1217            loc,1218            ..1219        } => {1220            let init_val = codegen_for_init(cx, init)?;1221            let test_expr = codegen_instruction_value_to_expression(cx, test)?;1222            let update_expr = update1223                .as_ref()1224                .map(|u| codegen_instruction_value_to_expression(cx, u))1225                .transpose()?;1226            let body = codegen_block(cx, loop_block)?;1227            Ok(Some(Statement::ForStatement(ForStatement {1228                base: base_node_with_loc("ForStatement", *loc),1229                init: init_val.map(|v| Box::new(v)),1230                test: Some(Box::new(test_expr)),1231                update: update_expr.map(Box::new),1232                body: Box::new(Statement::BlockStatement(body)),1233            })))1234        }1235        ReactiveTerminal::ForIn {1236            init,1237            loop_block,1238            loc,1239            ..1240        } => codegen_for_in(cx, init, loop_block, *loc),1241        ReactiveTerminal::ForOf {1242            init,1243            test,1244            loop_block,1245            loc,1246            ..1247        } => codegen_for_of(cx, init, test, loop_block, *loc),1248        ReactiveTerminal::Label { block, .. } => {1249            let body = codegen_block(cx, block)?;1250            Ok(Some(Statement::BlockStatement(body)))1251        }1252        ReactiveTerminal::Try {1253            block,1254            handler_binding,1255            handler,1256            loc,1257            ..1258        } => {1259            let catch_param = match handler_binding.as_ref() {1260                Some(binding) => {1261                    let ident = &cx.env.identifiers[binding.identifier.0 as usize];1262                    cx.temp.insert(ident.declaration_id, None);1263                    Some(PatternLike::Identifier(convert_identifier(1264                        binding.identifier,1265                        cx.env,1266                    )?))1267                }1268                None => None,1269            };1270            let try_block = codegen_block(cx, block)?;1271            let handler_block = codegen_block(cx, handler)?;1272            Ok(Some(Statement::TryStatement(TryStatement {1273                base: base_node_with_loc("TryStatement", *loc),1274                block: try_block,1275                handler: Some(CatchClause {1276                    base: BaseNode::typed("CatchClause"),1277                    param: catch_param,1278                    body: handler_block,1279                }),1280                finalizer: None,1281            })))1282        }1283    }1284}12851286fn codegen_for_in(1287    cx: &mut Context,1288    init: &ReactiveValue,1289    loop_block: &ReactiveBlock,1290    loc: Option<DiagSourceLocation>,1291) -> Result<Option<Statement>, CompilerError> {1292    let ReactiveValue::SequenceExpression { instructions, .. } = init else {1293        return Err(invariant_err(1294            "Expected a sequence expression init for for..in",1295            None,1296        ));1297    };1298    if instructions.len() != 2 {1299        cx.record_error(CompilerErrorDetail {1300            category: ErrorCategory::Todo,1301            reason: "Support non-trivial for..in inits".to_string(),1302            description: None,1303            loc,1304            suggestions: None,1305        })?;1306        return Ok(Some(Statement::EmptyStatement(EmptyStatement {1307            base: BaseNode::typed("EmptyStatement"),1308        })));1309    }1310    let iterable_collection = &instructions[0];1311    let iterable_item = &instructions[1];1312    let instr_value = get_instruction_value(&iterable_item.value)?;1313    let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..in", loc)?;1314    let right = codegen_instruction_value_to_expression(cx, &iterable_collection.value)?;1315    let body = codegen_block(cx, loop_block)?;1316    Ok(Some(Statement::ForInStatement(ForInStatement {1317        base: base_node_with_loc("ForInStatement", loc),1318        left: Box::new(1319            react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration {1320                base: BaseNode::typed("VariableDeclaration"),1321                declarations: vec![VariableDeclarator {1322                    base: BaseNode::typed("VariableDeclarator"),1323                    id: lval,1324                    init: None,1325                    definite: None,1326                }],1327                kind: var_decl_kind,1328                declare: None,1329            }),1330        ),1331        right: Box::new(right),1332        body: Box::new(Statement::BlockStatement(body)),1333    })))1334}13351336fn codegen_for_of(1337    cx: &mut Context,1338    init: &ReactiveValue,1339    test: &ReactiveValue,1340    loop_block: &ReactiveBlock,1341    loc: Option<DiagSourceLocation>,1342) -> Result<Option<Statement>, CompilerError> {1343    // Validate init is SequenceExpression with single GetIterator instruction1344    let ReactiveValue::SequenceExpression {1345        instructions: init_instrs,1346        ..1347    } = init1348    else {1349        return Err(invariant_err(1350            "Expected a sequence expression init for for..of",1351            None,1352        ));1353    };1354    if init_instrs.len() != 1 {1355        return Err(invariant_err(1356            "Expected a single-expression sequence expression init for for..of",1357            None,1358        ));1359    }1360    let get_iter_value = get_instruction_value(&init_instrs[0].value)?;1361    let InstructionValue::GetIterator { collection, .. } = get_iter_value else {1362        return Err(invariant_err("Expected GetIterator in for..of init", None));1363    };13641365    let ReactiveValue::SequenceExpression {1366        instructions: test_instrs,1367        ..1368    } = test1369    else {1370        return Err(invariant_err(1371            "Expected a sequence expression test for for..of",1372            None,1373        ));1374    };1375    if test_instrs.len() != 2 {1376        cx.record_error(CompilerErrorDetail {1377            category: ErrorCategory::Todo,1378            reason: "Support non-trivial for..of inits".to_string(),1379            description: None,1380            loc,1381            suggestions: None,1382        })?;1383        return Ok(Some(Statement::EmptyStatement(EmptyStatement {1384            base: BaseNode::typed("EmptyStatement"),1385        })));1386    }1387    let iterable_item = &test_instrs[1];1388    let instr_value = get_instruction_value(&iterable_item.value)?;1389    let (lval, var_decl_kind) = extract_for_in_of_lval(cx, instr_value, "for..of", loc)?;13901391    let right = codegen_place_to_expression(cx, collection)?;1392    let body = codegen_block(cx, loop_block)?;1393    Ok(Some(Statement::ForOfStatement(ForOfStatement {1394        base: base_node_with_loc("ForOfStatement", loc),1395        left: Box::new(1396            react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(VariableDeclaration {1397                base: BaseNode::typed("VariableDeclaration"),1398                declarations: vec![VariableDeclarator {1399                    base: BaseNode::typed("VariableDeclarator"),1400                    id: lval,1401                    init: None,1402                    definite: None,1403                }],1404                kind: var_decl_kind,1405                declare: None,1406            }),1407        ),1408        right: Box::new(right),1409        body: Box::new(Statement::BlockStatement(body)),1410        is_await: false,1411    })))1412}14131414/// Extract lval and declaration kind from a for-in/for-of iterable item instruction.1415fn extract_for_in_of_lval(1416    cx: &mut Context,1417    instr_value: &InstructionValue,1418    context_name: &str,1419    loc: Option<DiagSourceLocation>,1420) -> Result<(PatternLike, VariableDeclarationKind), CompilerError> {1421    let (lval, kind) = match instr_value {1422        InstructionValue::StoreLocal { lvalue, .. } => (1423            codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?,1424            lvalue.kind,1425        ),1426        InstructionValue::Destructure { lvalue, .. } => (1427            codegen_lvalue(cx, &LvalueRef::Pattern(&lvalue.pattern))?,1428            lvalue.kind,1429        ),1430        InstructionValue::StoreContext { .. } => {1431            cx.record_error(CompilerErrorDetail {1432                category: ErrorCategory::Todo,1433                reason: format!("Support non-trivial {} inits", context_name),1434                description: None,1435                loc,1436                suggestions: None,1437            })?;1438            return Ok((1439                PatternLike::Identifier(make_identifier("_")),1440                VariableDeclarationKind::Let,1441            ));1442        }1443        _ => {1444            return Err(invariant_err(1445                &format!(1446                    "Expected a StoreLocal or Destructure in {} collection, found {:?}",1447                    context_name,1448                    std::mem::discriminant(instr_value)1449                ),1450                None,1451            ));1452        }1453    };1454    let var_decl_kind = match kind {1455        InstructionKind::Const => VariableDeclarationKind::Const,1456        InstructionKind::Let => VariableDeclarationKind::Let,1457        _ => {1458            return Err(invariant_err(1459                &format!(1460                    "Unexpected {:?} variable in {} collection",1461                    kind, context_name1462                ),1463                None,1464            ));1465        }1466    };1467    Ok((lval, var_decl_kind))1468}14691470fn codegen_for_init(1471    cx: &mut Context,1472    init: &ReactiveValue,1473) -> Result<Option<ForInit>, CompilerError> {1474    if let ReactiveValue::SequenceExpression { instructions, .. } = init {1475        let block_items: Vec<ReactiveStatement> = instructions1476            .iter()1477            .map(|i| ReactiveStatement::Instruction(i.clone()))1478            .collect();1479        let body = codegen_block(cx, &block_items)?.body;1480        let mut declarators: Vec<VariableDeclarator> = Vec::new();1481        let mut kind = VariableDeclarationKind::Const;1482        for instr in body {1483            // Check if this is an assignment that can be folded into the last declarator1484            if let Statement::ExpressionStatement(ref expr_stmt) = instr {1485                if let Expression::AssignmentExpression(ref assign) = *expr_stmt.expression {1486                    if matches!(assign.operator, AssignmentOperator::Assign) {1487                        if let PatternLike::Identifier(ref left_ident) = *assign.left {1488                            if let Some(top) = declarators.last_mut() {1489                                if let PatternLike::Identifier(ref top_ident) = top.id {1490                                    if top_ident.name == left_ident.name && top.init.is_none() {1491                                        top.init = Some(assign.right.clone());1492                                        continue;1493                                    }1494                                }1495                            }1496                        }1497                    }1498                }1499            }15001501            if let Statement::VariableDeclaration(var_decl) = instr {1502                match var_decl.kind {1503                    VariableDeclarationKind::Let | VariableDeclarationKind::Const => {}1504                    _ => {1505                        return Err(invariant_err(1506                            "Expected a let or const variable declaration",1507                            None,1508                        ));1509                    }1510                }1511                if matches!(var_decl.kind, VariableDeclarationKind::Let) {1512                    kind = VariableDeclarationKind::Let;1513                }1514                declarators.extend(var_decl.declarations);1515            } else {1516                let stmt_type = get_statement_type_name(&instr);1517                let stmt_loc = get_statement_loc(&instr);1518                let reason = "Expected a variable declaration".to_string();1519                let mut err = CompilerError::new();1520                err.push_diagnostic(1521                    CompilerDiagnostic::new(1522                        ErrorCategory::Invariant,1523                        reason.clone(),1524                        Some(format!("Got {}", stmt_type)),1525                    )1526                    .with_detail(CompilerDiagnosticDetail::Error {1527                        loc: stmt_loc,1528                        message: Some(reason),1529                        identifier_name: None,1530                    }),1531                );1532                return Err(err);1533            }1534        }1535        if declarators.is_empty() {1536            return Err(invariant_err(1537                "Expected a variable declaration in for-init",1538                None,1539            ));1540        }1541        Ok(Some(ForInit::VariableDeclaration(VariableDeclaration {1542            base: BaseNode::typed("VariableDeclaration"),1543            declarations: declarators,1544            kind,1545            declare: None,1546        })))1547    } else {1548        let expr = codegen_instruction_value_to_expression(cx, init)?;1549        Ok(Some(ForInit::Expression(Box::new(expr))))1550    }1551}15521553// =============================================================================1554// Instruction codegen1555// =============================================================================15561557/// How statement-position codegen disposes of an `UnsupportedNode`'s1558/// `original_node`. See [`codegen_unsupported_original_node`].1559enum UnsupportedOriginalNode {1560    /// Emit this statement directly (early return).1561    Statement(Statement),1562    /// Flow through the general expression codegen path so the instruction's1563    /// lvalue temporary is bound/registered.1564    ExpressionCodegen,1565}15661567/// Discriminate an `UnsupportedNode`'s `original_node` by its `type` tag.1568///1569/// Lowering serializes typed `Expression`/`Statement`/`PatternLike` bailout1570/// nodes, plus the raw nodes of `Statement::Unknown` (whose tags are1571/// unmodeled by construction). Dispatch accordingly:1572///1573/// - Modeled statement tag: parse the typed statement and emit it directly.1574///   A parse failure here is a serialize/deserialize asymmetry, surfaced as1575///   an invariant rather than degraded.1576/// - Tag parseable as `Expression` or `PatternLike` (both enums are strict,1577///   no catch-all): expression codegen. Patterns (e.g. `ObjectPattern`1578///   destructuring targets) keep their existing placeholder fallback there.1579/// - Anything else is an unmodeled tag, producible only by the1580///   unknown-statement lowering bailout — i.e. it came from a statement1581///   position — so preserve it verbatim as `Statement::Unknown`, matching1582///   the TS codegen's `return node` for non-expressions.1583fn codegen_unsupported_original_node(1584    node: &serde_json::Value,1585) -> Result<UnsupportedOriginalNode, CompilerError> {1586    let tag = node.get("type").and_then(serde_json::Value::as_str);1587    if tag.is_some_and(is_known_statement_type) {1588        let stmt: Statement = serde_json::from_value(node.clone()).map_err(|e| {1589            invariant_err(1590                &format!("Failed to deserialize original AST node: {}", e),1591                None,1592            )1593        })?;1594        return Ok(UnsupportedOriginalNode::Statement(stmt));1595    }1596    if serde_json::from_value::<Expression>(node.clone()).is_ok()1597        || serde_json::from_value::<PatternLike>(node.clone()).is_ok()1598    {1599        return Ok(UnsupportedOriginalNode::ExpressionCodegen);1600    }1601    let unknown = UnknownStatement::from_raw(RawNode::from_value(node)).map_err(|e| {1602        invariant_err(1603            &format!("Failed to read unsupported original AST node: {}", e),1604            None,1605        )1606    })?;1607    Ok(UnsupportedOriginalNode::Statement(Statement::Unknown(1608        unknown,1609    )))1610}16111612fn codegen_instruction_nullable(1613    cx: &mut Context,1614    instr: &ReactiveInstruction,1615) -> Result<Option<Statement>, CompilerError> {1616    // Only check specific InstructionValue kinds for the base Instruction variant1617    if let ReactiveValue::Instruction(ref value) = instr.value {1618        match value {1619            InstructionValue::StoreLocal { .. }1620            | InstructionValue::StoreContext { .. }1621            | InstructionValue::Destructure { .. }1622            | InstructionValue::DeclareLocal { .. }1623            | InstructionValue::DeclareContext { .. } => {1624                return codegen_store_or_declare(cx, instr, value);1625            }1626            InstructionValue::StartMemoize { .. } | InstructionValue::FinishMemoize { .. } => {1627                return Ok(None);1628            }1629            InstructionValue::Debugger { .. } => {1630                return Ok(Some(Statement::DebuggerStatement(DebuggerStatement {1631                    base: base_node_with_loc("DebuggerStatement", instr.loc),1632                })));1633            }1634            InstructionValue::UnsupportedNode {1635                original_node: Some(node),1636                ..1637            } => {1638                // Statement-vs-expression discrimination must be explicit by1639                // `type` tag: `Statement`'s deserializer has a tolerant1640                // `Statement::Unknown` catch-all, so "does it deserialize as1641                // a Statement?" succeeds for ANY tagged object and would1642                // emit expression nodes as raw statements, orphaning their1643                // lvalue temporaries (the regression the explicit dispatch1644                // below prevents; TS codegen's equivalent check is1645                // `if (!t.isExpression(node)) return node; value = node`).1646                match codegen_unsupported_original_node(node)? {1647                    UnsupportedOriginalNode::Statement(stmt) => return Ok(Some(stmt)),1648                    UnsupportedOriginalNode::ExpressionCodegen => {1649                        // Expression (or pattern) node — fall through to the1650                        // general codegen path which handles lvalue binding1651                        // and temporary registration.1652                    }1653                }1654            }1655            InstructionValue::ObjectMethod { loc, .. } => {1656                invariant(1657                    instr.lvalue.is_some(),1658                    "Expected object methods to have a temp lvalue",1659                    None,1660                )?;1661                let lvalue = instr.lvalue.as_ref().unwrap();1662                cx.object_methods1663                    .insert(lvalue.identifier, (value.clone(), *loc));1664                return Ok(None);1665            }1666            _ => {} // fall through to general codegen1667        }1668    }1669    // General case: codegen the full ReactiveValue1670    let expr_value = codegen_instruction_value(cx, &instr.value)?;1671    let stmt = codegen_instruction(cx, instr, expr_value)?;1672    if matches!(stmt, Statement::EmptyStatement(_)) {1673        Ok(None)1674    } else {1675        Ok(Some(stmt))1676    }1677}16781679fn codegen_store_or_declare(1680    cx: &mut Context,1681    instr: &ReactiveInstruction,1682    value: &InstructionValue,1683) -> Result<Option<Statement>, CompilerError> {1684    match value {1685        InstructionValue::StoreLocal {1686            lvalue, value: val, ..1687        } => {1688            let mut kind = lvalue.kind;1689            if cx.has_declared(lvalue.place.identifier) {1690                kind = InstructionKind::Reassign;1691            }1692            let rhs = codegen_place_to_expression(cx, val)?;1693            emit_store(cx, instr, kind, &LvalueRef::Place(&lvalue.place), Some(rhs))1694        }1695        InstructionValue::StoreContext {1696            lvalue, value: val, ..1697        } => {1698            let rhs = codegen_place_to_expression(cx, val)?;1699            emit_store(1700                cx,1701                instr,1702                lvalue.kind,1703                &LvalueRef::Place(&lvalue.place),1704                Some(rhs),1705            )1706        }1707        InstructionValue::DeclareLocal { lvalue, .. }1708        | InstructionValue::DeclareContext { lvalue, .. } => {1709            if cx.has_declared(lvalue.place.identifier) {1710                return Ok(None);1711            }1712            emit_store(1713                cx,1714                instr,1715                lvalue.kind,1716                &LvalueRef::Place(&lvalue.place),1717                None,1718            )1719        }1720        InstructionValue::Destructure {1721            lvalue, value: val, ..1722        } => {1723            let kind = lvalue.kind;1724            // Register temporaries for unnamed pattern operands1725            for place in react_compiler_hir::visitors::each_pattern_operand(&lvalue.pattern) {1726                let ident = &cx.env.identifiers[place.identifier.0 as usize];1727                if kind != InstructionKind::Reassign && ident.name.is_none() {1728                    cx.temp.insert(ident.declaration_id, None);1729                }1730            }1731            let rhs = codegen_place_to_expression(cx, val)?;1732            emit_store(1733                cx,1734                instr,1735                kind,1736                &LvalueRef::Pattern(&lvalue.pattern),1737                Some(rhs),1738            )1739        }1740        _ => unreachable!(),1741    }1742}17431744fn emit_store(1745    cx: &mut Context,1746    instr: &ReactiveInstruction,1747    kind: InstructionKind,1748    lvalue: &LvalueRef,1749    value: Option<Expression>,1750) -> Result<Option<Statement>, CompilerError> {1751    match kind {1752        InstructionKind::Const => {1753            // Invariant: Const declarations cannot also have an outer lvalue1754            // (i.e., cannot be referenced as an expression)1755            if instr.lvalue.is_some() {1756                return Err(invariant_err_with_detail_message(1757                    "Const declaration cannot be referenced as an expression",1758                    "this is Const",1759                    instr.loc,1760                ));1761            }1762            let lval = codegen_lvalue(cx, lvalue)?;1763            Ok(Some(Statement::VariableDeclaration(VariableDeclaration {1764                base: base_node_with_loc("VariableDeclaration", instr.loc),1765                declarations: vec![make_var_declarator(lval, value)],1766                kind: VariableDeclarationKind::Const,1767                declare: None,1768            })))1769        }1770        InstructionKind::Function => {1771            let lval = codegen_lvalue(cx, lvalue)?;1772            let PatternLike::Identifier(fn_id) = lval else {1773                return Err(invariant_err(1774                    "Expected an identifier as function declaration lvalue",1775                    None,1776                ));1777            };1778            let Some(rhs) = value else {1779                return Err(invariant_err(1780                    "Expected a function value for function declaration",1781                    None,1782                ));1783            };1784            match rhs {1785                Expression::FunctionExpression(func_expr) => {1786                    Ok(Some(Statement::FunctionDeclaration(FunctionDeclaration {1787                        base: base_node_with_loc("FunctionDeclaration", instr.loc),1788                        id: Some(fn_id),1789                        params: func_expr.params,1790                        body: func_expr.body,1791                        generator: func_expr.generator,1792                        is_async: func_expr.is_async,1793                        declare: None,1794                        return_type: None,1795                        type_parameters: None,1796                        predicate: None,1797                        component_declaration: false,1798                        hook_declaration: false,1799                    })))1800                }1801                _ => Err(invariant_err(1802                    "Expected a function expression for function declaration",1803                    None,1804                )),1805            }1806        }1807        InstructionKind::Let => {1808            // Invariant: Let declarations cannot also have an outer lvalue1809            if instr.lvalue.is_some() {1810                return Err(invariant_err_with_detail_message(1811                    "Const declaration cannot be referenced as an expression",1812                    "this is Let",1813                    instr.loc,1814                ));1815            }1816            let lval = codegen_lvalue(cx, lvalue)?;1817            Ok(Some(Statement::VariableDeclaration(VariableDeclaration {1818                base: base_node_with_loc("VariableDeclaration", instr.loc),1819                declarations: vec![make_var_declarator(lval, value)],1820                kind: VariableDeclarationKind::Let,1821                declare: None,1822            })))1823        }1824        InstructionKind::Reassign => {1825            let Some(rhs) = value else {1826                return Err(invariant_err("Expected a value for reassignment", None));1827            };1828            let lval = codegen_lvalue(cx, lvalue)?;1829            let expr = Expression::AssignmentExpression(ast_expr::AssignmentExpression {1830                base: BaseNode::typed("AssignmentExpression"),1831                operator: AssignmentOperator::Assign,1832                left: Box::new(lval),1833                right: Box::new(rhs),1834            });1835            if let Some(ref lvalue_place) = instr.lvalue {1836                let is_store_context = matches!(1837                    &instr.value,1838                    ReactiveValue::Instruction(InstructionValue::StoreContext { .. })1839                );1840                if !is_store_context {1841                    let ident = &cx.env.identifiers[lvalue_place.identifier.0 as usize];1842                    cx.temp.insert(1843                        ident.declaration_id,1844                        Some(ExpressionOrJsxText::Expression(expr)),1845                    );1846                    return Ok(None);1847                } else {1848                    let stmt =1849                        codegen_instruction(cx, instr, ExpressionOrJsxText::Expression(expr))?;1850                    if matches!(stmt, Statement::EmptyStatement(_)) {1851                        return Ok(None);1852                    }1853                    return Ok(Some(stmt));1854                }1855            }1856            Ok(Some(Statement::ExpressionStatement(ExpressionStatement {1857                base: base_node_with_loc("ExpressionStatement", instr.loc),1858                expression: Box::new(expr),1859            })))1860        }1861        InstructionKind::Catch => Ok(Some(Statement::EmptyStatement(EmptyStatement {1862            base: BaseNode::typed("EmptyStatement"),1863        }))),1864        InstructionKind::HoistedLet1865        | InstructionKind::HoistedConst1866        | InstructionKind::HoistedFunction => Err(invariant_err(1867            &format!(1868                "Expected {:?} to have been pruned in PruneHoistedContexts",1869                kind1870            ),1871            None,1872        )),1873    }1874}18751876fn codegen_instruction(1877    cx: &mut Context,1878    instr: &ReactiveInstruction,1879    value: ExpressionOrJsxText,1880) -> Result<Statement, CompilerError> {1881    let Some(ref lvalue) = instr.lvalue else {1882        let expr = convert_value_to_expression(value);1883        return Ok(Statement::ExpressionStatement(ExpressionStatement {1884            base: base_node_with_loc("ExpressionStatement", instr.loc),1885            expression: Box::new(expr),1886        }));1887    };1888    let ident = &cx.env.identifiers[lvalue.identifier.0 as usize];1889    if ident.name.is_none() {1890        // temporary1891        cx.temp.insert(ident.declaration_id, Some(value));1892        return Ok(Statement::EmptyStatement(EmptyStatement {1893            base: BaseNode::typed("EmptyStatement"),1894        }));1895    }1896    let expr_value = convert_value_to_expression(value);1897    if cx.has_declared(lvalue.identifier) {1898        Ok(Statement::ExpressionStatement(ExpressionStatement {1899            base: base_node_with_loc("ExpressionStatement", instr.loc),1900            expression: Box::new(Expression::AssignmentExpression(1901                ast_expr::AssignmentExpression {1902                    base: BaseNode::typed("AssignmentExpression"),1903                    operator: AssignmentOperator::Assign,1904                    left: Box::new(PatternLike::Identifier(convert_identifier(1905                        lvalue.identifier,1906                        cx.env,1907                    )?)),1908                    right: Box::new(expr_value),1909                },1910            )),1911        }))1912    } else {1913        Ok(Statement::VariableDeclaration(VariableDeclaration {1914            base: base_node_with_loc("VariableDeclaration", instr.loc),1915            declarations: vec![make_var_declarator(1916                PatternLike::Identifier(convert_identifier(lvalue.identifier, cx.env)?),1917                Some(expr_value),1918            )],1919            kind: VariableDeclarationKind::Const,1920            declare: None,1921        }))1922    }1923}19241925// =============================================================================1926// Instruction value codegen1927// =============================================================================19281929fn codegen_instruction_value_to_expression(1930    cx: &mut Context,1931    instr_value: &ReactiveValue,1932) -> Result<Expression, CompilerError> {1933    let value = codegen_instruction_value(cx, instr_value)?;1934    Ok(convert_value_to_expression(value))1935}19361937fn codegen_instruction_value(1938    cx: &mut Context,1939    instr_value: &ReactiveValue,1940) -> Result<ExpressionOrJsxText, CompilerError> {1941    match instr_value {1942        ReactiveValue::Instruction(iv) => {1943            let mut result = codegen_base_instruction_value(cx, iv)?;1944            // Propagate instrValue.loc to the generated expression, matching TS:1945            //   if (instrValue.loc != null && instrValue.loc != GeneratedSource) {1946            //       value.loc = instrValue.loc;1947            //   }1948            if let Some(loc) = iv.loc() {1949                apply_loc_to_value(&mut result, *loc);1950            }1951            Ok(result)1952        }1953        ReactiveValue::LogicalExpression {1954            operator,1955            left,1956            right,1957            ..1958        } => {1959            let left_expr = codegen_instruction_value_to_expression(cx, left)?;1960            let right_expr = codegen_instruction_value_to_expression(cx, right)?;1961            Ok(ExpressionOrJsxText::Expression(1962                Expression::LogicalExpression(ast_expr::LogicalExpression {1963                    base: BaseNode::typed("LogicalExpression"),1964                    operator: convert_logical_operator(operator),1965                    left: Box::new(left_expr),1966                    right: Box::new(right_expr),1967                }),1968            ))1969        }1970        ReactiveValue::ConditionalExpression {1971            test,1972            consequent,1973            alternate,1974            ..1975        } => {1976            let test_expr = codegen_instruction_value_to_expression(cx, test)?;1977            let cons_expr = codegen_instruction_value_to_expression(cx, consequent)?;1978            let alt_expr = codegen_instruction_value_to_expression(cx, alternate)?;1979            Ok(ExpressionOrJsxText::Expression(1980                Expression::ConditionalExpression(ast_expr::ConditionalExpression {1981                    base: BaseNode::typed("ConditionalExpression"),1982                    test: Box::new(test_expr),1983                    consequent: Box::new(cons_expr),1984                    alternate: Box::new(alt_expr),1985                }),1986            ))1987        }1988        ReactiveValue::SequenceExpression {1989            instructions,1990            value,1991            ..1992        } => {1993            let block_items: Vec<ReactiveStatement> = instructions1994                .iter()1995                .map(|i| ReactiveStatement::Instruction(i.clone()))1996                .collect();1997            let body = codegen_block_no_reset(cx, &block_items)?.body;1998            let mut expressions: Vec<Expression> = Vec::new();1999            for stmt in body {2000                match stmt {

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.