Skip to main content

react_compiler_lowering/
build_hir.rs

1use std::collections::HashSet;
2
3use indexmap::IndexMap;
4use indexmap::IndexSet;
5use react_compiler_ast::scope::BindingId;
6use react_compiler_ast::scope::BindingKind as AstBindingKind;
7use react_compiler_ast::scope::ScopeId;
8use react_compiler_ast::scope::ScopeInfo;
9use react_compiler_ast::scope::ScopeKind;
10use react_compiler_diagnostics::CompilerDiagnostic;
11use react_compiler_diagnostics::CompilerDiagnosticDetail;
12use react_compiler_diagnostics::CompilerError;
13use react_compiler_diagnostics::CompilerErrorDetail;
14use react_compiler_diagnostics::ErrorCategory;
15use react_compiler_hir::environment::Environment;
16use react_compiler_hir::*;
17
18use crate::FunctionNode;
19use crate::find_context_identifiers::find_context_identifiers;
20use crate::hir_builder::HirBuilder;
21use crate::hir_builder::is_always_reserved_word;
22use crate::hir_builder::reserved_identifier_diagnostic;
23use crate::identifier_loc_index::IdentifierLocIndex;
24use crate::identifier_loc_index::build_identifier_loc_index;
25
26// =============================================================================
27// Source location conversion
28// =============================================================================
29
30/// Convert an AST SourceLocation to an HIR SourceLocation.
31fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
32    SourceLocation {
33        start: Position {
34            line: loc.start.line,
35            column: loc.start.column,
36            index: loc.start.index,
37        },
38        end: Position {
39            line: loc.end.line,
40            column: loc.end.column,
41            index: loc.end.index,
42        },
43    }
44}
45
46/// Convert an optional AST SourceLocation to an optional HIR SourceLocation.
47fn convert_opt_loc(
48    loc: &Option<react_compiler_ast::common::SourceLocation>,
49) -> Option<SourceLocation> {
50    loc.as_ref().map(convert_loc)
51}
52
53/// Serialize an expression to a serde_json::Value for UnsupportedNode's original_node.
54/// Returns None if serialization fails (should not happen for valid AST nodes).
55/// This should ONLY be called on error/bail paths — never eagerly before deciding
56/// to create an UnsupportedNode.
57fn serialize_expression(
58    expr: &react_compiler_ast::expressions::Expression,
59) -> Option<serde_json::Value> {
60    serde_json::to_value(expr).ok()
61}
62
63/// Serialize a statement to a serde_json::Value for UnsupportedNode's original_node.
64fn serialize_statement(
65    stmt: &react_compiler_ast::statements::Statement,
66) -> Option<serde_json::Value> {
67    serde_json::to_value(stmt).ok()
68}
69
70/// Serialize a pattern to a serde_json::Value for UnsupportedNode's original_node.
71fn serialize_pattern(pat: &react_compiler_ast::patterns::PatternLike) -> Option<serde_json::Value> {
72    serde_json::to_value(pat).ok()
73}
74
75fn pattern_like_loc(
76    pattern: &react_compiler_ast::patterns::PatternLike,
77) -> Option<react_compiler_ast::common::SourceLocation> {
78    use react_compiler_ast::patterns::PatternLike;
79    match pattern {
80        PatternLike::Identifier(id) => id.base.loc.clone(),
81        PatternLike::ObjectPattern(p) => p.base.loc.clone(),
82        PatternLike::ArrayPattern(p) => p.base.loc.clone(),
83        PatternLike::AssignmentPattern(p) => p.base.loc.clone(),
84        PatternLike::RestElement(p) => p.base.loc.clone(),
85        PatternLike::MemberExpression(p) => p.base.loc.clone(),
86        PatternLike::TSAsExpression(p) => p.base.loc.clone(),
87        PatternLike::TSSatisfiesExpression(p) => p.base.loc.clone(),
88        PatternLike::TSNonNullExpression(p) => p.base.loc.clone(),
89        PatternLike::TSTypeAssertion(p) => p.base.loc.clone(),
90        PatternLike::TypeCastExpression(p) => p.base.loc.clone(),
91    }
92}
93
94/// Extract the HIR SourceLocation from an Expression AST node.
95fn expression_loc(expr: &react_compiler_ast::expressions::Expression) -> Option<SourceLocation> {
96    use react_compiler_ast::expressions::Expression;
97    let loc = match expr {
98        Expression::Identifier(e) => e.base.loc.clone(),
99        Expression::StringLiteral(e) => e.base.loc.clone(),
100        Expression::NumericLiteral(e) => e.base.loc.clone(),
101        Expression::BooleanLiteral(e) => e.base.loc.clone(),
102        Expression::NullLiteral(e) => e.base.loc.clone(),
103        Expression::BigIntLiteral(e) => e.base.loc.clone(),
104        Expression::RegExpLiteral(e) => e.base.loc.clone(),
105        Expression::CallExpression(e) => e.base.loc.clone(),
106        Expression::MemberExpression(e) => e.base.loc.clone(),
107        Expression::OptionalCallExpression(e) => e.base.loc.clone(),
108        Expression::OptionalMemberExpression(e) => e.base.loc.clone(),
109        Expression::BinaryExpression(e) => e.base.loc.clone(),
110        Expression::LogicalExpression(e) => e.base.loc.clone(),
111        Expression::UnaryExpression(e) => e.base.loc.clone(),
112        Expression::UpdateExpression(e) => e.base.loc.clone(),
113        Expression::ConditionalExpression(e) => e.base.loc.clone(),
114        Expression::AssignmentExpression(e) => e.base.loc.clone(),
115        Expression::SequenceExpression(e) => e.base.loc.clone(),
116        Expression::ArrowFunctionExpression(e) => e.base.loc.clone(),
117        Expression::FunctionExpression(e) => e.base.loc.clone(),
118        Expression::ObjectExpression(e) => e.base.loc.clone(),
119        Expression::ArrayExpression(e) => e.base.loc.clone(),
120        Expression::NewExpression(e) => e.base.loc.clone(),
121        Expression::TemplateLiteral(e) => e.base.loc.clone(),
122        Expression::TaggedTemplateExpression(e) => e.base.loc.clone(),
123        Expression::AwaitExpression(e) => e.base.loc.clone(),
124        Expression::YieldExpression(e) => e.base.loc.clone(),
125        Expression::SpreadElement(e) => e.base.loc.clone(),
126        Expression::MetaProperty(e) => e.base.loc.clone(),
127        Expression::ClassExpression(e) => e.base.loc.clone(),
128        Expression::PrivateName(e) => e.base.loc.clone(),
129        Expression::Super(e) => e.base.loc.clone(),
130        Expression::Import(e) => e.base.loc.clone(),
131        Expression::ThisExpression(e) => e.base.loc.clone(),
132        Expression::ParenthesizedExpression(e) => e.base.loc.clone(),
133        Expression::JSXElement(e) => e.base.loc.clone(),
134        Expression::JSXFragment(e) => e.base.loc.clone(),
135        Expression::AssignmentPattern(e) => e.base.loc.clone(),
136        Expression::TSAsExpression(e) => e.base.loc.clone(),
137        Expression::TSSatisfiesExpression(e) => e.base.loc.clone(),
138        Expression::TSNonNullExpression(e) => e.base.loc.clone(),
139        Expression::TSTypeAssertion(e) => e.base.loc.clone(),
140        Expression::TSInstantiationExpression(e) => e.base.loc.clone(),
141        Expression::TypeCastExpression(e) => e.base.loc.clone(),
142    };
143    convert_opt_loc(&loc)
144}
145
146fn validate_ts_this_parameter(
147    scope_info: &ScopeInfo,
148    function_scope: ScopeId,
149) -> Result<(), CompilerError> {
150    let Some(scope) = scope_info.scopes.get(function_scope.0 as usize) else {
151        return Ok(());
152    };
153    let Some(binding_id) = scope.bindings.get("this") else {
154        return Ok(());
155    };
156    let Some(binding) = scope_info.bindings.get(binding_id.0 as usize) else {
157        return Ok(());
158    };
159    if matches!(binding.kind, AstBindingKind::Param) {
160        return Err(CompilerError::from(reserved_identifier_diagnostic("this")));
161    }
162    Ok(())
163}
164
165fn is_class_scope_descendant(scope_info: &ScopeInfo, mut scope_id: ScopeId) -> bool {
166    while let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) {
167        let Some(parent) = scope.parent else {
168            return false;
169        };
170        let Some(parent_scope) = scope_info.scopes.get(parent.0 as usize) else {
171            return false;
172        };
173        if matches!(parent_scope.kind, ScopeKind::Class) {
174            return true;
175        }
176        scope_id = parent;
177    }
178    false
179}
180
181fn validate_ts_this_parameters_in_function_range(
182    scope_info: &ScopeInfo,
183    start: u32,
184    end: u32,
185) -> Result<(), CompilerError> {
186    if start >= end {
187        return Ok(());
188    }
189    for (node_start, scope_id) in &scope_info.node_to_scope {
190        if *node_start < start || *node_start >= end {
191            continue;
192        }
193        let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) else {
194            continue;
195        };
196        if !matches!(scope.kind, ScopeKind::Function)
197            || is_class_scope_descendant(scope_info, *scope_id)
198        {
199            continue;
200        }
201        validate_ts_this_parameter(scope_info, *scope_id)?;
202    }
203    Ok(())
204}
205
206/// Get the Babel-style type name of an Expression node (e.g. "Identifier", "NumericLiteral").
207fn expression_type_name(expr: &react_compiler_ast::expressions::Expression) -> &'static str {
208    use react_compiler_ast::expressions::Expression;
209    match expr {
210        Expression::Identifier(_) => "Identifier",
211        Expression::StringLiteral(_) => "StringLiteral",
212        Expression::NumericLiteral(_) => "NumericLiteral",
213        Expression::BooleanLiteral(_) => "BooleanLiteral",
214        Expression::NullLiteral(_) => "NullLiteral",
215        Expression::BigIntLiteral(_) => "BigIntLiteral",
216        Expression::RegExpLiteral(_) => "RegExpLiteral",
217        Expression::CallExpression(_) => "CallExpression",
218        Expression::MemberExpression(_) => "MemberExpression",
219        Expression::OptionalCallExpression(_) => "OptionalCallExpression",
220        Expression::OptionalMemberExpression(_) => "OptionalMemberExpression",
221        Expression::BinaryExpression(_) => "BinaryExpression",
222        Expression::LogicalExpression(_) => "LogicalExpression",
223        Expression::UnaryExpression(_) => "UnaryExpression",
224        Expression::UpdateExpression(_) => "UpdateExpression",
225        Expression::ConditionalExpression(_) => "ConditionalExpression",
226        Expression::AssignmentExpression(_) => "AssignmentExpression",
227        Expression::SequenceExpression(_) => "SequenceExpression",
228        Expression::ArrowFunctionExpression(_) => "ArrowFunctionExpression",
229        Expression::FunctionExpression(_) => "FunctionExpression",
230        Expression::ObjectExpression(_) => "ObjectExpression",
231        Expression::ArrayExpression(_) => "ArrayExpression",
232        Expression::NewExpression(_) => "NewExpression",
233        Expression::TemplateLiteral(_) => "TemplateLiteral",
234        Expression::TaggedTemplateExpression(_) => "TaggedTemplateExpression",
235        Expression::AwaitExpression(_) => "AwaitExpression",
236        Expression::YieldExpression(_) => "YieldExpression",
237        Expression::SpreadElement(_) => "SpreadElement",
238        Expression::MetaProperty(_) => "MetaProperty",
239        Expression::ClassExpression(_) => "ClassExpression",
240        Expression::PrivateName(_) => "PrivateName",
241        Expression::Super(_) => "Super",
242        Expression::Import(_) => "Import",
243        Expression::ThisExpression(_) => "ThisExpression",
244        Expression::ParenthesizedExpression(_) => "ParenthesizedExpression",
245        Expression::JSXElement(_) => "JSXElement",
246        Expression::JSXFragment(_) => "JSXFragment",
247        Expression::AssignmentPattern(_) => "AssignmentPattern",
248        Expression::TSAsExpression(_) => "TSAsExpression",
249        Expression::TSSatisfiesExpression(_) => "TSSatisfiesExpression",
250        Expression::TSNonNullExpression(_) => "TSNonNullExpression",
251        Expression::TSTypeAssertion(_) => "TSTypeAssertion",
252        Expression::TSInstantiationExpression(_) => "TSInstantiationExpression",
253        Expression::TypeCastExpression(_) => "TypeCastExpression",
254    }
255}
256
257/// Extract the type annotation name from an identifier's typeAnnotation field.
258/// The Babel AST stores type annotations as:
259/// { "type": "TSTypeAnnotation", "typeAnnotation": { "type": "TSTypeReference", ... } }
260/// or { "type": "TypeAnnotation", "typeAnnotation": { "type": "GenericTypeAnnotation", ... } }
261/// We extract the inner typeAnnotation's `type` field name.
262fn extract_type_annotation_name(
263    type_annotation: &Option<Box<serde_json::Value>>,
264) -> Option<String> {
265    let val = type_annotation.as_ref()?;
266    // Navigate: typeAnnotation.typeAnnotation.type
267    let inner = val.get("typeAnnotation")?;
268    let type_name = inner.get("type")?.as_str()?;
269    Some(type_name.to_string())
270}
271
272// =============================================================================
273// Helper functions
274// =============================================================================
275
276fn build_temporary_place(builder: &mut HirBuilder, loc: Option<SourceLocation>) -> Place {
277    let id = builder.make_temporary(loc.clone());
278    Place {
279        identifier: id,
280        reactive: false,
281        effect: Effect::Unknown,
282        loc,
283    }
284}
285
286/// Promote a temporary identifier to a named identifier (for destructuring).
287/// Corresponds to TS `promoteTemporary(identifier)`.
288fn promote_temporary(builder: &mut HirBuilder, identifier_id: IdentifierId) {
289    let env = builder.environment_mut();
290    let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id;
291    env.identifiers[identifier_id.0 as usize].name =
292        Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
293}
294
295fn lower_value_to_temporary(
296    builder: &mut HirBuilder,
297    value: InstructionValue,
298) -> Result<Place, CompilerError> {
299    // Optimization: if loading an unnamed temporary, skip creating a new instruction
300    if let InstructionValue::LoadLocal { ref place, .. } = value {
301        let ident = &builder.environment().identifiers[place.identifier.0 as usize];
302        if ident.name.is_none() {
303            return Ok(place.clone());
304        }
305    }
306    let loc = value.loc().cloned();
307    let place = build_temporary_place(builder, loc.clone());
308    builder.push(Instruction {
309        id: EvaluationOrder(0),
310        lvalue: place.clone(),
311        value,
312        loc,
313        effects: None,
314    });
315    Ok(place)
316}
317
318fn lower_expression_to_temporary(
319    builder: &mut HirBuilder,
320    expr: &react_compiler_ast::expressions::Expression,
321) -> Result<Place, CompilerError> {
322    let value = lower_expression(builder, expr)?;
323    Ok(lower_value_to_temporary(builder, value)?)
324}
325
326// =============================================================================
327// Operator conversion
328// =============================================================================
329
330fn convert_binary_operator(op: &react_compiler_ast::operators::BinaryOperator) -> BinaryOperator {
331    use react_compiler_ast::operators::BinaryOperator as AstOp;
332    match op {
333        AstOp::Add => BinaryOperator::Add,
334        AstOp::Sub => BinaryOperator::Subtract,
335        AstOp::Mul => BinaryOperator::Multiply,
336        AstOp::Div => BinaryOperator::Divide,
337        AstOp::Rem => BinaryOperator::Modulo,
338        AstOp::Exp => BinaryOperator::Exponent,
339        AstOp::Eq => BinaryOperator::Equal,
340        AstOp::StrictEq => BinaryOperator::StrictEqual,
341        AstOp::Neq => BinaryOperator::NotEqual,
342        AstOp::StrictNeq => BinaryOperator::StrictNotEqual,
343        AstOp::Lt => BinaryOperator::LessThan,
344        AstOp::Lte => BinaryOperator::LessEqual,
345        AstOp::Gt => BinaryOperator::GreaterThan,
346        AstOp::Gte => BinaryOperator::GreaterEqual,
347        AstOp::Shl => BinaryOperator::ShiftLeft,
348        AstOp::Shr => BinaryOperator::ShiftRight,
349        AstOp::UShr => BinaryOperator::UnsignedShiftRight,
350        AstOp::BitOr => BinaryOperator::BitwiseOr,
351        AstOp::BitXor => BinaryOperator::BitwiseXor,
352        AstOp::BitAnd => BinaryOperator::BitwiseAnd,
353        AstOp::In => BinaryOperator::In,
354        AstOp::Instanceof => BinaryOperator::InstanceOf,
355        AstOp::Pipeline => {
356            unreachable!("Pipeline operator is checked before calling convert_binary_operator")
357        }
358    }
359}
360
361fn convert_unary_operator(op: &react_compiler_ast::operators::UnaryOperator) -> UnaryOperator {
362    use react_compiler_ast::operators::UnaryOperator as AstOp;
363    match op {
364        AstOp::Neg => UnaryOperator::Minus,
365        AstOp::Plus => UnaryOperator::Plus,
366        AstOp::Not => UnaryOperator::Not,
367        AstOp::BitNot => UnaryOperator::BitwiseNot,
368        AstOp::TypeOf => UnaryOperator::TypeOf,
369        AstOp::Void => UnaryOperator::Void,
370        AstOp::Delete | AstOp::Throw => unreachable!("delete/throw handled separately"),
371    }
372}
373
374// =============================================================================
375// lower_identifier
376// =============================================================================
377
378/// Resolve an identifier to a Place.
379///
380/// For local/context identifiers, returns a Place referencing the binding's identifier.
381/// For globals/imports, emits a LoadGlobal instruction and returns the temporary Place.
382fn lower_identifier(
383    builder: &mut HirBuilder,
384    name: &str,
385    start: u32,
386    loc: Option<SourceLocation>,
387    node_id: Option<u32>,
388) -> Result<Place, CompilerError> {
389    let binding = builder.resolve_identifier(name, start, loc.clone(), node_id)?;
390    match binding {
391        VariableBinding::Identifier { identifier, .. } => Ok(Place {
392            identifier,
393            effect: Effect::Unknown,
394            reactive: false,
395            loc,
396        }),
397        _ => {
398            if let VariableBinding::Global { ref name } = binding {
399                if name == "eval" {
400                    builder.record_error(CompilerErrorDetail {
401                        category: ErrorCategory::UnsupportedSyntax,
402                        reason: "The 'eval' function is not supported".to_string(),
403                        description: Some(
404                            "Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler".to_string(),
405                        ),
406                        loc: loc.clone(),
407                        suggestions: None,
408                    })?;
409                }
410            }
411            let non_local_binding = match binding {
412                VariableBinding::Global { name } => NonLocalBinding::Global { name },
413                VariableBinding::ImportDefault { name, module } => {
414                    NonLocalBinding::ImportDefault { name, module }
415                }
416                VariableBinding::ImportSpecifier {
417                    name,
418                    module,
419                    imported,
420                } => NonLocalBinding::ImportSpecifier {
421                    name,
422                    module,
423                    imported,
424                },
425                VariableBinding::ImportNamespace { name, module } => {
426                    NonLocalBinding::ImportNamespace { name, module }
427                }
428                VariableBinding::ModuleLocal { name } => NonLocalBinding::ModuleLocal { name },
429                VariableBinding::Identifier { .. } => unreachable!(),
430            };
431            let instr_value = InstructionValue::LoadGlobal {
432                binding: non_local_binding,
433                loc: loc.clone(),
434            };
435            Ok(lower_value_to_temporary(builder, instr_value)?)
436        }
437    }
438}
439
440// =============================================================================
441// lower_arguments
442// =============================================================================
443
444fn lower_arguments(
445    builder: &mut HirBuilder,
446    args: &[react_compiler_ast::expressions::Expression],
447) -> Result<Vec<PlaceOrSpread>, CompilerError> {
448    use react_compiler_ast::expressions::Expression;
449    let mut result = Vec::new();
450    for arg in args {
451        match arg {
452            Expression::SpreadElement(spread) => {
453                let place = lower_expression_to_temporary(builder, &spread.argument)?;
454                result.push(PlaceOrSpread::Spread(SpreadPattern { place }));
455            }
456            _ => {
457                let place = lower_expression_to_temporary(builder, arg)?;
458                result.push(PlaceOrSpread::Place(place));
459            }
460        }
461    }
462    Ok(result)
463}
464
465fn convert_update_operator(op: &react_compiler_ast::operators::UpdateOperator) -> UpdateOperator {
466    match op {
467        react_compiler_ast::operators::UpdateOperator::Increment => UpdateOperator::Increment,
468        react_compiler_ast::operators::UpdateOperator::Decrement => UpdateOperator::Decrement,
469    }
470}
471
472// =============================================================================
473// lower_member_expression
474// =============================================================================
475
476enum MemberProperty {
477    Literal(PropertyLiteral),
478    Computed(Place),
479}
480
481struct LoweredMemberExpression {
482    object: Place,
483    property: MemberProperty,
484    value: InstructionValue,
485}
486
487fn lower_member_expression(
488    builder: &mut HirBuilder,
489    member: &react_compiler_ast::expressions::MemberExpression,
490) -> Result<LoweredMemberExpression, CompilerError> {
491    Ok(lower_member_expression_impl(builder, member, None)?)
492}
493
494fn lower_member_expression_with_object(
495    builder: &mut HirBuilder,
496    member: &react_compiler_ast::expressions::OptionalMemberExpression,
497    lowered_object: Place,
498) -> Result<LoweredMemberExpression, CompilerError> {
499    // OptionalMemberExpression has the same shape as MemberExpression for property access
500    use react_compiler_ast::expressions::Expression;
501    let loc = convert_opt_loc(&member.base.loc);
502    let object = lowered_object;
503
504    if !member.computed {
505        let prop_literal = match member.property.as_ref() {
506            Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),
507            Expression::NumericLiteral(lit) => {
508                PropertyLiteral::Number(FloatValue::new(lit.precise_value()))
509            }
510            _ => {
511                builder.record_error(CompilerErrorDetail {
512                    category: ErrorCategory::Todo,
513                    reason: format!(
514                        "(BuildHIR::lowerMemberExpression) Handle {:?} property",
515                        member.property
516                    ),
517                    description: None,
518                    loc: loc.clone(),
519                    suggestions: None,
520                })?;
521                return Ok(LoweredMemberExpression {
522                    object,
523                    property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),
524                    value: InstructionValue::UnsupportedNode {
525                        node_type: Some("OptionalMemberExpression".to_string()),
526                        original_node: serialize_expression(
527                            &react_compiler_ast::expressions::Expression::OptionalMemberExpression(
528                                member.clone(),
529                            ),
530                        ),
531                        loc,
532                    },
533                });
534            }
535        };
536        let value = InstructionValue::PropertyLoad {
537            object: object.clone(),
538            property: prop_literal.clone(),
539            loc,
540        };
541        Ok(LoweredMemberExpression {
542            object,
543            property: MemberProperty::Literal(prop_literal),
544            value,
545        })
546    } else {
547        if let Expression::NumericLiteral(lit) = member.property.as_ref() {
548            let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));
549            let value = InstructionValue::PropertyLoad {
550                object: object.clone(),
551                property: prop_literal.clone(),
552                loc,
553            };
554            return Ok(LoweredMemberExpression {
555                object,
556                property: MemberProperty::Literal(prop_literal),
557                value,
558            });
559        }
560        let property = lower_expression_to_temporary(builder, &member.property)?;
561        let value = InstructionValue::ComputedLoad {
562            object: object.clone(),
563            property: property.clone(),
564            loc,
565        };
566        Ok(LoweredMemberExpression {
567            object,
568            property: MemberProperty::Computed(property),
569            value,
570        })
571    }
572}
573
574fn lower_member_expression_impl(
575    builder: &mut HirBuilder,
576    member: &react_compiler_ast::expressions::MemberExpression,
577    lowered_object: Option<Place>,
578) -> Result<LoweredMemberExpression, CompilerError> {
579    use react_compiler_ast::expressions::Expression;
580    let loc = convert_opt_loc(&member.base.loc);
581    let object = match lowered_object {
582        Some(obj) => obj,
583        None => lower_expression_to_temporary(builder, &member.object)?,
584    };
585
586    if !member.computed {
587        // Non-computed: property must be an identifier or numeric literal
588        let prop_literal = match member.property.as_ref() {
589            Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),
590            Expression::NumericLiteral(lit) => {
591                PropertyLiteral::Number(FloatValue::new(lit.precise_value()))
592            }
593            _ => {
594                builder.record_error(CompilerErrorDetail {
595                    category: ErrorCategory::Todo,
596                    reason: format!(
597                        "(BuildHIR::lowerMemberExpression) Handle {:?} property",
598                        member.property
599                    ),
600                    description: None,
601                    loc: loc.clone(),
602                    suggestions: None,
603                })?;
604                return Ok(LoweredMemberExpression {
605                    object,
606                    property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),
607                    value: InstructionValue::UnsupportedNode {
608                        node_type: Some("MemberExpression".to_string()),
609                        original_node: serialize_expression(
610                            &react_compiler_ast::expressions::Expression::MemberExpression(
611                                member.clone(),
612                            ),
613                        ),
614                        loc,
615                    },
616                });
617            }
618        };
619        let value = InstructionValue::PropertyLoad {
620            object: object.clone(),
621            property: prop_literal.clone(),
622            loc,
623        };
624        Ok(LoweredMemberExpression {
625            object,
626            property: MemberProperty::Literal(prop_literal),
627            value,
628        })
629    } else {
630        // Computed: check for numeric literal first (treated as PropertyLoad in TS)
631        if let Expression::NumericLiteral(lit) = member.property.as_ref() {
632            let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));
633            let value = InstructionValue::PropertyLoad {
634                object: object.clone(),
635                property: prop_literal.clone(),
636                loc,
637            };
638            return Ok(LoweredMemberExpression {
639                object,
640                property: MemberProperty::Literal(prop_literal),
641                value,
642            });
643        }
644        // Otherwise lower property to temporary for ComputedLoad
645        let property = lower_expression_to_temporary(builder, &member.property)?;
646        let value = InstructionValue::ComputedLoad {
647            object: object.clone(),
648            property: property.clone(),
649            loc,
650        };
651        Ok(LoweredMemberExpression {
652            object,
653            property: MemberProperty::Computed(property),
654            value,
655        })
656    }
657}
658
659// =============================================================================
660// lower_expression
661// =============================================================================
662
663fn lower_expression(
664    builder: &mut HirBuilder,
665    expr: &react_compiler_ast::expressions::Expression,
666) -> Result<InstructionValue, CompilerError> {
667    use react_compiler_ast::expressions::Expression;
668
669    match expr {
670        Expression::Identifier(ident) => {
671            let loc = convert_opt_loc(&ident.base.loc);
672            let start = ident.base.start.unwrap_or(0);
673            let place =
674                lower_identifier(builder, &ident.name, start, loc.clone(), ident.base.node_id)?;
675            // Determine LoadLocal vs LoadContext based on context identifier check
676            if builder.is_context_identifier(&ident.name, start, ident.base.node_id) {
677                Ok(InstructionValue::LoadContext { place, loc })
678            } else {
679                Ok(InstructionValue::LoadLocal { place, loc })
680            }
681        }
682        Expression::NullLiteral(lit) => {
683            let loc = convert_opt_loc(&lit.base.loc);
684            Ok(InstructionValue::Primitive {
685                value: PrimitiveValue::Null,
686                loc,
687            })
688        }
689        Expression::BooleanLiteral(lit) => {
690            let loc = convert_opt_loc(&lit.base.loc);
691            Ok(InstructionValue::Primitive {
692                value: PrimitiveValue::Boolean(lit.value),
693                loc,
694            })
695        }
696        Expression::NumericLiteral(lit) => {
697            let loc = convert_opt_loc(&lit.base.loc);
698            Ok(InstructionValue::Primitive {
699                value: PrimitiveValue::Number(FloatValue::new(lit.precise_value())),
700                loc,
701            })
702        }
703        Expression::StringLiteral(lit) => {
704            let loc = convert_opt_loc(&lit.base.loc);
705            Ok(InstructionValue::Primitive {
706                value: PrimitiveValue::String(lit.value.clone()),
707                loc,
708            })
709        }
710        Expression::BinaryExpression(bin) => {
711            let loc = convert_opt_loc(&bin.base.loc);
712            // Check for pipeline operator before lowering operands
713            if matches!(
714                bin.operator,
715                react_compiler_ast::operators::BinaryOperator::Pipeline
716            ) {
717                builder.record_error(CompilerErrorDetail {
718                    category: ErrorCategory::Todo,
719                    reason: "(BuildHIR::lowerExpression) Pipe operator not supported".to_string(),
720                    description: None,
721                    loc: loc.clone(),
722                    suggestions: None,
723                })?;
724                return Ok(InstructionValue::UnsupportedNode {
725                    node_type: Some("BinaryExpression".to_string()),
726                    original_node: serialize_expression(expr),
727                    loc,
728                });
729            }
730            let left = lower_expression_to_temporary(builder, &bin.left)?;
731            let right = lower_expression_to_temporary(builder, &bin.right)?;
732            let operator = convert_binary_operator(&bin.operator);
733            Ok(InstructionValue::BinaryExpression {
734                operator,
735                left,
736                right,
737                loc,
738            })
739        }
740        Expression::UnaryExpression(unary) => {
741            let loc = convert_opt_loc(&unary.base.loc);
742            match &unary.operator {
743                react_compiler_ast::operators::UnaryOperator::Delete => {
744                    // Delete can be on member expressions or identifiers
745                    let loc = convert_opt_loc(&unary.base.loc);
746                    match &*unary.argument {
747                        Expression::MemberExpression(member) => {
748                            let object = lower_expression_to_temporary(builder, &member.object)?;
749                            if !member.computed {
750                                match &*member.property {
751                                    Expression::Identifier(prop_id) => {
752                                        Ok(InstructionValue::PropertyDelete {
753                                            object,
754                                            property: PropertyLiteral::String(prop_id.name.clone()),
755                                            loc,
756                                        })
757                                    }
758                                    _ => {
759                                        builder.record_error(CompilerErrorDetail {
760                                            reason: "Unsupported delete target".to_string(),
761                                            category: ErrorCategory::Todo,
762                                            loc: loc.clone(),
763                                            description: None,
764                                            suggestions: None,
765                                        })?;
766                                        Ok(InstructionValue::UnsupportedNode {
767                                            node_type: Some("UnaryExpression".to_string()),
768                                            original_node: serialize_expression(expr),
769                                            loc,
770                                        })
771                                    }
772                                }
773                            } else {
774                                let property =
775                                    lower_expression_to_temporary(builder, &member.property)?;
776                                Ok(InstructionValue::ComputedDelete {
777                                    object,
778                                    property,
779                                    loc,
780                                })
781                            }
782                        }
783                        _ => {
784                            // delete on non-member expression (e.g., optional chain, identifier)
785                            builder.record_error(CompilerErrorDetail {
786                                reason: "Only object properties can be deleted".to_string(),
787                                category: ErrorCategory::Syntax,
788                                loc: loc.clone(),
789                                description: None,
790                                suggestions: None,
791                            })?;
792                            Ok(InstructionValue::UnsupportedNode {
793                                node_type: Some("UnaryExpression".to_string()),
794                                original_node: serialize_expression(expr),
795                                loc,
796                            })
797                        }
798                    }
799                }
800                react_compiler_ast::operators::UnaryOperator::Throw => {
801                    // throw as unary operator (Babel-specific)
802                    let loc = convert_opt_loc(&unary.base.loc);
803                    builder.record_error(CompilerErrorDetail {
804                        reason: "throw expressions are not supported".to_string(),
805                        category: ErrorCategory::Todo,
806                        loc: loc.clone(),
807                        description: None,
808                        suggestions: None,
809                    })?;
810                    Ok(InstructionValue::UnsupportedNode {
811                        node_type: Some("UnaryExpression".to_string()),
812                        original_node: serialize_expression(expr),
813                        loc,
814                    })
815                }
816                op => {
817                    let value = lower_expression_to_temporary(builder, &unary.argument)?;
818                    let operator = convert_unary_operator(op);
819                    Ok(InstructionValue::UnaryExpression {
820                        operator,
821                        value,
822                        loc,
823                    })
824                }
825            }
826        }
827        Expression::CallExpression(call) => {
828            let loc = convert_opt_loc(&call.base.loc);
829            // Check if callee is a MemberExpression => MethodCall
830            if let Expression::MemberExpression(member) = call.callee.as_ref() {
831                let lowered = lower_member_expression(builder, member)?;
832                let property = lower_value_to_temporary(builder, lowered.value)?;
833                let args = lower_arguments(builder, &call.arguments)?;
834                Ok(InstructionValue::MethodCall {
835                    receiver: lowered.object,
836                    property,
837                    args,
838                    loc,
839                })
840            } else {
841                let callee = lower_expression_to_temporary(builder, &call.callee)?;
842                let args = lower_arguments(builder, &call.arguments)?;
843                Ok(InstructionValue::CallExpression { callee, args, loc })
844            }
845        }
846        Expression::MemberExpression(member) => {
847            let lowered = lower_member_expression(builder, member)?;
848            Ok(lowered.value)
849        }
850        Expression::OptionalCallExpression(opt_call) => {
851            Ok(lower_optional_call_expression(builder, opt_call)?)
852        }
853        Expression::OptionalMemberExpression(opt_member) => {
854            Ok(lower_optional_member_expression(builder, opt_member)?)
855        }
856        Expression::LogicalExpression(expr) => {
857            let loc = convert_opt_loc(&expr.base.loc);
858            let continuation_block = builder.reserve(builder.current_block_kind());
859            let continuation_id = continuation_block.id;
860            let test_block = builder.reserve(BlockKind::Value);
861            let test_block_id = test_block.id;
862            let place = build_temporary_place(builder, loc.clone());
863            let left_loc = expression_loc(&expr.left);
864            let left_place = build_temporary_place(builder, left_loc);
865
866            // Block for short-circuit case: store left value as result, goto continuation
867            let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
868                lower_value_to_temporary(
869                    builder,
870                    InstructionValue::StoreLocal {
871                        lvalue: LValue {
872                            kind: InstructionKind::Const,
873                            place: place.clone(),
874                        },
875                        value: left_place.clone(),
876                        type_annotation: None,
877                        loc: left_place.loc.clone(),
878                    },
879                )?;
880                Ok(Terminal::Goto {
881                    block: continuation_id,
882                    variant: GotoVariant::Break,
883                    id: EvaluationOrder(0),
884                    loc: left_place.loc.clone(),
885                })
886            });
887
888            // Block for evaluating right side
889            let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
890                let right = lower_expression_to_temporary(builder, &expr.right)?;
891                let right_loc = right.loc.clone();
892                lower_value_to_temporary(
893                    builder,
894                    InstructionValue::StoreLocal {
895                        lvalue: LValue {
896                            kind: InstructionKind::Const,
897                            place: place.clone(),
898                        },
899                        value: right,
900                        type_annotation: None,
901                        loc: right_loc.clone(),
902                    },
903                )?;
904                Ok(Terminal::Goto {
905                    block: continuation_id,
906                    variant: GotoVariant::Break,
907                    id: EvaluationOrder(0),
908                    loc: right_loc,
909                })
910            });
911
912            let hir_op = match expr.operator {
913                react_compiler_ast::operators::LogicalOperator::And => LogicalOperator::And,
914                react_compiler_ast::operators::LogicalOperator::Or => LogicalOperator::Or,
915                react_compiler_ast::operators::LogicalOperator::NullishCoalescing => {
916                    LogicalOperator::NullishCoalescing
917                }
918            };
919
920            builder.terminate_with_continuation(
921                Terminal::Logical {
922                    operator: hir_op,
923                    test: test_block_id,
924                    fallthrough: continuation_id,
925                    id: EvaluationOrder(0),
926                    loc: loc.clone(),
927                },
928                test_block,
929            );
930
931            // Now in test block: lower left expression, copy to left_place
932            let left_value = lower_expression_to_temporary(builder, &expr.left)?;
933            builder.push(Instruction {
934                id: EvaluationOrder(0),
935                lvalue: left_place.clone(),
936                value: InstructionValue::LoadLocal {
937                    place: left_value,
938                    loc: loc.clone(),
939                },
940                effects: None,
941                loc: loc.clone(),
942            });
943
944            builder.terminate_with_continuation(
945                Terminal::Branch {
946                    test: left_place,
947                    consequent: consequent_block?,
948                    alternate: alternate_block?,
949                    fallthrough: continuation_id,
950                    id: EvaluationOrder(0),
951                    loc: loc.clone(),
952                },
953                continuation_block,
954            );
955
956            Ok(InstructionValue::LoadLocal {
957                place: place.clone(),
958                loc: place.loc.clone(),
959            })
960        }
961        Expression::UpdateExpression(update) => {
962            let loc = convert_opt_loc(&update.base.loc);
963            match update.argument.as_ref() {
964                Expression::MemberExpression(member) => {
965                    let binary_op = match &update.operator {
966                        react_compiler_ast::operators::UpdateOperator::Increment => {
967                            BinaryOperator::Add
968                        }
969                        react_compiler_ast::operators::UpdateOperator::Decrement => {
970                            BinaryOperator::Subtract
971                        }
972                    };
973                    // Use the member expression's loc (not the update expression's)
974                    // to match TS behavior where the inner operations use leftExpr.node.loc
975                    let member_loc = convert_opt_loc(&member.base.loc);
976                    let lowered = lower_member_expression(builder, member)?;
977                    let object = lowered.object;
978                    let lowered_property = lowered.property;
979                    let prev_value = lower_value_to_temporary(builder, lowered.value)?;
980
981                    let one = lower_value_to_temporary(
982                        builder,
983                        InstructionValue::Primitive {
984                            value: PrimitiveValue::Number(FloatValue::new(1.0)),
985                            loc: None,
986                        },
987                    )?;
988                    let updated = lower_value_to_temporary(
989                        builder,
990                        InstructionValue::BinaryExpression {
991                            operator: binary_op,
992                            left: prev_value.clone(),
993                            right: one,
994                            loc: member_loc.clone(),
995                        },
996                    )?;
997
998                    // Store back using the property from the lowered member expression.
999                    // For prefix, the result is the PropertyStore/ComputedStore lvalue
1000                    // (matching TS which uses newValuePlace). For postfix, it's prev_value.
1001                    let new_value_place = match lowered_property {
1002                        MemberProperty::Literal(prop_literal) => lower_value_to_temporary(
1003                            builder,
1004                            InstructionValue::PropertyStore {
1005                                object,
1006                                property: prop_literal,
1007                                value: updated.clone(),
1008                                loc: member_loc,
1009                            },
1010                        )?,
1011                        MemberProperty::Computed(prop_place) => lower_value_to_temporary(
1012                            builder,
1013                            InstructionValue::ComputedStore {
1014                                object,
1015                                property: prop_place,
1016                                value: updated.clone(),
1017                                loc: member_loc,
1018                            },
1019                        )?,
1020                    };
1021
1022                    // Return previous for postfix, newValuePlace for prefix
1023                    let result_place = if update.prefix {
1024                        new_value_place
1025                    } else {
1026                        prev_value
1027                    };
1028                    Ok(InstructionValue::LoadLocal {
1029                        place: result_place.clone(),
1030                        loc: result_place.loc.clone(),
1031                    })
1032                }
1033                Expression::Identifier(ident) => {
1034                    let start = ident.base.start.unwrap_or(0);
1035                    if builder.is_context_identifier(&ident.name, start, ident.base.node_id) {
1036                        builder.record_error(CompilerErrorDetail {
1037                            category: ErrorCategory::Todo,
1038                            reason: "(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.".to_string(),
1039                            description: None,
1040                            loc: loc.clone(),
1041                            suggestions: None,
1042                        })?;
1043                        return Ok(InstructionValue::UnsupportedNode {
1044                            node_type: Some("UpdateExpression".to_string()),
1045                            original_node: serialize_expression(expr),
1046                            loc,
1047                        });
1048                    }
1049
1050                    let ident_loc = convert_opt_loc(&ident.base.loc);
1051                    let binding = builder.resolve_identifier(
1052                        &ident.name,
1053                        start,
1054                        ident_loc.clone(),
1055                        ident.base.node_id,
1056                    )?;
1057                    match &binding {
1058                        VariableBinding::Global { .. } => {
1059                            builder.record_error(CompilerErrorDetail {
1060                                category: ErrorCategory::Todo,
1061                                reason: "UpdateExpression where argument is a global is not yet supported".to_string(),
1062                                description: None,
1063                                loc: loc.clone(),
1064                                suggestions: None,
1065                            })?;
1066                            return Ok(InstructionValue::UnsupportedNode {
1067                                node_type: Some("UpdateExpression".to_string()),
1068                                original_node: serialize_expression(expr),
1069                                loc,
1070                            });
1071                        }
1072                        _ => {}
1073                    }
1074                    let identifier = match binding {
1075                        VariableBinding::Identifier { identifier, .. } => identifier,
1076                        _ => {
1077                            builder.record_error(CompilerErrorDetail {
1078                                category: ErrorCategory::Todo,
1079                                reason: "(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global".to_string(),
1080                                description: None,
1081                                loc: loc.clone(),
1082                                suggestions: None,
1083                            })?;
1084                            return Ok(InstructionValue::UnsupportedNode {
1085                                node_type: Some("UpdateExpression".to_string()),
1086                                original_node: serialize_expression(expr),
1087                                loc,
1088                            });
1089                        }
1090                    };
1091                    let lvalue_place = Place {
1092                        identifier,
1093                        effect: Effect::Unknown,
1094                        reactive: false,
1095                        loc: ident_loc.clone(),
1096                    };
1097
1098                    // Load the current value
1099                    let value = lower_identifier(
1100                        builder,
1101                        &ident.name,
1102                        start,
1103                        ident_loc,
1104                        ident.base.node_id,
1105                    )?;
1106
1107                    let operation = convert_update_operator(&update.operator);
1108
1109                    if update.prefix {
1110                        Ok(InstructionValue::PrefixUpdate {
1111                            lvalue: lvalue_place,
1112                            operation,
1113                            value,
1114                            loc,
1115                        })
1116                    } else {
1117                        Ok(InstructionValue::PostfixUpdate {
1118                            lvalue: lvalue_place,
1119                            operation,
1120                            value,
1121                            loc,
1122                        })
1123                    }
1124                }
1125                _ => {
1126                    builder.record_error(CompilerErrorDetail {
1127                        category: ErrorCategory::Todo,
1128                        reason: format!("UpdateExpression with unsupported argument type"),
1129                        description: None,
1130                        loc: loc.clone(),
1131                        suggestions: None,
1132                    })?;
1133                    Ok(InstructionValue::UnsupportedNode {
1134                        node_type: Some("UpdateExpression".to_string()),
1135                        original_node: serialize_expression(expr),
1136                        loc,
1137                    })
1138                }
1139            }
1140        }
1141        Expression::ConditionalExpression(expr) => {
1142            let loc = convert_opt_loc(&expr.base.loc);
1143            let continuation_block = builder.reserve(builder.current_block_kind());
1144            let continuation_id = continuation_block.id;
1145            let test_block = builder.reserve(BlockKind::Value);
1146            let test_block_id = test_block.id;
1147            let place = build_temporary_place(builder, loc.clone());
1148
1149            // Block for the consequent (test is truthy)
1150            let consequent_ast_loc = expression_loc(&expr.consequent);
1151            let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
1152                let consequent = lower_expression_to_temporary(builder, &expr.consequent)?;
1153                lower_value_to_temporary(
1154                    builder,
1155                    InstructionValue::StoreLocal {
1156                        lvalue: LValue {
1157                            kind: InstructionKind::Const,
1158                            place: place.clone(),
1159                        },
1160                        value: consequent,
1161                        type_annotation: None,
1162                        loc: loc.clone(),
1163                    },
1164                )?;
1165                Ok(Terminal::Goto {
1166                    block: continuation_id,
1167                    variant: GotoVariant::Break,
1168                    id: EvaluationOrder(0),
1169                    loc: consequent_ast_loc,
1170                })
1171            });
1172
1173            // Block for the alternate (test is falsy)
1174            let alternate_ast_loc = expression_loc(&expr.alternate);
1175            let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
1176                let alternate = lower_expression_to_temporary(builder, &expr.alternate)?;
1177                lower_value_to_temporary(
1178                    builder,
1179                    InstructionValue::StoreLocal {
1180                        lvalue: LValue {
1181                            kind: InstructionKind::Const,
1182                            place: place.clone(),
1183                        },
1184                        value: alternate,
1185                        type_annotation: None,
1186                        loc: loc.clone(),
1187                    },
1188                )?;
1189                Ok(Terminal::Goto {
1190                    block: continuation_id,
1191                    variant: GotoVariant::Break,
1192                    id: EvaluationOrder(0),
1193                    loc: alternate_ast_loc,
1194                })
1195            });
1196
1197            builder.terminate_with_continuation(
1198                Terminal::Ternary {
1199                    test: test_block_id,
1200                    fallthrough: continuation_id,
1201                    id: EvaluationOrder(0),
1202                    loc: loc.clone(),
1203                },
1204                test_block,
1205            );
1206
1207            // Now in test block: lower test expression
1208            let test_place = lower_expression_to_temporary(builder, &expr.test)?;
1209            builder.terminate_with_continuation(
1210                Terminal::Branch {
1211                    test: test_place,
1212                    consequent: consequent_block?,
1213                    alternate: alternate_block?,
1214                    fallthrough: continuation_id,
1215                    id: EvaluationOrder(0),
1216                    loc: loc.clone(),
1217                },
1218                continuation_block,
1219            );
1220
1221            Ok(InstructionValue::LoadLocal {
1222                place: place.clone(),
1223                loc: place.loc.clone(),
1224            })
1225        }
1226        Expression::AssignmentExpression(expr) => {
1227            use react_compiler_ast::operators::AssignmentOperator;
1228            let loc = convert_opt_loc(&expr.base.loc);
1229
1230            if matches!(expr.operator, AssignmentOperator::Assign) {
1231                // Simple `=` assignment
1232                match &*expr.left {
1233                    react_compiler_ast::patterns::PatternLike::Identifier(ident) => {
1234                        // Handle simple identifier assignment directly
1235                        let start = ident.base.start.unwrap_or(0);
1236                        let right = lower_expression_to_temporary(builder, &expr.right)?;
1237                        let ident_loc = convert_opt_loc(&ident.base.loc);
1238                        let binding = builder.resolve_identifier(
1239                            &ident.name,
1240                            start,
1241                            ident_loc.clone(),
1242                            ident.base.node_id,
1243                        )?;
1244                        match binding {
1245                            VariableBinding::Identifier {
1246                                identifier,
1247                                binding_kind,
1248                            } => {
1249                                // Check for const reassignment
1250                                if binding_kind == BindingKind::Const {
1251                                    builder.record_error(CompilerErrorDetail {
1252                                        reason: "Cannot reassign a `const` variable".to_string(),
1253                                        category: ErrorCategory::Syntax,
1254                                        loc: ident_loc.clone(),
1255                                        description: Some(format!(
1256                                            "`{}` is declared as const",
1257                                            &ident.name
1258                                        )),
1259                                        suggestions: None,
1260                                    })?;
1261                                    return Ok(InstructionValue::UnsupportedNode {
1262                                        node_type: Some("Identifier".to_string()),
1263                                        original_node: serialize_expression(
1264                                            &Expression::AssignmentExpression(expr.clone()),
1265                                        ),
1266                                        loc: ident_loc,
1267                                    });
1268                                }
1269                                let place = Place {
1270                                    identifier,
1271                                    reactive: false,
1272                                    effect: Effect::Unknown,
1273                                    loc: ident_loc,
1274                                };
1275                                if builder.is_context_identifier(
1276                                    &ident.name,
1277                                    start,
1278                                    ident.base.node_id,
1279                                ) {
1280                                    let temp = lower_value_to_temporary(
1281                                        builder,
1282                                        InstructionValue::StoreContext {
1283                                            lvalue: LValue {
1284                                                kind: InstructionKind::Reassign,
1285                                                place: place.clone(),
1286                                            },
1287                                            value: right,
1288                                            loc: place.loc.clone(),
1289                                        },
1290                                    )?;
1291                                    Ok(InstructionValue::LoadLocal {
1292                                        place: temp.clone(),
1293                                        loc: temp.loc.clone(),
1294                                    })
1295                                } else {
1296                                    let temp = lower_value_to_temporary(
1297                                        builder,
1298                                        InstructionValue::StoreLocal {
1299                                            lvalue: LValue {
1300                                                kind: InstructionKind::Reassign,
1301                                                place: place.clone(),
1302                                            },
1303                                            value: right,
1304                                            type_annotation: None,
1305                                            loc: place.loc.clone(),
1306                                        },
1307                                    )?;
1308                                    Ok(InstructionValue::LoadLocal {
1309                                        place: temp.clone(),
1310                                        loc: temp.loc.clone(),
1311                                    })
1312                                }
1313                            }
1314                            _ => {
1315                                // Global or import assignment
1316                                let name = ident.name.clone();
1317                                let temp = lower_value_to_temporary(
1318                                    builder,
1319                                    InstructionValue::StoreGlobal {
1320                                        name,
1321                                        value: right,
1322                                        loc: ident_loc,
1323                                    },
1324                                )?;
1325                                Ok(InstructionValue::LoadLocal {
1326                                    place: temp.clone(),
1327                                    loc: temp.loc.clone(),
1328                                })
1329                            }
1330                        }
1331                    }
1332                    react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {
1333                        // Member expression assignment: a.b = value or a[b] = value
1334                        let right = lower_expression_to_temporary(builder, &expr.right)?;
1335                        let left_loc = convert_opt_loc(&member.base.loc);
1336                        let object = lower_expression_to_temporary(builder, &member.object)?;
1337                        let temp = if !member.computed
1338                            || matches!(
1339                                &*member.property,
1340                                react_compiler_ast::expressions::Expression::NumericLiteral(_)
1341                            ) {
1342                            match &*member.property {
1343                                react_compiler_ast::expressions::Expression::Identifier(
1344                                    prop_id,
1345                                ) => lower_value_to_temporary(
1346                                    builder,
1347                                    InstructionValue::PropertyStore {
1348                                        object,
1349                                        property: PropertyLiteral::String(prop_id.name.clone()),
1350                                        value: right,
1351                                        loc: left_loc,
1352                                    },
1353                                )?,
1354                                react_compiler_ast::expressions::Expression::NumericLiteral(
1355                                    num,
1356                                ) => lower_value_to_temporary(
1357                                    builder,
1358                                    InstructionValue::PropertyStore {
1359                                        object,
1360                                        property: PropertyLiteral::Number(FloatValue::new(
1361                                            num.precise_value(),
1362                                        )),
1363                                        value: right,
1364                                        loc: left_loc,
1365                                    },
1366                                )?,
1367                                _ => {
1368                                    let prop =
1369                                        lower_expression_to_temporary(builder, &member.property)?;
1370                                    lower_value_to_temporary(
1371                                        builder,
1372                                        InstructionValue::ComputedStore {
1373                                            object,
1374                                            property: prop,
1375                                            value: right,
1376                                            loc: left_loc,
1377                                        },
1378                                    )?
1379                                }
1380                            }
1381                        } else {
1382                            let prop = lower_expression_to_temporary(builder, &member.property)?;
1383                            lower_value_to_temporary(
1384                                builder,
1385                                InstructionValue::ComputedStore {
1386                                    object,
1387                                    property: prop,
1388                                    value: right,
1389                                    loc: left_loc,
1390                                },
1391                            )?
1392                        };
1393                        Ok(InstructionValue::LoadLocal {
1394                            place: temp.clone(),
1395                            loc: temp.loc.clone(),
1396                        })
1397                    }
1398                    _ => {
1399                        // Destructuring assignment
1400                        let right = lower_expression_to_temporary(builder, &expr.right)?;
1401                        let left_loc = pattern_like_hir_loc(&expr.left);
1402                        let result = lower_assignment(
1403                            builder,
1404                            left_loc,
1405                            InstructionKind::Reassign,
1406                            &expr.left,
1407                            right.clone(),
1408                            AssignmentStyle::Destructure,
1409                        )?;
1410                        match result {
1411                            Some(place) => Ok(InstructionValue::LoadLocal {
1412                                place: place.clone(),
1413                                loc: place.loc.clone(),
1414                            }),
1415                            None => Ok(InstructionValue::LoadLocal { place: right, loc }),
1416                        }
1417                    }
1418                }
1419            } else {
1420                // Compound assignment operators
1421                let binary_op = match expr.operator {
1422                    AssignmentOperator::AddAssign => Some(BinaryOperator::Add),
1423                    AssignmentOperator::SubAssign => Some(BinaryOperator::Subtract),
1424                    AssignmentOperator::MulAssign => Some(BinaryOperator::Multiply),
1425                    AssignmentOperator::DivAssign => Some(BinaryOperator::Divide),
1426                    AssignmentOperator::RemAssign => Some(BinaryOperator::Modulo),
1427                    AssignmentOperator::ExpAssign => Some(BinaryOperator::Exponent),
1428                    AssignmentOperator::ShlAssign => Some(BinaryOperator::ShiftLeft),
1429                    AssignmentOperator::ShrAssign => Some(BinaryOperator::ShiftRight),
1430                    AssignmentOperator::UShrAssign => Some(BinaryOperator::UnsignedShiftRight),
1431                    AssignmentOperator::BitOrAssign => Some(BinaryOperator::BitwiseOr),
1432                    AssignmentOperator::BitXorAssign => Some(BinaryOperator::BitwiseXor),
1433                    AssignmentOperator::BitAndAssign => Some(BinaryOperator::BitwiseAnd),
1434                    AssignmentOperator::OrAssign
1435                    | AssignmentOperator::AndAssign
1436                    | AssignmentOperator::NullishAssign => {
1437                        // Logical assignment operators (||=, &&=, ??=) - not yet supported
1438                        builder.record_error(CompilerErrorDetail {
1439                            reason:
1440                                "Logical assignment operators (||=, &&=, ??=) are not yet supported"
1441                                    .to_string(),
1442                            category: ErrorCategory::Todo,
1443                            loc: loc.clone(),
1444                            description: None,
1445                            suggestions: None,
1446                        })?;
1447                        return Ok(InstructionValue::UnsupportedNode {
1448                            node_type: Some("AssignmentExpression".to_string()),
1449                            original_node: serialize_expression(&Expression::AssignmentExpression(
1450                                expr.clone(),
1451                            )),
1452                            loc,
1453                        });
1454                    }
1455                    AssignmentOperator::Assign => unreachable!(),
1456                };
1457                let binary_op = match binary_op {
1458                    Some(op) => op,
1459                    None => {
1460                        return Ok(InstructionValue::UnsupportedNode {
1461                            node_type: Some("AssignmentExpression".to_string()),
1462                            original_node: serialize_expression(&Expression::AssignmentExpression(
1463                                expr.clone(),
1464                            )),
1465                            loc,
1466                        });
1467                    }
1468                };
1469
1470                match &*expr.left {
1471                    react_compiler_ast::patterns::PatternLike::Identifier(ident) => {
1472                        let start = ident.base.start.unwrap_or(0);
1473                        let left_place = lower_expression_to_temporary(
1474                            builder,
1475                            &react_compiler_ast::expressions::Expression::Identifier(ident.clone()),
1476                        )?;
1477                        let right = lower_expression_to_temporary(builder, &expr.right)?;
1478                        let binary_place = lower_value_to_temporary(
1479                            builder,
1480                            InstructionValue::BinaryExpression {
1481                                operator: binary_op,
1482                                left: left_place,
1483                                right,
1484                                loc: loc.clone(),
1485                            },
1486                        )?;
1487                        let ident_loc = convert_opt_loc(&ident.base.loc);
1488                        let binding = builder.resolve_identifier(
1489                            &ident.name,
1490                            start,
1491                            ident_loc.clone(),
1492                            ident.base.node_id,
1493                        )?;
1494                        match binding {
1495                            VariableBinding::Identifier { identifier, .. } => {
1496                                let place = Place {
1497                                    identifier,
1498                                    reactive: false,
1499                                    effect: Effect::Unknown,
1500                                    loc: ident_loc,
1501                                };
1502                                if builder.is_context_identifier(
1503                                    &ident.name,
1504                                    start,
1505                                    ident.base.node_id,
1506                                ) {
1507                                    lower_value_to_temporary(
1508                                        builder,
1509                                        InstructionValue::StoreContext {
1510                                            lvalue: LValue {
1511                                                kind: InstructionKind::Reassign,
1512                                                place: place.clone(),
1513                                            },
1514                                            value: binary_place,
1515                                            loc: loc.clone(),
1516                                        },
1517                                    )?;
1518                                    Ok(InstructionValue::LoadContext { place, loc })
1519                                } else {
1520                                    lower_value_to_temporary(
1521                                        builder,
1522                                        InstructionValue::StoreLocal {
1523                                            lvalue: LValue {
1524                                                kind: InstructionKind::Reassign,
1525                                                place: place.clone(),
1526                                            },
1527                                            value: binary_place,
1528                                            type_annotation: None,
1529                                            loc: loc.clone(),
1530                                        },
1531                                    )?;
1532                                    Ok(InstructionValue::LoadLocal { place, loc })
1533                                }
1534                            }
1535                            _ => {
1536                                // Global assignment
1537                                let name = ident.name.clone();
1538                                let temp = lower_value_to_temporary(
1539                                    builder,
1540                                    InstructionValue::StoreGlobal {
1541                                        name,
1542                                        value: binary_place,
1543                                        loc: loc.clone(),
1544                                    },
1545                                )?;
1546                                Ok(InstructionValue::LoadLocal {
1547                                    place: temp.clone(),
1548                                    loc: temp.loc.clone(),
1549                                })
1550                            }
1551                        }
1552                    }
1553                    react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {
1554                        // a.b += right: read, compute, store
1555                        // Match TS behavior: return the PropertyStore/ComputedStore value
1556                        // directly (let the caller lower it to a temporary)
1557                        let member_loc = convert_opt_loc(&member.base.loc);
1558                        let lowered = lower_member_expression(builder, member)?;
1559                        let object = lowered.object;
1560                        let lowered_property = lowered.property;
1561                        let current_value = lower_value_to_temporary(builder, lowered.value)?;
1562                        let right = lower_expression_to_temporary(builder, &expr.right)?;
1563                        let result = lower_value_to_temporary(
1564                            builder,
1565                            InstructionValue::BinaryExpression {
1566                                operator: binary_op,
1567                                left: current_value,
1568                                right,
1569                                loc: member_loc.clone(),
1570                            },
1571                        )?;
1572                        // Return the store instruction value directly (matching TS behavior)
1573                        match lowered_property {
1574                            MemberProperty::Literal(prop_literal) => {
1575                                Ok(InstructionValue::PropertyStore {
1576                                    object,
1577                                    property: prop_literal,
1578                                    value: result,
1579                                    loc: member_loc,
1580                                })
1581                            }
1582                            MemberProperty::Computed(prop_place) => {
1583                                Ok(InstructionValue::ComputedStore {
1584                                    object,
1585                                    property: prop_place,
1586                                    value: result,
1587                                    loc: member_loc,
1588                                })
1589                            }
1590                        }
1591                    }
1592                    _ => {
1593                        builder.record_error(CompilerErrorDetail {
1594                            reason: "Compound assignment to complex pattern is not yet supported"
1595                                .to_string(),
1596                            category: ErrorCategory::Todo,
1597                            loc: loc.clone(),
1598                            description: None,
1599                            suggestions: None,
1600                        })?;
1601                        Ok(InstructionValue::UnsupportedNode {
1602                            node_type: Some("AssignmentExpression".to_string()),
1603                            original_node: serialize_expression(&Expression::AssignmentExpression(
1604                                expr.clone(),
1605                            )),
1606                            loc,
1607                        })
1608                    }
1609                }
1610            }
1611        }
1612        Expression::SequenceExpression(seq) => {
1613            let loc = convert_opt_loc(&seq.base.loc);
1614
1615            if seq.expressions.is_empty() {
1616                builder.record_error(CompilerErrorDetail {
1617                    category: ErrorCategory::Syntax,
1618                    reason: "Expected sequence expression to have at least one expression"
1619                        .to_string(),
1620                    description: None,
1621                    loc: loc.clone(),
1622                    suggestions: None,
1623                })?;
1624                return Ok(InstructionValue::UnsupportedNode {
1625                    node_type: Some("SequenceExpression".to_string()),
1626                    original_node: serialize_expression(expr),
1627                    loc,
1628                });
1629            }
1630
1631            let continuation_block = builder.reserve(builder.current_block_kind());
1632            let continuation_id = continuation_block.id;
1633            let place = build_temporary_place(builder, loc.clone());
1634
1635            let sequence_block = builder.try_enter(BlockKind::Sequence, |builder, _block_id| {
1636                let mut last: Option<Place> = None;
1637                for item in &seq.expressions {
1638                    last = Some(lower_expression_to_temporary(builder, item)?);
1639                }
1640                if let Some(last) = last {
1641                    lower_value_to_temporary(
1642                        builder,
1643                        InstructionValue::StoreLocal {
1644                            lvalue: LValue {
1645                                kind: InstructionKind::Const,
1646                                place: place.clone(),
1647                            },
1648                            value: last,
1649                            type_annotation: None,
1650                            loc: loc.clone(),
1651                        },
1652                    )?;
1653                }
1654                Ok(Terminal::Goto {
1655                    block: continuation_id,
1656                    variant: GotoVariant::Break,
1657                    id: EvaluationOrder(0),
1658                    loc: loc.clone(),
1659                })
1660            });
1661
1662            builder.terminate_with_continuation(
1663                Terminal::Sequence {
1664                    block: sequence_block?,
1665                    fallthrough: continuation_id,
1666                    id: EvaluationOrder(0),
1667                    loc: loc.clone(),
1668                },
1669                continuation_block,
1670            );
1671            Ok(InstructionValue::LoadLocal { place, loc })
1672        }
1673        Expression::ArrowFunctionExpression(_) => Ok(lower_function_to_value(
1674            builder,
1675            expr,
1676            FunctionExpressionType::ArrowFunctionExpression,
1677        )?),
1678        Expression::FunctionExpression(_) => Ok(lower_function_to_value(
1679            builder,
1680            expr,
1681            FunctionExpressionType::FunctionExpression,
1682        )?),
1683        Expression::ObjectExpression(obj) => {
1684            let loc = convert_opt_loc(&obj.base.loc);
1685            let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new();
1686            for prop in &obj.properties {
1687                match prop {
1688                    react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(
1689                        p,
1690                    ) => {
1691                        let key = lower_object_property_key(builder, &p.key, p.computed)?;
1692                        let key = match key {
1693                            Some(k) => k,
1694                            None => continue,
1695                        };
1696                        let value = lower_expression_to_temporary(builder, &p.value)?;
1697                        properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {
1698                            key,
1699                            property_type: ObjectPropertyType::Property,
1700                            place: value,
1701                        }));
1702                    }
1703                    react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement(
1704                        spread,
1705                    ) => {
1706                        let place = lower_expression_to_temporary(builder, &spread.argument)?;
1707                        properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern { place }));
1708                    }
1709                    react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(
1710                        method,
1711                    ) => {
1712                        if let Some(prop) = lower_object_method(builder, method)? {
1713                            properties.push(ObjectPropertyOrSpread::Property(prop));
1714                        }
1715                    }
1716                }
1717            }
1718            Ok(InstructionValue::ObjectExpression { properties, loc })
1719        }
1720        Expression::ArrayExpression(arr) => {
1721            let loc = convert_opt_loc(&arr.base.loc);
1722            let mut elements: Vec<ArrayElement> = Vec::new();
1723            for element in &arr.elements {
1724                match element {
1725                    None => {
1726                        elements.push(ArrayElement::Hole);
1727                    }
1728                    Some(Expression::SpreadElement(spread)) => {
1729                        let place = lower_expression_to_temporary(builder, &spread.argument)?;
1730                        elements.push(ArrayElement::Spread(SpreadPattern { place }));
1731                    }
1732                    Some(expr) => {
1733                        let place = lower_expression_to_temporary(builder, expr)?;
1734                        elements.push(ArrayElement::Place(place));
1735                    }
1736                }
1737            }
1738            Ok(InstructionValue::ArrayExpression { elements, loc })
1739        }
1740        Expression::NewExpression(new_expr) => {
1741            let loc = convert_opt_loc(&new_expr.base.loc);
1742            let callee = lower_expression_to_temporary(builder, &new_expr.callee)?;
1743            let args = lower_arguments(builder, &new_expr.arguments)?;
1744            Ok(InstructionValue::NewExpression { callee, args, loc })
1745        }
1746        Expression::TemplateLiteral(tmpl) => {
1747            let loc = convert_opt_loc(&tmpl.base.loc);
1748            let subexprs: Vec<Place> = tmpl
1749                .expressions
1750                .iter()
1751                .map(|e| lower_expression_to_temporary(builder, e))
1752                .collect::<Result<Vec<_>, _>>()?;
1753            let quasis: Vec<TemplateQuasi> = tmpl
1754                .quasis
1755                .iter()
1756                .map(|q| TemplateQuasi {
1757                    raw: q.value.raw.clone(),
1758                    cooked: q.value.cooked.clone(),
1759                })
1760                .collect();
1761            Ok(InstructionValue::TemplateLiteral {
1762                subexprs,
1763                quasis,
1764                loc,
1765            })
1766        }
1767        Expression::TaggedTemplateExpression(tagged) => {
1768            let loc = convert_opt_loc(&tagged.base.loc);
1769            if !tagged.quasi.expressions.is_empty() {
1770                builder.record_error(CompilerErrorDetail {
1771                    category: ErrorCategory::Todo,
1772                    reason:
1773                        "(BuildHIR::lowerExpression) Handle tagged template with interpolations"
1774                            .to_string(),
1775                    description: None,
1776                    loc: loc.clone(),
1777                    suggestions: None,
1778                })?;
1779                return Ok(InstructionValue::UnsupportedNode {
1780                    node_type: Some("TaggedTemplateExpression".to_string()),
1781                    original_node: serialize_expression(expr),
1782                    loc,
1783                });
1784            }
1785            assert!(
1786                tagged.quasi.quasis.len() == 1,
1787                "there should be only one quasi as we don't support interpolations yet"
1788            );
1789            let quasi = &tagged.quasi.quasis[0];
1790            // Check if raw and cooked values differ (e.g., graphql tagged templates)
1791            if quasi.value.raw != quasi.value.cooked.clone().unwrap_or_default() {
1792                builder.record_error(CompilerErrorDetail {
1793                    category: ErrorCategory::Todo,
1794                    reason: "(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value".to_string(),
1795                    description: None,
1796                    loc: loc.clone(),
1797                    suggestions: None,
1798                })?;
1799                return Ok(InstructionValue::UnsupportedNode {
1800                    node_type: Some("TaggedTemplateExpression".to_string()),
1801                    original_node: serialize_expression(expr),
1802                    loc,
1803                });
1804            }
1805            let value = TemplateQuasi {
1806                raw: quasi.value.raw.clone(),
1807                cooked: quasi.value.cooked.clone(),
1808            };
1809            let tag = lower_expression_to_temporary(builder, &tagged.tag)?;
1810            Ok(InstructionValue::TaggedTemplateExpression { tag, value, loc })
1811        }
1812        Expression::AwaitExpression(await_expr) => {
1813            let loc = convert_opt_loc(&await_expr.base.loc);
1814            let value = lower_expression_to_temporary(builder, &await_expr.argument)?;
1815            Ok(InstructionValue::Await { value, loc })
1816        }
1817        Expression::YieldExpression(yld) => {
1818            let loc = convert_opt_loc(&yld.base.loc);
1819            builder.record_error(CompilerErrorDetail {
1820                category: ErrorCategory::Todo,
1821                reason: "(BuildHIR::lowerExpression) Handle YieldExpression expressions"
1822                    .to_string(),
1823                description: None,
1824                loc: loc.clone(),
1825                suggestions: None,
1826            })?;
1827            Ok(InstructionValue::UnsupportedNode {
1828                node_type: Some("YieldExpression".to_string()),
1829                original_node: serialize_expression(expr),
1830                loc,
1831            })
1832        }
1833        Expression::SpreadElement(spread) => {
1834            // SpreadElement should be handled by the parent context (array/object/call)
1835            // If we reach here, just lower the argument expression
1836            Ok(lower_expression(builder, &spread.argument)?)
1837        }
1838        Expression::MetaProperty(meta) => {
1839            let loc = convert_opt_loc(&meta.base.loc);
1840            if meta.meta.name == "import" && meta.property.name == "meta" {
1841                Ok(InstructionValue::MetaProperty {
1842                    meta: meta.meta.name.clone(),
1843                    property: meta.property.name.clone(),
1844                    loc,
1845                })
1846            } else {
1847                builder.record_error(CompilerErrorDetail {
1848                    category: ErrorCategory::Todo,
1849                    reason: "(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta".to_string(),
1850                    description: None,
1851                    loc: loc.clone(),
1852                    suggestions: None,
1853                })?;
1854                Ok(InstructionValue::UnsupportedNode {
1855                    node_type: Some("MetaProperty".to_string()),
1856                    original_node: serialize_expression(expr),
1857                    loc,
1858                })
1859            }
1860        }
1861        Expression::ClassExpression(cls) => {
1862            let loc = convert_opt_loc(&cls.base.loc);
1863            builder.record_error(CompilerErrorDetail {
1864                category: ErrorCategory::Todo,
1865                reason: "(BuildHIR::lowerExpression) Handle ClassExpression expressions"
1866                    .to_string(),
1867                description: None,
1868                loc: loc.clone(),
1869                suggestions: None,
1870            })?;
1871            Ok(InstructionValue::UnsupportedNode {
1872                node_type: Some("ClassExpression".to_string()),
1873                original_node: serialize_expression(expr),
1874                loc,
1875            })
1876        }
1877        Expression::PrivateName(pn) => {
1878            let loc = convert_opt_loc(&pn.base.loc);
1879            builder.record_error(CompilerErrorDetail {
1880                category: ErrorCategory::Todo,
1881                reason: "(BuildHIR::lowerExpression) Handle PrivateName expressions".to_string(),
1882                description: None,
1883                loc: loc.clone(),
1884                suggestions: None,
1885            })?;
1886            Ok(InstructionValue::UnsupportedNode {
1887                node_type: Some("PrivateName".to_string()),
1888                original_node: serialize_expression(expr),
1889                loc,
1890            })
1891        }
1892        Expression::Super(sup) => {
1893            let loc = convert_opt_loc(&sup.base.loc);
1894            builder.record_error(CompilerErrorDetail {
1895                category: ErrorCategory::Todo,
1896                reason: "(BuildHIR::lowerExpression) Handle Super expressions".to_string(),
1897                description: None,
1898                loc: loc.clone(),
1899                suggestions: None,
1900            })?;
1901            Ok(InstructionValue::UnsupportedNode {
1902                node_type: Some("Super".to_string()),
1903                original_node: serialize_expression(expr),
1904                loc,
1905            })
1906        }
1907        Expression::Import(imp) => {
1908            let loc = convert_opt_loc(&imp.base.loc);
1909            builder.record_error(CompilerErrorDetail {
1910                category: ErrorCategory::Todo,
1911                reason: "(BuildHIR::lowerExpression) Handle Import expressions".to_string(),
1912                description: None,
1913                loc: loc.clone(),
1914                suggestions: None,
1915            })?;
1916            Ok(InstructionValue::UnsupportedNode {
1917                node_type: Some("Import".to_string()),
1918                original_node: serialize_expression(expr),
1919                loc,
1920            })
1921        }
1922        Expression::ThisExpression(this) => {
1923            let loc = convert_opt_loc(&this.base.loc);
1924            builder.record_error(CompilerErrorDetail {
1925                category: ErrorCategory::Todo,
1926                reason: "(BuildHIR::lowerExpression) Handle ThisExpression expressions".to_string(),
1927                description: None,
1928                loc: loc.clone(),
1929                suggestions: None,
1930            })?;
1931            Ok(InstructionValue::UnsupportedNode {
1932                node_type: Some("ThisExpression".to_string()),
1933                original_node: serialize_expression(expr),
1934                loc,
1935            })
1936        }
1937        Expression::ParenthesizedExpression(paren) => {
1938            Ok(lower_expression(builder, &paren.expression)?)
1939        }
1940        Expression::JSXElement(jsx_element) => {
1941            let loc = convert_opt_loc(&jsx_element.base.loc);
1942            let opening_loc = convert_opt_loc(&jsx_element.opening_element.base.loc);
1943            let closing_loc = jsx_element
1944                .closing_element
1945                .as_ref()
1946                .and_then(|c| convert_opt_loc(&c.base.loc));
1947
1948            // Lower the tag name
1949            let tag = lower_jsx_element_name(builder, &jsx_element.opening_element.name)?;
1950
1951            // Lower attributes (props)
1952            let mut props: Vec<JsxAttribute> = Vec::new();
1953            for attr_item in &jsx_element.opening_element.attributes {
1954                use react_compiler_ast::jsx::JSXAttributeItem;
1955                use react_compiler_ast::jsx::JSXAttributeName;
1956                use react_compiler_ast::jsx::JSXAttributeValue;
1957                match attr_item {
1958                    JSXAttributeItem::JSXSpreadAttribute(spread) => {
1959                        let argument = lower_expression_to_temporary(builder, &spread.argument)?;
1960                        props.push(JsxAttribute::SpreadAttribute { argument });
1961                    }
1962                    JSXAttributeItem::JSXAttribute(attr) => {
1963                        // Get the attribute name
1964                        let prop_name = match &attr.name {
1965                            JSXAttributeName::JSXIdentifier(id) => {
1966                                let name = &id.name;
1967                                if name.contains(':') {
1968                                    builder.record_error(CompilerErrorDetail {
1969                                        category: ErrorCategory::Todo,
1970                                        reason: format!(
1971                                            "(BuildHIR::lowerExpression) Unexpected colon in attribute name `{}`",
1972                                            name
1973                                        ),
1974                                        description: None,
1975                                        loc: convert_opt_loc(&id.base.loc),
1976                                        suggestions: None,
1977                                    })?;
1978                                }
1979                                name.clone()
1980                            }
1981                            JSXAttributeName::JSXNamespacedName(ns) => {
1982                                format!("{}:{}", ns.namespace.name, ns.name.name)
1983                            }
1984                        };
1985
1986                        // Get the attribute value
1987                        let value = match &attr.value {
1988                            Some(JSXAttributeValue::StringLiteral(s)) => {
1989                                let str_loc = convert_opt_loc(&s.base.loc);
1990                                lower_value_to_temporary(
1991                                    builder,
1992                                    InstructionValue::Primitive {
1993                                        value: PrimitiveValue::String(s.value.clone()),
1994                                        loc: str_loc,
1995                                    },
1996                                )?
1997                            }
1998                            Some(JSXAttributeValue::JSXExpressionContainer(container)) => {
1999                                use react_compiler_ast::jsx::JSXExpressionContainerExpr;
2000                                match &container.expression {
2001                                    JSXExpressionContainerExpr::JSXEmptyExpression(_) => {
2002                                        // Empty expression container - skip this attribute
2003                                        continue;
2004                                    }
2005                                    JSXExpressionContainerExpr::Expression(expr) => {
2006                                        lower_expression_to_temporary(builder, expr)?
2007                                    }
2008                                }
2009                            }
2010                            Some(JSXAttributeValue::JSXElement(el)) => {
2011                                let val = lower_expression(
2012                                    builder,
2013                                    &react_compiler_ast::expressions::Expression::JSXElement(
2014                                        el.clone(),
2015                                    ),
2016                                )?;
2017                                lower_value_to_temporary(builder, val)?
2018                            }
2019                            Some(JSXAttributeValue::JSXFragment(frag)) => {
2020                                let val = lower_expression(
2021                                    builder,
2022                                    &react_compiler_ast::expressions::Expression::JSXFragment(
2023                                        frag.clone(),
2024                                    ),
2025                                )?;
2026                                lower_value_to_temporary(builder, val)?
2027                            }
2028                            None => {
2029                                // No value means boolean true (e.g., <div disabled />)
2030                                let attr_loc = convert_opt_loc(&attr.base.loc);
2031                                lower_value_to_temporary(
2032                                    builder,
2033                                    InstructionValue::Primitive {
2034                                        value: PrimitiveValue::Boolean(true),
2035                                        loc: attr_loc,
2036                                    },
2037                                )?
2038                            }
2039                        };
2040
2041                        props.push(JsxAttribute::Attribute {
2042                            name: prop_name,
2043                            place: value,
2044                        });
2045                    }
2046                }
2047            }
2048
2049            // Check if this is an fbt/fbs tag, which requires special whitespace handling
2050            let is_fbt = matches!(&tag, JsxTag::Builtin(b) if b.name == "fbt" || b.name == "fbs");
2051
2052            // Check that fbt/fbs tags are module-level imports, not local bindings.
2053            // Matches TS: CompilerError.invariant(tagIdentifier.kind !== 'Identifier', ...)
2054            if is_fbt {
2055                let tag_name = match &tag {
2056                    JsxTag::Builtin(b) => b.name.clone(),
2057                    _ => "fbt".to_string(),
2058                };
2059                // Get the opening element's name identifier and check if it's a local binding
2060                if let react_compiler_ast::jsx::JSXElementName::JSXIdentifier(jsx_id) =
2061                    &jsx_element.opening_element.name
2062                {
2063                    let id_loc = convert_opt_loc(&jsx_id.base.loc);
2064                    // Check if fbt/fbs tag name resolves to a local binding.
2065                    // JSX identifiers may not be in our position-based reference map,
2066                    // so check if ANY binding with this name exists in the function scope.
2067                    let is_local_binding = builder.has_local_binding(&jsx_id.name);
2068                    if is_local_binding {
2069                        // Record as a Diagnostic (not ErrorDetail) to match TS behavior
2070                        // where CompilerError.invariant creates a CompilerDiagnostic.
2071                        // TS invariant() throws immediately, so only the first fbt error
2072                        // is reported. We return Err to match this behavior.
2073                        let reason = format!("<{}> tags should be module-level imports", tag_name);
2074                        return Err(CompilerDiagnostic::new(
2075                            ErrorCategory::Invariant,
2076                            &reason,
2077                            None,
2078                        )
2079                        .with_detail(CompilerDiagnosticDetail::Error {
2080                            loc: id_loc.clone(),
2081                            message: Some(reason.clone()),
2082                            identifier_name: None,
2083                        })
2084                        .into());
2085                    }
2086                }
2087            }
2088
2089            // Check for duplicate fbt:enum, fbt:plural, fbt:pronoun tags
2090            if is_fbt {
2091                let tag_name = match &tag {
2092                    JsxTag::Builtin(b) => b.name.as_str(),
2093                    _ => "fbt",
2094                };
2095                let mut enum_locs: Vec<Option<SourceLocation>> = Vec::new();
2096                let mut plural_locs: Vec<Option<SourceLocation>> = Vec::new();
2097                let mut pronoun_locs: Vec<Option<SourceLocation>> = Vec::new();
2098                collect_fbt_sub_tags(
2099                    &jsx_element.children,
2100                    tag_name,
2101                    &mut enum_locs,
2102                    &mut plural_locs,
2103                    &mut pronoun_locs,
2104                );
2105
2106                for (name, locations) in [
2107                    ("enum", &enum_locs),
2108                    ("plural", &plural_locs),
2109                    ("pronoun", &pronoun_locs),
2110                ] {
2111                    if locations.len() > 1 {
2112                        use react_compiler_diagnostics::CompilerDiagnosticDetail;
2113                        let details: Vec<CompilerDiagnosticDetail> = locations
2114                            .iter()
2115                            .map(|loc| CompilerDiagnosticDetail::Error {
2116                                message: Some(format!(
2117                                    "Multiple `<{}:{}>` tags found",
2118                                    tag_name, name
2119                                )),
2120                                loc: loc.clone(),
2121                                identifier_name: None,
2122                            })
2123                            .collect();
2124                        let mut diag = react_compiler_diagnostics::CompilerDiagnostic::new(
2125                            ErrorCategory::Todo,
2126                            "Support duplicate fbt tags",
2127                            Some(format!(
2128                                "Support `<{}>` tags with multiple `<{}:{}>` values",
2129                                tag_name, tag_name, name
2130                            )),
2131                        );
2132                        diag.details = details;
2133                        builder.environment_mut().record_diagnostic(diag);
2134                    }
2135                }
2136            }
2137
2138            // Increment fbt counter before traversing into children, as whitespace
2139            // in jsx text is handled differently for fbt subtrees.
2140            if is_fbt {
2141                builder.fbt_depth += 1;
2142            }
2143
2144            // Lower children
2145            let children: Vec<Place> = jsx_element
2146                .children
2147                .iter()
2148                .map(|child| lower_jsx_element(builder, child))
2149                .collect::<Result<Vec<_>, _>>()?
2150                .into_iter()
2151                .flatten()
2152                .collect();
2153
2154            if is_fbt {
2155                builder.fbt_depth -= 1;
2156            }
2157
2158            Ok(InstructionValue::JsxExpression {
2159                tag,
2160                props,
2161                children: if children.is_empty() {
2162                    None
2163                } else {
2164                    Some(children)
2165                },
2166                loc,
2167                opening_loc,
2168                closing_loc,
2169            })
2170        }
2171        Expression::JSXFragment(jsx_fragment) => {
2172            let loc = convert_opt_loc(&jsx_fragment.base.loc);
2173
2174            // Lower children
2175            let children: Vec<Place> = jsx_fragment
2176                .children
2177                .iter()
2178                .map(|child| lower_jsx_element(builder, child))
2179                .collect::<Result<Vec<_>, _>>()?
2180                .into_iter()
2181                .flatten()
2182                .collect();
2183
2184            Ok(InstructionValue::JsxFragment { children, loc })
2185        }
2186        Expression::AssignmentPattern(_) => {
2187            let loc = convert_opt_loc(&match expr {
2188                Expression::AssignmentPattern(p) => p.base.loc.clone(),
2189                _ => unreachable!(),
2190            });
2191            builder.record_error(CompilerErrorDetail {
2192                reason: "(BuildHIR::lowerExpression) Handle AssignmentPattern expressions"
2193                    .to_string(),
2194                category: ErrorCategory::Todo,
2195                loc: loc.clone(),
2196                description: None,
2197                suggestions: None,
2198            })?;
2199            Ok(InstructionValue::UnsupportedNode {
2200                node_type: Some("AssignmentPattern".to_string()),
2201                original_node: serialize_expression(expr),
2202                loc,
2203            })
2204        }
2205        Expression::TSAsExpression(ts) => {
2206            let loc = convert_opt_loc(&ts.base.loc);
2207            let value = lower_expression_to_temporary(builder, &ts.expression)?;
2208            let type_annotation = &*ts.type_annotation;
2209            let type_ = lower_type_annotation(type_annotation, builder);
2210            let type_annotation_name = get_type_annotation_name(type_annotation);
2211            Ok(InstructionValue::TypeCastExpression {
2212                value,
2213                type_,
2214                type_annotation_name,
2215                type_annotation_kind: Some("as".to_string()),
2216                type_annotation: Some(ts.type_annotation.clone()),
2217                loc,
2218            })
2219        }
2220        Expression::TSSatisfiesExpression(ts) => {
2221            let loc = convert_opt_loc(&ts.base.loc);
2222            let value = lower_expression_to_temporary(builder, &ts.expression)?;
2223            let type_annotation = &*ts.type_annotation;
2224            let type_ = lower_type_annotation(type_annotation, builder);
2225            let type_annotation_name = get_type_annotation_name(type_annotation);
2226            Ok(InstructionValue::TypeCastExpression {
2227                value,
2228                type_,
2229                type_annotation_name,
2230                type_annotation_kind: Some("satisfies".to_string()),
2231                type_annotation: Some(ts.type_annotation.clone()),
2232                loc,
2233            })
2234        }
2235        Expression::TSNonNullExpression(ts) => Ok(lower_expression(builder, &ts.expression)?),
2236        Expression::TSTypeAssertion(ts) => {
2237            let loc = convert_opt_loc(&ts.base.loc);
2238            let value = lower_expression_to_temporary(builder, &ts.expression)?;
2239            let type_annotation = &*ts.type_annotation;
2240            let type_ = lower_type_annotation(type_annotation, builder);
2241            let type_annotation_name = get_type_annotation_name(type_annotation);
2242            Ok(InstructionValue::TypeCastExpression {
2243                value,
2244                type_,
2245                type_annotation_name,
2246                type_annotation_kind: Some("as".to_string()),
2247                type_annotation: Some(ts.type_annotation.clone()),
2248                loc,
2249            })
2250        }
2251        Expression::TSInstantiationExpression(ts) => Ok(lower_expression(builder, &ts.expression)?),
2252        Expression::TypeCastExpression(tc) => {
2253            let loc = convert_opt_loc(&tc.base.loc);
2254            let value = lower_expression_to_temporary(builder, &tc.expression)?;
2255            // Flow TypeCastExpression: typeAnnotation is a TypeAnnotation node wrapping the actual type
2256            let inner_type = tc
2257                .type_annotation
2258                .get("typeAnnotation")
2259                .unwrap_or(&*tc.type_annotation);
2260            let type_ = lower_type_annotation(inner_type, builder);
2261            let type_annotation_name = get_type_annotation_name(inner_type);
2262            Ok(InstructionValue::TypeCastExpression {
2263                value,
2264                type_,
2265                type_annotation_name,
2266                type_annotation_kind: Some("cast".to_string()),
2267                type_annotation: Some(tc.type_annotation.clone()),
2268                loc,
2269            })
2270        }
2271        Expression::BigIntLiteral(big) => {
2272            let loc = convert_opt_loc(&big.base.loc);
2273            builder.record_error(CompilerErrorDetail {
2274                category: ErrorCategory::Todo,
2275                reason: "(BuildHIR::lowerExpression) Handle BigIntLiteral expressions".to_string(),
2276                description: None,
2277                loc: loc.clone(),
2278                suggestions: None,
2279            })?;
2280            Ok(InstructionValue::UnsupportedNode {
2281                node_type: Some("BigIntLiteral".to_string()),
2282                original_node: serialize_expression(expr),
2283                loc,
2284            })
2285        }
2286        Expression::RegExpLiteral(re) => {
2287            let loc = convert_opt_loc(&re.base.loc);
2288            Ok(InstructionValue::RegExpLiteral {
2289                pattern: re.pattern.clone(),
2290                flags: re.flags.clone(),
2291                loc,
2292            })
2293        }
2294    }
2295}
2296
2297/// Check if a binding's declaration is a direct statement of the block
2298/// (not inside a nested control flow block like if/for/while).
2299/// Uses the binding's declaration_start position to check if it falls within
2300/// one of the block's direct VariableDeclaration, FunctionDeclaration, or
2301/// ClassDeclaration statements. This avoids false positives when two bindings
2302/// share the same name but are declared in different scopes (e.g., `const x`
2303/// inside an if-branch and `const x` after it).
2304fn is_binding_in_block_direct_statements(
2305    binding: &react_compiler_ast::scope::BindingData,
2306    stmts: &[react_compiler_ast::statements::Statement],
2307) -> bool {
2308    use react_compiler_ast::statements::Statement;
2309    let decl_start = match binding.declaration_start {
2310        Some(pos) => pos,
2311        None => return false,
2312    };
2313    for stmt in stmts {
2314        match stmt {
2315            Statement::VariableDeclaration(vd) => {
2316                let start = vd.base.start.unwrap_or(0);
2317                let end = vd.base.end.unwrap_or(u32::MAX);
2318                if decl_start >= start && decl_start < end {
2319                    return true;
2320                }
2321            }
2322            Statement::FunctionDeclaration(fd) => {
2323                let start = fd.base.start.unwrap_or(0);
2324                let end = fd.base.end.unwrap_or(u32::MAX);
2325                if decl_start >= start && decl_start < end {
2326                    return true;
2327                }
2328            }
2329            Statement::ClassDeclaration(cd) => {
2330                let start = cd.base.start.unwrap_or(0);
2331                let end = cd.base.end.unwrap_or(u32::MAX);
2332                if decl_start >= start && decl_start < end {
2333                    return true;
2334                }
2335            }
2336            _ => {}
2337        }
2338    }
2339    false
2340}
2341
2342#[allow(dead_code)]
2343fn pattern_declares_name(pattern: &react_compiler_ast::patterns::PatternLike, name: &str) -> bool {
2344    use react_compiler_ast::patterns::PatternLike;
2345    match pattern {
2346        PatternLike::Identifier(id) => id.name == name,
2347        PatternLike::ObjectPattern(op) => op.properties.iter().any(|prop| match prop {
2348            react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
2349                pattern_declares_name(&p.value, name)
2350            }
2351            react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
2352                pattern_declares_name(&r.argument, name)
2353            }
2354        }),
2355        PatternLike::ArrayPattern(ap) => ap.elements.iter().any(|el| {
2356            el.as_ref()
2357                .map_or(false, |e| pattern_declares_name(e, name))
2358        }),
2359        PatternLike::AssignmentPattern(ap) => pattern_declares_name(&ap.left, name),
2360        PatternLike::RestElement(r) => pattern_declares_name(&r.argument, name),
2361        PatternLike::MemberExpression(_) => false,
2362        PatternLike::TSAsExpression(_)
2363        | PatternLike::TSSatisfiesExpression(_)
2364        | PatternLike::TSNonNullExpression(_)
2365        | PatternLike::TSTypeAssertion(_)
2366        | PatternLike::TypeCastExpression(_) => false,
2367    }
2368}
2369
2370// =============================================================================
2371// Statement position helpers
2372// =============================================================================
2373
2374fn statement_start(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> {
2375    use react_compiler_ast::statements::Statement;
2376    match stmt {
2377        Statement::BlockStatement(s) => s.base.start,
2378        Statement::ReturnStatement(s) => s.base.start,
2379        Statement::IfStatement(s) => s.base.start,
2380        Statement::ForStatement(s) => s.base.start,
2381        Statement::WhileStatement(s) => s.base.start,
2382        Statement::DoWhileStatement(s) => s.base.start,
2383        Statement::ForInStatement(s) => s.base.start,
2384        Statement::ForOfStatement(s) => s.base.start,
2385        Statement::SwitchStatement(s) => s.base.start,
2386        Statement::ThrowStatement(s) => s.base.start,
2387        Statement::TryStatement(s) => s.base.start,
2388        Statement::BreakStatement(s) => s.base.start,
2389        Statement::ContinueStatement(s) => s.base.start,
2390        Statement::LabeledStatement(s) => s.base.start,
2391        Statement::ExpressionStatement(s) => s.base.start,
2392        Statement::EmptyStatement(s) => s.base.start,
2393        Statement::DebuggerStatement(s) => s.base.start,
2394        Statement::WithStatement(s) => s.base.start,
2395        Statement::VariableDeclaration(s) => s.base.start,
2396        Statement::FunctionDeclaration(s) => s.base.start,
2397        Statement::ClassDeclaration(s) => s.base.start,
2398        Statement::ImportDeclaration(s) => s.base.start,
2399        Statement::ExportNamedDeclaration(s) => s.base.start,
2400        Statement::ExportDefaultDeclaration(s) => s.base.start,
2401        Statement::ExportAllDeclaration(s) => s.base.start,
2402        Statement::TSTypeAliasDeclaration(s) => s.base.start,
2403        Statement::TSInterfaceDeclaration(s) => s.base.start,
2404        Statement::TSEnumDeclaration(s) => s.base.start,
2405        Statement::TSModuleDeclaration(s) => s.base.start,
2406        Statement::TSDeclareFunction(s) => s.base.start,
2407        Statement::TypeAlias(s) => s.base.start,
2408        Statement::OpaqueType(s) => s.base.start,
2409        Statement::InterfaceDeclaration(s) => s.base.start,
2410        Statement::DeclareVariable(s) => s.base.start,
2411        Statement::DeclareFunction(s) => s.base.start,
2412        Statement::DeclareClass(s) => s.base.start,
2413        Statement::DeclareModule(s) => s.base.start,
2414        Statement::DeclareModuleExports(s) => s.base.start,
2415        Statement::DeclareExportDeclaration(s) => s.base.start,
2416        Statement::DeclareExportAllDeclaration(s) => s.base.start,
2417        Statement::DeclareInterface(s) => s.base.start,
2418        Statement::DeclareTypeAlias(s) => s.base.start,
2419        Statement::DeclareOpaqueType(s) => s.base.start,
2420        Statement::EnumDeclaration(s) => s.base.start,
2421        Statement::Unknown(s) => s.base().start,
2422    }
2423}
2424
2425fn statement_end(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> {
2426    use react_compiler_ast::statements::Statement;
2427    match stmt {
2428        Statement::BlockStatement(s) => s.base.end,
2429        Statement::ReturnStatement(s) => s.base.end,
2430        Statement::IfStatement(s) => s.base.end,
2431        Statement::ForStatement(s) => s.base.end,
2432        Statement::WhileStatement(s) => s.base.end,
2433        Statement::DoWhileStatement(s) => s.base.end,
2434        Statement::ForInStatement(s) => s.base.end,
2435        Statement::ForOfStatement(s) => s.base.end,
2436        Statement::SwitchStatement(s) => s.base.end,
2437        Statement::ThrowStatement(s) => s.base.end,
2438        Statement::TryStatement(s) => s.base.end,
2439        Statement::BreakStatement(s) => s.base.end,
2440        Statement::ContinueStatement(s) => s.base.end,
2441        Statement::LabeledStatement(s) => s.base.end,
2442        Statement::ExpressionStatement(s) => s.base.end,
2443        Statement::EmptyStatement(s) => s.base.end,
2444        Statement::DebuggerStatement(s) => s.base.end,
2445        Statement::WithStatement(s) => s.base.end,
2446        Statement::VariableDeclaration(s) => s.base.end,
2447        Statement::FunctionDeclaration(s) => s.base.end,
2448        Statement::ClassDeclaration(s) => s.base.end,
2449        Statement::ImportDeclaration(s) => s.base.end,
2450        Statement::ExportNamedDeclaration(s) => s.base.end,
2451        Statement::ExportDefaultDeclaration(s) => s.base.end,
2452        Statement::ExportAllDeclaration(s) => s.base.end,
2453        Statement::TSTypeAliasDeclaration(s) => s.base.end,
2454        Statement::TSInterfaceDeclaration(s) => s.base.end,
2455        Statement::TSEnumDeclaration(s) => s.base.end,
2456        Statement::TSModuleDeclaration(s) => s.base.end,
2457        Statement::TSDeclareFunction(s) => s.base.end,
2458        Statement::TypeAlias(s) => s.base.end,
2459        Statement::OpaqueType(s) => s.base.end,
2460        Statement::InterfaceDeclaration(s) => s.base.end,
2461        Statement::DeclareVariable(s) => s.base.end,
2462        Statement::DeclareFunction(s) => s.base.end,
2463        Statement::DeclareClass(s) => s.base.end,
2464        Statement::DeclareModule(s) => s.base.end,
2465        Statement::DeclareModuleExports(s) => s.base.end,
2466        Statement::DeclareExportDeclaration(s) => s.base.end,
2467        Statement::DeclareExportAllDeclaration(s) => s.base.end,
2468        Statement::DeclareInterface(s) => s.base.end,
2469        Statement::DeclareTypeAlias(s) => s.base.end,
2470        Statement::DeclareOpaqueType(s) => s.base.end,
2471        Statement::EnumDeclaration(s) => s.base.end,
2472        Statement::Unknown(s) => s.base().end,
2473    }
2474}
2475
2476/// Extract the HIR SourceLocation from a Statement AST node.
2477fn statement_loc(stmt: &react_compiler_ast::statements::Statement) -> Option<SourceLocation> {
2478    use react_compiler_ast::statements::Statement;
2479    let loc = match stmt {
2480        Statement::BlockStatement(s) => s.base.loc.clone(),
2481        Statement::ReturnStatement(s) => s.base.loc.clone(),
2482        Statement::IfStatement(s) => s.base.loc.clone(),
2483        Statement::ForStatement(s) => s.base.loc.clone(),
2484        Statement::WhileStatement(s) => s.base.loc.clone(),
2485        Statement::DoWhileStatement(s) => s.base.loc.clone(),
2486        Statement::ForInStatement(s) => s.base.loc.clone(),
2487        Statement::ForOfStatement(s) => s.base.loc.clone(),
2488        Statement::SwitchStatement(s) => s.base.loc.clone(),
2489        Statement::ThrowStatement(s) => s.base.loc.clone(),
2490        Statement::TryStatement(s) => s.base.loc.clone(),
2491        Statement::BreakStatement(s) => s.base.loc.clone(),
2492        Statement::ContinueStatement(s) => s.base.loc.clone(),
2493        Statement::LabeledStatement(s) => s.base.loc.clone(),
2494        Statement::ExpressionStatement(s) => s.base.loc.clone(),
2495        Statement::EmptyStatement(s) => s.base.loc.clone(),
2496        Statement::DebuggerStatement(s) => s.base.loc.clone(),
2497        Statement::WithStatement(s) => s.base.loc.clone(),
2498        Statement::VariableDeclaration(s) => s.base.loc.clone(),
2499        Statement::FunctionDeclaration(s) => s.base.loc.clone(),
2500        Statement::ClassDeclaration(s) => s.base.loc.clone(),
2501        Statement::ImportDeclaration(s) => s.base.loc.clone(),
2502        Statement::ExportNamedDeclaration(s) => s.base.loc.clone(),
2503        Statement::ExportDefaultDeclaration(s) => s.base.loc.clone(),
2504        Statement::ExportAllDeclaration(s) => s.base.loc.clone(),
2505        Statement::TSTypeAliasDeclaration(s) => s.base.loc.clone(),
2506        Statement::TSInterfaceDeclaration(s) => s.base.loc.clone(),
2507        Statement::TSEnumDeclaration(s) => s.base.loc.clone(),
2508        Statement::TSModuleDeclaration(s) => s.base.loc.clone(),
2509        Statement::TSDeclareFunction(s) => s.base.loc.clone(),
2510        Statement::TypeAlias(s) => s.base.loc.clone(),
2511        Statement::OpaqueType(s) => s.base.loc.clone(),
2512        Statement::InterfaceDeclaration(s) => s.base.loc.clone(),
2513        Statement::DeclareVariable(s) => s.base.loc.clone(),
2514        Statement::DeclareFunction(s) => s.base.loc.clone(),
2515        Statement::DeclareClass(s) => s.base.loc.clone(),
2516        Statement::DeclareModule(s) => s.base.loc.clone(),
2517        Statement::DeclareModuleExports(s) => s.base.loc.clone(),
2518        Statement::DeclareExportDeclaration(s) => s.base.loc.clone(),
2519        Statement::DeclareExportAllDeclaration(s) => s.base.loc.clone(),
2520        Statement::DeclareInterface(s) => s.base.loc.clone(),
2521        Statement::DeclareTypeAlias(s) => s.base.loc.clone(),
2522        Statement::DeclareOpaqueType(s) => s.base.loc.clone(),
2523        Statement::EnumDeclaration(s) => s.base.loc.clone(),
2524        Statement::Unknown(s) => s.base().loc.clone(),
2525    };
2526    convert_opt_loc(&loc)
2527}
2528
2529/// Collect binding names from a pattern that are declared in the given scope.
2530fn collect_binding_names_from_pattern(
2531    pattern: &react_compiler_ast::patterns::PatternLike,
2532    scope_id: react_compiler_ast::scope::ScopeId,
2533    scope_info: &ScopeInfo,
2534    out: &mut HashSet<BindingId>,
2535) {
2536    use react_compiler_ast::patterns::PatternLike;
2537    match pattern {
2538        PatternLike::Identifier(id) => {
2539            if let Some(&binding_id) = scope_info.scopes[scope_id.0 as usize]
2540                .bindings
2541                .get(&id.name)
2542            {
2543                out.insert(binding_id);
2544            }
2545        }
2546        PatternLike::ObjectPattern(obj) => {
2547            for prop in &obj.properties {
2548                match prop {
2549                    react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
2550                        collect_binding_names_from_pattern(&p.value, scope_id, scope_info, out);
2551                    }
2552                    react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
2553                        collect_binding_names_from_pattern(&r.argument, scope_id, scope_info, out);
2554                    }
2555                }
2556            }
2557        }
2558        PatternLike::ArrayPattern(arr) => {
2559            for elem in &arr.elements {
2560                if let Some(e) = elem {
2561                    collect_binding_names_from_pattern(e, scope_id, scope_info, out);
2562                }
2563            }
2564        }
2565        PatternLike::AssignmentPattern(assign) => {
2566            collect_binding_names_from_pattern(&assign.left, scope_id, scope_info, out);
2567        }
2568        PatternLike::RestElement(rest) => {
2569            collect_binding_names_from_pattern(&rest.argument, scope_id, scope_info, out);
2570        }
2571        PatternLike::MemberExpression(_) => {}
2572        PatternLike::TSAsExpression(_)
2573        | PatternLike::TSSatisfiesExpression(_)
2574        | PatternLike::TSNonNullExpression(_)
2575        | PatternLike::TSTypeAssertion(_)
2576        | PatternLike::TypeCastExpression(_) => {}
2577    }
2578}
2579
2580// =============================================================================
2581// lower_block_statement (with hoisting)
2582// =============================================================================
2583
2584/// Lower a BlockStatement with hoisting support.
2585///
2586/// Implements the TS BlockStatement hoisting pass: identifies forward references to
2587/// block-scoped bindings and emits DeclareContext instructions to hoist them.
2588fn lower_block_statement(
2589    builder: &mut HirBuilder,
2590    block: &react_compiler_ast::statements::BlockStatement,
2591    parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2592) -> Result<(), CompilerError> {
2593    let _ = lower_block_statement_inner(builder, block, None, parent_scope);
2594    Ok(())
2595}
2596
2597fn lower_block_statement_with_scope(
2598    builder: &mut HirBuilder,
2599    block: &react_compiler_ast::statements::BlockStatement,
2600    scope_override: react_compiler_ast::scope::ScopeId,
2601) -> Result<(), CompilerError> {
2602    let _ = lower_block_statement_inner(builder, block, Some(scope_override), None);
2603    Ok(())
2604}
2605
2606fn lower_block_statement_inner(
2607    builder: &mut HirBuilder,
2608    block: &react_compiler_ast::statements::BlockStatement,
2609    scope_override: Option<react_compiler_ast::scope::ScopeId>,
2610    parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2611) -> Result<(), CompilerDiagnostic> {
2612    use react_compiler_ast::scope::BindingKind as AstBindingKind;
2613    use react_compiler_ast::statements::Statement;
2614
2615    // Look up the block's scope to identify hoistable bindings.
2616    // Use the scope override if provided (for function body blocks that share the function's scope).
2617    let block_scope_id = scope_override.or_else(|| {
2618        let found = builder
2619            .scope_info()
2620            .resolve_scope_for_node(block.base.node_id);
2621        if found.is_some() {
2622            return found;
2623        }
2624        // Fallback for synthetic blocks (start=0 from Hermes match desugar):
2625        // find a descendant scope of the parent that contains the block's declarations.
2626        let mut decl_names = Vec::new();
2627        for stmt in &block.body {
2628            if let Statement::VariableDeclaration(vd) = stmt {
2629                for d in &vd.declarations {
2630                    if let react_compiler_ast::patterns::PatternLike::Identifier(id) = &d.id {
2631                        decl_names.push(id.name.as_str());
2632                    }
2633                }
2634            }
2635        }
2636        if decl_names.is_empty() {
2637            return None;
2638        }
2639        let search_parent = parent_scope.unwrap_or_else(|| builder.function_scope());
2640        let found =
2641            builder
2642                .scope_info()
2643                .find_block_scope_by_bindings(&decl_names, search_parent, |sid| {
2644                    builder.is_synthetic_scope_claimed(sid)
2645                });
2646        if let Some(sid) = found {
2647            builder.claim_synthetic_scope(sid);
2648        }
2649        found
2650    });
2651
2652    let scope_id = match block_scope_id {
2653        Some(id) => id,
2654        None => {
2655            for body_stmt in &block.body {
2656                lower_statement(builder, body_stmt, None, parent_scope)?;
2657            }
2658            return Ok(());
2659        }
2660    };
2661
2662    // Collect hoistable bindings from this scope AND direct child block scopes.
2663    // In Babel, a function body BlockStatement shares the function's scope, so
2664    // all bindings (var, const, let) are in one scope. But our scope extraction
2665    // may split them: function scope has params/var, child block scope has const/let.
2666    // Including child block scope bindings matches TS behavior where
2667    // stmt.scope.bindings includes all bindings accessible in the block.
2668    //
2669    // IMPORTANT: Only include bindings whose declaration falls within THIS block's
2670    // statement range. Bindings declared in nested blocks (e.g., inside an `if`
2671    // branch) should NOT be hoisted at the parent level — they'll be handled when
2672    // that nested block is recursively lowered. This prevents DeclareContext from
2673    // being emitted before an `if` terminal for variables declared within the branch.
2674    let hoistable: Vec<(
2675        BindingId,
2676        String,
2677        AstBindingKind,
2678        String,
2679        Option<u32>,
2680        Option<u32>,
2681    )> = builder
2682        .scope_info()
2683        .scope_bindings_with_children(scope_id)
2684        .filter(|b| {
2685            !matches!(b.kind, AstBindingKind::Param | AstBindingKind::Module)
2686                && b.declaration_type != "FunctionExpression"
2687                && b.declaration_type != "TypeAlias"
2688                && b.declaration_type != "OpaqueType"
2689                && b.declaration_type != "InterfaceDeclaration"
2690                && b.declaration_type != "TSTypeAliasDeclaration"
2691                && b.declaration_type != "TSInterfaceDeclaration"
2692                && b.declaration_type != "TSEnumDeclaration"
2693        })
2694        .map(|b| {
2695            (
2696                b.id,
2697                b.name.clone(),
2698                b.kind.clone(),
2699                b.declaration_type.clone(),
2700                b.declaration_start,
2701                b.declaration_node_id,
2702            )
2703        })
2704        .collect();
2705
2706    if hoistable.is_empty() {
2707        // No hoistable bindings, just lower statements normally
2708        for body_stmt in &block.body {
2709            lower_statement(builder, body_stmt, None, Some(scope_id))?;
2710        }
2711        return Ok(());
2712    }
2713
2714    // Track which bindings have been "declared" (their declaration statement has been seen)
2715    let mut declared: HashSet<BindingId> = HashSet::new();
2716
2717    for body_stmt in &block.body {
2718        let stmt_start = statement_start(body_stmt).unwrap_or(0);
2719        let stmt_end = statement_end(body_stmt).unwrap_or(u32::MAX);
2720        let is_function_decl = matches!(body_stmt, Statement::FunctionDeclaration(_));
2721
2722        // Collect ranges of nested function scopes within this statement.
2723        // Used to check per-reference whether a reference is inside a nested function,
2724        // rather than checking once per-statement.
2725        let nested_function_ranges: Vec<(u32, u32)> = if is_function_decl {
2726            // For function declarations, fnDepth starts at 1 (all refs are inside)
2727            vec![(stmt_start, stmt_end)]
2728        } else {
2729            let scope_info = builder.scope_info();
2730            scope_info
2731                .node_to_scope
2732                .iter()
2733                .filter(|&(&pos, &sid)| {
2734                    pos > stmt_start
2735                        && pos < stmt_end
2736                        && matches!(scope_info.scopes[sid.0 as usize].kind, ScopeKind::Function)
2737                })
2738                .filter_map(|(&pos, _)| {
2739                    scope_info
2740                        .node_to_scope_end
2741                        .get(&pos)
2742                        .map(|&end| (pos, end))
2743                })
2744                .collect()
2745        };
2746
2747        // Find references to not-yet-declared hoistable bindings within this statement
2748        struct HoistInfo {
2749            binding_id: BindingId,
2750            name: String,
2751            kind: AstBindingKind,
2752            declaration_type: String,
2753            first_ref_pos: u32,
2754            first_ref_nid: u32,
2755        }
2756        let mut will_hoist: Vec<HoistInfo> = Vec::new();
2757
2758        for (binding_id, name, kind, decl_type, _decl_start, decl_node_id) in &hoistable {
2759            if declared.contains(binding_id) {
2760                continue;
2761            }
2762
2763            // Find the first reference (not declaration) to this binding in the statement's range.
2764            // Exclude JSX identifier references: while Babel's scope system links JSX
2765            // tag names to local bindings (and the context capture pass includes them),
2766            // the TS hoisting analysis does NOT traverse JSX elements. This mismatch
2767            // is intentional — it matches the TS behavior where <colgroup> adds
2768            // "colgroup" to the context but does NOT trigger hoisting, causing
2769            // EnterSSA to error with "Expected identifier to be defined before use".
2770            //
2771            // The decl_start filter excludes the binding's own declaration position from
2772            // counting as a reference. For hoisted bindings (function declarations), this
2773            // filter is only applied when the current statement IS a FunctionDeclaration,
2774            // since that's the only statement type where decl_start is a declaration, not
2775            // a reference.
2776            let apply_decl_filter = !matches!(kind, AstBindingKind::Hoisted) || is_function_decl;
2777            let refs_in_stmt: Vec<(u32, u32)> = builder
2778                .scope_info()
2779                .ref_node_id_to_binding
2780                .iter()
2781                .filter_map(|(&ref_nid, &ref_bid)| {
2782                    if ref_bid != *binding_id {
2783                        return None;
2784                    }
2785                    let entry = builder.identifier_locs().get(&ref_nid)?;
2786                    let ref_start = entry.start;
2787                    if ref_start < stmt_start || ref_start >= stmt_end {
2788                        return None;
2789                    }
2790                    if apply_decl_filter && *decl_node_id == Some(ref_nid) {
2791                        return None;
2792                    }
2793                    if entry.is_jsx {
2794                        return None;
2795                    }
2796                    Some((ref_start, ref_nid))
2797                })
2798                .collect();
2799
2800            if refs_in_stmt.is_empty() {
2801                continue;
2802            }
2803
2804            let (first_ref_pos, first_ref_nid) =
2805                *refs_in_stmt.iter().min_by_key(|(pos, _)| *pos).unwrap();
2806
2807            // Hoist if: (1) binding is "hoisted" kind (function declaration), or
2808            // (2) any reference to this binding is inside a nested function scope.
2809            // Check per-reference rather than per-statement to correctly handle
2810            // statements that contain both nested functions and top-level code.
2811            let is_hoisted_kind = matches!(kind, AstBindingKind::Hoisted);
2812            let refs_in_nested_fn: Vec<(u32, u32)> = refs_in_stmt
2813                .iter()
2814                .copied()
2815                .filter(|&(ref_pos, _)| {
2816                    nested_function_ranges
2817                        .iter()
2818                        .any(|&(fn_start, fn_end)| ref_pos >= fn_start && ref_pos < fn_end)
2819                })
2820                .collect();
2821            let should_hoist = is_hoisted_kind || !refs_in_nested_fn.is_empty();
2822            if should_hoist {
2823                // Bindings pulled in from CHILD block scopes (the
2824                // scope_bindings_with_children descent compensates for scope
2825                // splitting) only hoist when declared as a direct statement of
2826                // THIS block; ones declared inside nested control-flow blocks
2827                // are handled when those blocks are recursively lowered. TS
2828                // never sees child-block bindings here (Babel's
2829                // stmt.scope.bindings holds only the block's own scope), so the
2830                // guard must NOT apply to own-scope bindings: catch params and
2831                // for-in/for-of head vars belong to the block's scope without
2832                // being declared by any direct statement, and TS hoists them.
2833                let binding_data = &builder.scope_info().bindings[binding_id.0 as usize];
2834                if binding_data.scope != scope_id
2835                    && !is_binding_in_block_direct_statements(binding_data, &block.body)
2836                {
2837                    continue;
2838                }
2839                // For hoisted bindings (function declarations), use the first reference
2840                // overall. For non-hoisted bindings, use the first reference inside a
2841                // nested function.
2842                let (hoist_ref_pos, hoist_ref_nid) = if is_hoisted_kind {
2843                    (first_ref_pos, first_ref_nid)
2844                } else {
2845                    *refs_in_nested_fn
2846                        .iter()
2847                        .min_by_key(|(pos, _)| *pos)
2848                        .unwrap()
2849                };
2850                will_hoist.push(HoistInfo {
2851                    binding_id: *binding_id,
2852                    name: name.clone(),
2853                    kind: kind.clone(),
2854                    declaration_type: decl_type.clone(),
2855                    first_ref_pos: hoist_ref_pos,
2856                    first_ref_nid: hoist_ref_nid,
2857                });
2858            }
2859        }
2860
2861        // Sort by first reference position to match TS traversal order
2862        will_hoist.sort_by_key(|h| h.first_ref_pos);
2863
2864        // Emit DeclareContext for hoisted bindings
2865        for info in &will_hoist {
2866            if builder
2867                .environment()
2868                .is_hoisted_identifier(info.binding_id.0)
2869            {
2870                continue;
2871            }
2872
2873            let hoist_kind = match info.kind {
2874                AstBindingKind::Const | AstBindingKind::Var => InstructionKind::HoistedConst,
2875                AstBindingKind::Let => InstructionKind::HoistedLet,
2876                AstBindingKind::Hoisted => InstructionKind::HoistedFunction,
2877                _ => {
2878                    if info.declaration_type == "FunctionDeclaration" {
2879                        InstructionKind::HoistedFunction
2880                    } else if info.declaration_type == "VariableDeclarator" {
2881                        // Unsupported hoisting for this declaration kind
2882                        builder.record_error(CompilerErrorDetail {
2883                            category: ErrorCategory::Todo,
2884                            reason: "Handle non-const declarations for hoisting".to_string(),
2885                            description: Some(format!(
2886                                "variable \"{}\" declared with {:?}",
2887                                info.name, info.kind
2888                            )),
2889                            loc: None,
2890                            suggestions: None,
2891                        })?;
2892                        continue;
2893                    } else {
2894                        builder.record_error(CompilerErrorDetail {
2895                            category: ErrorCategory::Todo,
2896                            reason: "Unsupported declaration type for hoisting".to_string(),
2897                            description: Some(format!(
2898                                "variable \"{}\" declared with {}",
2899                                info.name, info.declaration_type
2900                            )),
2901                            loc: None,
2902                            suggestions: None,
2903                        })?;
2904                        continue;
2905                    }
2906                }
2907            };
2908
2909            // Look up the reference location for the DeclareContext instruction.
2910            let ref_loc = builder
2911                .identifier_locs()
2912                .get(&info.first_ref_nid)
2913                .map(|e| e.loc.clone());
2914            let identifier = builder.resolve_binding(&info.name, info.binding_id)?;
2915            let place = Place {
2916                effect: Effect::Unknown,
2917                identifier,
2918                reactive: false,
2919                loc: ref_loc.clone(),
2920            };
2921            lower_value_to_temporary(
2922                builder,
2923                InstructionValue::DeclareContext {
2924                    lvalue: LValue {
2925                        kind: hoist_kind,
2926                        place,
2927                    },
2928                    loc: ref_loc,
2929                },
2930            )?;
2931            builder
2932                .environment_mut()
2933                .add_hoisted_identifier(info.binding_id.0);
2934            // Hoisted identifiers also become context identifiers (matching TS addHoistedIdentifier)
2935            builder.add_context_identifier(info.binding_id);
2936        }
2937
2938        // After processing the statement, mark any bindings it declares as "seen".
2939        // This must cover all statement types that can introduce bindings.
2940        match body_stmt {
2941            Statement::FunctionDeclaration(func) => {
2942                if let Some(id) = &func.id {
2943                    if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize]
2944                        .bindings
2945                        .get(&id.name)
2946                    {
2947                        declared.insert(binding_id);
2948                    }
2949                }
2950            }
2951            Statement::VariableDeclaration(var_decl) => {
2952                for decl in &var_decl.declarations {
2953                    collect_binding_names_from_pattern(
2954                        &decl.id,
2955                        scope_id,
2956                        builder.scope_info(),
2957                        &mut declared,
2958                    );
2959                }
2960            }
2961            Statement::ClassDeclaration(cls) => {
2962                if let Some(id) = &cls.id {
2963                    if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize]
2964                        .bindings
2965                        .get(&id.name)
2966                    {
2967                        declared.insert(binding_id);
2968                    }
2969                }
2970            }
2971            _ => {
2972                // For other statement types (e.g. ForStatement with VariableDeclaration in init),
2973                // we rely on the reference_to_binding check for forward references.
2974                // Any bindings declared by child scopes won't be in this block's scope anyway.
2975            }
2976        }
2977
2978        lower_statement(builder, body_stmt, None, Some(scope_id))?;
2979    }
2980    Ok(())
2981}
2982
2983// =============================================================================
2984// lower_statement
2985// =============================================================================
2986
2987fn lower_statement(
2988    builder: &mut HirBuilder,
2989    stmt: &react_compiler_ast::statements::Statement,
2990    label: Option<&str>,
2991    parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2992) -> Result<(), CompilerDiagnostic> {
2993    use react_compiler_ast::statements::Statement;
2994
2995    match stmt {
2996        Statement::EmptyStatement(_) => {
2997            // no-op
2998        }
2999        Statement::DebuggerStatement(dbg) => {
3000            let loc = convert_opt_loc(&dbg.base.loc);
3001            let value = InstructionValue::Debugger { loc };
3002            lower_value_to_temporary(builder, value)?;
3003        }
3004        Statement::ExpressionStatement(expr_stmt) => {
3005            lower_expression_to_temporary(builder, &expr_stmt.expression)?;
3006        }
3007        Statement::ReturnStatement(ret) => {
3008            let loc = convert_opt_loc(&ret.base.loc);
3009            let value = if let Some(arg) = &ret.argument {
3010                lower_expression_to_temporary(builder, arg)?
3011            } else {
3012                let undefined_value = InstructionValue::Primitive {
3013                    value: PrimitiveValue::Undefined,
3014                    loc: None,
3015                };
3016                lower_value_to_temporary(builder, undefined_value)?
3017            };
3018            let fallthrough = builder.reserve(BlockKind::Block);
3019            builder.terminate_with_continuation(
3020                Terminal::Return {
3021                    value,
3022                    return_variant: ReturnVariant::Explicit,
3023                    id: EvaluationOrder(0),
3024                    loc,
3025                    effects: None,
3026                },
3027                fallthrough,
3028            );
3029        }
3030        Statement::ThrowStatement(throw) => {
3031            let loc = convert_opt_loc(&throw.base.loc);
3032            let value = lower_expression_to_temporary(builder, &throw.argument)?;
3033
3034            // Check for throw handler (try/catch)
3035            if let Some(_handler) = builder.resolve_throw_handler() {
3036                builder.record_error(CompilerErrorDetail {
3037                    category: ErrorCategory::Todo,
3038                    reason: "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch"
3039                        .to_string(),
3040                    description: None,
3041                    loc: loc.clone(),
3042                    suggestions: None,
3043                })?;
3044            }
3045
3046            let fallthrough = builder.reserve(BlockKind::Block);
3047            builder.terminate_with_continuation(
3048                Terminal::Throw {
3049                    value,
3050                    id: EvaluationOrder(0),
3051                    loc,
3052                },
3053                fallthrough,
3054            );
3055        }
3056        Statement::BlockStatement(block) => {
3057            lower_block_statement(builder, block, parent_scope)?;
3058        }
3059        Statement::VariableDeclaration(var_decl) => {
3060            use react_compiler_ast::patterns::PatternLike;
3061            use react_compiler_ast::statements::VariableDeclarationKind;
3062            if matches!(var_decl.kind, VariableDeclarationKind::Var) {
3063                builder.record_error(CompilerErrorDetail {
3064                    reason: "(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration"
3065                        .to_string(),
3066                    category: ErrorCategory::Todo,
3067                    loc: convert_opt_loc(&var_decl.base.loc),
3068                    description: None,
3069                    suggestions: None,
3070                })?;
3071                // Treat `var` as `let` so references to the variable don't break
3072            }
3073            let kind = match var_decl.kind {
3074                VariableDeclarationKind::Let | VariableDeclarationKind::Var => InstructionKind::Let,
3075                VariableDeclarationKind::Const | VariableDeclarationKind::Using => {
3076                    InstructionKind::Const
3077                }
3078            };
3079            for declarator in &var_decl.declarations {
3080                let stmt_loc = convert_opt_loc(&var_decl.base.loc);
3081                if let Some(init) = &declarator.init {
3082                    let value = lower_expression_to_temporary(builder, init)?;
3083                    let assign_style = match &declarator.id {
3084                        PatternLike::ObjectPattern(_) | PatternLike::ArrayPattern(_) => {
3085                            AssignmentStyle::Destructure
3086                        }
3087                        _ => AssignmentStyle::Assignment,
3088                    };
3089                    lower_assignment(builder, stmt_loc, kind, &declarator.id, value, assign_style)?;
3090                } else if let PatternLike::Identifier(id) = &declarator.id {
3091                    // No init: emit DeclareLocal or DeclareContext
3092                    let id_loc = convert_opt_loc(&id.base.loc);
3093                    let mut binding = builder.resolve_identifier(
3094                        &id.name,
3095                        id.base.start.unwrap_or(0),
3096                        id_loc.clone(),
3097                        id.base.node_id,
3098                    )?;
3099                    if !matches!(binding, VariableBinding::Identifier { .. }) {
3100                        // Position-based resolution failed (synthetic $$gen vars
3101                        // at position 0). Try scope lookup including descendants.
3102                        if let Some((binding_id, binding_data)) = builder
3103                            .scope_info()
3104                            .find_binding_id_in_descendants(&id.name, builder.function_scope())
3105                        {
3106                            let binding_kind = crate::convert_binding_kind(&binding_data.kind);
3107                            let identifier = builder.resolve_binding_with_loc(
3108                                &id.name,
3109                                binding_id,
3110                                id_loc.clone(),
3111                            )?;
3112                            binding = VariableBinding::Identifier {
3113                                identifier,
3114                                binding_kind,
3115                            };
3116                        }
3117                    }
3118                    match binding {
3119                        VariableBinding::Identifier { identifier, .. } => {
3120                            // Update the identifier's loc to the declaration site
3121                            // (it may have been first created at a reference site during hoisting)
3122                            builder.set_identifier_declaration_loc(identifier, &id_loc);
3123                            let place = Place {
3124                                identifier,
3125                                effect: Effect::Unknown,
3126                                reactive: false,
3127                                loc: id_loc.clone(),
3128                            };
3129                            if builder.is_context_identifier(
3130                                &id.name,
3131                                id.base.start.unwrap_or(0),
3132                                id.base.node_id,
3133                            ) {
3134                                if kind == InstructionKind::Const {
3135                                    builder.record_error(CompilerErrorDetail {
3136                                        reason: "Expect `const` declaration not to be reassigned"
3137                                            .to_string(),
3138                                        category: ErrorCategory::Syntax,
3139                                        loc: id_loc.clone(),
3140                                        description: None,
3141                                        suggestions: None,
3142                                    })?;
3143                                }
3144                                lower_value_to_temporary(
3145                                    builder,
3146                                    InstructionValue::DeclareContext {
3147                                        lvalue: LValue {
3148                                            kind: InstructionKind::Let,
3149                                            place,
3150                                        },
3151                                        loc: id_loc,
3152                                    },
3153                                )?;
3154                            } else {
3155                                let type_annotation =
3156                                    extract_type_annotation_name(&id.type_annotation);
3157                                lower_value_to_temporary(
3158                                    builder,
3159                                    InstructionValue::DeclareLocal {
3160                                        lvalue: LValue { kind, place },
3161                                        type_annotation,
3162                                        loc: id_loc,
3163                                    },
3164                                )?;
3165                            }
3166                        }
3167                        _ => {
3168                            builder.record_error(CompilerErrorDetail {
3169                                reason: "Could not find binding for declaration".to_string(),
3170                                category: ErrorCategory::Invariant,
3171                                loc: id_loc,
3172                                description: None,
3173                                suggestions: None,
3174                            })?;
3175                        }
3176                    }
3177                } else {
3178                    builder.record_error(CompilerErrorDetail {
3179                        reason: "Expected variable declaration to be an identifier if no initializer was provided".to_string(),
3180                        category: ErrorCategory::Syntax,
3181                        loc: convert_opt_loc(&declarator.base.loc),
3182                        description: None,
3183                        suggestions: None,
3184                    })?;
3185                }
3186            }
3187        }
3188        Statement::BreakStatement(brk) => {
3189            let loc = convert_opt_loc(&brk.base.loc);
3190            let label_name = brk.label.as_ref().map(|l| l.name.as_str());
3191            let target = builder.lookup_break(label_name)?;
3192            let fallthrough = builder.reserve(BlockKind::Block);
3193            builder.terminate_with_continuation(
3194                Terminal::Goto {
3195                    block: target,
3196                    variant: GotoVariant::Break,
3197                    id: EvaluationOrder(0),
3198                    loc,
3199                },
3200                fallthrough,
3201            );
3202        }
3203        Statement::ContinueStatement(cont) => {
3204            let loc = convert_opt_loc(&cont.base.loc);
3205            let label_name = cont.label.as_ref().map(|l| l.name.as_str());
3206            let target = builder.lookup_continue(label_name)?;
3207            let fallthrough = builder.reserve(BlockKind::Block);
3208            builder.terminate_with_continuation(
3209                Terminal::Goto {
3210                    block: target,
3211                    variant: GotoVariant::Continue,
3212                    id: EvaluationOrder(0),
3213                    loc,
3214                },
3215                fallthrough,
3216            );
3217        }
3218        Statement::IfStatement(if_stmt) => {
3219            let loc = convert_opt_loc(&if_stmt.base.loc);
3220            // Block for code following the if
3221            let continuation_block = builder.reserve(BlockKind::Block);
3222            let continuation_id = continuation_block.id;
3223
3224            // Block for the consequent (if the test is truthy)
3225            let consequent_loc = statement_loc(&if_stmt.consequent);
3226            let consequent_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3227                lower_statement(builder, &if_stmt.consequent, None, parent_scope)?;
3228                Ok(Terminal::Goto {
3229                    block: continuation_id,
3230                    variant: GotoVariant::Break,
3231                    id: EvaluationOrder(0),
3232                    loc: consequent_loc,
3233                })
3234            })?;
3235
3236            // Block for the alternate (if the test is not truthy)
3237            let alternate_block = if let Some(alternate) = &if_stmt.alternate {
3238                let alternate_loc = statement_loc(alternate);
3239                builder.try_enter(BlockKind::Block, |builder, _block_id| {
3240                    lower_statement(builder, alternate, None, parent_scope)?;
3241                    Ok(Terminal::Goto {
3242                        block: continuation_id,
3243                        variant: GotoVariant::Break,
3244                        id: EvaluationOrder(0),
3245                        loc: alternate_loc,
3246                    })
3247                })?
3248            } else {
3249                // If there is no else clause, use the continuation directly
3250                continuation_id
3251            };
3252
3253            let test = lower_expression_to_temporary(builder, &if_stmt.test)?;
3254            builder.terminate_with_continuation(
3255                Terminal::If {
3256                    test,
3257                    consequent: consequent_block,
3258                    alternate: alternate_block,
3259                    fallthrough: continuation_id,
3260                    id: EvaluationOrder(0),
3261                    loc,
3262                },
3263                continuation_block,
3264            );
3265        }
3266        Statement::ForStatement(for_stmt) => {
3267            let loc = convert_opt_loc(&for_stmt.base.loc);
3268
3269            let test_block = builder.reserve(BlockKind::Loop);
3270            let test_block_id = test_block.id;
3271            // Block for code following the loop
3272            let continuation_block = builder.reserve(BlockKind::Block);
3273            let continuation_id = continuation_block.id;
3274
3275            // Init block: lower init expression/declaration, then goto test
3276            let init_block = builder.try_enter(BlockKind::Loop, |builder, _block_id| {
3277                let init_loc = match &for_stmt.init {
3278                    None => {
3279                        // No init expression (e.g., `for (; ...)`), add a placeholder
3280                        let placeholder = InstructionValue::Primitive {
3281                            value: PrimitiveValue::Undefined,
3282                            loc: loc.clone(),
3283                        };
3284                        lower_value_to_temporary(builder, placeholder)?;
3285                        loc.clone()
3286                    }
3287                    Some(init) => {
3288                        match init.as_ref() {
3289                            react_compiler_ast::statements::ForInit::VariableDeclaration(var_decl) => {
3290                                let init_loc = convert_opt_loc(&var_decl.base.loc);
3291                                lower_statement(builder, &Statement::VariableDeclaration(var_decl.clone()), None, parent_scope)?;
3292                                init_loc
3293                            }
3294                            react_compiler_ast::statements::ForInit::Expression(expr) => {
3295                                let init_loc = expression_loc(expr);
3296                                builder.record_error(CompilerErrorDetail {
3297                                    category: ErrorCategory::Todo,
3298                                    reason: "(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement".to_string(),
3299                                    description: None,
3300                                    loc: loc.clone(),
3301                                    suggestions: None,
3302                                })?;
3303                                lower_expression_to_temporary(builder, expr)?;
3304                                init_loc
3305                            }
3306                        }
3307                    }
3308                };
3309                Ok(Terminal::Goto {
3310                    block: test_block_id,
3311                    variant: GotoVariant::Break,
3312                    id: EvaluationOrder(0),
3313                    loc: init_loc,
3314                })
3315            })?;
3316
3317            // Update block (optional)
3318            let update_block_id = if let Some(update) = &for_stmt.update {
3319                let update_loc = expression_loc(update);
3320                Some(builder.try_enter(BlockKind::Loop, |builder, _block_id| {
3321                    lower_expression_to_temporary(builder, update)?;
3322                    Ok(Terminal::Goto {
3323                        block: test_block_id,
3324                        variant: GotoVariant::Break,
3325                        id: EvaluationOrder(0),
3326                        loc: update_loc,
3327                    })
3328                })?)
3329            } else {
3330                None
3331            };
3332
3333            // Loop body block
3334            let continue_target = update_block_id.unwrap_or(test_block_id);
3335            let body_loc = statement_loc(&for_stmt.body);
3336            let body_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3337                builder.loop_scope(
3338                    label.map(|s| s.to_string()),
3339                    continue_target,
3340                    continuation_id,
3341                    |builder| {
3342                        lower_statement(builder, &for_stmt.body, None, parent_scope)?;
3343                        Ok(Terminal::Goto {
3344                            block: continue_target,
3345                            variant: GotoVariant::Continue,
3346                            id: EvaluationOrder(0),
3347                            loc: body_loc,
3348                        })
3349                    },
3350                )
3351            })?;
3352
3353            // Emit For terminal, then fill in the test block
3354            builder.terminate_with_continuation(
3355                Terminal::For {
3356                    init: init_block,
3357                    test: test_block_id,
3358                    update: update_block_id,
3359                    loop_block: body_block,
3360                    fallthrough: continuation_id,
3361                    id: EvaluationOrder(0),
3362                    loc: loc.clone(),
3363                },
3364                test_block,
3365            );
3366
3367            // Fill in the test block
3368            if let Some(test_expr) = &for_stmt.test {
3369                let test = lower_expression_to_temporary(builder, test_expr)?;
3370                builder.terminate_with_continuation(
3371                    Terminal::Branch {
3372                        test,
3373                        consequent: body_block,
3374                        alternate: continuation_id,
3375                        fallthrough: continuation_id,
3376                        id: EvaluationOrder(0),
3377                        loc: loc.clone(),
3378                    },
3379                    continuation_block,
3380                );
3381            } else {
3382                builder.record_error(CompilerErrorDetail {
3383                    category: ErrorCategory::Todo,
3384                    reason: "(BuildHIR::lowerStatement) Handle empty test in ForStatement"
3385                        .to_string(),
3386                    description: None,
3387                    loc: loc.clone(),
3388                    suggestions: None,
3389                })?;
3390                // Treat `for(;;)` as `while(true)` to keep the builder state consistent
3391                let true_val = InstructionValue::Primitive {
3392                    value: PrimitiveValue::Boolean(true),
3393                    loc: loc.clone(),
3394                };
3395                let test = lower_value_to_temporary(builder, true_val)?;
3396                builder.terminate_with_continuation(
3397                    Terminal::Branch {
3398                        test,
3399                        consequent: body_block,
3400                        alternate: continuation_id,
3401                        fallthrough: continuation_id,
3402                        id: EvaluationOrder(0),
3403                        loc,
3404                    },
3405                    continuation_block,
3406                );
3407            }
3408        }
3409        Statement::WhileStatement(while_stmt) => {
3410            let loc = convert_opt_loc(&while_stmt.base.loc);
3411            // Block used to evaluate whether to (re)enter or exit the loop
3412            let conditional_block = builder.reserve(BlockKind::Loop);
3413            let conditional_id = conditional_block.id;
3414            // Block for code following the loop
3415            let continuation_block = builder.reserve(BlockKind::Block);
3416            let continuation_id = continuation_block.id;
3417
3418            // Loop body
3419            let body_loc = statement_loc(&while_stmt.body);
3420            let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3421                builder.loop_scope(
3422                    label.map(|s| s.to_string()),
3423                    conditional_id,
3424                    continuation_id,
3425                    |builder| {
3426                        lower_statement(builder, &while_stmt.body, None, parent_scope)?;
3427                        Ok(Terminal::Goto {
3428                            block: conditional_id,
3429                            variant: GotoVariant::Continue,
3430                            id: EvaluationOrder(0),
3431                            loc: body_loc,
3432                        })
3433                    },
3434                )
3435            })?;
3436
3437            // Emit While terminal, jumping to the conditional block
3438            builder.terminate_with_continuation(
3439                Terminal::While {
3440                    test: conditional_id,
3441                    loop_block,
3442                    fallthrough: continuation_id,
3443                    id: EvaluationOrder(0),
3444                    loc: loc.clone(),
3445                },
3446                conditional_block,
3447            );
3448
3449            // Fill in the conditional block: lower test, branch
3450            let test = lower_expression_to_temporary(builder, &while_stmt.test)?;
3451            builder.terminate_with_continuation(
3452                Terminal::Branch {
3453                    test,
3454                    consequent: loop_block,
3455                    alternate: continuation_id,
3456                    fallthrough: conditional_id,
3457                    id: EvaluationOrder(0),
3458                    loc,
3459                },
3460                continuation_block,
3461            );
3462        }
3463        Statement::DoWhileStatement(do_while_stmt) => {
3464            let loc = convert_opt_loc(&do_while_stmt.base.loc);
3465            // Block used to evaluate whether to (re)enter or exit the loop
3466            let conditional_block = builder.reserve(BlockKind::Loop);
3467            let conditional_id = conditional_block.id;
3468            // Block for code following the loop
3469            let continuation_block = builder.reserve(BlockKind::Block);
3470            let continuation_id = continuation_block.id;
3471
3472            // Loop body, executed at least once unconditionally prior to exit
3473            let body_loc = statement_loc(&do_while_stmt.body);
3474            let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3475                builder.loop_scope(
3476                    label.map(|s| s.to_string()),
3477                    conditional_id,
3478                    continuation_id,
3479                    |builder| {
3480                        lower_statement(builder, &do_while_stmt.body, None, parent_scope)?;
3481                        Ok(Terminal::Goto {
3482                            block: conditional_id,
3483                            variant: GotoVariant::Continue,
3484                            id: EvaluationOrder(0),
3485                            loc: body_loc,
3486                        })
3487                    },
3488                )
3489            })?;
3490
3491            // Jump to the conditional block
3492            builder.terminate_with_continuation(
3493                Terminal::DoWhile {
3494                    loop_block,
3495                    test: conditional_id,
3496                    fallthrough: continuation_id,
3497                    id: EvaluationOrder(0),
3498                    loc: loc.clone(),
3499                },
3500                conditional_block,
3501            );
3502
3503            // Fill in the conditional block: lower test, branch
3504            let test = lower_expression_to_temporary(builder, &do_while_stmt.test)?;
3505            builder.terminate_with_continuation(
3506                Terminal::Branch {
3507                    test,
3508                    consequent: loop_block,
3509                    alternate: continuation_id,
3510                    fallthrough: conditional_id,
3511                    id: EvaluationOrder(0),
3512                    loc,
3513                },
3514                continuation_block,
3515            );
3516        }
3517        Statement::ForInStatement(for_in) => {
3518            let loc = convert_opt_loc(&for_in.base.loc);
3519            let continuation_block = builder.reserve(BlockKind::Block);
3520            let continuation_id = continuation_block.id;
3521            let init_block = builder.reserve(BlockKind::Loop);
3522            let init_block_id = init_block.id;
3523
3524            let body_loc = statement_loc(&for_in.body);
3525            let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3526                builder.loop_scope(
3527                    label.map(|s| s.to_string()),
3528                    init_block_id,
3529                    continuation_id,
3530                    |builder| {
3531                        lower_statement(builder, &for_in.body, None, parent_scope)?;
3532                        Ok(Terminal::Goto {
3533                            block: init_block_id,
3534                            variant: GotoVariant::Continue,
3535                            id: EvaluationOrder(0),
3536                            loc: body_loc,
3537                        })
3538                    },
3539                )
3540            })?;
3541
3542            let value = lower_expression_to_temporary(builder, &for_in.right)?;
3543            builder.terminate_with_continuation(
3544                Terminal::ForIn {
3545                    init: init_block_id,
3546                    loop_block,
3547                    fallthrough: continuation_id,
3548                    id: EvaluationOrder(0),
3549                    loc: loc.clone(),
3550                },
3551                init_block,
3552            );
3553
3554            // Lower the init: NextPropertyOf + assignment
3555            let left_loc = match for_in.left.as_ref() {
3556                react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3557                    convert_opt_loc(&var_decl.base.loc).or(loc.clone())
3558                }
3559                react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => {
3560                    pattern_like_hir_loc(pat).or(loc.clone())
3561                }
3562            };
3563            let next_property = lower_value_to_temporary(
3564                builder,
3565                InstructionValue::NextPropertyOf {
3566                    value,
3567                    loc: left_loc.clone(),
3568                },
3569            )?;
3570
3571            let assign_result = match for_in.left.as_ref() {
3572                react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3573                    if var_decl.declarations.len() != 1 {
3574                        builder.record_error(CompilerErrorDetail {
3575                            category: ErrorCategory::Invariant,
3576                            reason: format!(
3577                                "Expected only one declaration in ForInStatement init, got {}",
3578                                var_decl.declarations.len()
3579                            ),
3580                            description: None,
3581                            loc: left_loc.clone(),
3582                            suggestions: None,
3583                        })?;
3584                    }
3585                    if let Some(declarator) = var_decl.declarations.first() {
3586                        lower_assignment(
3587                            builder,
3588                            left_loc.clone(),
3589                            InstructionKind::Let,
3590                            &declarator.id,
3591                            next_property.clone(),
3592                            AssignmentStyle::Assignment,
3593                        )?
3594                    } else {
3595                        None
3596                    }
3597                }
3598                react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment(
3599                    builder,
3600                    left_loc.clone(),
3601                    InstructionKind::Reassign,
3602                    pattern,
3603                    next_property.clone(),
3604                    AssignmentStyle::Assignment,
3605                )?,
3606            };
3607            // Use the assign result (StoreLocal temp) as the test, matching TS behavior
3608            let test_value = assign_result.unwrap_or(next_property);
3609            let test = lower_value_to_temporary(
3610                builder,
3611                InstructionValue::LoadLocal {
3612                    place: test_value,
3613                    loc: left_loc.clone(),
3614                },
3615            )?;
3616            builder.terminate_with_continuation(
3617                Terminal::Branch {
3618                    test,
3619                    consequent: loop_block,
3620                    alternate: continuation_id,
3621                    fallthrough: continuation_id,
3622                    id: EvaluationOrder(0),
3623                    loc: loc.clone(),
3624                },
3625                continuation_block,
3626            );
3627        }
3628        Statement::ForOfStatement(for_of) => {
3629            let loc = convert_opt_loc(&for_of.base.loc);
3630            let continuation_block = builder.reserve(BlockKind::Block);
3631            let continuation_id = continuation_block.id;
3632            let init_block = builder.reserve(BlockKind::Loop);
3633            let init_block_id = init_block.id;
3634            let test_block = builder.reserve(BlockKind::Loop);
3635            let test_block_id = test_block.id;
3636
3637            if for_of.is_await {
3638                builder.record_error(CompilerErrorDetail {
3639                    category: ErrorCategory::Todo,
3640                    reason: "(BuildHIR::lowerStatement) Handle for-await loops".to_string(),
3641                    description: None,
3642                    loc: loc.clone(),
3643                    suggestions: None,
3644                })?;
3645                return Ok(());
3646            }
3647
3648            let body_loc = statement_loc(&for_of.body);
3649            let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3650                builder.loop_scope(
3651                    label.map(|s| s.to_string()),
3652                    init_block_id,
3653                    continuation_id,
3654                    |builder| {
3655                        lower_statement(builder, &for_of.body, None, parent_scope)?;
3656                        Ok(Terminal::Goto {
3657                            block: init_block_id,
3658                            variant: GotoVariant::Continue,
3659                            id: EvaluationOrder(0),
3660                            loc: body_loc,
3661                        })
3662                    },
3663                )
3664            })?;
3665
3666            let value = lower_expression_to_temporary(builder, &for_of.right)?;
3667            builder.terminate_with_continuation(
3668                Terminal::ForOf {
3669                    init: init_block_id,
3670                    test: test_block_id,
3671                    loop_block,
3672                    fallthrough: continuation_id,
3673                    id: EvaluationOrder(0),
3674                    loc: loc.clone(),
3675                },
3676                init_block,
3677            );
3678
3679            // Init block: GetIterator, goto test
3680            let iterator = lower_value_to_temporary(
3681                builder,
3682                InstructionValue::GetIterator {
3683                    collection: value.clone(),
3684                    loc: value.loc.clone(),
3685                },
3686            )?;
3687            builder.terminate_with_continuation(
3688                Terminal::Goto {
3689                    block: test_block_id,
3690                    variant: GotoVariant::Break,
3691                    id: EvaluationOrder(0),
3692                    loc: loc.clone(),
3693                },
3694                test_block,
3695            );
3696
3697            // Test block: IteratorNext, assign, branch
3698            let left_loc = match for_of.left.as_ref() {
3699                react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3700                    convert_opt_loc(&var_decl.base.loc).or(loc.clone())
3701                }
3702                react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => {
3703                    pattern_like_hir_loc(pat).or(loc.clone())
3704                }
3705            };
3706            let advance_iterator = lower_value_to_temporary(
3707                builder,
3708                InstructionValue::IteratorNext {
3709                    iterator: iterator.clone(),
3710                    collection: value.clone(),
3711                    loc: left_loc.clone(),
3712                },
3713            )?;
3714
3715            let assign_result = match for_of.left.as_ref() {
3716                react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3717                    if var_decl.declarations.len() != 1 {
3718                        builder.record_error(CompilerErrorDetail {
3719                            category: ErrorCategory::Invariant,
3720                            reason: format!(
3721                                "Expected only one declaration in ForOfStatement init, got {}",
3722                                var_decl.declarations.len()
3723                            ),
3724                            description: None,
3725                            loc: left_loc.clone(),
3726                            suggestions: None,
3727                        })?;
3728                    }
3729                    if let Some(declarator) = var_decl.declarations.first() {
3730                        lower_assignment(
3731                            builder,
3732                            left_loc.clone(),
3733                            InstructionKind::Let,
3734                            &declarator.id,
3735                            advance_iterator.clone(),
3736                            AssignmentStyle::Assignment,
3737                        )?
3738                    } else {
3739                        None
3740                    }
3741                }
3742                react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment(
3743                    builder,
3744                    left_loc.clone(),
3745                    InstructionKind::Reassign,
3746                    pattern,
3747                    advance_iterator.clone(),
3748                    AssignmentStyle::Assignment,
3749                )?,
3750            };
3751            // Use the assign result (StoreLocal temp) as the test, matching TS behavior
3752            let test_value = assign_result.unwrap_or(advance_iterator);
3753            let test = lower_value_to_temporary(
3754                builder,
3755                InstructionValue::LoadLocal {
3756                    place: test_value,
3757                    loc: left_loc.clone(),
3758                },
3759            )?;
3760            builder.terminate_with_continuation(
3761                Terminal::Branch {
3762                    test,
3763                    consequent: loop_block,
3764                    alternate: continuation_id,
3765                    fallthrough: continuation_id,
3766                    id: EvaluationOrder(0),
3767                    loc: loc.clone(),
3768                },
3769                continuation_block,
3770            );
3771        }
3772        Statement::SwitchStatement(switch_stmt) => {
3773            let loc = convert_opt_loc(&switch_stmt.base.loc);
3774            let continuation_block = builder.reserve(BlockKind::Block);
3775            let continuation_id = continuation_block.id;
3776
3777            // Iterate through cases in reverse order so that previous blocks can
3778            // fallthrough to successors
3779            let mut fallthrough = continuation_id;
3780            let mut cases: Vec<Case> = Vec::new();
3781            let mut has_default = false;
3782
3783            for ii in (0..switch_stmt.cases.len()).rev() {
3784                let case = &switch_stmt.cases[ii];
3785                let case_loc = convert_opt_loc(&case.base.loc);
3786
3787                if case.test.is_none() {
3788                    if has_default {
3789                        builder.record_error(CompilerErrorDetail {
3790                            category: ErrorCategory::Syntax,
3791                            reason: "Expected at most one `default` branch in a switch statement"
3792                                .to_string(),
3793                            description: None,
3794                            loc: case_loc.clone(),
3795                            suggestions: None,
3796                        })?;
3797                        break;
3798                    }
3799                    has_default = true;
3800                }
3801
3802                let fallthrough_target = fallthrough;
3803                let block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3804                    builder.switch_scope(label.map(|s| s.to_string()), continuation_id, |builder| {
3805                        for consequent in &case.consequent {
3806                            lower_statement(builder, consequent, None, parent_scope)?;
3807                        }
3808                        Ok(Terminal::Goto {
3809                            block: fallthrough_target,
3810                            variant: GotoVariant::Break,
3811                            id: EvaluationOrder(0),
3812                            loc: case_loc.clone(),
3813                        })
3814                    })
3815                })?;
3816
3817                let test = if let Some(test_expr) = &case.test {
3818                    Some(lower_reorderable_expression(builder, test_expr)?)
3819                } else {
3820                    None
3821                };
3822
3823                cases.push(Case { test, block });
3824                fallthrough = block;
3825            }
3826
3827            // Reverse back to original order
3828            cases.reverse();
3829
3830            // If no default case, add one that jumps to continuation
3831            if !has_default {
3832                cases.push(Case {
3833                    test: None,
3834                    block: continuation_id,
3835                });
3836            }
3837
3838            let test = lower_expression_to_temporary(builder, &switch_stmt.discriminant)?;
3839            builder.terminate_with_continuation(
3840                Terminal::Switch {
3841                    test,
3842                    cases,
3843                    fallthrough: continuation_id,
3844                    id: EvaluationOrder(0),
3845                    loc,
3846                },
3847                continuation_block,
3848            );
3849        }
3850        Statement::TryStatement(try_stmt) => {
3851            let loc = convert_opt_loc(&try_stmt.base.loc);
3852            let continuation_block = builder.reserve(BlockKind::Block);
3853            let continuation_id = continuation_block.id;
3854
3855            let handler_clause = match &try_stmt.handler {
3856                Some(h) => h,
3857                None => {
3858                    builder.record_error(CompilerErrorDetail {
3859                        category: ErrorCategory::Todo,
3860                        reason:
3861                            "(BuildHIR::lowerStatement) Handle TryStatement without a catch clause"
3862                                .to_string(),
3863                        description: None,
3864                        loc: loc.clone(),
3865                        suggestions: None,
3866                    })?;
3867                    return Ok(());
3868                }
3869            };
3870
3871            if try_stmt.finalizer.is_some() {
3872                builder.record_error(CompilerErrorDetail {
3873                    category: ErrorCategory::Todo,
3874                    reason: "(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause".to_string(),
3875                    description: None,
3876                    loc: loc.clone(),
3877                    suggestions: None,
3878                })?;
3879            }
3880
3881            // Set up handler binding if catch has a param
3882            let handler_binding_info: Option<(Place, react_compiler_ast::patterns::PatternLike)> =
3883                if let Some(param) = &handler_clause.param {
3884                    // Check for destructuring in catch clause params.
3885                    // Match TS behavior: Babel doesn't register destructured catch bindings
3886                    // in its scope, so resolveIdentifier fails and records an invariant error.
3887                    let is_destructuring = matches!(
3888                        param,
3889                        react_compiler_ast::patterns::PatternLike::ObjectPattern(_)
3890                            | react_compiler_ast::patterns::PatternLike::ArrayPattern(_)
3891                    );
3892                    if is_destructuring {
3893                        // Iterate the pattern to find all identifier locs for error reporting
3894                        fn collect_identifier_locs(
3895                            pat: &react_compiler_ast::patterns::PatternLike,
3896                            locs: &mut Vec<Option<SourceLocation>>,
3897                        ) {
3898                            match pat {
3899                                react_compiler_ast::patterns::PatternLike::Identifier(id) => {
3900                                    locs.push(convert_opt_loc(&id.base.loc));
3901                                }
3902                                react_compiler_ast::patterns::PatternLike::ObjectPattern(obj) => {
3903                                    for prop in &obj.properties {
3904                                        match prop {
3905                                            react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
3906                                                collect_identifier_locs(&p.value, locs);
3907                                            }
3908                                            react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
3909                                                collect_identifier_locs(&r.argument, locs);
3910                                            }
3911                                        }
3912                                    }
3913                                }
3914                                react_compiler_ast::patterns::PatternLike::ArrayPattern(arr) => {
3915                                    for elem in &arr.elements {
3916                                        if let Some(e) = elem {
3917                                            collect_identifier_locs(e, locs);
3918                                        }
3919                                    }
3920                                }
3921                                _ => {}
3922                            }
3923                        }
3924                        let mut id_locs = Vec::new();
3925                        collect_identifier_locs(param, &mut id_locs);
3926                        for id_loc in id_locs {
3927                            builder.record_error(CompilerErrorDetail {
3928                                reason: "(BuildHIR::lowerAssignment) Could not find binding for declaration.".to_string(),
3929                                category: ErrorCategory::Invariant,
3930                                loc: id_loc,
3931                                description: None,
3932                                suggestions: None,
3933                            })?;
3934                        }
3935                        None
3936                    } else {
3937                        let param_loc = convert_opt_loc(&pattern_like_loc(param));
3938                        let id = builder.make_temporary(param_loc.clone());
3939                        promote_temporary(builder, id);
3940                        let place = Place {
3941                            identifier: id,
3942                            effect: Effect::Unknown,
3943                            reactive: false,
3944                            loc: param_loc.clone(),
3945                        };
3946                        // Emit DeclareLocal for the catch binding
3947                        lower_value_to_temporary(
3948                            builder,
3949                            InstructionValue::DeclareLocal {
3950                                lvalue: LValue {
3951                                    kind: InstructionKind::Catch,
3952                                    place: place.clone(),
3953                                },
3954                                type_annotation: None,
3955                                loc: param_loc,
3956                            },
3957                        )?;
3958                        Some((place, param.clone()))
3959                    }
3960                } else {
3961                    None
3962                };
3963
3964            // Create the handler (catch) block
3965            let handler_binding_for_block = handler_binding_info.clone();
3966            let handler_loc = convert_opt_loc(&handler_clause.base.loc);
3967            // Use the catch param's loc for the assignment, matching TS: handlerBinding.path.node.loc
3968            let handler_param_loc = handler_clause
3969                .param
3970                .as_ref()
3971                .and_then(|p| convert_opt_loc(&pattern_like_loc(p)));
3972            let handler_block = builder.try_enter(BlockKind::Catch, |builder, _block_id| {
3973                if let Some((ref place, ref pattern)) = handler_binding_for_block {
3974                    lower_assignment(
3975                        builder,
3976                        handler_param_loc.clone().or_else(|| handler_loc.clone()),
3977                        InstructionKind::Catch,
3978                        pattern,
3979                        place.clone(),
3980                        AssignmentStyle::Assignment,
3981                    )?;
3982                }
3983                // Lower the catch body using lower_block_statement to get hoisting support.
3984                // Match TS behavior where `lowerStatement(builder, handlerPath.get('body'))`
3985                // processes the catch body as a BlockStatement (with hoisting).
3986                // Use the catch clause's scope since the catch body block shares
3987                // the CatchClause scope in Babel (contains the catch param binding).
3988                // Use the catch clause's scope (which contains the catch param binding).
3989                // Fall back to the body block's own scope if the catch clause scope is missing.
3990                let catch_scope = builder
3991                    .scope_info()
3992                    .resolve_scope_for_node(handler_clause.base.node_id)
3993                    .or_else(|| {
3994                        builder
3995                            .scope_info()
3996                            .resolve_scope_for_node(handler_clause.body.base.node_id)
3997                    });
3998                if let Some(scope_id) = catch_scope {
3999                    lower_block_statement_with_scope(builder, &handler_clause.body, scope_id)?;
4000                } else {
4001                    // No scope found — this shouldn't happen with well-formed Babel output.
4002                    // Fall back to plain block lowering (no hoisting) rather than panicking,
4003                    // since this is a non-critical degradation.
4004                    lower_block_statement(builder, &handler_clause.body, parent_scope)?;
4005                }
4006                Ok(Terminal::Goto {
4007                    block: continuation_id,
4008                    variant: GotoVariant::Break,
4009                    id: EvaluationOrder(0),
4010                    loc: handler_loc.clone(),
4011                })
4012            })?;
4013
4014            // Create the try block
4015            // Use lower_block_statement to get hoisting support for bindings
4016            // declared inside the try body. This matches the catch block's use of
4017            // lower_block_statement_with_scope and ensures self-referencing function
4018            // declarations (e.g., `const loop = () => { loop(); }`) inside try blocks
4019            // are correctly promoted to context variables.
4020            let try_body_loc = convert_opt_loc(&try_stmt.block.base.loc);
4021            let try_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
4022                builder.try_enter_try_catch(handler_block, |builder| {
4023                    lower_block_statement(builder, &try_stmt.block, parent_scope)?;
4024                    Ok(())
4025                })?;
4026                Ok(Terminal::Goto {
4027                    block: continuation_id,
4028                    variant: GotoVariant::Try,
4029                    id: EvaluationOrder(0),
4030                    loc: try_body_loc.clone(),
4031                })
4032            })?;
4033
4034            builder.terminate_with_continuation(
4035                Terminal::Try {
4036                    block: try_block,
4037                    handler_binding: handler_binding_info.map(|(place, _)| place),
4038                    handler: handler_block,
4039                    fallthrough: continuation_id,
4040                    id: EvaluationOrder(0),
4041                    loc,
4042                },
4043                continuation_block,
4044            );
4045        }
4046        Statement::LabeledStatement(labeled_stmt) => {
4047            let label_name = &labeled_stmt.label.name;
4048            let loc = convert_opt_loc(&labeled_stmt.base.loc);
4049
4050            // Check if the body is a loop statement - if so, delegate with label
4051            match labeled_stmt.body.as_ref() {
4052                Statement::ForStatement(_)
4053                | Statement::WhileStatement(_)
4054                | Statement::DoWhileStatement(_)
4055                | Statement::ForInStatement(_)
4056                | Statement::ForOfStatement(_) => {
4057                    // Labeled loops are special because of continue, push the label down
4058                    lower_statement(builder, &labeled_stmt.body, Some(label_name), parent_scope)?;
4059                }
4060                _ => {
4061                    // All other statements create a continuation block to allow `break`
4062                    let continuation_block = builder.reserve(BlockKind::Block);
4063                    let continuation_id = continuation_block.id;
4064                    let body_loc = statement_loc(&labeled_stmt.body);
4065
4066                    let block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
4067                        builder.label_scope(label_name.clone(), continuation_id, |builder| {
4068                            lower_statement(builder, &labeled_stmt.body, None, parent_scope)?;
4069                            Ok(())
4070                        })?;
4071                        Ok(Terminal::Goto {
4072                            block: continuation_id,
4073                            variant: GotoVariant::Break,
4074                            id: EvaluationOrder(0),
4075                            loc: body_loc,
4076                        })
4077                    })?;
4078
4079                    builder.terminate_with_continuation(
4080                        Terminal::Label {
4081                            block,
4082                            fallthrough: continuation_id,
4083                            id: EvaluationOrder(0),
4084                            loc,
4085                        },
4086                        continuation_block,
4087                    );
4088                }
4089            }
4090        }
4091        Statement::WithStatement(with_stmt) => {
4092            let loc = convert_opt_loc(&with_stmt.base.loc);
4093            builder.record_error(CompilerErrorDetail {
4094                category: ErrorCategory::UnsupportedSyntax,
4095                reason: "JavaScript 'with' syntax is not supported".to_string(),
4096                description: Some("'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives".to_string()),
4097                loc: loc.clone(),
4098                suggestions: None,
4099            })?;
4100            lower_value_to_temporary(
4101                builder,
4102                InstructionValue::UnsupportedNode {
4103                    node_type: Some("WithStatement".to_string()),
4104                    original_node: serialize_statement(stmt),
4105                    loc,
4106                },
4107            )?;
4108        }
4109        Statement::FunctionDeclaration(func_decl) => {
4110            lower_function_declaration(builder, func_decl)?;
4111        }
4112        Statement::ClassDeclaration(cls) => {
4113            let loc = convert_opt_loc(&cls.base.loc);
4114            builder.record_error(CompilerErrorDetail {
4115                category: ErrorCategory::UnsupportedSyntax,
4116                reason: "Inline `class` declarations are not supported".to_string(),
4117                description: Some(
4118                    "Move class declarations outside of components/hooks".to_string(),
4119                ),
4120                loc: loc.clone(),
4121                suggestions: None,
4122            })?;
4123            lower_value_to_temporary(
4124                builder,
4125                InstructionValue::UnsupportedNode {
4126                    node_type: Some("ClassDeclaration".to_string()),
4127                    original_node: serialize_statement(stmt),
4128                    loc,
4129                },
4130            )?;
4131        }
4132        Statement::ImportDeclaration(_)
4133        | Statement::ExportNamedDeclaration(_)
4134        | Statement::ExportDefaultDeclaration(_)
4135        | Statement::ExportAllDeclaration(_) => {
4136            let (loc, node_type_name) = match stmt {
4137                Statement::ImportDeclaration(s) => {
4138                    (convert_opt_loc(&s.base.loc), "ImportDeclaration")
4139                }
4140                Statement::ExportNamedDeclaration(s) => {
4141                    (convert_opt_loc(&s.base.loc), "ExportNamedDeclaration")
4142                }
4143                Statement::ExportDefaultDeclaration(s) => {
4144                    (convert_opt_loc(&s.base.loc), "ExportDefaultDeclaration")
4145                }
4146                Statement::ExportAllDeclaration(s) => {
4147                    (convert_opt_loc(&s.base.loc), "ExportAllDeclaration")
4148                }
4149                _ => unreachable!(),
4150            };
4151            builder.record_error(CompilerErrorDetail {
4152                category: ErrorCategory::Syntax,
4153                reason: "JavaScript `import` and `export` statements may only appear at the top level of a module".to_string(),
4154                description: None,
4155                loc: loc.clone(),
4156                suggestions: None,
4157            })?;
4158            lower_value_to_temporary(
4159                builder,
4160                InstructionValue::UnsupportedNode {
4161                    node_type: Some(node_type_name.to_string()),
4162                    original_node: serialize_statement(stmt),
4163                    loc,
4164                },
4165            )?;
4166        }
4167        // TypeScript/Flow declarations are type-only, skip them
4168        Statement::TSEnumDeclaration(e) => {
4169            let loc = convert_opt_loc(&e.base.loc);
4170            let original_node = serde_json::to_value(
4171                &react_compiler_ast::statements::Statement::TSEnumDeclaration(e.clone()),
4172            )
4173            .ok();
4174            lower_value_to_temporary(
4175                builder,
4176                InstructionValue::UnsupportedNode {
4177                    node_type: Some("TSEnumDeclaration".to_string()),
4178                    original_node,
4179                    loc,
4180                },
4181            )?;
4182        }
4183        Statement::EnumDeclaration(e) => {
4184            let loc = convert_opt_loc(&e.base.loc);
4185            let original_node = serde_json::to_value(
4186                &react_compiler_ast::statements::Statement::EnumDeclaration(e.clone()),
4187            )
4188            .ok();
4189            lower_value_to_temporary(
4190                builder,
4191                InstructionValue::UnsupportedNode {
4192                    node_type: Some("EnumDeclaration".to_string()),
4193                    original_node,
4194                    loc,
4195                },
4196            )?;
4197        }
4198        // TypeScript/Flow type declarations are type-only, skip them
4199        Statement::TSTypeAliasDeclaration(_)
4200        | Statement::TSInterfaceDeclaration(_)
4201        | Statement::TSModuleDeclaration(_)
4202        | Statement::TSDeclareFunction(_)
4203        | Statement::TypeAlias(_)
4204        | Statement::OpaqueType(_)
4205        | Statement::InterfaceDeclaration(_)
4206        | Statement::DeclareVariable(_)
4207        | Statement::DeclareFunction(_)
4208        | Statement::DeclareClass(_)
4209        | Statement::DeclareModule(_)
4210        | Statement::DeclareModuleExports(_)
4211        | Statement::DeclareExportDeclaration(_)
4212        | Statement::DeclareExportAllDeclaration(_)
4213        | Statement::DeclareInterface(_)
4214        | Statement::DeclareTypeAlias(_)
4215        | Statement::DeclareOpaqueType(_) => {}
4216        // The TS reference can only reach its equivalent default case via
4217        // assertExhaustive (Babel's closed Statement type), so it crashes;
4218        // here unmodeled syntax is reachable by construction and degrades
4219        // like the other unsupported-statement arms instead.
4220        Statement::Unknown(unknown) => {
4221            let loc = convert_opt_loc(&unknown.base().loc);
4222            let node_type = unknown.node_type().to_string();
4223            builder.record_error(CompilerErrorDetail {
4224                category: ErrorCategory::UnsupportedSyntax,
4225                reason: format!("Unsupported statement kind '{node_type}'"),
4226                description: None,
4227                loc: loc.clone(),
4228                suggestions: None,
4229            })?;
4230            lower_value_to_temporary(
4231                builder,
4232                InstructionValue::UnsupportedNode {
4233                    node_type: Some(node_type),
4234                    original_node: Some(unknown.raw().clone()),
4235                    loc,
4236                },
4237            )?;
4238        }
4239    }
4240    Ok(())
4241}
4242
4243// =============================================================================
4244// lower() entry point
4245// =============================================================================
4246
4247enum FunctionBody<'a> {
4248    Block(&'a react_compiler_ast::statements::BlockStatement),
4249    Expression(&'a react_compiler_ast::expressions::Expression),
4250}
4251
4252/// Main entry point: lower a function AST node into HIR.
4253///
4254/// Receives a `FunctionNode` (discovered by the entrypoint) and lowers it to HIR.
4255/// The `id` parameter provides the function name (which may come from the variable
4256/// declarator rather than the function node itself, e.g. `const Foo = () => {}`).
4257pub fn lower(
4258    func: &FunctionNode<'_>,
4259    _id: Option<&str>,
4260    scope_info: &ScopeInfo,
4261    env: &mut Environment,
4262) -> Result<HirFunction, CompilerError> {
4263    // Extract params, body, generator, is_async, loc, scope_id, and the AST function's own id
4264    // Note: `id` param may include inferred names (e.g., from `const Foo = () => {}`),
4265    // but the HIR function's `id` field should only include the function's own AST id
4266    // (FunctionDeclaration.id or FunctionExpression.id, NOT arrow functions).
4267    let (params, body, generator, is_async, loc, start, end, ast_id) = match func {
4268        FunctionNode::FunctionDeclaration(decl) => (
4269            &decl.params[..],
4270            FunctionBody::Block(&decl.body),
4271            decl.generator,
4272            decl.is_async,
4273            convert_opt_loc(&decl.base.loc),
4274            decl.base.start.unwrap_or(0),
4275            decl.base.end.unwrap_or(0),
4276            decl.id.as_ref().map(|id| id.name.as_str()),
4277        ),
4278        FunctionNode::FunctionExpression(expr) => (
4279            &expr.params[..],
4280            FunctionBody::Block(&expr.body),
4281            expr.generator,
4282            expr.is_async,
4283            convert_opt_loc(&expr.base.loc),
4284            expr.base.start.unwrap_or(0),
4285            expr.base.end.unwrap_or(0),
4286            expr.id.as_ref().map(|id| id.name.as_str()),
4287        ),
4288        FunctionNode::ArrowFunctionExpression(arrow) => {
4289            let body = match arrow.body.as_ref() {
4290                react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
4291                    FunctionBody::Block(block)
4292                }
4293                react_compiler_ast::expressions::ArrowFunctionBody::Expression(expr) => {
4294                    FunctionBody::Expression(expr)
4295                }
4296            };
4297            (
4298                &arrow.params[..],
4299                body,
4300                arrow.generator,
4301                arrow.is_async,
4302                convert_opt_loc(&arrow.base.loc),
4303                arrow.base.start.unwrap_or(0),
4304                arrow.base.end.unwrap_or(0),
4305                None, // Arrow functions never have an AST id
4306            )
4307        }
4308    };
4309
4310    let scope_id = scope_info
4311        .resolve_scope_for_node(func.node_id())
4312        .unwrap_or(scope_info.program_scope);
4313
4314    validate_ts_this_parameters_in_function_range(scope_info, start, end)?;
4315
4316    // Build identifier location index from the AST (replaces serialized referenceLocs/jsxReferencePositions)
4317    let identifier_locs = build_identifier_loc_index(func, scope_info);
4318
4319    // Pre-compute context identifiers: variables captured across function boundaries
4320    let context_identifiers = find_context_identifiers(func, scope_info, env, &identifier_locs)?;
4321
4322    // For top-level functions, context is empty (no captured refs)
4323    let context_map: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> =
4324        IndexMap::new();
4325
4326    let (hir_func, _used_names, _child_bindings) = lower_inner(
4327        params,
4328        body,
4329        ast_id,
4330        generator,
4331        is_async,
4332        loc,
4333        scope_info,
4334        env,
4335        None, // no pre-existing bindings for top-level
4336        None, // no pre-existing used_names for top-level
4337        context_map,
4338        scope_id,
4339        scope_id, // component_scope = function_scope for top-level
4340        &context_identifiers,
4341        true, // is_top_level
4342        &identifier_locs,
4343    )?;
4344
4345    Ok(hir_func)
4346}
4347
4348// =============================================================================
4349// Stubs for future milestones
4350// =============================================================================
4351
4352/// Result of resolving an identifier for assignment.
4353enum IdentifierForAssignment {
4354    /// A local place (identifier binding)
4355    Place(Place),
4356    /// A global variable (non-local, non-import)
4357    Global { name: String },
4358}
4359
4360/// Resolve an identifier for use as an assignment target.
4361/// Returns None if the binding could not be found (error recorded).
4362fn lower_identifier_for_assignment(
4363    builder: &mut HirBuilder,
4364    loc: Option<SourceLocation>,
4365    ident_loc: Option<SourceLocation>,
4366    kind: InstructionKind,
4367    name: &str,
4368    start: u32,
4369    node_id: Option<u32>,
4370) -> Result<Option<IdentifierForAssignment>, CompilerError> {
4371    let mut binding = builder.resolve_identifier(name, start, ident_loc.clone(), node_id)?;
4372    if !matches!(binding, VariableBinding::Identifier { .. }) && kind != InstructionKind::Reassign {
4373        if let Some((binding_id, binding_data)) = builder
4374            .scope_info()
4375            .find_binding_id_in_descendants(name, builder.function_scope())
4376        {
4377            let bk = crate::convert_binding_kind(&binding_data.kind);
4378            let identifier =
4379                builder.resolve_binding_with_loc(name, binding_id, ident_loc.clone())?;
4380            binding = VariableBinding::Identifier {
4381                identifier,
4382                binding_kind: bk,
4383            };
4384        }
4385    }
4386    match binding {
4387        VariableBinding::Identifier {
4388            identifier,
4389            binding_kind,
4390            ..
4391        } => {
4392            // Set the identifier's loc from the declaration site (not for reassignments,
4393            // which should keep the original declaration loc)
4394            if kind != InstructionKind::Reassign {
4395                builder.set_identifier_declaration_loc(identifier, &ident_loc);
4396            }
4397            if binding_kind == BindingKind::Const && kind == InstructionKind::Reassign {
4398                builder.record_error(CompilerErrorDetail {
4399                    reason: "Cannot reassign a `const` variable".to_string(),
4400                    category: ErrorCategory::Syntax,
4401                    loc: loc.clone(),
4402                    description: Some(format!("`{}` is declared as const", name)),
4403                    suggestions: None,
4404                })?;
4405                return Ok(None);
4406            }
4407            Ok(Some(IdentifierForAssignment::Place(Place {
4408                identifier,
4409                effect: Effect::Unknown,
4410                reactive: false,
4411                loc,
4412            })))
4413        }
4414        VariableBinding::Global { name: gname } => {
4415            if kind == InstructionKind::Reassign {
4416                Ok(Some(IdentifierForAssignment::Global { name: gname }))
4417            } else {
4418                builder.record_error(CompilerErrorDetail {
4419                    reason: "Could not find binding for declaration".to_string(),
4420                    category: ErrorCategory::Invariant,
4421                    loc,
4422                    description: None,
4423                    suggestions: None,
4424                })?;
4425                Ok(None)
4426            }
4427        }
4428        _ => {
4429            // Import bindings can't be assigned to
4430            if kind == InstructionKind::Reassign {
4431                Ok(Some(IdentifierForAssignment::Global {
4432                    name: name.to_string(),
4433                }))
4434            } else {
4435                builder.record_error(CompilerErrorDetail {
4436                    reason: "Could not find binding for declaration".to_string(),
4437                    category: ErrorCategory::Invariant,
4438                    loc,
4439                    description: None,
4440                    suggestions: None,
4441                })?;
4442                Ok(None)
4443            }
4444        }
4445    }
4446}
4447
4448fn lower_assignment(
4449    builder: &mut HirBuilder,
4450    loc: Option<SourceLocation>,
4451    kind: InstructionKind,
4452    target: &react_compiler_ast::patterns::PatternLike,
4453    value: Place,
4454    assignment_style: AssignmentStyle,
4455) -> Result<Option<Place>, CompilerError> {
4456    use react_compiler_ast::patterns::PatternLike;
4457
4458    match target {
4459        PatternLike::Identifier(id) => {
4460            let id_loc = convert_opt_loc(&id.base.loc);
4461            let result = lower_identifier_for_assignment(
4462                builder,
4463                loc.clone(),
4464                id_loc,
4465                kind,
4466                &id.name,
4467                id.base.start.unwrap_or(0),
4468                id.base.node_id,
4469            )?;
4470            match result {
4471                None => {
4472                    // Error already recorded
4473                    return Ok(None);
4474                }
4475                Some(IdentifierForAssignment::Global { name }) => {
4476                    let temp = lower_value_to_temporary(
4477                        builder,
4478                        InstructionValue::StoreGlobal { name, value, loc },
4479                    )?;
4480                    return Ok(Some(temp));
4481                }
4482                Some(IdentifierForAssignment::Place(place)) => {
4483                    let start = id.base.start.unwrap_or(0);
4484                    if builder.is_context_identifier(&id.name, start, id.base.node_id) {
4485                        // Check if the binding is hoisted before flagging const reassignment
4486                        let is_hoisted = builder
4487                            .scope_info()
4488                            .resolve_reference_for_node(id.base.node_id)
4489                            .map(|b| builder.environment().is_hoisted_identifier(b.id.0))
4490                            .unwrap_or(false);
4491                        if kind == InstructionKind::Const && !is_hoisted {
4492                            builder.record_error(CompilerErrorDetail {
4493                                reason: "Expected `const` declaration not to be reassigned"
4494                                    .to_string(),
4495                                category: ErrorCategory::Syntax,
4496                                loc: loc.clone(),
4497                                suggestions: None,
4498                                description: None,
4499                            })?;
4500                        }
4501                        if kind != InstructionKind::Const
4502                            && kind != InstructionKind::Reassign
4503                            && kind != InstructionKind::Let
4504                            && kind != InstructionKind::Function
4505                        {
4506                            builder.record_error(CompilerErrorDetail {
4507                                reason: "Unexpected context variable kind".to_string(),
4508                                category: ErrorCategory::Syntax,
4509                                loc: loc.clone(),
4510                                suggestions: None,
4511                                description: None,
4512                            })?;
4513                            let temp = lower_value_to_temporary(
4514                                builder,
4515                                InstructionValue::UnsupportedNode {
4516                                    node_type: Some("Identifier".to_string()),
4517                                    original_node: serialize_pattern(target),
4518                                    loc,
4519                                },
4520                            )?;
4521                            return Ok(Some(temp));
4522                        }
4523                        let temp = lower_value_to_temporary(
4524                            builder,
4525                            InstructionValue::StoreContext {
4526                                lvalue: LValue { place, kind },
4527                                value,
4528                                loc,
4529                            },
4530                        )?;
4531                        return Ok(Some(temp));
4532                    } else {
4533                        let type_annotation = extract_type_annotation_name(&id.type_annotation);
4534                        let temp = lower_value_to_temporary(
4535                            builder,
4536                            InstructionValue::StoreLocal {
4537                                lvalue: LValue { place, kind },
4538                                value,
4539                                type_annotation,
4540                                loc,
4541                            },
4542                        )?;
4543                        return Ok(Some(temp));
4544                    }
4545                }
4546            }
4547        }
4548
4549        PatternLike::MemberExpression(member) => {
4550            // MemberExpression may only appear in an assignment expression (Reassign)
4551            if kind != InstructionKind::Reassign {
4552                builder.record_error(CompilerErrorDetail {
4553                    category: ErrorCategory::Invariant,
4554                    reason: "MemberExpression may only appear in an assignment expression"
4555                        .to_string(),
4556                    description: None,
4557                    loc: loc.clone(),
4558                    suggestions: None,
4559                })?;
4560                return Ok(None);
4561            }
4562            let object = lower_expression_to_temporary(builder, &member.object)?;
4563            let temp = if !member.computed
4564                || matches!(
4565                    &*member.property,
4566                    react_compiler_ast::expressions::Expression::NumericLiteral(_)
4567                ) {
4568                match &*member.property {
4569                    react_compiler_ast::expressions::Expression::Identifier(prop_id) => {
4570                        lower_value_to_temporary(
4571                            builder,
4572                            InstructionValue::PropertyStore {
4573                                object,
4574                                property: PropertyLiteral::String(prop_id.name.clone()),
4575                                value,
4576                                loc,
4577                            },
4578                        )?
4579                    }
4580                    react_compiler_ast::expressions::Expression::NumericLiteral(num) => {
4581                        lower_value_to_temporary(
4582                            builder,
4583                            InstructionValue::PropertyStore {
4584                                object,
4585                                property: PropertyLiteral::Number(FloatValue::new(
4586                                    num.precise_value(),
4587                                )),
4588                                value,
4589                                loc,
4590                            },
4591                        )?
4592                    }
4593                    _ => {
4594                        builder.record_error(CompilerErrorDetail {
4595                            reason: format!("(BuildHIR::lowerAssignment) Handle {} properties in MemberExpression", expression_type_name(&member.property)),
4596                            category: ErrorCategory::Todo,
4597                            loc: expression_loc(&member.property),
4598                            description: None,
4599                            suggestions: None,
4600                        })?;
4601                        lower_value_to_temporary(
4602                            builder,
4603                            InstructionValue::UnsupportedNode {
4604                                node_type: Some("MemberExpression".to_string()),
4605                                original_node: serialize_pattern(target),
4606                                loc,
4607                            },
4608                        )?
4609                    }
4610                }
4611            } else {
4612                if matches!(
4613                    &*member.property,
4614                    react_compiler_ast::expressions::Expression::PrivateName(_)
4615                ) {
4616                    builder.record_error(CompilerErrorDetail {
4617                        reason: "(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property".to_string(),
4618                        category: ErrorCategory::Todo,
4619                        loc: expression_loc(&member.property),
4620                        description: None,
4621                        suggestions: None,
4622                    })?;
4623                    lower_value_to_temporary(
4624                        builder,
4625                        InstructionValue::UnsupportedNode {
4626                            node_type: Some("MemberExpression".to_string()),
4627                            original_node: serialize_pattern(target),
4628                            loc,
4629                        },
4630                    )?
4631                } else {
4632                    let property_place = lower_expression_to_temporary(builder, &member.property)?;
4633                    lower_value_to_temporary(
4634                        builder,
4635                        InstructionValue::ComputedStore {
4636                            object,
4637                            property: property_place,
4638                            value,
4639                            loc,
4640                        },
4641                    )?
4642                }
4643            };
4644            Ok(Some(temp))
4645        }
4646
4647        PatternLike::ArrayPattern(pattern) => {
4648            let mut items: Vec<ArrayPatternElement> = Vec::new();
4649            let mut followups: Vec<(Place, &PatternLike)> = Vec::new();
4650
4651            // Compute forceTemporaries: when kind is Reassign and any element is
4652            // non-identifier, a context variable, or a non-local binding
4653            let force_temporaries = if kind == InstructionKind::Reassign {
4654                let mut found = false;
4655                for elem in &pattern.elements {
4656                    match elem {
4657                        Some(PatternLike::Identifier(id)) => {
4658                            let start = id.base.start.unwrap_or(0);
4659                            if builder.is_context_identifier(&id.name, start, id.base.node_id) {
4660                                found = true;
4661                                break;
4662                            }
4663                            let ident_loc = convert_opt_loc(&id.base.loc);
4664                            match builder.resolve_identifier(
4665                                &id.name,
4666                                start,
4667                                ident_loc,
4668                                id.base.node_id,
4669                            )? {
4670                                VariableBinding::Identifier { .. } => {}
4671                                _ => {
4672                                    found = true;
4673                                    break;
4674                                }
4675                            }
4676                        }
4677                        _ => {
4678                            // Non-identifier elements (including None/holes and RestElements)
4679                            // trigger forceTemporaries, matching TS where `!element.isIdentifier()`
4680                            // returns true for null elements
4681                            found = true;
4682                            break;
4683                        }
4684                    }
4685                }
4686                found
4687            } else {
4688                false
4689            };
4690
4691            for element in &pattern.elements {
4692                match element {
4693                    None => {
4694                        items.push(ArrayPatternElement::Hole);
4695                    }
4696                    Some(PatternLike::RestElement(rest)) => {
4697                        match &*rest.argument {
4698                            PatternLike::Identifier(id) => {
4699                                let start = id.base.start.unwrap_or(0);
4700                                let is_context =
4701                                    builder.is_context_identifier(&id.name, start, id.base.node_id);
4702                                let can_use_direct = !force_temporaries
4703                                    && (matches!(assignment_style, AssignmentStyle::Assignment)
4704                                        || !is_context);
4705                                if can_use_direct {
4706                                    match lower_identifier_for_assignment(
4707                                        builder,
4708                                        convert_opt_loc(&rest.base.loc),
4709                                        convert_opt_loc(&id.base.loc),
4710                                        kind,
4711                                        &id.name,
4712                                        start,
4713                                        id.base.node_id,
4714                                    )? {
4715                                        Some(IdentifierForAssignment::Place(place)) => {
4716                                            items.push(ArrayPatternElement::Spread(
4717                                                SpreadPattern { place },
4718                                            ));
4719                                        }
4720                                        Some(IdentifierForAssignment::Global { .. }) => {
4721                                            let temp = build_temporary_place(
4722                                                builder,
4723                                                convert_opt_loc(&rest.base.loc),
4724                                            );
4725                                            promote_temporary(builder, temp.identifier);
4726                                            items.push(ArrayPatternElement::Spread(
4727                                                SpreadPattern {
4728                                                    place: temp.clone(),
4729                                                },
4730                                            ));
4731                                            followups.push((temp, &rest.argument));
4732                                        }
4733                                        None => {
4734                                            // Error already recorded
4735                                        }
4736                                    }
4737                                } else {
4738                                    let temp = build_temporary_place(
4739                                        builder,
4740                                        convert_opt_loc(&rest.base.loc),
4741                                    );
4742                                    promote_temporary(builder, temp.identifier);
4743                                    items.push(ArrayPatternElement::Spread(SpreadPattern {
4744                                        place: temp.clone(),
4745                                    }));
4746                                    followups.push((temp, &rest.argument));
4747                                }
4748                            }
4749                            _ => {
4750                                let temp =
4751                                    build_temporary_place(builder, convert_opt_loc(&rest.base.loc));
4752                                promote_temporary(builder, temp.identifier);
4753                                items.push(ArrayPatternElement::Spread(SpreadPattern {
4754                                    place: temp.clone(),
4755                                }));
4756                                followups.push((temp, &rest.argument));
4757                            }
4758                        }
4759                    }
4760                    Some(PatternLike::Identifier(id)) => {
4761                        let start = id.base.start.unwrap_or(0);
4762                        let is_context =
4763                            builder.is_context_identifier(&id.name, start, id.base.node_id);
4764                        let can_use_direct = !force_temporaries
4765                            && (matches!(assignment_style, AssignmentStyle::Assignment)
4766                                || !is_context);
4767                        if can_use_direct {
4768                            match lower_identifier_for_assignment(
4769                                builder,
4770                                convert_opt_loc(&id.base.loc),
4771                                convert_opt_loc(&id.base.loc),
4772                                kind,
4773                                &id.name,
4774                                start,
4775                                id.base.node_id,
4776                            )? {
4777                                Some(IdentifierForAssignment::Place(place)) => {
4778                                    items.push(ArrayPatternElement::Place(place));
4779                                }
4780                                Some(IdentifierForAssignment::Global { .. }) => {
4781                                    let temp = build_temporary_place(
4782                                        builder,
4783                                        convert_opt_loc(&id.base.loc),
4784                                    );
4785                                    promote_temporary(builder, temp.identifier);
4786                                    items.push(ArrayPatternElement::Place(temp.clone()));
4787                                    followups.push((temp, element.as_ref().unwrap()));
4788                                }
4789                                None => {
4790                                    items.push(ArrayPatternElement::Hole);
4791                                }
4792                            }
4793                        } else {
4794                            // Context variable or force_temporaries: use promoted temporary
4795                            let temp =
4796                                build_temporary_place(builder, convert_opt_loc(&id.base.loc));
4797                            promote_temporary(builder, temp.identifier);
4798                            items.push(ArrayPatternElement::Place(temp.clone()));
4799                            followups.push((temp, element.as_ref().unwrap()));
4800                        }
4801                    }
4802                    Some(other) => {
4803                        // Nested pattern: use temporary + followup
4804                        let elem_loc = pattern_like_hir_loc(other);
4805                        let temp = build_temporary_place(builder, elem_loc);
4806                        promote_temporary(builder, temp.identifier);
4807                        items.push(ArrayPatternElement::Place(temp.clone()));
4808                        followups.push((temp, other));
4809                    }
4810                }
4811            }
4812
4813            let temporary = lower_value_to_temporary(
4814                builder,
4815                InstructionValue::Destructure {
4816                    lvalue: LValuePattern {
4817                        pattern: Pattern::Array(ArrayPattern {
4818                            items,
4819                            loc: convert_opt_loc(&pattern.base.loc),
4820                        }),
4821                        kind,
4822                    },
4823                    value: value.clone(),
4824                    loc: loc.clone(),
4825                },
4826            )?;
4827
4828            for (place, path) in followups {
4829                let followup_loc = pattern_like_hir_loc(path).or(loc.clone());
4830                lower_assignment(builder, followup_loc, kind, path, place, assignment_style)?;
4831            }
4832            Ok(Some(temporary))
4833        }
4834
4835        PatternLike::ObjectPattern(pattern) => {
4836            let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new();
4837            let mut followups: Vec<(Place, &PatternLike)> = Vec::new();
4838
4839            // Compute forceTemporaries for ObjectPattern
4840            let force_temporaries = if kind == InstructionKind::Reassign {
4841                use react_compiler_ast::patterns::ObjectPatternProperty;
4842                let mut found = false;
4843                for prop in &pattern.properties {
4844                    match prop {
4845                        ObjectPatternProperty::RestElement(_) => {
4846                            found = true;
4847                            break;
4848                        }
4849                        ObjectPatternProperty::ObjectProperty(obj_prop) => match &*obj_prop.value {
4850                            PatternLike::Identifier(id) => {
4851                                let start = id.base.start.unwrap_or(0);
4852                                let ident_loc = convert_opt_loc(&id.base.loc);
4853                                match builder.resolve_identifier(
4854                                    &id.name,
4855                                    start,
4856                                    ident_loc,
4857                                    id.base.node_id,
4858                                )? {
4859                                    VariableBinding::Identifier { .. } => {}
4860                                    _ => {
4861                                        found = true;
4862                                        break;
4863                                    }
4864                                }
4865                            }
4866                            _ => {
4867                                found = true;
4868                                break;
4869                            }
4870                        },
4871                    }
4872                }
4873                found
4874            } else {
4875                false
4876            };
4877
4878            for prop in &pattern.properties {
4879                match prop {
4880                    react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => {
4881                        match &*rest.argument {
4882                            PatternLike::Identifier(id) => {
4883                                let start = id.base.start.unwrap_or(0);
4884                                let is_context =
4885                                    builder.is_context_identifier(&id.name, start, id.base.node_id);
4886                                let can_use_direct = !force_temporaries
4887                                    && (matches!(assignment_style, AssignmentStyle::Assignment)
4888                                        || !is_context);
4889                                if can_use_direct {
4890                                    match lower_identifier_for_assignment(
4891                                        builder,
4892                                        convert_opt_loc(&rest.base.loc),
4893                                        convert_opt_loc(&id.base.loc),
4894                                        kind,
4895                                        &id.name,
4896                                        start,
4897                                        id.base.node_id,
4898                                    )? {
4899                                        Some(IdentifierForAssignment::Place(place)) => {
4900                                            properties.push(ObjectPropertyOrSpread::Spread(
4901                                                SpreadPattern { place },
4902                                            ));
4903                                        }
4904                                        Some(IdentifierForAssignment::Global { .. }) => {
4905                                            builder.record_error(CompilerErrorDetail {
4906                                                reason: "Expected reassignment of globals to enable forceTemporaries".to_string(),
4907                                                category: ErrorCategory::Todo,
4908                                                loc: convert_opt_loc(&rest.base.loc),
4909                                                description: None,
4910                                                suggestions: None,
4911                                            })?;
4912                                        }
4913                                        None => {}
4914                                    }
4915                                } else {
4916                                    let temp = build_temporary_place(
4917                                        builder,
4918                                        convert_opt_loc(&rest.base.loc),
4919                                    );
4920                                    promote_temporary(builder, temp.identifier);
4921                                    properties.push(ObjectPropertyOrSpread::Spread(
4922                                        SpreadPattern {
4923                                            place: temp.clone(),
4924                                        },
4925                                    ));
4926                                    followups.push((temp, &rest.argument));
4927                                }
4928                            }
4929                            _ => {
4930                                builder.record_error(CompilerErrorDetail {
4931                                    reason: format!("(BuildHIR::lowerAssignment) Handle {} rest element in ObjectPattern",
4932                                        match &*rest.argument {
4933                                            PatternLike::ObjectPattern(_) => "ObjectPattern",
4934                                            PatternLike::ArrayPattern(_) => "ArrayPattern",
4935                                            PatternLike::AssignmentPattern(_) => "AssignmentPattern",
4936                                            PatternLike::MemberExpression(_) => "MemberExpression",
4937                                            _ => "unknown",
4938                                        }),
4939                                    category: ErrorCategory::Todo,
4940                                    loc: convert_opt_loc(&rest.base.loc),
4941                                    description: None,
4942                                    suggestions: None,
4943                                })?;
4944                            }
4945                        }
4946                    }
4947                    react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(
4948                        obj_prop,
4949                    ) => {
4950                        if obj_prop.computed {
4951                            builder.record_error(CompilerErrorDetail {
4952                                reason: "(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern".to_string(),
4953                                category: ErrorCategory::Todo,
4954                                loc: convert_opt_loc(&obj_prop.base.loc),
4955                                description: None,
4956                                suggestions: None,
4957                            })?;
4958                            continue;
4959                        }
4960
4961                        let key = match lower_object_property_key(builder, &obj_prop.key, false)? {
4962                            Some(k) => k,
4963                            None => continue,
4964                        };
4965
4966                        match &*obj_prop.value {
4967                            PatternLike::Identifier(id) => {
4968                                let start = id.base.start.unwrap_or(0);
4969                                let is_context =
4970                                    builder.is_context_identifier(&id.name, start, id.base.node_id);
4971                                let can_use_direct = !force_temporaries
4972                                    && (matches!(assignment_style, AssignmentStyle::Assignment)
4973                                        || !is_context);
4974                                if can_use_direct {
4975                                    match lower_identifier_for_assignment(
4976                                        builder,
4977                                        convert_opt_loc(&id.base.loc),
4978                                        convert_opt_loc(&id.base.loc),
4979                                        kind,
4980                                        &id.name,
4981                                        start,
4982                                        id.base.node_id,
4983                                    )? {
4984                                        Some(IdentifierForAssignment::Place(place)) => {
4985                                            properties.push(ObjectPropertyOrSpread::Property(
4986                                                ObjectProperty {
4987                                                    key,
4988                                                    property_type: ObjectPropertyType::Property,
4989                                                    place,
4990                                                },
4991                                            ));
4992                                        }
4993                                        Some(IdentifierForAssignment::Global { .. }) => {
4994                                            builder.record_error(CompilerErrorDetail {
4995                                                reason: "Expected reassignment of globals to enable forceTemporaries".to_string(),
4996                                                category: ErrorCategory::Todo,
4997                                                loc: convert_opt_loc(&id.base.loc),
4998                                                description: None,
4999                                                suggestions: None,
5000                                            })?;
5001                                        }
5002                                        None => {
5003                                            continue;
5004                                        }
5005                                    }
5006                                } else {
5007                                    // Context variable or force_temporaries: use promoted temporary
5008                                    let temp = build_temporary_place(
5009                                        builder,
5010                                        convert_opt_loc(&id.base.loc),
5011                                    );
5012                                    promote_temporary(builder, temp.identifier);
5013                                    properties.push(ObjectPropertyOrSpread::Property(
5014                                        ObjectProperty {
5015                                            key,
5016                                            property_type: ObjectPropertyType::Property,
5017                                            place: temp.clone(),
5018                                        },
5019                                    ));
5020                                    followups.push((temp, &*obj_prop.value));
5021                                }
5022                            }
5023                            other => {
5024                                // Nested pattern: use temporary + followup
5025                                let elem_loc = pattern_like_hir_loc(other);
5026                                let temp = build_temporary_place(builder, elem_loc);
5027                                promote_temporary(builder, temp.identifier);
5028                                properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {
5029                                    key,
5030                                    property_type: ObjectPropertyType::Property,
5031                                    place: temp.clone(),
5032                                }));
5033                                followups.push((temp, other));
5034                            }
5035                        }
5036                    }
5037                }
5038            }
5039
5040            let temporary = lower_value_to_temporary(
5041                builder,
5042                InstructionValue::Destructure {
5043                    lvalue: LValuePattern {
5044                        pattern: Pattern::Object(ObjectPattern {
5045                            properties,
5046                            loc: convert_opt_loc(&pattern.base.loc),
5047                        }),
5048                        kind,
5049                    },
5050                    value: value.clone(),
5051                    loc: loc.clone(),
5052                },
5053            )?;
5054
5055            for (place, path) in followups {
5056                let followup_loc = pattern_like_hir_loc(path).or(loc.clone());
5057                lower_assignment(builder, followup_loc, kind, path, place, assignment_style)?;
5058            }
5059            Ok(Some(temporary))
5060        }
5061
5062        PatternLike::AssignmentPattern(pattern) => {
5063            // Default value: if value === undefined, use default, else use value
5064            let pat_loc = convert_opt_loc(&pattern.base.loc);
5065
5066            let temp = build_temporary_place(builder, pat_loc.clone());
5067
5068            let test_block = builder.reserve(BlockKind::Value);
5069            let continuation_block = builder.reserve(builder.current_block_kind());
5070
5071            // Consequent: use default value
5072            let consequent = builder.try_enter(BlockKind::Value, |builder, _| {
5073                let default_value = lower_reorderable_expression(builder, &pattern.right)?;
5074                lower_value_to_temporary(
5075                    builder,
5076                    InstructionValue::StoreLocal {
5077                        lvalue: LValue {
5078                            place: temp.clone(),
5079                            kind: InstructionKind::Const,
5080                        },
5081                        value: default_value,
5082                        type_annotation: None,
5083                        loc: pat_loc.clone(),
5084                    },
5085                )?;
5086                Ok(Terminal::Goto {
5087                    block: continuation_block.id,
5088                    variant: GotoVariant::Break,
5089                    id: EvaluationOrder(0),
5090                    loc: pat_loc.clone(),
5091                })
5092            });
5093
5094            // Alternate: use the original value
5095            let alternate = builder.try_enter(BlockKind::Value, |builder, _| {
5096                lower_value_to_temporary(
5097                    builder,
5098                    InstructionValue::StoreLocal {
5099                        lvalue: LValue {
5100                            place: temp.clone(),
5101                            kind: InstructionKind::Const,
5102                        },
5103                        value: value.clone(),
5104                        type_annotation: None,
5105                        loc: pat_loc.clone(),
5106                    },
5107                )?;
5108                Ok(Terminal::Goto {
5109                    block: continuation_block.id,
5110                    variant: GotoVariant::Break,
5111                    id: EvaluationOrder(0),
5112                    loc: pat_loc.clone(),
5113                })
5114            });
5115
5116            // Ternary terminal
5117            builder.terminate_with_continuation(
5118                Terminal::Ternary {
5119                    test: test_block.id,
5120                    fallthrough: continuation_block.id,
5121                    id: EvaluationOrder(0),
5122                    loc: pat_loc.clone(),
5123                },
5124                test_block,
5125            );
5126
5127            // In test block: check if value === undefined
5128            let undef = lower_value_to_temporary(
5129                builder,
5130                InstructionValue::Primitive {
5131                    value: PrimitiveValue::Undefined,
5132                    loc: pat_loc.clone(),
5133                },
5134            )?;
5135            let test = lower_value_to_temporary(
5136                builder,
5137                InstructionValue::BinaryExpression {
5138                    left: value,
5139                    operator: BinaryOperator::StrictEqual,
5140                    right: undef,
5141                    loc: pat_loc.clone(),
5142                },
5143            )?;
5144            builder.terminate_with_continuation(
5145                Terminal::Branch {
5146                    test,
5147                    consequent: consequent?,
5148                    alternate: alternate?,
5149                    fallthrough: continuation_block.id,
5150                    id: EvaluationOrder(0),
5151                    loc: pat_loc.clone(),
5152                },
5153                continuation_block,
5154            );
5155
5156            // Recursively assign the resolved value to the left pattern
5157            Ok(lower_assignment(
5158                builder,
5159                pat_loc,
5160                kind,
5161                &pattern.left,
5162                temp,
5163                assignment_style,
5164            )?)
5165        }
5166
5167        PatternLike::RestElement(rest) => {
5168            // Delegate to the argument pattern
5169            Ok(lower_assignment(
5170                builder,
5171                loc,
5172                kind,
5173                &rest.argument,
5174                value,
5175                assignment_style,
5176            )?)
5177        }
5178
5179        // TS assignment-target wrappers (e.g. `(x as T) = ...`) and the Flow
5180        // analogue `TypeCastExpression`. For destructuring targets the
5181        // TS-faithful Todo is recorded once in `find_context_identifiers`, so
5182        // it is not recorded again here. `for (... of ...)` heads also reach
5183        // this arm directly without that Todo; emitted code matches the TS
5184        // reference there, but the recorded diagnostics do not yet.
5185        PatternLike::TSAsExpression(_)
5186        | PatternLike::TSSatisfiesExpression(_)
5187        | PatternLike::TSNonNullExpression(_)
5188        | PatternLike::TSTypeAssertion(_)
5189        | PatternLike::TypeCastExpression(_) => Ok(None),
5190    }
5191}
5192
5193/// Helper to extract HIR loc from a PatternLike (converts AST loc)
5194fn pattern_like_hir_loc(pat: &react_compiler_ast::patterns::PatternLike) -> Option<SourceLocation> {
5195    convert_opt_loc(&pattern_like_loc(pat))
5196}
5197
5198fn lower_optional_member_expression(
5199    builder: &mut HirBuilder,
5200    expr: &react_compiler_ast::expressions::OptionalMemberExpression,
5201) -> Result<InstructionValue, CompilerError> {
5202    let place = lower_optional_member_expression_impl(builder, expr, None)?.1;
5203    Ok(InstructionValue::LoadLocal {
5204        loc: place.loc.clone(),
5205        place,
5206    })
5207}
5208
5209/// Returns (object, value_place) pair.
5210/// The `value_place` is stored into a temporary; we also return it as an InstructionValue
5211/// via LoadLocal for the top-level call.
5212fn lower_optional_member_expression_impl(
5213    builder: &mut HirBuilder,
5214    expr: &react_compiler_ast::expressions::OptionalMemberExpression,
5215    parent_alternate: Option<BlockId>,
5216) -> Result<(Place, Place), CompilerError> {
5217    use react_compiler_ast::expressions::Expression;
5218    let optional = expr.optional;
5219    let loc = convert_opt_loc(&expr.base.loc);
5220    let place = build_temporary_place(builder, loc.clone());
5221    let continuation_block = builder.reserve(builder.current_block_kind());
5222    let continuation_id = continuation_block.id;
5223    let consequent = builder.reserve(BlockKind::Value);
5224
5225    // Block to evaluate if the callee is null/undefined — sets result to undefined.
5226    // Only create an alternate when first entering an optional subtree.
5227    let alternate = if let Some(parent_alt) = parent_alternate {
5228        Ok(parent_alt)
5229    } else {
5230        builder.try_enter(BlockKind::Value, |builder, _block_id| {
5231            let temp = lower_value_to_temporary(
5232                builder,
5233                InstructionValue::Primitive {
5234                    value: PrimitiveValue::Undefined,
5235                    loc: loc.clone(),
5236                },
5237            )?;
5238            lower_value_to_temporary(
5239                builder,
5240                InstructionValue::StoreLocal {
5241                    lvalue: LValue {
5242                        kind: InstructionKind::Const,
5243                        place: place.clone(),
5244                    },
5245                    value: temp,
5246                    type_annotation: None,
5247                    loc: loc.clone(),
5248                },
5249            )?;
5250            Ok(Terminal::Goto {
5251                block: continuation_id,
5252                variant: GotoVariant::Break,
5253                id: EvaluationOrder(0),
5254                loc: loc.clone(),
5255            })
5256        })
5257    }?;
5258
5259    let mut object: Option<Place> = None;
5260    let test_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
5261        match expr.object.as_ref() {
5262            Expression::OptionalMemberExpression(opt_member) => {
5263                let (_obj, value) =
5264                    lower_optional_member_expression_impl(builder, opt_member, Some(alternate))?;
5265                object = Some(value);
5266            }
5267            Expression::OptionalCallExpression(opt_call) => {
5268                let value =
5269                    lower_optional_call_expression_impl(builder, opt_call, Some(alternate))?;
5270                let value_place = lower_value_to_temporary(builder, value)?;
5271                object = Some(value_place);
5272            }
5273            other => {
5274                object = Some(lower_expression_to_temporary(builder, other)?);
5275            }
5276        }
5277        let test_place = object.as_ref().unwrap().clone();
5278        Ok(Terminal::Branch {
5279            test: test_place,
5280            consequent: consequent.id,
5281            alternate,
5282            fallthrough: continuation_id,
5283            id: EvaluationOrder(0),
5284            loc: loc.clone(),
5285        })
5286    });
5287
5288    let obj = object.unwrap();
5289
5290    // Block to evaluate if the callee is non-null/undefined
5291    builder.try_enter_reserved(consequent, |builder| {
5292        let lowered = lower_member_expression_with_object(builder, expr, obj.clone())?;
5293        let temp = lower_value_to_temporary(builder, lowered.value)?;
5294        lower_value_to_temporary(
5295            builder,
5296            InstructionValue::StoreLocal {
5297                lvalue: LValue {
5298                    kind: InstructionKind::Const,
5299                    place: place.clone(),
5300                },
5301                value: temp,
5302                type_annotation: None,
5303                loc: loc.clone(),
5304            },
5305        )?;
5306        Ok(Terminal::Goto {
5307            block: continuation_id,
5308            variant: GotoVariant::Break,
5309            id: EvaluationOrder(0),
5310            loc: loc.clone(),
5311        })
5312    })?;
5313
5314    builder.terminate_with_continuation(
5315        Terminal::Optional {
5316            optional,
5317            test: test_block?,
5318            fallthrough: continuation_id,
5319            id: EvaluationOrder(0),
5320            loc: loc.clone(),
5321        },
5322        continuation_block,
5323    );
5324
5325    Ok((obj, place))
5326}
5327
5328fn lower_optional_call_expression(
5329    builder: &mut HirBuilder,
5330    expr: &react_compiler_ast::expressions::OptionalCallExpression,
5331) -> Result<InstructionValue, CompilerError> {
5332    Ok(lower_optional_call_expression_impl(builder, expr, None)?)
5333}
5334
5335fn lower_optional_call_expression_impl(
5336    builder: &mut HirBuilder,
5337    expr: &react_compiler_ast::expressions::OptionalCallExpression,
5338    parent_alternate: Option<BlockId>,
5339) -> Result<InstructionValue, CompilerError> {
5340    use react_compiler_ast::expressions::Expression;
5341    let optional = expr.optional;
5342    let loc = convert_opt_loc(&expr.base.loc);
5343    let place = build_temporary_place(builder, loc.clone());
5344    let continuation_block = builder.reserve(builder.current_block_kind());
5345    let continuation_id = continuation_block.id;
5346    let consequent = builder.reserve(BlockKind::Value);
5347
5348    // Block to evaluate if the callee is null/undefined
5349    let alternate = if let Some(parent_alt) = parent_alternate {
5350        Ok(parent_alt)
5351    } else {
5352        builder.try_enter(BlockKind::Value, |builder, _block_id| {
5353            let temp = lower_value_to_temporary(
5354                builder,
5355                InstructionValue::Primitive {
5356                    value: PrimitiveValue::Undefined,
5357                    loc: loc.clone(),
5358                },
5359            )?;
5360            lower_value_to_temporary(
5361                builder,
5362                InstructionValue::StoreLocal {
5363                    lvalue: LValue {
5364                        kind: InstructionKind::Const,
5365                        place: place.clone(),
5366                    },
5367                    value: temp,
5368                    type_annotation: None,
5369                    loc: loc.clone(),
5370                },
5371            )?;
5372            Ok(Terminal::Goto {
5373                block: continuation_id,
5374                variant: GotoVariant::Break,
5375                id: EvaluationOrder(0),
5376                loc: loc.clone(),
5377            })
5378        })
5379    }?;
5380
5381    // Track callee info for building the call in the consequent block
5382    enum CalleeInfo {
5383        CallExpression { callee: Place },
5384        MethodCall { receiver: Place, property: Place },
5385    }
5386
5387    let mut callee_info: Option<CalleeInfo> = None;
5388
5389    let test_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
5390        match expr.callee.as_ref() {
5391            Expression::OptionalCallExpression(opt_call) => {
5392                let value =
5393                    lower_optional_call_expression_impl(builder, opt_call, Some(alternate))?;
5394                let value_place = lower_value_to_temporary(builder, value)?;
5395                callee_info = Some(CalleeInfo::CallExpression {
5396                    callee: value_place,
5397                });
5398            }
5399            Expression::OptionalMemberExpression(opt_member) => {
5400                let (obj, value) =
5401                    lower_optional_member_expression_impl(builder, opt_member, Some(alternate))?;
5402                callee_info = Some(CalleeInfo::MethodCall {
5403                    receiver: obj,
5404                    property: value,
5405                });
5406            }
5407            Expression::MemberExpression(member) => {
5408                let lowered = lower_member_expression(builder, member)?;
5409                let property_place = lower_value_to_temporary(builder, lowered.value)?;
5410                callee_info = Some(CalleeInfo::MethodCall {
5411                    receiver: lowered.object,
5412                    property: property_place,
5413                });
5414            }
5415            other => {
5416                let callee_place = lower_expression_to_temporary(builder, other)?;
5417                callee_info = Some(CalleeInfo::CallExpression {
5418                    callee: callee_place,
5419                });
5420            }
5421        }
5422
5423        let test_place = match callee_info.as_ref().unwrap() {
5424            CalleeInfo::CallExpression { callee } => callee.clone(),
5425            CalleeInfo::MethodCall { property, .. } => property.clone(),
5426        };
5427
5428        Ok(Terminal::Branch {
5429            test: test_place,
5430            consequent: consequent.id,
5431            alternate,
5432            fallthrough: continuation_id,
5433            id: EvaluationOrder(0),
5434            loc: loc.clone(),
5435        })
5436    });
5437
5438    // Block to evaluate if the callee is non-null/undefined
5439    builder.try_enter_reserved(consequent, |builder| {
5440        let args = lower_arguments(builder, &expr.arguments)?;
5441        let temp = build_temporary_place(builder, loc.clone());
5442
5443        match callee_info.as_ref().unwrap() {
5444            CalleeInfo::CallExpression { callee } => {
5445                builder.push(Instruction {
5446                    id: EvaluationOrder(0),
5447                    lvalue: temp.clone(),
5448                    value: InstructionValue::CallExpression {
5449                        callee: callee.clone(),
5450                        args,
5451                        loc: loc.clone(),
5452                    },
5453                    loc: loc.clone(),
5454                    effects: None,
5455                });
5456            }
5457            CalleeInfo::MethodCall { receiver, property } => {
5458                builder.push(Instruction {
5459                    id: EvaluationOrder(0),
5460                    lvalue: temp.clone(),
5461                    value: InstructionValue::MethodCall {
5462                        receiver: receiver.clone(),
5463                        property: property.clone(),
5464                        args,
5465                        loc: loc.clone(),
5466                    },
5467                    loc: loc.clone(),
5468                    effects: None,
5469                });
5470            }
5471        }
5472
5473        lower_value_to_temporary(
5474            builder,
5475            InstructionValue::StoreLocal {
5476                lvalue: LValue {
5477                    kind: InstructionKind::Const,
5478                    place: place.clone(),
5479                },
5480                value: temp,
5481                type_annotation: None,
5482                loc: loc.clone(),
5483            },
5484        )?;
5485        Ok(Terminal::Goto {
5486            block: continuation_id,
5487            variant: GotoVariant::Break,
5488            id: EvaluationOrder(0),
5489            loc: loc.clone(),
5490        })
5491    })?;
5492
5493    builder.terminate_with_continuation(
5494        Terminal::Optional {
5495            optional,
5496            test: test_block?,
5497            fallthrough: continuation_id,
5498            id: EvaluationOrder(0),
5499            loc: loc.clone(),
5500        },
5501        continuation_block,
5502    );
5503
5504    Ok(InstructionValue::LoadLocal {
5505        place: place.clone(),
5506        loc: place.loc,
5507    })
5508}
5509
5510fn lower_function_to_value(
5511    builder: &mut HirBuilder,
5512    expr: &react_compiler_ast::expressions::Expression,
5513    expr_type: FunctionExpressionType,
5514) -> Result<InstructionValue, CompilerDiagnostic> {
5515    use react_compiler_ast::expressions::Expression;
5516    let loc = match expr {
5517        Expression::ArrowFunctionExpression(arrow) => convert_opt_loc(&arrow.base.loc),
5518        Expression::FunctionExpression(func) => convert_opt_loc(&func.base.loc),
5519        _ => None,
5520    };
5521    let name = match expr {
5522        Expression::FunctionExpression(func) => func.id.as_ref().map(|id| id.name.clone()),
5523        _ => None,
5524    };
5525    let lowered_func = lower_function(builder, expr)?;
5526    Ok(InstructionValue::FunctionExpression {
5527        name,
5528        name_hint: None,
5529        lowered_func,
5530        expr_type,
5531        loc,
5532    })
5533}
5534
5535fn lower_function(
5536    builder: &mut HirBuilder,
5537    expr: &react_compiler_ast::expressions::Expression,
5538) -> Result<LoweredFunction, CompilerDiagnostic> {
5539    use react_compiler_ast::expressions::Expression;
5540
5541    // Extract function parts from the AST node
5542    let (params, body, id, generator, is_async, func_start, func_end, func_loc, func_node_id) =
5543        match expr {
5544            Expression::ArrowFunctionExpression(arrow) => {
5545                let body = match arrow.body.as_ref() {
5546                    react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
5547                        FunctionBody::Block(block)
5548                    }
5549                    react_compiler_ast::expressions::ArrowFunctionBody::Expression(expr) => {
5550                        FunctionBody::Expression(expr)
5551                    }
5552                };
5553                (
5554                    &arrow.params[..],
5555                    body,
5556                    None::<&str>,
5557                    arrow.generator,
5558                    arrow.is_async,
5559                    arrow.base.start.unwrap_or(0),
5560                    arrow.base.end.unwrap_or(0),
5561                    convert_opt_loc(&arrow.base.loc),
5562                    arrow.base.node_id,
5563                )
5564            }
5565            Expression::FunctionExpression(func) => (
5566                &func.params[..],
5567                FunctionBody::Block(&func.body),
5568                func.id.as_ref().map(|id| id.name.as_str()),
5569                func.generator,
5570                func.is_async,
5571                func.base.start.unwrap_or(0),
5572                func.base.end.unwrap_or(0),
5573                convert_opt_loc(&func.base.loc),
5574                func.base.node_id,
5575            ),
5576            _ => {
5577                return Err(CompilerDiagnostic::new(
5578                    ErrorCategory::Invariant,
5579                    "lower_function called with non-function expression",
5580                    None,
5581                ));
5582            }
5583        };
5584
5585    // Find the function's scope. For synthetic zero-width functions (e.g., desugared
5586    // match IIFEs from Hermes with start=end=0), node_id_to_scope won't have an entry.
5587    let function_scope =
5588        if let Some(scope) = builder.scope_info().resolve_scope_for_node(func_node_id) {
5589            scope
5590        } else if func_start < func_end {
5591            builder.scope_info().program_scope
5592        } else {
5593            let parent = builder.function_scope();
5594            let scope_info = builder.scope_info();
5595            let mapped: std::collections::HashSet<react_compiler_ast::scope::ScopeId> =
5596                scope_info.node_id_to_scope.values().copied().collect();
5597            let param_names: Vec<String> = params
5598                .iter()
5599                .filter_map(|p| {
5600                    if let react_compiler_ast::patterns::PatternLike::Identifier(id) = p {
5601                        Some(id.name.clone())
5602                    } else {
5603                        None
5604                    }
5605                })
5606                .collect();
5607            let mut descendants = std::collections::HashSet::new();
5608            descendants.insert(parent);
5609            let mut changed = true;
5610            while changed {
5611                changed = false;
5612                for (i, scope) in scope_info.scopes.iter().enumerate() {
5613                    let sid = react_compiler_ast::scope::ScopeId(i as u32);
5614                    if let Some(p) = scope.parent {
5615                        if descendants.contains(&p) && !descendants.contains(&sid) {
5616                            descendants.insert(sid);
5617                            changed = true;
5618                        }
5619                    }
5620                }
5621            }
5622            let mut found = scope_info.program_scope;
5623            for (i, scope) in scope_info.scopes.iter().enumerate() {
5624                let sid = react_compiler_ast::scope::ScopeId(i as u32);
5625                if let Some(p) = scope.parent {
5626                    if descendants.contains(&p)
5627                        && matches!(scope.kind, ScopeKind::Function)
5628                        && !mapped.contains(&sid)
5629                        && !builder.is_synthetic_scope_claimed(sid)
5630                    {
5631                        if !param_names.is_empty() {
5632                            let all_match = param_names
5633                                .iter()
5634                                .all(|name| scope.bindings.contains_key(name));
5635                            if !all_match {
5636                                continue;
5637                            }
5638                        }
5639                        found = sid;
5640                        break;
5641                    }
5642                }
5643            }
5644            builder.claim_synthetic_scope(found);
5645            found
5646        };
5647
5648    let component_scope = builder.component_scope();
5649    let scope_info = builder.scope_info();
5650
5651    let parent_bindings = builder.bindings().clone();
5652    let parent_used_names = builder.used_names().clone();
5653    let context_ids = builder.context_identifiers().clone();
5654    let ident_locs = builder.identifier_locs();
5655
5656    // For synthetic functions with zero-width position ranges, position-based
5657    // reference filtering fails. Walk the body AST to collect actual positions.
5658    let ref_override = if func_start >= func_end {
5659        Some(collect_identifier_node_ids_from_body(&body))
5660    } else {
5661        None
5662    };
5663
5664    // Gather captured context
5665    let captured_context = gather_captured_context(
5666        scope_info,
5667        function_scope,
5668        component_scope,
5669        func_start,
5670        func_end,
5671        ident_locs,
5672        ref_override.as_ref(),
5673    );
5674    let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = {
5675        let parent_context = builder.context().clone();
5676        let mut merged = parent_context;
5677        for (k, v) in captured_context {
5678            merged.insert(k, v);
5679        }
5680        merged
5681    };
5682
5683    // Use scope_info_and_env_mut to avoid conflicting borrows
5684    let (scope_info, env) = builder.scope_info_and_env_mut();
5685    let (hir_func, child_used_names, child_bindings) = lower_inner(
5686        params,
5687        body,
5688        id,
5689        generator,
5690        is_async,
5691        func_loc,
5692        scope_info,
5693        env,
5694        Some(parent_bindings),
5695        Some(parent_used_names),
5696        merged_context,
5697        function_scope,
5698        component_scope,
5699        &context_ids,
5700        false, // nested function
5701        ident_locs,
5702    )?;
5703
5704    builder.merge_used_names(child_used_names);
5705    builder.merge_bindings(child_bindings);
5706
5707    let func_id = builder.environment_mut().add_function(hir_func);
5708    Ok(LoweredFunction { func: func_id })
5709}
5710
5711/// Lower a function declaration statement to a FunctionExpression + StoreLocal.
5712fn lower_function_declaration(
5713    builder: &mut HirBuilder,
5714    func_decl: &react_compiler_ast::statements::FunctionDeclaration,
5715) -> Result<(), CompilerError> {
5716    let loc = convert_opt_loc(&func_decl.base.loc);
5717    let func_start = func_decl.base.start.unwrap_or(0);
5718    let func_end = func_decl.base.end.unwrap_or(0);
5719
5720    let func_name = func_decl.id.as_ref().map(|id| id.name.clone());
5721
5722    // Find the function's scope
5723    let function_scope = builder
5724        .scope_info()
5725        .resolve_scope_for_node(func_decl.base.node_id)
5726        .unwrap_or(builder.scope_info().program_scope);
5727
5728    let component_scope = builder.component_scope();
5729    let scope_info = builder.scope_info();
5730
5731    let parent_bindings = builder.bindings().clone();
5732    let parent_used_names = builder.used_names().clone();
5733    let context_ids = builder.context_identifiers().clone();
5734    let ident_locs = builder.identifier_locs();
5735
5736    // Gather captured context
5737    let captured_context = gather_captured_context(
5738        scope_info,
5739        function_scope,
5740        component_scope,
5741        func_start,
5742        func_end,
5743        ident_locs,
5744        None,
5745    );
5746    let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = {
5747        let parent_context = builder.context().clone();
5748        let mut merged = parent_context;
5749        for (k, v) in captured_context {
5750            merged.insert(k, v);
5751        }
5752        merged
5753    };
5754
5755    let (scope_info, env) = builder.scope_info_and_env_mut();
5756    let (hir_func, child_used_names, child_bindings) = lower_inner(
5757        &func_decl.params,
5758        FunctionBody::Block(&func_decl.body),
5759        func_decl.id.as_ref().map(|id| id.name.as_str()),
5760        func_decl.generator,
5761        func_decl.is_async,
5762        loc.clone(),
5763        scope_info,
5764        env,
5765        Some(parent_bindings),
5766        Some(parent_used_names),
5767        merged_context,
5768        function_scope,
5769        component_scope,
5770        &context_ids,
5771        false, // nested function
5772        ident_locs,
5773    )?;
5774
5775    builder.merge_used_names(child_used_names);
5776    builder.merge_bindings(child_bindings);
5777
5778    let func_id = builder.environment_mut().add_function(hir_func);
5779    let lowered_func = LoweredFunction { func: func_id };
5780
5781    // Emit FunctionExpression instruction
5782    let fn_value = InstructionValue::FunctionExpression {
5783        name: func_name.clone(),
5784        name_hint: None,
5785        lowered_func,
5786        expr_type: FunctionExpressionType::FunctionDeclaration,
5787        loc: loc.clone(),
5788    };
5789    let fn_place = lower_value_to_temporary(builder, fn_value)?;
5790
5791    // Resolve the binding for the function name and store. TS resolves the id
5792    // via Babel's `path.scope.getBinding(name)`, which starts at the function's
5793    // OWN scope: a body-level local that shadows the function's name resolves
5794    // to that inner binding — storing the function into the shadow while
5795    // references elsewhere resolve to the hoisted binding in the parent scope.
5796    // This is a known TS quirk that we reproduce for parity (see
5797    // todo-repro-named-function-with-shadowed-local-same-name). Fall back to
5798    // node-based resolution when the scope walk fails (degraded scope info,
5799    // e.g. synthetic scopes, or backends that split function-body scopes).
5800    if let Some(ref name) = func_name {
5801        if let Some(id_node) = &func_decl.id {
5802            let start = id_node.base.start.unwrap_or(0);
5803            let ident_loc = convert_opt_loc(&id_node.base.loc);
5804            let scope_binding = builder.get_function_declaration_binding(function_scope, name);
5805            let mut is_context = false;
5806            let binding = match scope_binding {
5807                Some(binding_id) => {
5808                    is_context = builder.is_context_binding(binding_id);
5809                    let binding_kind = crate::convert_binding_kind(
5810                        &builder.scope_info().bindings[binding_id.0 as usize].kind,
5811                    );
5812                    let identifier =
5813                        builder.resolve_binding_with_loc(name, binding_id, ident_loc.clone())?;
5814                    VariableBinding::Identifier {
5815                        identifier,
5816                        binding_kind,
5817                    }
5818                }
5819                None => {
5820                    let mut binding = builder.resolve_identifier(
5821                        name,
5822                        start,
5823                        ident_loc.clone(),
5824                        id_node.base.node_id,
5825                    )?;
5826                    if matches!(&binding, VariableBinding::Global { .. }) {
5827                        // For function redeclarations (e.g., `function x() {} function x() {}`),
5828                        // the redeclaration's identifier may not be in ref_node_id_to_binding
5829                        // (OXC/SWC don't map constant violations). Retry using the first
5830                        // declaration's node_id from the scope chain.
5831                        let fallback = {
5832                            let si = builder.scope_info();
5833                            let scope_id = si
5834                                .resolve_scope_for_node(func_decl.base.node_id)
5835                                .unwrap_or(si.program_scope);
5836                            si.get_binding(scope_id, name).map(|bid| {
5837                                let b = &si.bindings[bid.0 as usize];
5838                                (b.declaration_start.unwrap_or(0), b.declaration_node_id)
5839                            })
5840                        };
5841                        if let Some((ds, ds_node_id)) = fallback {
5842                            binding = builder.resolve_identifier(
5843                                name,
5844                                ds,
5845                                ident_loc.clone(),
5846                                ds_node_id,
5847                            )?;
5848                        }
5849                    }
5850                    if matches!(&binding, VariableBinding::Identifier { .. }) {
5851                        is_context =
5852                            builder.is_context_identifier(name, start, id_node.base.node_id);
5853                    }
5854                    binding
5855                }
5856            };
5857            match binding {
5858                VariableBinding::Identifier { identifier, .. } => {
5859                    // Don't override the identifier's declaration loc here.
5860                    // For function redeclarations (e.g., `function x() {} function x() {}`),
5861                    // the identifier's loc should remain the first declaration's loc,
5862                    // which was already set during define_binding.
5863                    // Use the full function declaration loc for the Place,
5864                    // matching the TS behavior where lowerAssignment uses stmt.node.loc
5865                    let place = Place {
5866                        identifier,
5867                        reactive: false,
5868                        effect: Effect::Unknown,
5869                        loc: loc.clone(),
5870                    };
5871                    if is_context {
5872                        lower_value_to_temporary(
5873                            builder,
5874                            InstructionValue::StoreContext {
5875                                lvalue: LValue {
5876                                    kind: InstructionKind::Function,
5877                                    place,
5878                                },
5879                                value: fn_place,
5880                                loc,
5881                            },
5882                        )?;
5883                    } else {
5884                        lower_value_to_temporary(
5885                            builder,
5886                            InstructionValue::StoreLocal {
5887                                lvalue: LValue {
5888                                    kind: InstructionKind::Function,
5889                                    place,
5890                                },
5891                                value: fn_place,
5892                                type_annotation: None,
5893                                loc,
5894                            },
5895                        )?;
5896                    }
5897                }
5898                _ => {
5899                    builder.record_error(CompilerErrorDetail {
5900                        category: ErrorCategory::Invariant,
5901                        reason: format!(
5902                            "Could not find binding for function declaration `{}`",
5903                            name
5904                        ),
5905                        description: None,
5906                        loc,
5907                        suggestions: None,
5908                    })?;
5909                }
5910            }
5911        }
5912    }
5913    Ok(())
5914}
5915
5916/// Lower a function expression used as an object method.
5917fn lower_function_for_object_method(
5918    builder: &mut HirBuilder,
5919    method: &react_compiler_ast::expressions::ObjectMethod,
5920) -> Result<LoweredFunction, CompilerError> {
5921    let func_start = method.base.start.unwrap_or(0);
5922    let func_end = method.base.end.unwrap_or(0);
5923    let func_loc = convert_opt_loc(&method.base.loc);
5924
5925    let function_scope = builder
5926        .scope_info()
5927        .resolve_scope_for_node(method.base.node_id)
5928        .unwrap_or(builder.scope_info().program_scope);
5929
5930    let component_scope = builder.component_scope();
5931    let scope_info = builder.scope_info();
5932
5933    let parent_bindings = builder.bindings().clone();
5934    let parent_used_names = builder.used_names().clone();
5935    let context_ids = builder.context_identifiers().clone();
5936    let ident_locs = builder.identifier_locs();
5937
5938    let captured_context = gather_captured_context(
5939        scope_info,
5940        function_scope,
5941        component_scope,
5942        func_start,
5943        func_end,
5944        ident_locs,
5945        None,
5946    );
5947    let merged_context: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> = {
5948        let parent_context = builder.context().clone();
5949        let mut merged = parent_context;
5950        for (k, v) in captured_context {
5951            merged.insert(k, v);
5952        }
5953        merged
5954    };
5955
5956    let (scope_info, env) = builder.scope_info_and_env_mut();
5957    let (hir_func, child_used_names, child_bindings) = lower_inner(
5958        &method.params,
5959        FunctionBody::Block(&method.body),
5960        None,
5961        method.generator,
5962        method.is_async,
5963        func_loc,
5964        scope_info,
5965        env,
5966        Some(parent_bindings),
5967        Some(parent_used_names),
5968        merged_context,
5969        function_scope,
5970        component_scope,
5971        &context_ids,
5972        false, // nested function
5973        ident_locs,
5974    )?;
5975
5976    builder.merge_used_names(child_used_names);
5977    builder.merge_bindings(child_bindings);
5978
5979    let func_id = builder.environment_mut().add_function(hir_func);
5980    Ok(LoweredFunction { func: func_id })
5981}
5982
5983/// Internal helper: lower a function given its extracted parts.
5984/// Used by both the top-level `lower()` and nested `lower_function()`.
5985fn lower_inner(
5986    params: &[react_compiler_ast::patterns::PatternLike],
5987    body: FunctionBody<'_>,
5988    id: Option<&str>,
5989    generator: bool,
5990    is_async: bool,
5991    loc: Option<SourceLocation>,
5992    scope_info: &ScopeInfo,
5993    env: &mut Environment,
5994    parent_bindings: Option<IndexMap<react_compiler_ast::scope::BindingId, IdentifierId>>,
5995    parent_used_names: Option<IndexMap<String, react_compiler_ast::scope::BindingId>>,
5996    context_map: IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>>,
5997    function_scope: react_compiler_ast::scope::ScopeId,
5998    component_scope: react_compiler_ast::scope::ScopeId,
5999    context_identifiers: &HashSet<react_compiler_ast::scope::BindingId>,
6000    is_top_level: bool,
6001    identifier_locs: &IdentifierLocIndex,
6002) -> Result<
6003    (
6004        HirFunction,
6005        IndexMap<String, react_compiler_ast::scope::BindingId>,
6006        IndexMap<react_compiler_ast::scope::BindingId, IdentifierId>,
6007    ),
6008    CompilerError,
6009> {
6010    validate_ts_this_parameter(scope_info, function_scope)?;
6011
6012    let mut builder = HirBuilder::new(
6013        env,
6014        scope_info,
6015        function_scope,
6016        component_scope,
6017        context_identifiers.clone(),
6018        parent_bindings,
6019        Some(context_map.clone()),
6020        None,
6021        parent_used_names,
6022        identifier_locs,
6023    );
6024
6025    // Build context places from the captured refs
6026    let mut context: Vec<Place> = Vec::new();
6027    for (&binding_id, ctx_loc) in &context_map {
6028        let binding = &scope_info.bindings[binding_id.0 as usize];
6029        let identifier = builder.resolve_binding(&binding.name, binding_id)?;
6030        context.push(Place {
6031            identifier,
6032            effect: Effect::Unknown,
6033            reactive: false,
6034            loc: ctx_loc.clone(),
6035        });
6036    }
6037
6038    // Process parameters
6039    let mut hir_params: Vec<ParamPattern> = Vec::new();
6040    for param in params {
6041        match param {
6042            react_compiler_ast::patterns::PatternLike::Identifier(ident) => {
6043                if is_always_reserved_word(&ident.name) {
6044                    return Err(CompilerError::from(reserved_identifier_diagnostic(
6045                        &ident.name,
6046                    )));
6047                }
6048                let start = ident.base.start.unwrap_or(0);
6049                let param_loc = convert_opt_loc(&ident.base.loc);
6050                let mut binding = builder.resolve_identifier(
6051                    &ident.name,
6052                    start,
6053                    param_loc.clone(),
6054                    ident.base.node_id,
6055                )?;
6056                if !matches!(binding, VariableBinding::Identifier { .. }) {
6057                    // Position-based resolution failed (common for synthetic params
6058                    // like $$gen$m0 at position 0). Try lookup in function scope
6059                    // and descendants.
6060                    if let Some((binding_id, binding_data)) = builder
6061                        .scope_info()
6062                        .find_binding_id_in_descendants(&ident.name, builder.function_scope())
6063                    {
6064                        let binding_kind = crate::convert_binding_kind(&binding_data.kind);
6065                        let identifier = builder.resolve_binding_with_loc(
6066                            &ident.name,
6067                            binding_id,
6068                            param_loc.clone(),
6069                        )?;
6070                        binding = VariableBinding::Identifier {
6071                            identifier,
6072                            binding_kind,
6073                        };
6074                    }
6075                }
6076                match binding {
6077                    VariableBinding::Identifier { identifier, .. } => {
6078                        builder.set_identifier_declaration_loc(identifier, &param_loc);
6079                        let place = Place {
6080                            identifier,
6081                            effect: Effect::Unknown,
6082                            reactive: false,
6083                            loc: param_loc,
6084                        };
6085                        hir_params.push(ParamPattern::Place(place));
6086                    }
6087                    _ => {
6088                        builder.record_diagnostic(
6089                            CompilerDiagnostic::new(
6090                                ErrorCategory::Invariant,
6091                                "Could not find binding",
6092                                Some(format!(
6093                                    "[BuildHIR] Could not find binding for param `{}`",
6094                                    ident.name
6095                                )),
6096                            )
6097                            .with_detail(
6098                                CompilerDiagnosticDetail::Error {
6099                                    loc: convert_opt_loc(&ident.base.loc),
6100                                    message: Some("Could not find binding".to_string()),
6101                                    identifier_name: None,
6102                                },
6103                            ),
6104                        );
6105                    }
6106                }
6107            }
6108            react_compiler_ast::patterns::PatternLike::RestElement(rest) => {
6109                let rest_loc = convert_opt_loc(&rest.base.loc);
6110                // Create a temporary place for the spread param
6111                let place = build_temporary_place(&mut builder, rest_loc.clone());
6112                hir_params.push(ParamPattern::Spread(SpreadPattern {
6113                    place: place.clone(),
6114                }));
6115                // Delegate the assignment of the rest argument
6116                lower_assignment(
6117                    &mut builder,
6118                    rest_loc,
6119                    InstructionKind::Let,
6120                    &rest.argument,
6121                    place,
6122                    AssignmentStyle::Assignment,
6123                )?;
6124            }
6125            react_compiler_ast::patterns::PatternLike::ObjectPattern(_)
6126            | react_compiler_ast::patterns::PatternLike::ArrayPattern(_)
6127            | react_compiler_ast::patterns::PatternLike::AssignmentPattern(_) => {
6128                let param_loc = convert_opt_loc(&pattern_like_loc(param));
6129                let place = build_temporary_place(&mut builder, param_loc.clone());
6130                promote_temporary(&mut builder, place.identifier);
6131                hir_params.push(ParamPattern::Place(place.clone()));
6132                lower_assignment(
6133                    &mut builder,
6134                    param_loc,
6135                    InstructionKind::Let,
6136                    param,
6137                    place,
6138                    AssignmentStyle::Assignment,
6139                )?;
6140            }
6141            react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {
6142                builder.record_diagnostic(
6143                    CompilerDiagnostic::new(
6144                        ErrorCategory::Todo,
6145                        "Handle MemberExpression parameters",
6146                        Some("[BuildHIR] Add support for MemberExpression parameters".to_string()),
6147                    )
6148                    .with_detail(CompilerDiagnosticDetail::Error {
6149                        loc: convert_opt_loc(&member.base.loc),
6150                        message: Some("Unsupported parameter type".to_string()),
6151                        identifier_name: None,
6152                    }),
6153                );
6154            }
6155            react_compiler_ast::patterns::PatternLike::TSAsExpression(_)
6156            | react_compiler_ast::patterns::PatternLike::TSSatisfiesExpression(_)
6157            | react_compiler_ast::patterns::PatternLike::TSNonNullExpression(_)
6158            | react_compiler_ast::patterns::PatternLike::TSTypeAssertion(_)
6159            | react_compiler_ast::patterns::PatternLike::TypeCastExpression(_) => {}
6160        }
6161    }
6162
6163    // Lower the body
6164    let mut directives: Vec<String> = Vec::new();
6165    match body {
6166        FunctionBody::Expression(expr) => {
6167            let fallthrough = builder.reserve(BlockKind::Block);
6168            let value = lower_expression_to_temporary(&mut builder, expr)?;
6169            builder.terminate_with_continuation(
6170                Terminal::Return {
6171                    value,
6172                    return_variant: ReturnVariant::Implicit,
6173                    id: EvaluationOrder(0),
6174                    loc: None,
6175                    effects: None,
6176                },
6177                fallthrough,
6178            );
6179        }
6180        FunctionBody::Block(block) => {
6181            directives = block
6182                .directives
6183                .iter()
6184                .map(|d| d.value.value.clone())
6185                .collect();
6186            // Use lower_block_statement_with_scope to get hoisting support for the function body.
6187            // Pass the function scope since in Babel, a function body BlockStatement shares
6188            // the function's scope (node_to_scope maps the function node, not the block).
6189            lower_block_statement_with_scope(&mut builder, block, function_scope)?;
6190        }
6191    }
6192
6193    // Emit final Return(Void, undefined)
6194    let undefined_value = InstructionValue::Primitive {
6195        value: PrimitiveValue::Undefined,
6196        loc: None,
6197    };
6198    let return_value = lower_value_to_temporary(&mut builder, undefined_value)?;
6199    builder.terminate(
6200        Terminal::Return {
6201            value: return_value,
6202            return_variant: ReturnVariant::Void,
6203            id: EvaluationOrder(0),
6204            loc: None,
6205            effects: None,
6206        },
6207        None,
6208    );
6209
6210    // Build the HIR
6211    let (hir_body, instructions, used_names, child_bindings) = builder.build()?;
6212
6213    // Create the returns place
6214    let returns = crate::hir_builder::create_temporary_place(env, loc.clone());
6215
6216    Ok((
6217        HirFunction {
6218            loc,
6219            id: id.map(|s| s.to_string()),
6220            name_hint: None,
6221            fn_type: if is_top_level {
6222                env.fn_type
6223            } else {
6224                ReactFunctionType::Other
6225            },
6226            params: hir_params,
6227            return_type_annotation: None,
6228            returns,
6229            context,
6230            body: hir_body,
6231            instructions,
6232            generator,
6233            is_async,
6234            directives,
6235            aliasing_effects: None,
6236        },
6237        used_names,
6238        child_bindings,
6239    ))
6240}
6241
6242fn lower_jsx_element_name(
6243    builder: &mut HirBuilder,
6244    name: &react_compiler_ast::jsx::JSXElementName,
6245) -> Result<JsxTag, CompilerError> {
6246    use react_compiler_ast::jsx::JSXElementName;
6247    match name {
6248        JSXElementName::JSXIdentifier(id) => {
6249            let tag = &id.name;
6250            let loc = convert_opt_loc(&id.base.loc);
6251            let start = id.base.start.unwrap_or(0);
6252            if tag.starts_with(|c: char| c.is_ascii_uppercase()) {
6253                // Component tag: resolve as identifier and load
6254                let place = lower_identifier(builder, tag, start, loc.clone(), id.base.node_id)?;
6255                let load_value = if builder.is_context_identifier(tag, start, id.base.node_id) {
6256                    InstructionValue::LoadContext { place, loc }
6257                } else {
6258                    InstructionValue::LoadLocal { place, loc }
6259                };
6260                let temp = lower_value_to_temporary(builder, load_value)?;
6261                Ok(JsxTag::Place(temp))
6262            } else {
6263                // Builtin HTML tag
6264                Ok(JsxTag::Builtin(BuiltinTag {
6265                    name: tag.clone(),
6266                    loc,
6267                }))
6268            }
6269        }
6270        JSXElementName::JSXMemberExpression(member) => {
6271            let place = lower_jsx_member_expression(builder, member)?;
6272            Ok(JsxTag::Place(place))
6273        }
6274        JSXElementName::JSXNamespacedName(ns) => {
6275            let namespace = &ns.namespace.name;
6276            let name = &ns.name.name;
6277            let tag = format!("{}:{}", namespace, name);
6278            let loc = convert_opt_loc(&ns.base.loc);
6279            if namespace.contains(':') || name.contains(':') {
6280                builder.record_error(CompilerErrorDetail {
6281                    category: ErrorCategory::Syntax,
6282                    reason: "Expected JSXNamespacedName to have no colons in the namespace or name"
6283                        .to_string(),
6284                    description: Some(format!("Got `{}` : `{}`", namespace, name)),
6285                    loc: loc.clone(),
6286                    suggestions: None,
6287                })?;
6288            }
6289            let place = lower_value_to_temporary(
6290                builder,
6291                InstructionValue::Primitive {
6292                    value: PrimitiveValue::String(tag),
6293                    loc: loc.clone(),
6294                },
6295            )?;
6296            Ok(JsxTag::Place(place))
6297        }
6298    }
6299}
6300
6301fn lower_jsx_member_expression(
6302    builder: &mut HirBuilder,
6303    expr: &react_compiler_ast::jsx::JSXMemberExpression,
6304) -> Result<Place, CompilerError> {
6305    use react_compiler_ast::jsx::JSXMemberExprObject;
6306    // Use the full member expression's loc for instruction locs (matching TS: exprPath.node.loc)
6307    let expr_loc = convert_opt_loc(&expr.base.loc);
6308    let object = match &*expr.object {
6309        JSXMemberExprObject::JSXIdentifier(id) => {
6310            let id_loc = convert_opt_loc(&id.base.loc);
6311            let start = id.base.start.unwrap_or(0);
6312            // Use identifier's own loc for the place, but member expression's loc for the instruction
6313            let place = lower_identifier(builder, &id.name, start, id_loc, id.base.node_id)?;
6314            let load_value = if builder.is_context_identifier(&id.name, start, id.base.node_id) {
6315                InstructionValue::LoadContext {
6316                    place,
6317                    loc: expr_loc.clone(),
6318                }
6319            } else {
6320                InstructionValue::LoadLocal {
6321                    place,
6322                    loc: expr_loc.clone(),
6323                }
6324            };
6325            lower_value_to_temporary(builder, load_value)?
6326        }
6327        JSXMemberExprObject::JSXMemberExpression(inner) => {
6328            lower_jsx_member_expression(builder, inner)?
6329        }
6330    };
6331    let prop_name = &expr.property.name;
6332    let value = InstructionValue::PropertyLoad {
6333        object,
6334        property: PropertyLiteral::String(prop_name.clone()),
6335        loc: expr_loc,
6336    };
6337    Ok(lower_value_to_temporary(builder, value)?)
6338}
6339
6340fn lower_jsx_element(
6341    builder: &mut HirBuilder,
6342    child: &react_compiler_ast::jsx::JSXChild,
6343) -> Result<Option<Place>, CompilerError> {
6344    use react_compiler_ast::jsx::JSXChild;
6345    use react_compiler_ast::jsx::JSXExpressionContainerExpr;
6346    match child {
6347        JSXChild::JSXText(text) => {
6348            // FBT whitespace normalization differs from standard JSX.
6349            // Since the fbt transform runs after, preserve all whitespace
6350            // in FBT subtrees as is.
6351            let value = if builder.fbt_depth > 0 {
6352                Some(text.value.clone())
6353            } else {
6354                trim_jsx_text(&text.value)
6355            };
6356            match value {
6357                None => Ok(None),
6358                Some(value) => {
6359                    let loc = convert_opt_loc(&text.base.loc);
6360                    let place = lower_value_to_temporary(
6361                        builder,
6362                        InstructionValue::JSXText { value, loc },
6363                    )?;
6364                    Ok(Some(place))
6365                }
6366            }
6367        }
6368        JSXChild::JSXElement(element) => {
6369            let value = lower_expression(
6370                builder,
6371                &react_compiler_ast::expressions::Expression::JSXElement(element.clone()),
6372            )?;
6373            Ok(Some(lower_value_to_temporary(builder, value)?))
6374        }
6375        JSXChild::JSXFragment(fragment) => {
6376            let value = lower_expression(
6377                builder,
6378                &react_compiler_ast::expressions::Expression::JSXFragment(fragment.clone()),
6379            )?;
6380            Ok(Some(lower_value_to_temporary(builder, value)?))
6381        }
6382        JSXChild::JSXExpressionContainer(container) => match &container.expression {
6383            JSXExpressionContainerExpr::JSXEmptyExpression(_) => Ok(None),
6384            JSXExpressionContainerExpr::Expression(expr) => {
6385                Ok(Some(lower_expression_to_temporary(builder, expr)?))
6386            }
6387        },
6388        JSXChild::JSXSpreadChild(spread) => Ok(Some(lower_expression_to_temporary(
6389            builder,
6390            &spread.expression,
6391        )?)),
6392    }
6393}
6394
6395/// Split a string on line endings, handling \r\n, \n, and \r.
6396fn split_line_endings(s: &str) -> Vec<&str> {
6397    let mut lines = Vec::new();
6398    let mut start = 0;
6399    let bytes = s.as_bytes();
6400    let mut i = 0;
6401    while i < bytes.len() {
6402        if bytes[i] == b'\r' {
6403            lines.push(&s[start..i]);
6404            if i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
6405                i += 2;
6406            } else {
6407                i += 1;
6408            }
6409            start = i;
6410        } else if bytes[i] == b'\n' {
6411            lines.push(&s[start..i]);
6412            i += 1;
6413            start = i;
6414        } else {
6415            i += 1;
6416        }
6417    }
6418    lines.push(&s[start..]);
6419    lines
6420}
6421
6422/// Trims whitespace according to the JSX spec.
6423/// Implementation ported from Babel's cleanJSXElementLiteralChild.
6424fn trim_jsx_text(original: &str) -> Option<String> {
6425    // Split on \r\n, \n, or \r to handle all line ending styles (matching TS split(/\r\n|\n|\r/))
6426    let lines: Vec<&str> = split_line_endings(original);
6427
6428    // NOTE: when builder.fbt_depth > 0, the TS skips whitespace trimming entirely.
6429    // That check is handled by the caller (lower_jsx_element) before calling this function.
6430
6431    let mut last_non_empty_line = 0;
6432    for (i, line) in lines.iter().enumerate() {
6433        if line.contains(|c: char| c != ' ' && c != '\t') {
6434            last_non_empty_line = i;
6435        }
6436    }
6437
6438    let mut str = String::new();
6439
6440    for (i, line) in lines.iter().enumerate() {
6441        let is_first_line = i == 0;
6442        let is_last_line = i == lines.len() - 1;
6443        let is_last_non_empty_line = i == last_non_empty_line;
6444
6445        // Replace rendered whitespace tabs with spaces
6446        let mut trimmed_line = line.replace('\t', " ");
6447
6448        // Trim whitespace touching a newline (leading whitespace on non-first lines)
6449        if !is_first_line {
6450            trimmed_line = trimmed_line.trim_start_matches(' ').to_string();
6451        }
6452
6453        // Trim whitespace touching an endline (trailing whitespace on non-last lines)
6454        if !is_last_line {
6455            trimmed_line = trimmed_line.trim_end_matches(' ').to_string();
6456        }
6457
6458        if !trimmed_line.is_empty() {
6459            if !is_last_non_empty_line {
6460                trimmed_line.push(' ');
6461            }
6462            str.push_str(&trimmed_line);
6463        }
6464    }
6465
6466    if str.is_empty() { None } else { Some(str) }
6467}
6468
6469fn lower_object_method(
6470    builder: &mut HirBuilder,
6471    method: &react_compiler_ast::expressions::ObjectMethod,
6472) -> Result<Option<ObjectProperty>, CompilerError> {
6473    use react_compiler_ast::expressions::ObjectMethodKind;
6474    if !matches!(method.kind, ObjectMethodKind::Method) {
6475        let kind_str = match method.kind {
6476            ObjectMethodKind::Get => "get",
6477            ObjectMethodKind::Set => "set",
6478            ObjectMethodKind::Method => "method",
6479        };
6480        builder.record_error(CompilerErrorDetail {
6481            reason: format!(
6482                "(BuildHIR::lowerExpression) Handle {} functions in ObjectExpression",
6483                kind_str
6484            ),
6485            category: ErrorCategory::Todo,
6486            loc: convert_opt_loc(&method.base.loc),
6487            description: None,
6488            suggestions: None,
6489        })?;
6490        return Ok(None);
6491    }
6492    let key = lower_object_property_key(builder, &method.key, method.computed)?.unwrap_or(
6493        ObjectPropertyKey::String {
6494            name: String::new(),
6495        },
6496    );
6497
6498    let lowered_func = lower_function_for_object_method(builder, method)?;
6499
6500    let loc = convert_opt_loc(&method.base.loc);
6501    let method_value = InstructionValue::ObjectMethod {
6502        loc: loc.clone(),
6503        lowered_func,
6504    };
6505    let method_place = lower_value_to_temporary(builder, method_value)?;
6506
6507    Ok(Some(ObjectProperty {
6508        key,
6509        property_type: ObjectPropertyType::Method,
6510        place: method_place,
6511    }))
6512}
6513
6514fn lower_object_property_key(
6515    builder: &mut HirBuilder,
6516    key: &react_compiler_ast::expressions::Expression,
6517    computed: bool,
6518) -> Result<Option<ObjectPropertyKey>, CompilerError> {
6519    use react_compiler_ast::expressions::Expression;
6520    match key {
6521        Expression::StringLiteral(lit) => Ok(Some(ObjectPropertyKey::String {
6522            name: lit.value.clone(),
6523        })),
6524        Expression::Identifier(ident) if !computed => Ok(Some(ObjectPropertyKey::Identifier {
6525            name: ident.name.clone(),
6526        })),
6527        Expression::NumericLiteral(lit) if !computed => Ok(Some(ObjectPropertyKey::Identifier {
6528            name: lit.value.to_string(),
6529        })),
6530        _ if computed => {
6531            let place = lower_expression_to_temporary(builder, key)?;
6532            Ok(Some(ObjectPropertyKey::Computed { name: place }))
6533        }
6534        _ => {
6535            let loc = match key {
6536                Expression::Identifier(i) => convert_opt_loc(&i.base.loc),
6537                _ => None,
6538            };
6539            builder.record_error(CompilerErrorDetail {
6540                category: ErrorCategory::Todo,
6541                reason: "Unsupported key type in ObjectExpression".to_string(),
6542                description: None,
6543                loc,
6544                suggestions: None,
6545            })?;
6546            Ok(None)
6547        }
6548    }
6549}
6550
6551fn lower_reorderable_expression(
6552    builder: &mut HirBuilder,
6553    expr: &react_compiler_ast::expressions::Expression,
6554) -> Result<Place, CompilerError> {
6555    if !is_reorderable_expression(builder, expr, true) {
6556        builder.record_error(CompilerErrorDetail {
6557            category: ErrorCategory::Todo,
6558            reason: format!(
6559                "(BuildHIR::node.lowerReorderableExpression) Expression type `{}` cannot be safely reordered",
6560                expression_type_name(expr)
6561            ),
6562            description: None,
6563            loc: expression_loc(expr),
6564            suggestions: None,
6565        })?;
6566    }
6567    Ok(lower_expression_to_temporary(builder, expr)?)
6568}
6569
6570fn is_reorderable_expression(
6571    builder: &HirBuilder,
6572    expr: &react_compiler_ast::expressions::Expression,
6573    allow_local_identifiers: bool,
6574) -> bool {
6575    use react_compiler_ast::expressions::Expression;
6576    match expr {
6577        Expression::Identifier(ident) => {
6578            let binding = builder
6579                .scope_info()
6580                .resolve_reference_for_node(ident.base.node_id);
6581            match binding {
6582                None => {
6583                    // global, safe to reorder
6584                    true
6585                }
6586                Some(b) => {
6587                    if b.scope == builder.scope_info().program_scope {
6588                        // Module-scope binding (ModuleLocal, imports), safe to reorder
6589                        true
6590                    } else {
6591                        allow_local_identifiers
6592                    }
6593                }
6594            }
6595        }
6596        Expression::RegExpLiteral(_)
6597        | Expression::StringLiteral(_)
6598        | Expression::NumericLiteral(_)
6599        | Expression::NullLiteral(_)
6600        | Expression::BooleanLiteral(_)
6601        | Expression::BigIntLiteral(_) => true,
6602        Expression::UnaryExpression(unary) => {
6603            use react_compiler_ast::operators::UnaryOperator;
6604            matches!(
6605                unary.operator,
6606                UnaryOperator::Not | UnaryOperator::Plus | UnaryOperator::Neg
6607            ) && is_reorderable_expression(builder, &unary.argument, allow_local_identifiers)
6608        }
6609        Expression::LogicalExpression(logical) => {
6610            is_reorderable_expression(builder, &logical.left, allow_local_identifiers)
6611                && is_reorderable_expression(builder, &logical.right, allow_local_identifiers)
6612        }
6613        Expression::ConditionalExpression(cond) => {
6614            is_reorderable_expression(builder, &cond.test, allow_local_identifiers)
6615                && is_reorderable_expression(builder, &cond.consequent, allow_local_identifiers)
6616                && is_reorderable_expression(builder, &cond.alternate, allow_local_identifiers)
6617        }
6618        Expression::ArrayExpression(arr) => {
6619            arr.elements.iter().all(|element| {
6620                match element {
6621                    Some(e) => is_reorderable_expression(builder, e, allow_local_identifiers),
6622                    None => false, // holes are not reorderable
6623                }
6624            })
6625        }
6626        Expression::ObjectExpression(obj) => obj.properties.iter().all(|prop| match prop {
6627            react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(p) => {
6628                !p.computed && is_reorderable_expression(builder, &p.value, allow_local_identifiers)
6629            }
6630            _ => false,
6631        }),
6632        Expression::MemberExpression(member) => {
6633            // Allow member expressions where the innermost object is a global or module-local
6634            let mut inner = member.object.as_ref();
6635            while let Expression::MemberExpression(m) = inner {
6636                inner = m.object.as_ref();
6637            }
6638            if let Expression::Identifier(ident) = inner {
6639                match builder
6640                    .scope_info()
6641                    .resolve_reference_for_node(ident.base.node_id)
6642                {
6643                    None => true, // global
6644                    Some(binding) => {
6645                        // Module-scope bindings (ModuleLocal, imports) are safe to reorder
6646                        binding.scope == builder.scope_info().program_scope
6647                    }
6648                }
6649            } else {
6650                false
6651            }
6652        }
6653        Expression::ArrowFunctionExpression(arrow) => {
6654            use react_compiler_ast::expressions::ArrowFunctionBody;
6655            match arrow.body.as_ref() {
6656                ArrowFunctionBody::BlockStatement(block) => block.body.is_empty(),
6657                ArrowFunctionBody::Expression(body_expr) => {
6658                    is_reorderable_expression(builder, body_expr, false)
6659                }
6660            }
6661        }
6662        Expression::CallExpression(call) => {
6663            is_reorderable_expression(builder, &call.callee, allow_local_identifiers)
6664                && call
6665                    .arguments
6666                    .iter()
6667                    .all(|arg| is_reorderable_expression(builder, arg, allow_local_identifiers))
6668        }
6669        Expression::NewExpression(new_expr) => {
6670            is_reorderable_expression(builder, &new_expr.callee, allow_local_identifiers)
6671                && new_expr
6672                    .arguments
6673                    .iter()
6674                    .all(|arg| is_reorderable_expression(builder, arg, allow_local_identifiers))
6675        }
6676        // TypeScript/Flow type wrappers: recurse into the inner expression
6677        Expression::TSAsExpression(ts) => {
6678            is_reorderable_expression(builder, &ts.expression, allow_local_identifiers)
6679        }
6680        Expression::TSSatisfiesExpression(ts) => {
6681            is_reorderable_expression(builder, &ts.expression, allow_local_identifiers)
6682        }
6683        Expression::TSNonNullExpression(ts) => {
6684            is_reorderable_expression(builder, &ts.expression, allow_local_identifiers)
6685        }
6686        Expression::TSInstantiationExpression(ts) => {
6687            is_reorderable_expression(builder, &ts.expression, allow_local_identifiers)
6688        }
6689        Expression::TypeCastExpression(tc) => {
6690            is_reorderable_expression(builder, &tc.expression, allow_local_identifiers)
6691        }
6692        Expression::TSTypeAssertion(ts) => {
6693            is_reorderable_expression(builder, &ts.expression, allow_local_identifiers)
6694        }
6695        Expression::ParenthesizedExpression(p) => {
6696            is_reorderable_expression(builder, &p.expression, allow_local_identifiers)
6697        }
6698        _ => false,
6699    }
6700}
6701
6702/// Extract the type name from a type annotation serde_json::Value.
6703/// Returns the "type" field value, e.g. "TSTypeReference", "GenericTypeAnnotation".
6704fn get_type_annotation_name(val: &serde_json::Value) -> Option<String> {
6705    val.get("type")
6706        .and_then(|v| v.as_str())
6707        .map(|s| s.to_string())
6708}
6709
6710/// Lower a type annotation JSON value to an HIR Type.
6711/// Mirrors the TS `lowerType` function.
6712fn lower_type_annotation(val: &serde_json::Value, builder: &mut HirBuilder) -> Type {
6713    let type_name = match val.get("type").and_then(|v| v.as_str()) {
6714        Some(name) => name,
6715        None => return builder.make_type(),
6716    };
6717    match type_name {
6718        "GenericTypeAnnotation" => {
6719            // Check if it's Array
6720            if let Some(id) = val.get("id") {
6721                if id.get("type").and_then(|v| v.as_str()) == Some("Identifier") {
6722                    if id.get("name").and_then(|v| v.as_str()) == Some("Array") {
6723                        return Type::Object {
6724                            shape_id: Some("BuiltInArray".to_string()),
6725                        };
6726                    }
6727                }
6728            }
6729            builder.make_type()
6730        }
6731        "TSTypeReference" => {
6732            if let Some(type_name_val) = val.get("typeName") {
6733                if type_name_val.get("type").and_then(|v| v.as_str()) == Some("Identifier") {
6734                    if type_name_val.get("name").and_then(|v| v.as_str()) == Some("Array") {
6735                        return Type::Object {
6736                            shape_id: Some("BuiltInArray".to_string()),
6737                        };
6738                    }
6739                }
6740            }
6741            builder.make_type()
6742        }
6743        "ArrayTypeAnnotation" | "TSArrayType" => Type::Object {
6744            shape_id: Some("BuiltInArray".to_string()),
6745        },
6746        "BooleanLiteralTypeAnnotation"
6747        | "BooleanTypeAnnotation"
6748        | "NullLiteralTypeAnnotation"
6749        | "NumberLiteralTypeAnnotation"
6750        | "NumberTypeAnnotation"
6751        | "StringLiteralTypeAnnotation"
6752        | "StringTypeAnnotation"
6753        | "TSBooleanKeyword"
6754        | "TSNullKeyword"
6755        | "TSNumberKeyword"
6756        | "TSStringKeyword"
6757        | "TSSymbolKeyword"
6758        | "TSUndefinedKeyword"
6759        | "TSVoidKeyword"
6760        | "VoidTypeAnnotation" => Type::Primitive,
6761        _ => builder.make_type(),
6762    }
6763}
6764
6765/// Gather captured context variables for a nested function.
6766///
6767/// Walks through all identifier references (via `reference_to_binding`) and checks
6768/// which ones resolve to bindings declared in scopes between the function's parent scope
6769/// and the component scope. These are "free variables" that become the function's `context`.
6770fn gather_captured_context(
6771    scope_info: &ScopeInfo,
6772    function_scope: react_compiler_ast::scope::ScopeId,
6773    component_scope: react_compiler_ast::scope::ScopeId,
6774    func_start: u32,
6775    func_end: u32,
6776    identifier_locs: &IdentifierLocIndex,
6777    ref_node_ids_override: Option<&IndexSet<u32>>,
6778) -> IndexMap<react_compiler_ast::scope::BindingId, Option<SourceLocation>> {
6779    let parent_scope = scope_info.scopes[function_scope.0 as usize].parent;
6780    let pure_scopes = match parent_scope {
6781        Some(parent) => capture_scopes(scope_info, parent, component_scope),
6782        None => IndexSet::new(),
6783    };
6784
6785    // Collect the earliest (lowest source position) reference location for each
6786    // captured binding. Using the minimum position makes the result independent of
6787    // ref_node_id_to_binding iteration order, matching the behavior the TS compiler
6788    // gets from Babel's position-ordered traversal.
6789    let mut captured: std::collections::HashMap<
6790        react_compiler_ast::scope::BindingId,
6791        (u32, Option<SourceLocation>), // (min_position, loc)
6792    > = std::collections::HashMap::new();
6793
6794    for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding {
6795        if let Some(allowed) = ref_node_ids_override {
6796            if !allowed.contains(&ref_nid) {
6797                continue;
6798            }
6799        } else {
6800            // Range check: use the position stored in identifier_locs
6801            let ref_start = identifier_locs.get(&ref_nid).map(|e| e.start).unwrap_or(0);
6802            if ref_start < func_start || ref_start >= func_end {
6803                continue;
6804            }
6805        }
6806        let binding = &scope_info.bindings[binding_id.0 as usize];
6807        // Skip references that are actually the binding's own declaration site
6808        if binding.declaration_node_id == Some(ref_nid) {
6809            continue;
6810        }
6811        // Skip function/class declaration names that are not expression references.
6812        // Skip type-annotation references: TS's gatherCapturedContext traverse
6813        // skips TypeAnnotation/TSTypeAnnotation/TypeAlias/TSTypeAliasDeclaration
6814        // subtrees, so identifiers there never become captures (they DO still
6815        // feed FindContextIdentifiers and the hoisting analysis, which have no
6816        // such skip in TS).
6817        if let Some(entry) = identifier_locs.get(&ref_nid) {
6818            if entry.is_declaration_name || entry.in_type_annotation {
6819                continue;
6820            }
6821        }
6822        // Skip type-only bindings
6823        if binding.declaration_type == "TypeAlias"
6824            || binding.declaration_type == "OpaqueType"
6825            || binding.declaration_type == "InterfaceDeclaration"
6826            || binding.declaration_type == "TSTypeAliasDeclaration"
6827            || binding.declaration_type == "TSInterfaceDeclaration"
6828            || binding.declaration_type == "TSEnumDeclaration"
6829        {
6830            continue;
6831        }
6832        if pure_scopes.contains(&binding.scope) {
6833            let ref_start = identifier_locs.get(&ref_nid).map(|e| e.start).unwrap_or(0);
6834            // Skip references whose start offset aliases the binding's own
6835            // declaration offset. Hermes desugars (component syntax) reuse the
6836            // original source offsets for generated nodes, so a sibling
6837            // reference structurally OUTSIDE this function (e.g. the forwardRef
6838            // argument naming the desugared inner function) can fall inside the
6839            // function's position range and alias the declaration position. In
6840            // real source a non-declaration reference can never share its
6841            // declaration's offset, so this only filters desugared aliases.
6842            if binding.declaration_start == Some(ref_start) {
6843                continue;
6844            }
6845            let loc = identifier_locs.get(&ref_nid).map(|entry| {
6846                if let Some(oe_loc) = &entry.opening_element_loc {
6847                    oe_loc.clone()
6848                } else {
6849                    entry.loc.clone()
6850                }
6851            });
6852            captured
6853                .entry(binding.id)
6854                .and_modify(|(min_pos, existing_loc)| {
6855                    if ref_start < *min_pos {
6856                        *min_pos = ref_start;
6857                        *existing_loc = loc.clone();
6858                    }
6859                })
6860                .or_insert((ref_start, loc));
6861        }
6862    }
6863
6864    // Sort captured entries by source position so context declarations appear
6865    // in source order, matching the TS compiler's position-ordered traversal.
6866    let mut sorted: Vec<_> = captured.into_iter().collect();
6867    sorted.sort_by_key(|(_, (pos, _))| *pos);
6868
6869    sorted
6870        .into_iter()
6871        .map(|(bid, (_, loc))| (bid, loc))
6872        .collect()
6873}
6874
6875fn capture_scopes(
6876    scope_info: &ScopeInfo,
6877    from: react_compiler_ast::scope::ScopeId,
6878    to: react_compiler_ast::scope::ScopeId,
6879) -> IndexSet<react_compiler_ast::scope::ScopeId> {
6880    let mut result = IndexSet::new();
6881    let mut current = Some(from);
6882    while let Some(scope_id) = current {
6883        result.insert(scope_id);
6884        if scope_id == to {
6885            break;
6886        }
6887        current = scope_info.scopes[scope_id.0 as usize].parent;
6888    }
6889    result
6890}
6891
6892/// The style of assignment (used internally by lower_assignment).
6893#[derive(Clone, Copy)]
6894pub enum AssignmentStyle {
6895    /// Assignment via `=`
6896    Assignment,
6897    /// Destructuring assignment
6898    Destructure,
6899}
6900
6901/// Collect locations of fbt:enum, fbt:plural, fbt:pronoun sub-tags
6902/// within the children of an fbt/fbs JSX element.
6903fn collect_fbt_sub_tags(
6904    children: &[react_compiler_ast::jsx::JSXChild],
6905    tag_name: &str,
6906    enum_locs: &mut Vec<Option<SourceLocation>>,
6907    plural_locs: &mut Vec<Option<SourceLocation>>,
6908    pronoun_locs: &mut Vec<Option<SourceLocation>>,
6909) {
6910    use react_compiler_ast::jsx::JSXChild;
6911    for child in children {
6912        match child {
6913            JSXChild::JSXElement(el) => {
6914                collect_fbt_sub_tags_from_element(
6915                    el,
6916                    tag_name,
6917                    enum_locs,
6918                    plural_locs,
6919                    pronoun_locs,
6920                );
6921            }
6922            JSXChild::JSXFragment(frag) => {
6923                collect_fbt_sub_tags(
6924                    &frag.children,
6925                    tag_name,
6926                    enum_locs,
6927                    plural_locs,
6928                    pronoun_locs,
6929                );
6930            }
6931            JSXChild::JSXExpressionContainer(container) => {
6932                if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(expr) =
6933                    &container.expression
6934                {
6935                    collect_fbt_sub_tags_from_expr(
6936                        expr,
6937                        tag_name,
6938                        enum_locs,
6939                        plural_locs,
6940                        pronoun_locs,
6941                    );
6942                }
6943            }
6944            _ => {}
6945        }
6946    }
6947}
6948
6949fn collect_fbt_sub_tags_from_element(
6950    el: &react_compiler_ast::jsx::JSXElement,
6951    tag_name: &str,
6952    enum_locs: &mut Vec<Option<SourceLocation>>,
6953    plural_locs: &mut Vec<Option<SourceLocation>>,
6954    pronoun_locs: &mut Vec<Option<SourceLocation>>,
6955) {
6956    use react_compiler_ast::jsx::JSXElementName;
6957    if let JSXElementName::JSXNamespacedName(ns) = &el.opening_element.name {
6958        if ns.namespace.name == tag_name {
6959            let loc = convert_opt_loc(&ns.base.loc);
6960            match ns.name.name.as_str() {
6961                "enum" => enum_locs.push(loc),
6962                "plural" => plural_locs.push(loc),
6963                "pronoun" => pronoun_locs.push(loc),
6964                _ => {}
6965            }
6966        }
6967    }
6968    collect_fbt_sub_tags(&el.children, tag_name, enum_locs, plural_locs, pronoun_locs);
6969    // Also traverse JSX attributes (matching TS expr.traverse which visits all nodes)
6970    for attr in &el.opening_element.attributes {
6971        if let react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) = attr {
6972            if let Some(val) = &a.value {
6973                if let react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(
6974                    container,
6975                ) = val
6976                {
6977                    if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(expr) =
6978                        &container.expression
6979                    {
6980                        collect_fbt_sub_tags_from_expr(
6981                            expr,
6982                            tag_name,
6983                            enum_locs,
6984                            plural_locs,
6985                            pronoun_locs,
6986                        );
6987                    }
6988                } else if let react_compiler_ast::jsx::JSXAttributeValue::JSXElement(nested) = val {
6989                    collect_fbt_sub_tags_from_element(
6990                        nested,
6991                        tag_name,
6992                        enum_locs,
6993                        plural_locs,
6994                        pronoun_locs,
6995                    );
6996                }
6997            }
6998        }
6999    }
7000}
7001
7002fn collect_fbt_sub_tags_from_expr(
7003    expr: &react_compiler_ast::expressions::Expression,
7004    tag_name: &str,
7005    enum_locs: &mut Vec<Option<SourceLocation>>,
7006    plural_locs: &mut Vec<Option<SourceLocation>>,
7007    pronoun_locs: &mut Vec<Option<SourceLocation>>,
7008) {
7009    use react_compiler_ast::expressions::Expression;
7010    match expr {
7011        Expression::JSXElement(el) => {
7012            collect_fbt_sub_tags_from_element(el, tag_name, enum_locs, plural_locs, pronoun_locs);
7013        }
7014        Expression::JSXFragment(frag) => {
7015            collect_fbt_sub_tags(
7016                &frag.children,
7017                tag_name,
7018                enum_locs,
7019                plural_locs,
7020                pronoun_locs,
7021            );
7022        }
7023        Expression::ConditionalExpression(cond) => {
7024            collect_fbt_sub_tags_from_expr(
7025                &cond.consequent,
7026                tag_name,
7027                enum_locs,
7028                plural_locs,
7029                pronoun_locs,
7030            );
7031            collect_fbt_sub_tags_from_expr(
7032                &cond.alternate,
7033                tag_name,
7034                enum_locs,
7035                plural_locs,
7036                pronoun_locs,
7037            );
7038        }
7039        Expression::LogicalExpression(log) => {
7040            collect_fbt_sub_tags_from_expr(
7041                &log.left,
7042                tag_name,
7043                enum_locs,
7044                plural_locs,
7045                pronoun_locs,
7046            );
7047            collect_fbt_sub_tags_from_expr(
7048                &log.right,
7049                tag_name,
7050                enum_locs,
7051                plural_locs,
7052                pronoun_locs,
7053            );
7054        }
7055        Expression::ParenthesizedExpression(paren) => {
7056            collect_fbt_sub_tags_from_expr(
7057                &paren.expression,
7058                tag_name,
7059                enum_locs,
7060                plural_locs,
7061                pronoun_locs,
7062            );
7063        }
7064        Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_ref() {
7065            react_compiler_ast::expressions::ArrowFunctionBody::Expression(body_expr) => {
7066                collect_fbt_sub_tags_from_expr(
7067                    body_expr,
7068                    tag_name,
7069                    enum_locs,
7070                    plural_locs,
7071                    pronoun_locs,
7072                );
7073            }
7074            react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
7075                collect_fbt_sub_tags_from_stmts(
7076                    &block.body,
7077                    tag_name,
7078                    enum_locs,
7079                    plural_locs,
7080                    pronoun_locs,
7081                );
7082            }
7083        },
7084        Expression::CallExpression(call) => {
7085            for arg in &call.arguments {
7086                collect_fbt_sub_tags_from_expr(arg, tag_name, enum_locs, plural_locs, pronoun_locs);
7087            }
7088        }
7089        _ => {}
7090    }
7091}
7092
7093fn collect_fbt_sub_tags_from_stmts(
7094    stmts: &[react_compiler_ast::statements::Statement],
7095    tag_name: &str,
7096    enum_locs: &mut Vec<Option<SourceLocation>>,
7097    plural_locs: &mut Vec<Option<SourceLocation>>,
7098    pronoun_locs: &mut Vec<Option<SourceLocation>>,
7099) {
7100    for stmt in stmts {
7101        if let react_compiler_ast::statements::Statement::ReturnStatement(ret) = stmt {
7102            if let Some(arg) = &ret.argument {
7103                collect_fbt_sub_tags_from_expr(arg, tag_name, enum_locs, plural_locs, pronoun_locs);
7104            }
7105        } else if let react_compiler_ast::statements::Statement::ExpressionStatement(expr_stmt) =
7106            stmt
7107        {
7108            collect_fbt_sub_tags_from_expr(
7109                &expr_stmt.expression,
7110                tag_name,
7111                enum_locs,
7112                plural_locs,
7113                pronoun_locs,
7114            );
7115        }
7116    }
7117}
7118
7119fn collect_identifier_node_ids_from_body(body: &FunctionBody) -> IndexSet<u32> {
7120    let mut positions = IndexSet::new();
7121    match body {
7122        FunctionBody::Block(block) => {
7123            for stmt in &block.body {
7124                collect_identifier_node_ids_from_stmt(stmt, &mut positions);
7125            }
7126        }
7127        FunctionBody::Expression(expr) => {
7128            collect_identifier_node_ids_from_expr(expr, &mut positions);
7129        }
7130    }
7131    positions
7132}
7133
7134fn collect_identifier_node_ids_from_stmt(
7135    stmt: &react_compiler_ast::statements::Statement,
7136    positions: &mut IndexSet<u32>,
7137) {
7138    use react_compiler_ast::statements::Statement;
7139    match stmt {
7140        Statement::ExpressionStatement(s) => {
7141            collect_identifier_node_ids_from_expr(&s.expression, positions)
7142        }
7143        Statement::ReturnStatement(s) => {
7144            if let Some(arg) = &s.argument {
7145                collect_identifier_node_ids_from_expr(arg, positions);
7146            }
7147        }
7148        Statement::ThrowStatement(s) => {
7149            collect_identifier_node_ids_from_expr(&s.argument, positions)
7150        }
7151        Statement::BlockStatement(s) => {
7152            for stmt in &s.body {
7153                collect_identifier_node_ids_from_stmt(stmt, positions);
7154            }
7155        }
7156        Statement::IfStatement(s) => {
7157            collect_identifier_node_ids_from_expr(&s.test, positions);
7158            collect_identifier_node_ids_from_stmt(&s.consequent, positions);
7159            if let Some(alt) = &s.alternate {
7160                collect_identifier_node_ids_from_stmt(alt, positions);
7161            }
7162        }
7163        Statement::VariableDeclaration(s) => {
7164            for decl in &s.declarations {
7165                if let Some(init) = &decl.init {
7166                    collect_identifier_node_ids_from_expr(init, positions);
7167                }
7168            }
7169        }
7170        _ => {}
7171    }
7172}
7173
7174fn collect_identifier_node_ids_from_expr(
7175    expr: &react_compiler_ast::expressions::Expression,
7176    positions: &mut IndexSet<u32>,
7177) {
7178    use react_compiler_ast::expressions::Expression;
7179    match expr {
7180        Expression::Identifier(id) => {
7181            if let Some(nid) = id.base.node_id {
7182                positions.insert(nid);
7183            }
7184        }
7185        Expression::CallExpression(call) => {
7186            collect_identifier_node_ids_from_expr(&call.callee, positions);
7187            for arg in &call.arguments {
7188                collect_identifier_node_ids_from_expr(arg, positions);
7189            }
7190        }
7191        Expression::BinaryExpression(e) => {
7192            collect_identifier_node_ids_from_expr(&e.left, positions);
7193            collect_identifier_node_ids_from_expr(&e.right, positions);
7194        }
7195        Expression::ConditionalExpression(e) => {
7196            collect_identifier_node_ids_from_expr(&e.test, positions);
7197            collect_identifier_node_ids_from_expr(&e.consequent, positions);
7198            collect_identifier_node_ids_from_expr(&e.alternate, positions);
7199        }
7200        Expression::LogicalExpression(e) => {
7201            collect_identifier_node_ids_from_expr(&e.left, positions);
7202            collect_identifier_node_ids_from_expr(&e.right, positions);
7203        }
7204        Expression::MemberExpression(e) => {
7205            collect_identifier_node_ids_from_expr(&e.object, positions);
7206        }
7207        Expression::OptionalMemberExpression(e) => {
7208            collect_identifier_node_ids_from_expr(&e.object, positions);
7209        }
7210        Expression::OptionalCallExpression(e) => {
7211            collect_identifier_node_ids_from_expr(&e.callee, positions);
7212            for arg in &e.arguments {
7213                collect_identifier_node_ids_from_expr(arg, positions);
7214            }
7215        }
7216        Expression::UpdateExpression(e) => {
7217            collect_identifier_node_ids_from_expr(&e.argument, positions);
7218        }
7219        Expression::FunctionExpression(func) => {
7220            for stmt in &func.body.body {
7221                collect_identifier_node_ids_from_stmt(stmt, positions);
7222            }
7223        }
7224        Expression::UnaryExpression(e) => {
7225            collect_identifier_node_ids_from_expr(&e.argument, positions);
7226        }
7227        Expression::ParenthesizedExpression(e) => {
7228            collect_identifier_node_ids_from_expr(&e.expression, positions);
7229        }
7230        Expression::TypeCastExpression(e) => {
7231            collect_identifier_node_ids_from_expr(&e.expression, positions);
7232        }
7233        Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_ref() {
7234            react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
7235                for stmt in &block.body {
7236                    collect_identifier_node_ids_from_stmt(stmt, positions);
7237                }
7238            }
7239            react_compiler_ast::expressions::ArrowFunctionBody::Expression(e) => {
7240                collect_identifier_node_ids_from_expr(e, positions);
7241            }
7242        },
7243        Expression::JSXElement(el) => {
7244            if let react_compiler_ast::jsx::JSXElementName::JSXIdentifier(id) =
7245                &el.opening_element.name
7246            {
7247                if let Some(nid) = id.base.node_id {
7248                    positions.insert(nid);
7249                }
7250            }
7251            for attr in &el.opening_element.attributes {
7252                match attr {
7253                    react_compiler_ast::jsx::JSXAttributeItem::JSXAttribute(a) => {
7254                        if let Some(val) = &a.value {
7255                            match val {
7256                                react_compiler_ast::jsx::JSXAttributeValue::JSXExpressionContainer(c) => {
7257                                    if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) = &c.expression {
7258                                        collect_identifier_node_ids_from_expr(e, positions);
7259                                    }
7260                                }
7261                                _ => {}
7262                            }
7263                        }
7264                    }
7265                    react_compiler_ast::jsx::JSXAttributeItem::JSXSpreadAttribute(a) => {
7266                        collect_identifier_node_ids_from_expr(&a.argument, positions);
7267                    }
7268                }
7269            }
7270            for child in &el.children {
7271                match child {
7272                    react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => {
7273                        if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) =
7274                            &c.expression
7275                        {
7276                            collect_identifier_node_ids_from_expr(e, positions);
7277                        }
7278                    }
7279                    react_compiler_ast::jsx::JSXChild::JSXElement(child_el) => {
7280                        collect_identifier_node_ids_from_expr(
7281                            &Expression::JSXElement(child_el.clone()),
7282                            positions,
7283                        );
7284                    }
7285                    react_compiler_ast::jsx::JSXChild::JSXSpreadChild(s) => {
7286                        collect_identifier_node_ids_from_expr(&s.expression, positions);
7287                    }
7288                    _ => {}
7289                }
7290            }
7291        }
7292        Expression::JSXFragment(frag) => {
7293            for child in &frag.children {
7294                match child {
7295                    react_compiler_ast::jsx::JSXChild::JSXExpressionContainer(c) => {
7296                        if let react_compiler_ast::jsx::JSXExpressionContainerExpr::Expression(e) =
7297                            &c.expression
7298                        {
7299                            collect_identifier_node_ids_from_expr(e, positions);
7300                        }
7301                    }
7302                    react_compiler_ast::jsx::JSXChild::JSXElement(child_el) => {
7303                        collect_identifier_node_ids_from_expr(
7304                            &Expression::JSXElement(child_el.clone()),
7305                            positions,
7306                        );
7307                    }
7308                    _ => {}
7309                }
7310            }
7311        }
7312        Expression::ArrayExpression(arr) => {
7313            for elem in &arr.elements {
7314                if let Some(e) = elem {
7315                    collect_identifier_node_ids_from_expr(e, positions);
7316                }
7317            }
7318        }
7319        Expression::ObjectExpression(obj) => {
7320            for prop in &obj.properties {
7321                match prop {
7322                    react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(
7323                        p,
7324                    ) => {
7325                        collect_identifier_node_ids_from_expr(&p.value, positions);
7326                    }
7327                    react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement(s) => {
7328                        collect_identifier_node_ids_from_expr(&s.argument, positions);
7329                    }
7330                    _ => {}
7331                }
7332            }
7333        }
7334        Expression::NewExpression(e) => {
7335            collect_identifier_node_ids_from_expr(&e.callee, positions);
7336            for arg in &e.arguments {
7337                collect_identifier_node_ids_from_expr(arg, positions);
7338            }
7339        }
7340        Expression::AssignmentExpression(e) => {
7341            collect_identifier_node_ids_from_expr(&e.right, positions);
7342        }
7343        Expression::TemplateLiteral(e) => {
7344            for expr in &e.expressions {
7345                collect_identifier_node_ids_from_expr(expr, positions);
7346            }
7347        }
7348        Expression::SpreadElement(e) => {
7349            collect_identifier_node_ids_from_expr(&e.argument, positions);
7350        }
7351        Expression::SequenceExpression(e) => {
7352            for expr in &e.expressions {
7353                collect_identifier_node_ids_from_expr(expr, positions);
7354            }
7355        }
7356        _ => {}
7357    }
7358}