main
rs 7,390 lines 316 KB
Raw
1 use rustc_hash::{FxBuildHasher, FxHashSet};
2
3 use indexmap::{IndexMap, IndexSet};
4 use react_compiler_ast::scope::BindingId;
5 use react_compiler_ast::scope::BindingKind as AstBindingKind;
6 use react_compiler_ast::scope::ScopeId;
7 use react_compiler_ast::scope::ScopeInfo;
8 use react_compiler_ast::scope::ScopeKind;
9 use react_compiler_diagnostics::CompilerDiagnostic;
10 use react_compiler_diagnostics::CompilerDiagnosticDetail;
11 use react_compiler_diagnostics::CompilerError;
12 use react_compiler_diagnostics::CompilerErrorDetail;
13 use react_compiler_diagnostics::ErrorCategory;
14 use react_compiler_hir::environment::Environment;
15 use react_compiler_hir::*;
16
17 use crate::FunctionNode;
18 use crate::find_context_identifiers::find_context_identifiers;
19 use crate::hir_builder::HirBuilder;
20 use crate::hir_builder::is_always_reserved_word;
21 use crate::hir_builder::reserved_identifier_diagnostic;
22 use crate::identifier_loc_index::IdentifierLocIndex;
23 use crate::identifier_loc_index::build_identifier_loc_index;
24
25 // =============================================================================
26 // Source location conversion
27 // =============================================================================
28
29 /// Convert an AST SourceLocation to an HIR SourceLocation.
30 fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
31 SourceLocation {
32 start: Position {
33 line: loc.start.line,
34 column: loc.start.column,
35 index: loc.start.index,
36 },
37 end: Position {
38 line: loc.end.line,
39 column: loc.end.column,
40 index: loc.end.index,
41 },
42 }
43 }
44
45 /// Convert an optional AST SourceLocation to an optional HIR SourceLocation.
46 fn convert_opt_loc(
47 loc: &Option<react_compiler_ast::common::SourceLocation>,
48 ) -> Option<SourceLocation> {
49 loc.as_ref().map(convert_loc)
50 }
51
52 /// Serialize an expression to a serde_json::Value for UnsupportedNode's original_node.
53 /// Returns None if serialization fails (should not happen for valid AST nodes).
54 /// This should ONLY be called on error/bail paths — never eagerly before deciding
55 /// to create an UnsupportedNode.
56 fn serialize_expression(
57 expr: &react_compiler_ast::expressions::Expression,
58 ) -> Option<serde_json::Value> {
59 serde_json::to_value(expr).ok()
60 }
61
62 /// Serialize a statement to a serde_json::Value for UnsupportedNode's original_node.
63 fn serialize_statement(
64 stmt: &react_compiler_ast::statements::Statement,
65 ) -> Option<serde_json::Value> {
66 serde_json::to_value(stmt).ok()
67 }
68
69 /// Serialize a pattern to a serde_json::Value for UnsupportedNode's original_node.
70 fn serialize_pattern(pat: &react_compiler_ast::patterns::PatternLike) -> Option<serde_json::Value> {
71 serde_json::to_value(pat).ok()
72 }
73
74 fn pattern_like_loc(
75 pattern: &react_compiler_ast::patterns::PatternLike,
76 ) -> Option<react_compiler_ast::common::SourceLocation> {
77 use react_compiler_ast::patterns::PatternLike;
78 match pattern {
79 PatternLike::Identifier(id) => id.base.loc.clone(),
80 PatternLike::ObjectPattern(p) => p.base.loc.clone(),
81 PatternLike::ArrayPattern(p) => p.base.loc.clone(),
82 PatternLike::AssignmentPattern(p) => p.base.loc.clone(),
83 PatternLike::RestElement(p) => p.base.loc.clone(),
84 PatternLike::MemberExpression(p) => p.base.loc.clone(),
85 PatternLike::TSAsExpression(p) => p.base.loc.clone(),
86 PatternLike::TSSatisfiesExpression(p) => p.base.loc.clone(),
87 PatternLike::TSNonNullExpression(p) => p.base.loc.clone(),
88 PatternLike::TSTypeAssertion(p) => p.base.loc.clone(),
89 PatternLike::TypeCastExpression(p) => p.base.loc.clone(),
90 }
91 }
92
93 /// Extract the HIR SourceLocation from an Expression AST node.
94 fn expression_loc(expr: &react_compiler_ast::expressions::Expression) -> Option<SourceLocation> {
95 use react_compiler_ast::expressions::Expression;
96 let loc = match expr {
97 Expression::Identifier(e) => e.base.loc.clone(),
98 Expression::StringLiteral(e) => e.base.loc.clone(),
99 Expression::NumericLiteral(e) => e.base.loc.clone(),
100 Expression::BooleanLiteral(e) => e.base.loc.clone(),
101 Expression::NullLiteral(e) => e.base.loc.clone(),
102 Expression::BigIntLiteral(e) => e.base.loc.clone(),
103 Expression::RegExpLiteral(e) => e.base.loc.clone(),
104 Expression::CallExpression(e) => e.base.loc.clone(),
105 Expression::MemberExpression(e) => e.base.loc.clone(),
106 Expression::OptionalCallExpression(e) => e.base.loc.clone(),
107 Expression::OptionalMemberExpression(e) => e.base.loc.clone(),
108 Expression::BinaryExpression(e) => e.base.loc.clone(),
109 Expression::LogicalExpression(e) => e.base.loc.clone(),
110 Expression::UnaryExpression(e) => e.base.loc.clone(),
111 Expression::UpdateExpression(e) => e.base.loc.clone(),
112 Expression::ConditionalExpression(e) => e.base.loc.clone(),
113 Expression::AssignmentExpression(e) => e.base.loc.clone(),
114 Expression::SequenceExpression(e) => e.base.loc.clone(),
115 Expression::ArrowFunctionExpression(e) => e.base.loc.clone(),
116 Expression::FunctionExpression(e) => e.base.loc.clone(),
117 Expression::ObjectExpression(e) => e.base.loc.clone(),
118 Expression::ArrayExpression(e) => e.base.loc.clone(),
119 Expression::NewExpression(e) => e.base.loc.clone(),
120 Expression::TemplateLiteral(e) => e.base.loc.clone(),
121 Expression::TaggedTemplateExpression(e) => e.base.loc.clone(),
122 Expression::AwaitExpression(e) => e.base.loc.clone(),
123 Expression::YieldExpression(e) => e.base.loc.clone(),
124 Expression::SpreadElement(e) => e.base.loc.clone(),
125 Expression::MetaProperty(e) => e.base.loc.clone(),
126 Expression::ClassExpression(e) => e.base.loc.clone(),
127 Expression::PrivateName(e) => e.base.loc.clone(),
128 Expression::Super(e) => e.base.loc.clone(),
129 Expression::Import(e) => e.base.loc.clone(),
130 Expression::ThisExpression(e) => e.base.loc.clone(),
131 Expression::ParenthesizedExpression(e) => e.base.loc.clone(),
132 Expression::JSXElement(e) => e.base.loc.clone(),
133 Expression::JSXFragment(e) => e.base.loc.clone(),
134 Expression::AssignmentPattern(e) => e.base.loc.clone(),
135 Expression::TSAsExpression(e) => e.base.loc.clone(),
136 Expression::TSSatisfiesExpression(e) => e.base.loc.clone(),
137 Expression::TSNonNullExpression(e) => e.base.loc.clone(),
138 Expression::TSTypeAssertion(e) => e.base.loc.clone(),
139 Expression::TSInstantiationExpression(e) => e.base.loc.clone(),
140 Expression::TypeCastExpression(e) => e.base.loc.clone(),
141 };
142 convert_opt_loc(&loc)
143 }
144
145 fn validate_ts_this_parameter(
146 scope_info: &ScopeInfo,
147 function_scope: ScopeId,
148 ) -> Result<(), CompilerError> {
149 let Some(scope) = scope_info.scopes.get(function_scope.0 as usize) else {
150 return Ok(());
151 };
152 let Some(binding_id) = scope.bindings.get("this") else {
153 return Ok(());
154 };
155 let Some(binding) = scope_info.bindings.get(binding_id.0 as usize) else {
156 return Ok(());
157 };
158 if matches!(binding.kind, AstBindingKind::Param) {
159 return Err(CompilerError::from(reserved_identifier_diagnostic("this")));
160 }
161 Ok(())
162 }
163
164 fn is_class_scope_descendant(scope_info: &ScopeInfo, mut scope_id: ScopeId) -> bool {
165 while let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) {
166 let Some(parent) = scope.parent else {
167 return false;
168 };
169 let Some(parent_scope) = scope_info.scopes.get(parent.0 as usize) else {
170 return false;
171 };
172 if matches!(parent_scope.kind, ScopeKind::Class) {
173 return true;
174 }
175 scope_id = parent;
176 }
177 false
178 }
179
180 fn validate_ts_this_parameters_in_function_range(
181 scope_info: &ScopeInfo,
182 start: u32,
183 end: u32,
184 ) -> Result<(), CompilerError> {
185 if start >= end {
186 return Ok(());
187 }
188 for (node_start, scope_id) in &scope_info.node_to_scope {
189 if *node_start < start || *node_start >= end {
190 continue;
191 }
192 let Some(scope) = scope_info.scopes.get(scope_id.0 as usize) else {
193 continue;
194 };
195 if !matches!(scope.kind, ScopeKind::Function)
196 || is_class_scope_descendant(scope_info, *scope_id)
197 {
198 continue;
199 }
200 validate_ts_this_parameter(scope_info, *scope_id)?;
201 }
202 Ok(())
203 }
204
205 /// Get the Babel-style type name of an Expression node (e.g. "Identifier", "NumericLiteral").
206 fn expression_type_name(expr: &react_compiler_ast::expressions::Expression) -> &'static str {
207 use react_compiler_ast::expressions::Expression;
208 match expr {
209 Expression::Identifier(_) => "Identifier",
210 Expression::StringLiteral(_) => "StringLiteral",
211 Expression::NumericLiteral(_) => "NumericLiteral",
212 Expression::BooleanLiteral(_) => "BooleanLiteral",
213 Expression::NullLiteral(_) => "NullLiteral",
214 Expression::BigIntLiteral(_) => "BigIntLiteral",
215 Expression::RegExpLiteral(_) => "RegExpLiteral",
216 Expression::CallExpression(_) => "CallExpression",
217 Expression::MemberExpression(_) => "MemberExpression",
218 Expression::OptionalCallExpression(_) => "OptionalCallExpression",
219 Expression::OptionalMemberExpression(_) => "OptionalMemberExpression",
220 Expression::BinaryExpression(_) => "BinaryExpression",
221 Expression::LogicalExpression(_) => "LogicalExpression",
222 Expression::UnaryExpression(_) => "UnaryExpression",
223 Expression::UpdateExpression(_) => "UpdateExpression",
224 Expression::ConditionalExpression(_) => "ConditionalExpression",
225 Expression::AssignmentExpression(_) => "AssignmentExpression",
226 Expression::SequenceExpression(_) => "SequenceExpression",
227 Expression::ArrowFunctionExpression(_) => "ArrowFunctionExpression",
228 Expression::FunctionExpression(_) => "FunctionExpression",
229 Expression::ObjectExpression(_) => "ObjectExpression",
230 Expression::ArrayExpression(_) => "ArrayExpression",
231 Expression::NewExpression(_) => "NewExpression",
232 Expression::TemplateLiteral(_) => "TemplateLiteral",
233 Expression::TaggedTemplateExpression(_) => "TaggedTemplateExpression",
234 Expression::AwaitExpression(_) => "AwaitExpression",
235 Expression::YieldExpression(_) => "YieldExpression",
236 Expression::SpreadElement(_) => "SpreadElement",
237 Expression::MetaProperty(_) => "MetaProperty",
238 Expression::ClassExpression(_) => "ClassExpression",
239 Expression::PrivateName(_) => "PrivateName",
240 Expression::Super(_) => "Super",
241 Expression::Import(_) => "Import",
242 Expression::ThisExpression(_) => "ThisExpression",
243 Expression::ParenthesizedExpression(_) => "ParenthesizedExpression",
244 Expression::JSXElement(_) => "JSXElement",
245 Expression::JSXFragment(_) => "JSXFragment",
246 Expression::AssignmentPattern(_) => "AssignmentPattern",
247 Expression::TSAsExpression(_) => "TSAsExpression",
248 Expression::TSSatisfiesExpression(_) => "TSSatisfiesExpression",
249 Expression::TSNonNullExpression(_) => "TSNonNullExpression",
250 Expression::TSTypeAssertion(_) => "TSTypeAssertion",
251 Expression::TSInstantiationExpression(_) => "TSInstantiationExpression",
252 Expression::TypeCastExpression(_) => "TypeCastExpression",
253 }
254 }
255
256 /// Extract the type annotation name from an identifier's typeAnnotation field.
257 /// The Babel AST stores type annotations as:
258 /// { "type": "TSTypeAnnotation", "typeAnnotation": { "type": "TSTypeReference", ... } }
259 /// or { "type": "TypeAnnotation", "typeAnnotation": { "type": "GenericTypeAnnotation", ... } }
260 /// We extract the inner typeAnnotation's `type` field name.
261 fn extract_type_annotation_name(
262 type_annotation: &Option<react_compiler_ast::common::RawNode>,
263 ) -> Option<String> {
264 let val = type_annotation.as_ref()?.parse_value();
265 // Navigate: typeAnnotation.typeAnnotation.type
266 let inner = val.get("typeAnnotation")?;
267 let type_name = inner.get("type")?.as_str()?;
268 Some(type_name.to_string())
269 }
270
271 // =============================================================================
272 // Helper functions
273 // =============================================================================
274
275 fn build_temporary_place(builder: &mut HirBuilder, loc: Option<SourceLocation>) -> Place {
276 let id = builder.make_temporary(loc.clone());
277 Place {
278 identifier: id,
279 reactive: false,
280 effect: Effect::Unknown,
281 loc,
282 }
283 }
284
285 /// Promote a temporary identifier to a named identifier (for destructuring).
286 /// Corresponds to TS `promoteTemporary(identifier)`.
287 fn promote_temporary(builder: &mut HirBuilder, identifier_id: IdentifierId) {
288 let env = builder.environment_mut();
289 let decl_id = env.identifiers[identifier_id.0 as usize].declaration_id;
290 env.identifiers[identifier_id.0 as usize].name =
291 Some(IdentifierName::Promoted(format!("#t{}", decl_id.0)));
292 }
293
294 fn lower_value_to_temporary(
295 builder: &mut HirBuilder,
296 value: InstructionValue,
297 ) -> Result<Place, CompilerError> {
298 // Optimization: if loading an unnamed temporary, skip creating a new instruction
299 if let InstructionValue::LoadLocal { ref place, .. } = value {
300 let ident = &builder.environment().identifiers[place.identifier.0 as usize];
301 if ident.name.is_none() {
302 return Ok(place.clone());
303 }
304 }
305 let loc = value.loc().cloned();
306 let place = build_temporary_place(builder, loc.clone());
307 builder.push(Instruction {
308 id: EvaluationOrder(0),
309 lvalue: place.clone(),
310 value,
311 loc,
312 effects: None,
313 });
314 Ok(place)
315 }
316
317 fn lower_expression_to_temporary(
318 builder: &mut HirBuilder,
319 expr: &react_compiler_ast::expressions::Expression,
320 ) -> Result<Place, CompilerError> {
321 let value = lower_expression(builder, expr)?;
322 Ok(lower_value_to_temporary(builder, value)?)
323 }
324
325 // =============================================================================
326 // Operator conversion
327 // =============================================================================
328
329 fn convert_binary_operator(op: &react_compiler_ast::operators::BinaryOperator) -> BinaryOperator {
330 use react_compiler_ast::operators::BinaryOperator as AstOp;
331 match op {
332 AstOp::Add => BinaryOperator::Add,
333 AstOp::Sub => BinaryOperator::Subtract,
334 AstOp::Mul => BinaryOperator::Multiply,
335 AstOp::Div => BinaryOperator::Divide,
336 AstOp::Rem => BinaryOperator::Modulo,
337 AstOp::Exp => BinaryOperator::Exponent,
338 AstOp::Eq => BinaryOperator::Equal,
339 AstOp::StrictEq => BinaryOperator::StrictEqual,
340 AstOp::Neq => BinaryOperator::NotEqual,
341 AstOp::StrictNeq => BinaryOperator::StrictNotEqual,
342 AstOp::Lt => BinaryOperator::LessThan,
343 AstOp::Lte => BinaryOperator::LessEqual,
344 AstOp::Gt => BinaryOperator::GreaterThan,
345 AstOp::Gte => BinaryOperator::GreaterEqual,
346 AstOp::Shl => BinaryOperator::ShiftLeft,
347 AstOp::Shr => BinaryOperator::ShiftRight,
348 AstOp::UShr => BinaryOperator::UnsignedShiftRight,
349 AstOp::BitOr => BinaryOperator::BitwiseOr,
350 AstOp::BitXor => BinaryOperator::BitwiseXor,
351 AstOp::BitAnd => BinaryOperator::BitwiseAnd,
352 AstOp::In => BinaryOperator::In,
353 AstOp::Instanceof => BinaryOperator::InstanceOf,
354 AstOp::Pipeline => {
355 unreachable!("Pipeline operator is checked before calling convert_binary_operator")
356 }
357 }
358 }
359
360 fn convert_unary_operator(op: &react_compiler_ast::operators::UnaryOperator) -> UnaryOperator {
361 use react_compiler_ast::operators::UnaryOperator as AstOp;
362 match op {
363 AstOp::Neg => UnaryOperator::Minus,
364 AstOp::Plus => UnaryOperator::Plus,
365 AstOp::Not => UnaryOperator::Not,
366 AstOp::BitNot => UnaryOperator::BitwiseNot,
367 AstOp::TypeOf => UnaryOperator::TypeOf,
368 AstOp::Void => UnaryOperator::Void,
369 AstOp::Delete | AstOp::Throw => unreachable!("delete/throw handled separately"),
370 }
371 }
372
373 // =============================================================================
374 // lower_identifier
375 // =============================================================================
376
377 /// Resolve an identifier to a Place.
378 ///
379 /// For local/context identifiers, returns a Place referencing the binding's identifier.
380 /// For globals/imports, emits a LoadGlobal instruction and returns the temporary Place.
381 fn lower_identifier(
382 builder: &mut HirBuilder,
383 name: &str,
384 start: u32,
385 loc: Option<SourceLocation>,
386 node_id: Option<u32>,
387 ) -> Result<Place, CompilerError> {
388 let binding = builder.resolve_identifier(name, start, loc.clone(), node_id)?;
389 match binding {
390 VariableBinding::Identifier { identifier, .. } => Ok(Place {
391 identifier,
392 effect: Effect::Unknown,
393 reactive: false,
394 loc,
395 }),
396 _ => {
397 if let VariableBinding::Global { ref name } = binding {
398 if name == "eval" {
399 builder.record_error(CompilerErrorDetail {
400 category: ErrorCategory::UnsupportedSyntax,
401 reason: "The 'eval' function is not supported".to_string(),
402 description: Some(
403 "Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler".to_string(),
404 ),
405 loc: loc.clone(),
406 suggestions: None,
407 })?;
408 }
409 }
410 let non_local_binding = match binding {
411 VariableBinding::Global { name } => NonLocalBinding::Global { name },
412 VariableBinding::ImportDefault { name, module } => {
413 NonLocalBinding::ImportDefault { name, module }
414 }
415 VariableBinding::ImportSpecifier {
416 name,
417 module,
418 imported,
419 } => NonLocalBinding::ImportSpecifier {
420 name,
421 module,
422 imported,
423 },
424 VariableBinding::ImportNamespace { name, module } => {
425 NonLocalBinding::ImportNamespace { name, module }
426 }
427 VariableBinding::ModuleLocal { name } => NonLocalBinding::ModuleLocal { name },
428 VariableBinding::Identifier { .. } => unreachable!(),
429 };
430 let instr_value = InstructionValue::LoadGlobal {
431 binding: non_local_binding,
432 loc: loc.clone(),
433 };
434 Ok(lower_value_to_temporary(builder, instr_value)?)
435 }
436 }
437 }
438
439 // =============================================================================
440 // lower_arguments
441 // =============================================================================
442
443 fn lower_arguments(
444 builder: &mut HirBuilder,
445 args: &[react_compiler_ast::expressions::Expression],
446 ) -> Result<Vec<PlaceOrSpread>, CompilerError> {
447 use react_compiler_ast::expressions::Expression;
448 let mut result = Vec::new();
449 for arg in args {
450 match arg {
451 Expression::SpreadElement(spread) => {
452 let place = lower_expression_to_temporary(builder, &spread.argument)?;
453 result.push(PlaceOrSpread::Spread(SpreadPattern { place }));
454 }
455 _ => {
456 let place = lower_expression_to_temporary(builder, arg)?;
457 result.push(PlaceOrSpread::Place(place));
458 }
459 }
460 }
461 Ok(result)
462 }
463
464 fn convert_update_operator(op: &react_compiler_ast::operators::UpdateOperator) -> UpdateOperator {
465 match op {
466 react_compiler_ast::operators::UpdateOperator::Increment => UpdateOperator::Increment,
467 react_compiler_ast::operators::UpdateOperator::Decrement => UpdateOperator::Decrement,
468 }
469 }
470
471 // =============================================================================
472 // lower_member_expression
473 // =============================================================================
474
475 enum MemberProperty {
476 Literal(PropertyLiteral),
477 Computed(Place),
478 }
479
480 struct LoweredMemberExpression {
481 object: Place,
482 property: MemberProperty,
483 value: InstructionValue,
484 }
485
486 fn lower_member_expression(
487 builder: &mut HirBuilder,
488 member: &react_compiler_ast::expressions::MemberExpression,
489 ) -> Result<LoweredMemberExpression, CompilerError> {
490 Ok(lower_member_expression_impl(builder, member, None)?)
491 }
492
493 fn lower_member_expression_with_object(
494 builder: &mut HirBuilder,
495 member: &react_compiler_ast::expressions::OptionalMemberExpression,
496 lowered_object: Place,
497 ) -> Result<LoweredMemberExpression, CompilerError> {
498 // OptionalMemberExpression has the same shape as MemberExpression for property access
499 use react_compiler_ast::expressions::Expression;
500 let loc = convert_opt_loc(&member.base.loc);
501 let object = lowered_object;
502
503 if !member.computed {
504 let prop_literal = match member.property.as_ref() {
505 Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),
506 Expression::NumericLiteral(lit) => {
507 PropertyLiteral::Number(FloatValue::new(lit.precise_value()))
508 }
509 _ => {
510 builder.record_error(CompilerErrorDetail {
511 category: ErrorCategory::Todo,
512 reason: format!(
513 "(BuildHIR::lowerMemberExpression) Handle {:?} property",
514 member.property
515 ),
516 description: None,
517 loc: loc.clone(),
518 suggestions: None,
519 })?;
520 return Ok(LoweredMemberExpression {
521 object,
522 property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),
523 value: InstructionValue::UnsupportedNode {
524 node_type: Some("OptionalMemberExpression".to_string()),
525 original_node: serialize_expression(
526 &react_compiler_ast::expressions::Expression::OptionalMemberExpression(
527 member.clone(),
528 ),
529 ),
530 loc,
531 },
532 });
533 }
534 };
535 let value = InstructionValue::PropertyLoad {
536 object: object.clone(),
537 property: prop_literal.clone(),
538 loc,
539 };
540 Ok(LoweredMemberExpression {
541 object,
542 property: MemberProperty::Literal(prop_literal),
543 value,
544 })
545 } else {
546 if let Expression::NumericLiteral(lit) = member.property.as_ref() {
547 let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));
548 let value = InstructionValue::PropertyLoad {
549 object: object.clone(),
550 property: prop_literal.clone(),
551 loc,
552 };
553 return Ok(LoweredMemberExpression {
554 object,
555 property: MemberProperty::Literal(prop_literal),
556 value,
557 });
558 }
559 let property = lower_expression_to_temporary(builder, &member.property)?;
560 let value = InstructionValue::ComputedLoad {
561 object: object.clone(),
562 property: property.clone(),
563 loc,
564 };
565 Ok(LoweredMemberExpression {
566 object,
567 property: MemberProperty::Computed(property),
568 value,
569 })
570 }
571 }
572
573 fn lower_member_expression_impl(
574 builder: &mut HirBuilder,
575 member: &react_compiler_ast::expressions::MemberExpression,
576 lowered_object: Option<Place>,
577 ) -> Result<LoweredMemberExpression, CompilerError> {
578 use react_compiler_ast::expressions::Expression;
579 let loc = convert_opt_loc(&member.base.loc);
580 let object = match lowered_object {
581 Some(obj) => obj,
582 None => lower_expression_to_temporary(builder, &member.object)?,
583 };
584
585 if !member.computed {
586 // Non-computed: property must be an identifier or numeric literal
587 let prop_literal = match member.property.as_ref() {
588 Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),
589 Expression::NumericLiteral(lit) => {
590 PropertyLiteral::Number(FloatValue::new(lit.precise_value()))
591 }
592 _ => {
593 builder.record_error(CompilerErrorDetail {
594 category: ErrorCategory::Todo,
595 reason: format!(
596 "(BuildHIR::lowerMemberExpression) Handle {:?} property",
597 member.property
598 ),
599 description: None,
600 loc: loc.clone(),
601 suggestions: None,
602 })?;
603 return Ok(LoweredMemberExpression {
604 object,
605 property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),
606 value: InstructionValue::UnsupportedNode {
607 node_type: Some("MemberExpression".to_string()),
608 original_node: serialize_expression(
609 &react_compiler_ast::expressions::Expression::MemberExpression(
610 member.clone(),
611 ),
612 ),
613 loc,
614 },
615 });
616 }
617 };
618 let value = InstructionValue::PropertyLoad {
619 object: object.clone(),
620 property: prop_literal.clone(),
621 loc,
622 };
623 Ok(LoweredMemberExpression {
624 object,
625 property: MemberProperty::Literal(prop_literal),
626 value,
627 })
628 } else {
629 // Computed: check for numeric literal first (treated as PropertyLoad in TS)
630 if let Expression::NumericLiteral(lit) = member.property.as_ref() {
631 let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));
632 let value = InstructionValue::PropertyLoad {
633 object: object.clone(),
634 property: prop_literal.clone(),
635 loc,
636 };
637 return Ok(LoweredMemberExpression {
638 object,
639 property: MemberProperty::Literal(prop_literal),
640 value,
641 });
642 }
643 // Otherwise lower property to temporary for ComputedLoad
644 let property = lower_expression_to_temporary(builder, &member.property)?;
645 let value = InstructionValue::ComputedLoad {
646 object: object.clone(),
647 property: property.clone(),
648 loc,
649 };
650 Ok(LoweredMemberExpression {
651 object,
652 property: MemberProperty::Computed(property),
653 value,
654 })
655 }
656 }
657
658 // =============================================================================
659 // lower_expression
660 // =============================================================================
661
662 fn lower_expression(
663 builder: &mut HirBuilder,
664 expr: &react_compiler_ast::expressions::Expression,
665 ) -> Result<InstructionValue, CompilerError> {
666 use react_compiler_ast::expressions::Expression;
667
668 match expr {
669 Expression::Identifier(ident) => {
670 let loc = convert_opt_loc(&ident.base.loc);
671 let start = ident.base.start.unwrap_or(0);
672 let place =
673 lower_identifier(builder, &ident.name, start, loc.clone(), ident.base.node_id)?;
674 // Determine LoadLocal vs LoadContext based on context identifier check
675 if builder.is_context_identifier(&ident.name, start, ident.base.node_id) {
676 Ok(InstructionValue::LoadContext { place, loc })
677 } else {
678 Ok(InstructionValue::LoadLocal { place, loc })
679 }
680 }
681 Expression::NullLiteral(lit) => {
682 let loc = convert_opt_loc(&lit.base.loc);
683 Ok(InstructionValue::Primitive {
684 value: PrimitiveValue::Null,
685 loc,
686 })
687 }
688 Expression::BooleanLiteral(lit) => {
689 let loc = convert_opt_loc(&lit.base.loc);
690 Ok(InstructionValue::Primitive {
691 value: PrimitiveValue::Boolean(lit.value),
692 loc,
693 })
694 }
695 Expression::NumericLiteral(lit) => {
696 let loc = convert_opt_loc(&lit.base.loc);
697 Ok(InstructionValue::Primitive {
698 value: PrimitiveValue::Number(FloatValue::new(lit.precise_value())),
699 loc,
700 })
701 }
702 Expression::StringLiteral(lit) => {
703 let loc = convert_opt_loc(&lit.base.loc);
704 Ok(InstructionValue::Primitive {
705 value: PrimitiveValue::String(lit.value.clone()),
706 loc,
707 })
708 }
709 Expression::BinaryExpression(bin) => {
710 let loc = convert_opt_loc(&bin.base.loc);
711 // Check for pipeline operator before lowering operands
712 if matches!(
713 bin.operator,
714 react_compiler_ast::operators::BinaryOperator::Pipeline
715 ) {
716 builder.record_error(CompilerErrorDetail {
717 category: ErrorCategory::Todo,
718 reason: "(BuildHIR::lowerExpression) Pipe operator not supported".to_string(),
719 description: None,
720 loc: loc.clone(),
721 suggestions: None,
722 })?;
723 return Ok(InstructionValue::UnsupportedNode {
724 node_type: Some("BinaryExpression".to_string()),
725 original_node: serialize_expression(expr),
726 loc,
727 });
728 }
729 let left = lower_expression_to_temporary(builder, &bin.left)?;
730 let right = lower_expression_to_temporary(builder, &bin.right)?;
731 let operator = convert_binary_operator(&bin.operator);
732 Ok(InstructionValue::BinaryExpression {
733 operator,
734 left,
735 right,
736 loc,
737 })
738 }
739 Expression::UnaryExpression(unary) => {
740 let loc = convert_opt_loc(&unary.base.loc);
741 match &unary.operator {
742 react_compiler_ast::operators::UnaryOperator::Delete => {
743 // Delete can be on member expressions or identifiers
744 let loc = convert_opt_loc(&unary.base.loc);
745 match &*unary.argument {
746 Expression::MemberExpression(member) => {
747 let object = lower_expression_to_temporary(builder, &member.object)?;
748 if !member.computed {
749 match &*member.property {
750 Expression::Identifier(prop_id) => {
751 Ok(InstructionValue::PropertyDelete {
752 object,
753 property: PropertyLiteral::String(prop_id.name.clone()),
754 loc,
755 })
756 }
757 _ => {
758 builder.record_error(CompilerErrorDetail {
759 reason: "Unsupported delete target".to_string(),
760 category: ErrorCategory::Todo,
761 loc: loc.clone(),
762 description: None,
763 suggestions: None,
764 })?;
765 Ok(InstructionValue::UnsupportedNode {
766 node_type: Some("UnaryExpression".to_string()),
767 original_node: serialize_expression(expr),
768 loc,
769 })
770 }
771 }
772 } else {
773 let property =
774 lower_expression_to_temporary(builder, &member.property)?;
775 Ok(InstructionValue::ComputedDelete {
776 object,
777 property,
778 loc,
779 })
780 }
781 }
782 _ => {
783 // delete on non-member expression (e.g., optional chain, identifier)
784 builder.record_error(CompilerErrorDetail {
785 reason: "Only object properties can be deleted".to_string(),
786 category: ErrorCategory::Syntax,
787 loc: loc.clone(),
788 description: None,
789 suggestions: None,
790 })?;
791 Ok(InstructionValue::UnsupportedNode {
792 node_type: Some("UnaryExpression".to_string()),
793 original_node: serialize_expression(expr),
794 loc,
795 })
796 }
797 }
798 }
799 react_compiler_ast::operators::UnaryOperator::Throw => {
800 // throw as unary operator (Babel-specific)
801 let loc = convert_opt_loc(&unary.base.loc);
802 builder.record_error(CompilerErrorDetail {
803 reason: "throw expressions are not supported".to_string(),
804 category: ErrorCategory::Todo,
805 loc: loc.clone(),
806 description: None,
807 suggestions: None,
808 })?;
809 Ok(InstructionValue::UnsupportedNode {
810 node_type: Some("UnaryExpression".to_string()),
811 original_node: serialize_expression(expr),
812 loc,
813 })
814 }
815 op => {
816 let value = lower_expression_to_temporary(builder, &unary.argument)?;
817 let operator = convert_unary_operator(op);
818 Ok(InstructionValue::UnaryExpression {
819 operator,
820 value,
821 loc,
822 })
823 }
824 }
825 }
826 Expression::CallExpression(call) => {
827 let loc = convert_opt_loc(&call.base.loc);
828 // Check if callee is a MemberExpression => MethodCall
829 if let Expression::MemberExpression(member) = call.callee.as_ref() {
830 let lowered = lower_member_expression(builder, member)?;
831 let property = lower_value_to_temporary(builder, lowered.value)?;
832 let args = lower_arguments(builder, &call.arguments)?;
833 Ok(InstructionValue::MethodCall {
834 receiver: lowered.object,
835 property,
836 args,
837 loc,
838 })
839 } else {
840 let callee = lower_expression_to_temporary(builder, &call.callee)?;
841 let args = lower_arguments(builder, &call.arguments)?;
842 Ok(InstructionValue::CallExpression { callee, args, loc })
843 }
844 }
845 Expression::MemberExpression(member) => {
846 let lowered = lower_member_expression(builder, member)?;
847 Ok(lowered.value)
848 }
849 Expression::OptionalCallExpression(opt_call) => {
850 Ok(lower_optional_call_expression(builder, opt_call)?)
851 }
852 Expression::OptionalMemberExpression(opt_member) => {
853 Ok(lower_optional_member_expression(builder, opt_member)?)
854 }
855 Expression::LogicalExpression(expr) => {
856 let loc = convert_opt_loc(&expr.base.loc);
857 let continuation_block = builder.reserve(builder.current_block_kind());
858 let continuation_id = continuation_block.id;
859 let test_block = builder.reserve(BlockKind::Value);
860 let test_block_id = test_block.id;
861 let place = build_temporary_place(builder, loc.clone());
862 let left_loc = expression_loc(&expr.left);
863 let left_place = build_temporary_place(builder, left_loc);
864
865 // Block for short-circuit case: store left value as result, goto continuation
866 let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
867 lower_value_to_temporary(
868 builder,
869 InstructionValue::StoreLocal {
870 lvalue: LValue {
871 kind: InstructionKind::Const,
872 place: place.clone(),
873 },
874 value: left_place.clone(),
875 type_annotation: None,
876 loc: left_place.loc.clone(),
877 },
878 )?;
879 Ok(Terminal::Goto {
880 block: continuation_id,
881 variant: GotoVariant::Break,
882 id: EvaluationOrder(0),
883 loc: left_place.loc.clone(),
884 })
885 });
886
887 // Block for evaluating right side
888 let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
889 let right = lower_expression_to_temporary(builder, &expr.right)?;
890 let right_loc = right.loc.clone();
891 lower_value_to_temporary(
892 builder,
893 InstructionValue::StoreLocal {
894 lvalue: LValue {
895 kind: InstructionKind::Const,
896 place: place.clone(),
897 },
898 value: right,
899 type_annotation: None,
900 loc: right_loc.clone(),
901 },
902 )?;
903 Ok(Terminal::Goto {
904 block: continuation_id,
905 variant: GotoVariant::Break,
906 id: EvaluationOrder(0),
907 loc: right_loc,
908 })
909 });
910
911 let hir_op = match expr.operator {
912 react_compiler_ast::operators::LogicalOperator::And => LogicalOperator::And,
913 react_compiler_ast::operators::LogicalOperator::Or => LogicalOperator::Or,
914 react_compiler_ast::operators::LogicalOperator::NullishCoalescing => {
915 LogicalOperator::NullishCoalescing
916 }
917 };
918
919 builder.terminate_with_continuation(
920 Terminal::Logical {
921 operator: hir_op,
922 test: test_block_id,
923 fallthrough: continuation_id,
924 id: EvaluationOrder(0),
925 loc: loc.clone(),
926 },
927 test_block,
928 );
929
930 // Now in test block: lower left expression, copy to left_place
931 let left_value = lower_expression_to_temporary(builder, &expr.left)?;
932 builder.push(Instruction {
933 id: EvaluationOrder(0),
934 lvalue: left_place.clone(),
935 value: InstructionValue::LoadLocal {
936 place: left_value,
937 loc: loc.clone(),
938 },
939 effects: None,
940 loc: loc.clone(),
941 });
942
943 builder.terminate_with_continuation(
944 Terminal::Branch {
945 test: left_place,
946 consequent: consequent_block?,
947 alternate: alternate_block?,
948 fallthrough: continuation_id,
949 id: EvaluationOrder(0),
950 loc: loc.clone(),
951 },
952 continuation_block,
953 );
954
955 Ok(InstructionValue::LoadLocal {
956 place: place.clone(),
957 loc: place.loc.clone(),
958 })
959 }
960 Expression::UpdateExpression(update) => {
961 let loc = convert_opt_loc(&update.base.loc);
962 match update.argument.as_ref() {
963 Expression::MemberExpression(member) => {
964 let binary_op = match &update.operator {
965 react_compiler_ast::operators::UpdateOperator::Increment => {
966 BinaryOperator::Add
967 }
968 react_compiler_ast::operators::UpdateOperator::Decrement => {
969 BinaryOperator::Subtract
970 }
971 };
972 // Use the member expression's loc (not the update expression's)
973 // to match TS behavior where the inner operations use leftExpr.node.loc
974 let member_loc = convert_opt_loc(&member.base.loc);
975 let lowered = lower_member_expression(builder, member)?;
976 let object = lowered.object;
977 let lowered_property = lowered.property;
978 let prev_value = lower_value_to_temporary(builder, lowered.value)?;
979
980 let one = lower_value_to_temporary(
981 builder,
982 InstructionValue::Primitive {
983 value: PrimitiveValue::Number(FloatValue::new(1.0)),
984 loc: None,
985 },
986 )?;
987 let updated = lower_value_to_temporary(
988 builder,
989 InstructionValue::BinaryExpression {
990 operator: binary_op,
991 left: prev_value.clone(),
992 right: one,
993 loc: member_loc.clone(),
994 },
995 )?;
996
997 // Store back using the property from the lowered member expression.
998 // For prefix, the result is the PropertyStore/ComputedStore lvalue
999 // (matching TS which uses newValuePlace). For postfix, it's prev_value.
1000 let new_value_place = match lowered_property {
1001 MemberProperty::Literal(prop_literal) => lower_value_to_temporary(
1002 builder,
1003 InstructionValue::PropertyStore {
1004 object,
1005 property: prop_literal,
1006 value: updated.clone(),
1007 loc: member_loc,
1008 },
1009 )?,
1010 MemberProperty::Computed(prop_place) => lower_value_to_temporary(
1011 builder,
1012 InstructionValue::ComputedStore {
1013 object,
1014 property: prop_place,
1015 value: updated.clone(),
1016 loc: member_loc,
1017 },
1018 )?,
1019 };
1020
1021 // Return previous for postfix, newValuePlace for prefix
1022 let result_place = if update.prefix {
1023 new_value_place
1024 } else {
1025 prev_value
1026 };
1027 Ok(InstructionValue::LoadLocal {
1028 place: result_place.clone(),
1029 loc: result_place.loc.clone(),
1030 })
1031 }
1032 Expression::Identifier(ident) => {
1033 let start = ident.base.start.unwrap_or(0);
1034 if builder.is_context_identifier(&ident.name, start, ident.base.node_id) {
1035 builder.record_error(CompilerErrorDetail {
1036 category: ErrorCategory::Todo,
1037 reason: "(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.".to_string(),
1038 description: None,
1039 loc: loc.clone(),
1040 suggestions: None,
1041 })?;
1042 return Ok(InstructionValue::UnsupportedNode {
1043 node_type: Some("UpdateExpression".to_string()),
1044 original_node: serialize_expression(expr),
1045 loc,
1046 });
1047 }
1048
1049 let ident_loc = convert_opt_loc(&ident.base.loc);
1050 let binding = builder.resolve_identifier(
1051 &ident.name,
1052 start,
1053 ident_loc.clone(),
1054 ident.base.node_id,
1055 )?;
1056 match &binding {
1057 VariableBinding::Global { .. } => {
1058 builder.record_error(CompilerErrorDetail {
1059 category: ErrorCategory::Todo,
1060 reason: "UpdateExpression where argument is a global is not yet supported".to_string(),
1061 description: None,
1062 loc: loc.clone(),
1063 suggestions: None,
1064 })?;
1065 return Ok(InstructionValue::UnsupportedNode {
1066 node_type: Some("UpdateExpression".to_string()),
1067 original_node: serialize_expression(expr),
1068 loc,
1069 });
1070 }
1071 _ => {}
1072 }
1073 let identifier = match binding {
1074 VariableBinding::Identifier { identifier, .. } => identifier,
1075 _ => {
1076 builder.record_error(CompilerErrorDetail {
1077 category: ErrorCategory::Todo,
1078 reason: "(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global".to_string(),
1079 description: None,
1080 loc: loc.clone(),
1081 suggestions: None,
1082 })?;
1083 return Ok(InstructionValue::UnsupportedNode {
1084 node_type: Some("UpdateExpression".to_string()),
1085 original_node: serialize_expression(expr),
1086 loc,
1087 });
1088 }
1089 };
1090 let lvalue_place = Place {
1091 identifier,
1092 effect: Effect::Unknown,
1093 reactive: false,
1094 loc: ident_loc.clone(),
1095 };
1096
1097 // Load the current value
1098 let value = lower_identifier(
1099 builder,
1100 &ident.name,
1101 start,
1102 ident_loc,
1103 ident.base.node_id,
1104 )?;
1105
1106 let operation = convert_update_operator(&update.operator);
1107
1108 if update.prefix {
1109 Ok(InstructionValue::PrefixUpdate {
1110 lvalue: lvalue_place,
1111 operation,
1112 value,
1113 loc,
1114 })
1115 } else {
1116 Ok(InstructionValue::PostfixUpdate {
1117 lvalue: lvalue_place,
1118 operation,
1119 value,
1120 loc,
1121 })
1122 }
1123 }
1124 _ => {
1125 builder.record_error(CompilerErrorDetail {
1126 category: ErrorCategory::Todo,
1127 reason: format!("UpdateExpression with unsupported argument type"),
1128 description: None,
1129 loc: loc.clone(),
1130 suggestions: None,
1131 })?;
1132 Ok(InstructionValue::UnsupportedNode {
1133 node_type: Some("UpdateExpression".to_string()),
1134 original_node: serialize_expression(expr),
1135 loc,
1136 })
1137 }
1138 }
1139 }
1140 Expression::ConditionalExpression(expr) => {
1141 let loc = convert_opt_loc(&expr.base.loc);
1142 let continuation_block = builder.reserve(builder.current_block_kind());
1143 let continuation_id = continuation_block.id;
1144 let test_block = builder.reserve(BlockKind::Value);
1145 let test_block_id = test_block.id;
1146 let place = build_temporary_place(builder, loc.clone());
1147
1148 // Block for the consequent (test is truthy)
1149 let consequent_ast_loc = expression_loc(&expr.consequent);
1150 let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
1151 let consequent = lower_expression_to_temporary(builder, &expr.consequent)?;
1152 lower_value_to_temporary(
1153 builder,
1154 InstructionValue::StoreLocal {
1155 lvalue: LValue {
1156 kind: InstructionKind::Const,
1157 place: place.clone(),
1158 },
1159 value: consequent,
1160 type_annotation: None,
1161 loc: loc.clone(),
1162 },
1163 )?;
1164 Ok(Terminal::Goto {
1165 block: continuation_id,
1166 variant: GotoVariant::Break,
1167 id: EvaluationOrder(0),
1168 loc: consequent_ast_loc,
1169 })
1170 });
1171
1172 // Block for the alternate (test is falsy)
1173 let alternate_ast_loc = expression_loc(&expr.alternate);
1174 let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {
1175 let alternate = lower_expression_to_temporary(builder, &expr.alternate)?;
1176 lower_value_to_temporary(
1177 builder,
1178 InstructionValue::StoreLocal {
1179 lvalue: LValue {
1180 kind: InstructionKind::Const,
1181 place: place.clone(),
1182 },
1183 value: alternate,
1184 type_annotation: None,
1185 loc: loc.clone(),
1186 },
1187 )?;
1188 Ok(Terminal::Goto {
1189 block: continuation_id,
1190 variant: GotoVariant::Break,
1191 id: EvaluationOrder(0),
1192 loc: alternate_ast_loc,
1193 })
1194 });
1195
1196 builder.terminate_with_continuation(
1197 Terminal::Ternary {
1198 test: test_block_id,
1199 fallthrough: continuation_id,
1200 id: EvaluationOrder(0),
1201 loc: loc.clone(),
1202 },
1203 test_block,
1204 );
1205
1206 // Now in test block: lower test expression
1207 let test_place = lower_expression_to_temporary(builder, &expr.test)?;
1208 builder.terminate_with_continuation(
1209 Terminal::Branch {
1210 test: test_place,
1211 consequent: consequent_block?,
1212 alternate: alternate_block?,
1213 fallthrough: continuation_id,
1214 id: EvaluationOrder(0),
1215 loc: loc.clone(),
1216 },
1217 continuation_block,
1218 );
1219
1220 Ok(InstructionValue::LoadLocal {
1221 place: place.clone(),
1222 loc: place.loc.clone(),
1223 })
1224 }
1225 Expression::AssignmentExpression(expr) => {
1226 use react_compiler_ast::operators::AssignmentOperator;
1227 let loc = convert_opt_loc(&expr.base.loc);
1228
1229 if matches!(expr.operator, AssignmentOperator::Assign) {
1230 // Simple `=` assignment
1231 match &*expr.left {
1232 react_compiler_ast::patterns::PatternLike::Identifier(ident) => {
1233 // Handle simple identifier assignment directly
1234 let start = ident.base.start.unwrap_or(0);
1235 let right = lower_expression_to_temporary(builder, &expr.right)?;
1236 let ident_loc = convert_opt_loc(&ident.base.loc);
1237 let binding = builder.resolve_identifier(
1238 &ident.name,
1239 start,
1240 ident_loc.clone(),
1241 ident.base.node_id,
1242 )?;
1243 match binding {
1244 VariableBinding::Identifier {
1245 identifier,
1246 binding_kind,
1247 } => {
1248 // Check for const reassignment
1249 if binding_kind == BindingKind::Const {
1250 builder.record_error(CompilerErrorDetail {
1251 reason: "Cannot reassign a `const` variable".to_string(),
1252 category: ErrorCategory::Syntax,
1253 loc: ident_loc.clone(),
1254 description: Some(format!(
1255 "`{}` is declared as const",
1256 &ident.name
1257 )),
1258 suggestions: None,
1259 })?;
1260 return Ok(InstructionValue::UnsupportedNode {
1261 node_type: Some("Identifier".to_string()),
1262 original_node: serialize_expression(
1263 &Expression::AssignmentExpression(expr.clone()),
1264 ),
1265 loc: ident_loc,
1266 });
1267 }
1268 let place = Place {
1269 identifier,
1270 reactive: false,
1271 effect: Effect::Unknown,
1272 loc: ident_loc,
1273 };
1274 if builder.is_context_identifier(
1275 &ident.name,
1276 start,
1277 ident.base.node_id,
1278 ) {
1279 let temp = lower_value_to_temporary(
1280 builder,
1281 InstructionValue::StoreContext {
1282 lvalue: LValue {
1283 kind: InstructionKind::Reassign,
1284 place: place.clone(),
1285 },
1286 value: right,
1287 loc: place.loc.clone(),
1288 },
1289 )?;
1290 Ok(InstructionValue::LoadLocal {
1291 place: temp.clone(),
1292 loc: temp.loc.clone(),
1293 })
1294 } else {
1295 let temp = lower_value_to_temporary(
1296 builder,
1297 InstructionValue::StoreLocal {
1298 lvalue: LValue {
1299 kind: InstructionKind::Reassign,
1300 place: place.clone(),
1301 },
1302 value: right,
1303 type_annotation: None,
1304 loc: place.loc.clone(),
1305 },
1306 )?;
1307 Ok(InstructionValue::LoadLocal {
1308 place: temp.clone(),
1309 loc: temp.loc.clone(),
1310 })
1311 }
1312 }
1313 _ => {
1314 // Global or import assignment
1315 let name = ident.name.clone();
1316 let temp = lower_value_to_temporary(
1317 builder,
1318 InstructionValue::StoreGlobal {
1319 name,
1320 value: right,
1321 loc: ident_loc,
1322 },
1323 )?;
1324 Ok(InstructionValue::LoadLocal {
1325 place: temp.clone(),
1326 loc: temp.loc.clone(),
1327 })
1328 }
1329 }
1330 }
1331 react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {
1332 // Member expression assignment: a.b = value or a[b] = value
1333 let right = lower_expression_to_temporary(builder, &expr.right)?;
1334 let left_loc = convert_opt_loc(&member.base.loc);
1335 let object = lower_expression_to_temporary(builder, &member.object)?;
1336 let temp = if !member.computed
1337 || matches!(
1338 &*member.property,
1339 react_compiler_ast::expressions::Expression::NumericLiteral(_)
1340 ) {
1341 match &*member.property {
1342 react_compiler_ast::expressions::Expression::Identifier(
1343 prop_id,
1344 ) => lower_value_to_temporary(
1345 builder,
1346 InstructionValue::PropertyStore {
1347 object,
1348 property: PropertyLiteral::String(prop_id.name.clone()),
1349 value: right,
1350 loc: left_loc,
1351 },
1352 )?,
1353 react_compiler_ast::expressions::Expression::NumericLiteral(
1354 num,
1355 ) => lower_value_to_temporary(
1356 builder,
1357 InstructionValue::PropertyStore {
1358 object,
1359 property: PropertyLiteral::Number(FloatValue::new(
1360 num.precise_value(),
1361 )),
1362 value: right,
1363 loc: left_loc,
1364 },
1365 )?,
1366 _ => {
1367 let prop =
1368 lower_expression_to_temporary(builder, &member.property)?;
1369 lower_value_to_temporary(
1370 builder,
1371 InstructionValue::ComputedStore {
1372 object,
1373 property: prop,
1374 value: right,
1375 loc: left_loc,
1376 },
1377 )?
1378 }
1379 }
1380 } else {
1381 let prop = lower_expression_to_temporary(builder, &member.property)?;
1382 lower_value_to_temporary(
1383 builder,
1384 InstructionValue::ComputedStore {
1385 object,
1386 property: prop,
1387 value: right,
1388 loc: left_loc,
1389 },
1390 )?
1391 };
1392 Ok(InstructionValue::LoadLocal {
1393 place: temp.clone(),
1394 loc: temp.loc.clone(),
1395 })
1396 }
1397 _ => {
1398 // Destructuring assignment
1399 let right = lower_expression_to_temporary(builder, &expr.right)?;
1400 let left_loc = pattern_like_hir_loc(&expr.left);
1401 let result = lower_assignment(
1402 builder,
1403 left_loc,
1404 InstructionKind::Reassign,
1405 &expr.left,
1406 right.clone(),
1407 AssignmentStyle::Destructure,
1408 )?;
1409 match result {
1410 Some(place) => Ok(InstructionValue::LoadLocal {
1411 place: place.clone(),
1412 loc: place.loc.clone(),
1413 }),
1414 None => Ok(InstructionValue::LoadLocal { place: right, loc }),
1415 }
1416 }
1417 }
1418 } else {
1419 // Compound assignment operators
1420 let binary_op = match expr.operator {
1421 AssignmentOperator::AddAssign => Some(BinaryOperator::Add),
1422 AssignmentOperator::SubAssign => Some(BinaryOperator::Subtract),
1423 AssignmentOperator::MulAssign => Some(BinaryOperator::Multiply),
1424 AssignmentOperator::DivAssign => Some(BinaryOperator::Divide),
1425 AssignmentOperator::RemAssign => Some(BinaryOperator::Modulo),
1426 AssignmentOperator::ExpAssign => Some(BinaryOperator::Exponent),
1427 AssignmentOperator::ShlAssign => Some(BinaryOperator::ShiftLeft),
1428 AssignmentOperator::ShrAssign => Some(BinaryOperator::ShiftRight),
1429 AssignmentOperator::UShrAssign => Some(BinaryOperator::UnsignedShiftRight),
1430 AssignmentOperator::BitOrAssign => Some(BinaryOperator::BitwiseOr),
1431 AssignmentOperator::BitXorAssign => Some(BinaryOperator::BitwiseXor),
1432 AssignmentOperator::BitAndAssign => Some(BinaryOperator::BitwiseAnd),
1433 AssignmentOperator::OrAssign
1434 | AssignmentOperator::AndAssign
1435 | AssignmentOperator::NullishAssign => {
1436 // Logical assignment operators (||=, &&=, ??=) - not yet supported
1437 builder.record_error(CompilerErrorDetail {
1438 reason:
1439 "Logical assignment operators (||=, &&=, ??=) are not yet supported"
1440 .to_string(),
1441 category: ErrorCategory::Todo,
1442 loc: loc.clone(),
1443 description: None,
1444 suggestions: None,
1445 })?;
1446 return Ok(InstructionValue::UnsupportedNode {
1447 node_type: Some("AssignmentExpression".to_string()),
1448 original_node: serialize_expression(&Expression::AssignmentExpression(
1449 expr.clone(),
1450 )),
1451 loc,
1452 });
1453 }
1454 AssignmentOperator::Assign => unreachable!(),
1455 };
1456 let binary_op = match binary_op {
1457 Some(op) => op,
1458 None => {
1459 return Ok(InstructionValue::UnsupportedNode {
1460 node_type: Some("AssignmentExpression".to_string()),
1461 original_node: serialize_expression(&Expression::AssignmentExpression(
1462 expr.clone(),
1463 )),
1464 loc,
1465 });
1466 }
1467 };
1468
1469 match &*expr.left {
1470 react_compiler_ast::patterns::PatternLike::Identifier(ident) => {
1471 let start = ident.base.start.unwrap_or(0);
1472 let left_place = lower_expression_to_temporary(
1473 builder,
1474 &react_compiler_ast::expressions::Expression::Identifier(ident.clone()),
1475 )?;
1476 let right = lower_expression_to_temporary(builder, &expr.right)?;
1477 let binary_place = lower_value_to_temporary(
1478 builder,
1479 InstructionValue::BinaryExpression {
1480 operator: binary_op,
1481 left: left_place,
1482 right,
1483 loc: loc.clone(),
1484 },
1485 )?;
1486 let ident_loc = convert_opt_loc(&ident.base.loc);
1487 let binding = builder.resolve_identifier(
1488 &ident.name,
1489 start,
1490 ident_loc.clone(),
1491 ident.base.node_id,
1492 )?;
1493 match binding {
1494 VariableBinding::Identifier { identifier, .. } => {
1495 let place = Place {
1496 identifier,
1497 reactive: false,
1498 effect: Effect::Unknown,
1499 loc: ident_loc,
1500 };
1501 if builder.is_context_identifier(
1502 &ident.name,
1503 start,
1504 ident.base.node_id,
1505 ) {
1506 lower_value_to_temporary(
1507 builder,
1508 InstructionValue::StoreContext {
1509 lvalue: LValue {
1510 kind: InstructionKind::Reassign,
1511 place: place.clone(),
1512 },
1513 value: binary_place,
1514 loc: loc.clone(),
1515 },
1516 )?;
1517 Ok(InstructionValue::LoadContext { place, loc })
1518 } else {
1519 lower_value_to_temporary(
1520 builder,
1521 InstructionValue::StoreLocal {
1522 lvalue: LValue {
1523 kind: InstructionKind::Reassign,
1524 place: place.clone(),
1525 },
1526 value: binary_place,
1527 type_annotation: None,
1528 loc: loc.clone(),
1529 },
1530 )?;
1531 Ok(InstructionValue::LoadLocal { place, loc })
1532 }
1533 }
1534 _ => {
1535 // Global assignment
1536 let name = ident.name.clone();
1537 let temp = lower_value_to_temporary(
1538 builder,
1539 InstructionValue::StoreGlobal {
1540 name,
1541 value: binary_place,
1542 loc: loc.clone(),
1543 },
1544 )?;
1545 Ok(InstructionValue::LoadLocal {
1546 place: temp.clone(),
1547 loc: temp.loc.clone(),
1548 })
1549 }
1550 }
1551 }
1552 react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {
1553 // a.b += right: read, compute, store
1554 // Match TS behavior: return the PropertyStore/ComputedStore value
1555 // directly (let the caller lower it to a temporary)
1556 let member_loc = convert_opt_loc(&member.base.loc);
1557 let lowered = lower_member_expression(builder, member)?;
1558 let object = lowered.object;
1559 let lowered_property = lowered.property;
1560 let current_value = lower_value_to_temporary(builder, lowered.value)?;
1561 let right = lower_expression_to_temporary(builder, &expr.right)?;
1562 let result = lower_value_to_temporary(
1563 builder,
1564 InstructionValue::BinaryExpression {
1565 operator: binary_op,
1566 left: current_value,
1567 right,
1568 loc: member_loc.clone(),
1569 },
1570 )?;
1571 // Return the store instruction value directly (matching TS behavior)
1572 match lowered_property {
1573 MemberProperty::Literal(prop_literal) => {
1574 Ok(InstructionValue::PropertyStore {
1575 object,
1576 property: prop_literal,
1577 value: result,
1578 loc: member_loc,
1579 })
1580 }
1581 MemberProperty::Computed(prop_place) => {
1582 Ok(InstructionValue::ComputedStore {
1583 object,
1584 property: prop_place,
1585 value: result,
1586 loc: member_loc,
1587 })
1588 }
1589 }
1590 }
1591 _ => {
1592 builder.record_error(CompilerErrorDetail {
1593 reason: "Compound assignment to complex pattern is not yet supported"
1594 .to_string(),
1595 category: ErrorCategory::Todo,
1596 loc: loc.clone(),
1597 description: None,
1598 suggestions: None,
1599 })?;
1600 Ok(InstructionValue::UnsupportedNode {
1601 node_type: Some("AssignmentExpression".to_string()),
1602 original_node: serialize_expression(&Expression::AssignmentExpression(
1603 expr.clone(),
1604 )),
1605 loc,
1606 })
1607 }
1608 }
1609 }
1610 }
1611 Expression::SequenceExpression(seq) => {
1612 let loc = convert_opt_loc(&seq.base.loc);
1613
1614 if seq.expressions.is_empty() {
1615 builder.record_error(CompilerErrorDetail {
1616 category: ErrorCategory::Syntax,
1617 reason: "Expected sequence expression to have at least one expression"
1618 .to_string(),
1619 description: None,
1620 loc: loc.clone(),
1621 suggestions: None,
1622 })?;
1623 return Ok(InstructionValue::UnsupportedNode {
1624 node_type: Some("SequenceExpression".to_string()),
1625 original_node: serialize_expression(expr),
1626 loc,
1627 });
1628 }
1629
1630 let continuation_block = builder.reserve(builder.current_block_kind());
1631 let continuation_id = continuation_block.id;
1632 let place = build_temporary_place(builder, loc.clone());
1633
1634 let sequence_block = builder.try_enter(BlockKind::Sequence, |builder, _block_id| {
1635 let mut last: Option<Place> = None;
1636 for item in &seq.expressions {
1637 last = Some(lower_expression_to_temporary(builder, item)?);
1638 }
1639 if let Some(last) = last {
1640 lower_value_to_temporary(
1641 builder,
1642 InstructionValue::StoreLocal {
1643 lvalue: LValue {
1644 kind: InstructionKind::Const,
1645 place: place.clone(),
1646 },
1647 value: last,
1648 type_annotation: None,
1649 loc: loc.clone(),
1650 },
1651 )?;
1652 }
1653 Ok(Terminal::Goto {
1654 block: continuation_id,
1655 variant: GotoVariant::Break,
1656 id: EvaluationOrder(0),
1657 loc: loc.clone(),
1658 })
1659 });
1660
1661 builder.terminate_with_continuation(
1662 Terminal::Sequence {
1663 block: sequence_block?,
1664 fallthrough: continuation_id,
1665 id: EvaluationOrder(0),
1666 loc: loc.clone(),
1667 },
1668 continuation_block,
1669 );
1670 Ok(InstructionValue::LoadLocal { place, loc })
1671 }
1672 Expression::ArrowFunctionExpression(_) => Ok(lower_function_to_value(
1673 builder,
1674 expr,
1675 FunctionExpressionType::ArrowFunctionExpression,
1676 )?),
1677 Expression::FunctionExpression(_) => Ok(lower_function_to_value(
1678 builder,
1679 expr,
1680 FunctionExpressionType::FunctionExpression,
1681 )?),
1682 Expression::ObjectExpression(obj) => {
1683 let loc = convert_opt_loc(&obj.base.loc);
1684 let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new();
1685 for prop in &obj.properties {
1686 match prop {
1687 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(
1688 p,
1689 ) => {
1690 let key = lower_object_property_key(builder, &p.key, p.computed)?;
1691 let key = match key {
1692 Some(k) => k,
1693 None => continue,
1694 };
1695 let value = lower_expression_to_temporary(builder, &p.value)?;
1696 properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {
1697 key,
1698 property_type: ObjectPropertyType::Property,
1699 place: value,
1700 }));
1701 }
1702 react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement(
1703 spread,
1704 ) => {
1705 let place = lower_expression_to_temporary(builder, &spread.argument)?;
1706 properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern { place }));
1707 }
1708 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(
1709 method,
1710 ) => {
1711 if let Some(prop) = lower_object_method(builder, method)? {
1712 properties.push(ObjectPropertyOrSpread::Property(prop));
1713 }
1714 }
1715 }
1716 }
1717 Ok(InstructionValue::ObjectExpression { properties, loc })
1718 }
1719 Expression::ArrayExpression(arr) => {
1720 let loc = convert_opt_loc(&arr.base.loc);
1721 let mut elements: Vec<ArrayElement> = Vec::new();
1722 for element in &arr.elements {
1723 match element {
1724 None => {
1725 elements.push(ArrayElement::Hole);
1726 }
1727 Some(Expression::SpreadElement(spread)) => {
1728 let place = lower_expression_to_temporary(builder, &spread.argument)?;
1729 elements.push(ArrayElement::Spread(SpreadPattern { place }));
1730 }
1731 Some(expr) => {
1732 let place = lower_expression_to_temporary(builder, expr)?;
1733 elements.push(ArrayElement::Place(place));
1734 }
1735 }
1736 }
1737 Ok(InstructionValue::ArrayExpression { elements, loc })
1738 }
1739 Expression::NewExpression(new_expr) => {
1740 let loc = convert_opt_loc(&new_expr.base.loc);
1741 let callee = lower_expression_to_temporary(builder, &new_expr.callee)?;
1742 let args = lower_arguments(builder, &new_expr.arguments)?;
1743 Ok(InstructionValue::NewExpression { callee, args, loc })
1744 }
1745 Expression::TemplateLiteral(tmpl) => {
1746 let loc = convert_opt_loc(&tmpl.base.loc);
1747 let subexprs: Vec<Place> = tmpl
1748 .expressions
1749 .iter()
1750 .map(|e| lower_expression_to_temporary(builder, e))
1751 .collect::<Result<Vec<_>, _>>()?;
1752 let quasis: Vec<TemplateQuasi> = tmpl
1753 .quasis
1754 .iter()
1755 .map(|q| TemplateQuasi {
1756 raw: q.value.raw.clone(),
1757 cooked: q.value.cooked.clone(),
1758 })
1759 .collect();
1760 Ok(InstructionValue::TemplateLiteral {
1761 subexprs,
1762 quasis,
1763 loc,
1764 })
1765 }
1766 Expression::TaggedTemplateExpression(tagged) => {
1767 let loc = convert_opt_loc(&tagged.base.loc);
1768 if !tagged.quasi.expressions.is_empty() {
1769 builder.record_error(CompilerErrorDetail {
1770 category: ErrorCategory::Todo,
1771 reason:
1772 "(BuildHIR::lowerExpression) Handle tagged template with interpolations"
1773 .to_string(),
1774 description: None,
1775 loc: loc.clone(),
1776 suggestions: None,
1777 })?;
1778 return Ok(InstructionValue::UnsupportedNode {
1779 node_type: Some("TaggedTemplateExpression".to_string()),
1780 original_node: serialize_expression(expr),
1781 loc,
1782 });
1783 }
1784 assert!(
1785 tagged.quasi.quasis.len() == 1,
1786 "there should be only one quasi as we don't support interpolations yet"
1787 );
1788 let quasi = &tagged.quasi.quasis[0];
1789 // Check if raw and cooked values differ (e.g., graphql tagged templates)
1790 if quasi.value.raw != quasi.value.cooked.clone().unwrap_or_default() {
1791 builder.record_error(CompilerErrorDetail {
1792 category: ErrorCategory::Todo,
1793 reason: "(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value".to_string(),
1794 description: None,
1795 loc: loc.clone(),
1796 suggestions: None,
1797 })?;
1798 return Ok(InstructionValue::UnsupportedNode {
1799 node_type: Some("TaggedTemplateExpression".to_string()),
1800 original_node: serialize_expression(expr),
1801 loc,
1802 });
1803 }
1804 let value = TemplateQuasi {
1805 raw: quasi.value.raw.clone(),
1806 cooked: quasi.value.cooked.clone(),
1807 };
1808 let tag = lower_expression_to_temporary(builder, &tagged.tag)?;
1809 Ok(InstructionValue::TaggedTemplateExpression { tag, value, loc })
1810 }
1811 Expression::AwaitExpression(await_expr) => {
1812 let loc = convert_opt_loc(&await_expr.base.loc);
1813 let value = lower_expression_to_temporary(builder, &await_expr.argument)?;
1814 Ok(InstructionValue::Await { value, loc })
1815 }
1816 Expression::YieldExpression(yld) => {
1817 let loc = convert_opt_loc(&yld.base.loc);
1818 builder.record_error(CompilerErrorDetail {
1819 category: ErrorCategory::Todo,
1820 reason: "(BuildHIR::lowerExpression) Handle YieldExpression expressions"
1821 .to_string(),
1822 description: None,
1823 loc: loc.clone(),
1824 suggestions: None,
1825 })?;
1826 Ok(InstructionValue::UnsupportedNode {
1827 node_type: Some("YieldExpression".to_string()),
1828 original_node: serialize_expression(expr),
1829 loc,
1830 })
1831 }
1832 Expression::SpreadElement(spread) => {
1833 // SpreadElement should be handled by the parent context (array/object/call)
1834 // If we reach here, just lower the argument expression
1835 Ok(lower_expression(builder, &spread.argument)?)
1836 }
1837 Expression::MetaProperty(meta) => {
1838 let loc = convert_opt_loc(&meta.base.loc);
1839 if meta.meta.name == "import" && meta.property.name == "meta" {
1840 Ok(InstructionValue::MetaProperty {
1841 meta: meta.meta.name.clone(),
1842 property: meta.property.name.clone(),
1843 loc,
1844 })
1845 } else {
1846 builder.record_error(CompilerErrorDetail {
1847 category: ErrorCategory::Todo,
1848 reason: "(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta".to_string(),
1849 description: None,
1850 loc: loc.clone(),
1851 suggestions: None,
1852 })?;
1853 Ok(InstructionValue::UnsupportedNode {
1854 node_type: Some("MetaProperty".to_string()),
1855 original_node: serialize_expression(expr),
1856 loc,
1857 })
1858 }
1859 }
1860 Expression::ClassExpression(cls) => {
1861 let loc = convert_opt_loc(&cls.base.loc);
1862 builder.record_error(CompilerErrorDetail {
1863 category: ErrorCategory::Todo,
1864 reason: "(BuildHIR::lowerExpression) Handle ClassExpression expressions"
1865 .to_string(),
1866 description: None,
1867 loc: loc.clone(),
1868 suggestions: None,
1869 })?;
1870 Ok(InstructionValue::UnsupportedNode {
1871 node_type: Some("ClassExpression".to_string()),
1872 original_node: serialize_expression(expr),
1873 loc,
1874 })
1875 }
1876 Expression::PrivateName(pn) => {
1877 let loc = convert_opt_loc(&pn.base.loc);
1878 builder.record_error(CompilerErrorDetail {
1879 category: ErrorCategory::Todo,
1880 reason: "(BuildHIR::lowerExpression) Handle PrivateName expressions".to_string(),
1881 description: None,
1882 loc: loc.clone(),
1883 suggestions: None,
1884 })?;
1885 Ok(InstructionValue::UnsupportedNode {
1886 node_type: Some("PrivateName".to_string()),
1887 original_node: serialize_expression(expr),
1888 loc,
1889 })
1890 }
1891 Expression::Super(sup) => {
1892 let loc = convert_opt_loc(&sup.base.loc);
1893 builder.record_error(CompilerErrorDetail {
1894 category: ErrorCategory::Todo,
1895 reason: "(BuildHIR::lowerExpression) Handle Super expressions".to_string(),
1896 description: None,
1897 loc: loc.clone(),
1898 suggestions: None,
1899 })?;
1900 Ok(InstructionValue::UnsupportedNode {
1901 node_type: Some("Super".to_string()),
1902 original_node: serialize_expression(expr),
1903 loc,
1904 })
1905 }
1906 Expression::Import(imp) => {
1907 let loc = convert_opt_loc(&imp.base.loc);
1908 builder.record_error(CompilerErrorDetail {
1909 category: ErrorCategory::Todo,
1910 reason: "(BuildHIR::lowerExpression) Handle Import expressions".to_string(),
1911 description: None,
1912 loc: loc.clone(),
1913 suggestions: None,
1914 })?;
1915 Ok(InstructionValue::UnsupportedNode {
1916 node_type: Some("Import".to_string()),
1917 original_node: serialize_expression(expr),
1918 loc,
1919 })
1920 }
1921 Expression::ThisExpression(this) => {
1922 let loc = convert_opt_loc(&this.base.loc);
1923 builder.record_error(CompilerErrorDetail {
1924 category: ErrorCategory::Todo,
1925 reason: "(BuildHIR::lowerExpression) Handle ThisExpression expressions".to_string(),
1926 description: None,
1927 loc: loc.clone(),
1928 suggestions: None,
1929 })?;
1930 Ok(InstructionValue::UnsupportedNode {
1931 node_type: Some("ThisExpression".to_string()),
1932 original_node: serialize_expression(expr),
1933 loc,
1934 })
1935 }
1936 Expression::ParenthesizedExpression(paren) => {
1937 Ok(lower_expression(builder, &paren.expression)?)
1938 }
1939 Expression::JSXElement(jsx_element) => {
1940 let loc = convert_opt_loc(&jsx_element.base.loc);
1941 let opening_loc = convert_opt_loc(&jsx_element.opening_element.base.loc);
1942 let closing_loc = jsx_element
1943 .closing_element
1944 .as_ref()
1945 .and_then(|c| convert_opt_loc(&c.base.loc));
1946
1947 // Lower the tag name
1948 let tag = lower_jsx_element_name(builder, &jsx_element.opening_element.name)?;
1949
1950 // Lower attributes (props)
1951 let mut props: Vec<JsxAttribute> = Vec::new();
1952 for attr_item in &jsx_element.opening_element.attributes {
1953 use react_compiler_ast::jsx::JSXAttributeItem;
1954 use react_compiler_ast::jsx::JSXAttributeName;
1955 use react_compiler_ast::jsx::JSXAttributeValue;
1956 match attr_item {
1957 JSXAttributeItem::JSXSpreadAttribute(spread) => {
1958 let argument = lower_expression_to_temporary(builder, &spread.argument)?;
1959 props.push(JsxAttribute::SpreadAttribute { argument });
1960 }
1961 JSXAttributeItem::JSXAttribute(attr) => {
1962 // Get the attribute name
1963 let prop_name = match &attr.name {
1964 JSXAttributeName::JSXIdentifier(id) => {
1965 let name = &id.name;
1966 if name.contains(':') {
1967 builder.record_error(CompilerErrorDetail {
1968 category: ErrorCategory::Todo,
1969 reason: format!(
1970 "(BuildHIR::lowerExpression) Unexpected colon in attribute name `{}`",
1971 name
1972 ),
1973 description: None,
1974 loc: convert_opt_loc(&id.base.loc),
1975 suggestions: None,
1976 })?;
1977 }
1978 name.clone()
1979 }
1980 JSXAttributeName::JSXNamespacedName(ns) => {
1981 format!("{}:{}", ns.namespace.name, ns.name.name)
1982 }
1983 };
1984
1985 // Get the attribute value
1986 let value = match &attr.value {
1987 Some(JSXAttributeValue::StringLiteral(s)) => {
1988 let str_loc = convert_opt_loc(&s.base.loc);
1989 lower_value_to_temporary(
1990 builder,
1991 InstructionValue::Primitive {
1992 value: PrimitiveValue::String(s.value.clone()),
1993 loc: str_loc,
1994 },
1995 )?
1996 }
1997 Some(JSXAttributeValue::JSXExpressionContainer(container)) => {
1998 use react_compiler_ast::jsx::JSXExpressionContainerExpr;
1999 match &container.expression {
2000 JSXExpressionContainerExpr::JSXEmptyExpression(_) => {
2001 // Empty expression container - skip this attribute
2002 continue;
2003 }
2004 JSXExpressionContainerExpr::Expression(expr) => {
2005 lower_expression_to_temporary(builder, expr)?
2006 }
2007 }
2008 }
2009 Some(JSXAttributeValue::JSXElement(el)) => {
2010 let val = lower_expression(
2011 builder,
2012 &react_compiler_ast::expressions::Expression::JSXElement(
2013 el.clone(),
2014 ),
2015 )?;
2016 lower_value_to_temporary(builder, val)?
2017 }
2018 Some(JSXAttributeValue::JSXFragment(frag)) => {
2019 let val = lower_expression(
2020 builder,
2021 &react_compiler_ast::expressions::Expression::JSXFragment(
2022 frag.clone(),
2023 ),
2024 )?;
2025 lower_value_to_temporary(builder, val)?
2026 }
2027 None => {
2028 // No value means boolean true (e.g., <div disabled />)
2029 let attr_loc = convert_opt_loc(&attr.base.loc);
2030 lower_value_to_temporary(
2031 builder,
2032 InstructionValue::Primitive {
2033 value: PrimitiveValue::Boolean(true),
2034 loc: attr_loc,
2035 },
2036 )?
2037 }
2038 };
2039
2040 props.push(JsxAttribute::Attribute {
2041 name: prop_name,
2042 place: value,
2043 });
2044 }
2045 }
2046 }
2047
2048 // Check if this is an fbt/fbs tag, which requires special whitespace handling
2049 let is_fbt = matches!(&tag, JsxTag::Builtin(b) if b.name == "fbt" || b.name == "fbs");
2050
2051 // Check that fbt/fbs tags are module-level imports, not local bindings.
2052 // Matches TS: CompilerError.invariant(tagIdentifier.kind !== 'Identifier', ...)
2053 if is_fbt {
2054 let tag_name = match &tag {
2055 JsxTag::Builtin(b) => b.name.clone(),
2056 _ => "fbt".to_string(),
2057 };
2058 // Get the opening element's name identifier and check if it's a local binding
2059 if let react_compiler_ast::jsx::JSXElementName::JSXIdentifier(jsx_id) =
2060 &jsx_element.opening_element.name
2061 {
2062 let id_loc = convert_opt_loc(&jsx_id.base.loc);
2063 // Check if fbt/fbs tag name resolves to a local binding.
2064 // JSX identifiers may not be in our position-based reference map,
2065 // so check if ANY binding with this name exists in the function scope.
2066 let is_local_binding = builder.has_local_binding(&jsx_id.name);
2067 if is_local_binding {
2068 // Record as a Diagnostic (not ErrorDetail) to match TS behavior
2069 // where CompilerError.invariant creates a CompilerDiagnostic.
2070 // TS invariant() throws immediately, so only the first fbt error
2071 // is reported. We return Err to match this behavior.
2072 let reason = format!("<{}> tags should be module-level imports", tag_name);
2073 return Err(CompilerDiagnostic::new(
2074 ErrorCategory::Invariant,
2075 &reason,
2076 None,
2077 )
2078 .with_detail(CompilerDiagnosticDetail::Error {
2079 loc: id_loc.clone(),
2080 message: Some(reason.clone()),
2081 identifier_name: None,
2082 })
2083 .into());
2084 }
2085 }
2086 }
2087
2088 // Check for duplicate fbt:enum, fbt:plural, fbt:pronoun tags
2089 if is_fbt {
2090 let tag_name = match &tag {
2091 JsxTag::Builtin(b) => b.name.as_str(),
2092 _ => "fbt",
2093 };
2094 let mut enum_locs: Vec<Option<SourceLocation>> = Vec::new();
2095 let mut plural_locs: Vec<Option<SourceLocation>> = Vec::new();
2096 let mut pronoun_locs: Vec<Option<SourceLocation>> = Vec::new();
2097 collect_fbt_sub_tags(
2098 &jsx_element.children,
2099 tag_name,
2100 &mut enum_locs,
2101 &mut plural_locs,
2102 &mut pronoun_locs,
2103 );
2104
2105 for (name, locations) in [
2106 ("enum", &enum_locs),
2107 ("plural", &plural_locs),
2108 ("pronoun", &pronoun_locs),
2109 ] {
2110 if locations.len() > 1 {
2111 use react_compiler_diagnostics::CompilerDiagnosticDetail;
2112 let details: Vec<CompilerDiagnosticDetail> = locations
2113 .iter()
2114 .map(|loc| CompilerDiagnosticDetail::Error {
2115 message: Some(format!(
2116 "Multiple `<{}:{}>` tags found",
2117 tag_name, name
2118 )),
2119 loc: loc.clone(),
2120 identifier_name: None,
2121 })
2122 .collect();
2123 let mut diag = react_compiler_diagnostics::CompilerDiagnostic::new(
2124 ErrorCategory::Todo,
2125 "Support duplicate fbt tags",
2126 Some(format!(
2127 "Support `<{}>` tags with multiple `<{}:{}>` values",
2128 tag_name, tag_name, name
2129 )),
2130 );
2131 diag.details = details;
2132 builder.environment_mut().record_diagnostic(diag);
2133 }
2134 }
2135 }
2136
2137 // Increment fbt counter before traversing into children, as whitespace
2138 // in jsx text is handled differently for fbt subtrees.
2139 if is_fbt {
2140 builder.fbt_depth += 1;
2141 }
2142
2143 // Lower children
2144 let children: Vec<Place> = jsx_element
2145 .children
2146 .iter()
2147 .map(|child| lower_jsx_element(builder, child))
2148 .collect::<Result<Vec<_>, _>>()?
2149 .into_iter()
2150 .flatten()
2151 .collect();
2152
2153 if is_fbt {
2154 builder.fbt_depth -= 1;
2155 }
2156
2157 Ok(InstructionValue::JsxExpression {
2158 tag,
2159 props,
2160 children: if children.is_empty() {
2161 None
2162 } else {
2163 Some(children)
2164 },
2165 loc,
2166 opening_loc,
2167 closing_loc,
2168 })
2169 }
2170 Expression::JSXFragment(jsx_fragment) => {
2171 let loc = convert_opt_loc(&jsx_fragment.base.loc);
2172
2173 // Lower children
2174 let children: Vec<Place> = jsx_fragment
2175 .children
2176 .iter()
2177 .map(|child| lower_jsx_element(builder, child))
2178 .collect::<Result<Vec<_>, _>>()?
2179 .into_iter()
2180 .flatten()
2181 .collect();
2182
2183 Ok(InstructionValue::JsxFragment { children, loc })
2184 }
2185 Expression::AssignmentPattern(_) => {
2186 let loc = convert_opt_loc(&match expr {
2187 Expression::AssignmentPattern(p) => p.base.loc.clone(),
2188 _ => unreachable!(),
2189 });
2190 builder.record_error(CompilerErrorDetail {
2191 reason: "(BuildHIR::lowerExpression) Handle AssignmentPattern expressions"
2192 .to_string(),
2193 category: ErrorCategory::Todo,
2194 loc: loc.clone(),
2195 description: None,
2196 suggestions: None,
2197 })?;
2198 Ok(InstructionValue::UnsupportedNode {
2199 node_type: Some("AssignmentPattern".to_string()),
2200 original_node: serialize_expression(expr),
2201 loc,
2202 })
2203 }
2204 Expression::TSAsExpression(ts) => {
2205 let loc = convert_opt_loc(&ts.base.loc);
2206 let value = lower_expression_to_temporary(builder, &ts.expression)?;
2207 let type_annotation = ts.type_annotation.parse_value();
2208 let type_ = lower_type_annotation(&type_annotation, builder);
2209 let type_annotation_name = get_type_annotation_name(&type_annotation);
2210 Ok(InstructionValue::TypeCastExpression {
2211 value,
2212 type_,
2213 type_annotation_name,
2214 type_annotation_kind: Some("as".to_string()),
2215 type_annotation: Some(Box::new(type_annotation)),
2216 loc,
2217 })
2218 }
2219 Expression::TSSatisfiesExpression(ts) => {
2220 let loc = convert_opt_loc(&ts.base.loc);
2221 let value = lower_expression_to_temporary(builder, &ts.expression)?;
2222 let type_annotation = ts.type_annotation.parse_value();
2223 let type_ = lower_type_annotation(&type_annotation, builder);
2224 let type_annotation_name = get_type_annotation_name(&type_annotation);
2225 Ok(InstructionValue::TypeCastExpression {
2226 value,
2227 type_,
2228 type_annotation_name,
2229 type_annotation_kind: Some("satisfies".to_string()),
2230 type_annotation: Some(Box::new(type_annotation)),
2231 loc,
2232 })
2233 }
2234 Expression::TSNonNullExpression(ts) => Ok(lower_expression(builder, &ts.expression)?),
2235 Expression::TSTypeAssertion(ts) => {
2236 let loc = convert_opt_loc(&ts.base.loc);
2237 let value = lower_expression_to_temporary(builder, &ts.expression)?;
2238 let type_annotation = ts.type_annotation.parse_value();
2239 let type_ = lower_type_annotation(&type_annotation, builder);
2240 let type_annotation_name = get_type_annotation_name(&type_annotation);
2241 Ok(InstructionValue::TypeCastExpression {
2242 value,
2243 type_,
2244 type_annotation_name,
2245 type_annotation_kind: Some("as".to_string()),
2246 type_annotation: Some(Box::new(type_annotation)),
2247 loc,
2248 })
2249 }
2250 Expression::TSInstantiationExpression(ts) => Ok(lower_expression(builder, &ts.expression)?),
2251 Expression::TypeCastExpression(tc) => {
2252 let loc = convert_opt_loc(&tc.base.loc);
2253 let value = lower_expression_to_temporary(builder, &tc.expression)?;
2254 let annotation_value = tc.type_annotation.parse_value();
2255 // Flow TypeCastExpression: typeAnnotation is a TypeAnnotation node wrapping the actual type
2256 let inner_type = annotation_value
2257 .get("typeAnnotation")
2258 .unwrap_or(&annotation_value);
2259 let type_ = lower_type_annotation(inner_type, builder);
2260 let type_annotation_name = get_type_annotation_name(inner_type);
2261 Ok(InstructionValue::TypeCastExpression {
2262 value,
2263 type_,
2264 type_annotation_name,
2265 type_annotation_kind: Some("cast".to_string()),
2266 type_annotation: Some(Box::new(annotation_value)),
2267 loc,
2268 })
2269 }
2270 Expression::BigIntLiteral(big) => {
2271 let loc = convert_opt_loc(&big.base.loc);
2272 builder.record_error(CompilerErrorDetail {
2273 category: ErrorCategory::Todo,
2274 reason: "(BuildHIR::lowerExpression) Handle BigIntLiteral expressions".to_string(),
2275 description: None,
2276 loc: loc.clone(),
2277 suggestions: None,
2278 })?;
2279 Ok(InstructionValue::UnsupportedNode {
2280 node_type: Some("BigIntLiteral".to_string()),
2281 original_node: serialize_expression(expr),
2282 loc,
2283 })
2284 }
2285 Expression::RegExpLiteral(re) => {
2286 let loc = convert_opt_loc(&re.base.loc);
2287 Ok(InstructionValue::RegExpLiteral {
2288 pattern: re.pattern.clone(),
2289 flags: re.flags.clone(),
2290 loc,
2291 })
2292 }
2293 }
2294 }
2295
2296 /// Check if a binding's declaration is a direct statement of the block
2297 /// (not inside a nested control flow block like if/for/while).
2298 /// Uses the binding's declaration_start position to check if it falls within
2299 /// one of the block's direct VariableDeclaration, FunctionDeclaration, or
2300 /// ClassDeclaration statements. This avoids false positives when two bindings
2301 /// share the same name but are declared in different scopes (e.g., `const x`
2302 /// inside an if-branch and `const x` after it).
2303 fn is_binding_in_block_direct_statements(
2304 binding: &react_compiler_ast::scope::BindingData,
2305 stmts: &[react_compiler_ast::statements::Statement],
2306 ) -> bool {
2307 use react_compiler_ast::statements::Statement;
2308 let decl_start = match binding.declaration_start {
2309 Some(pos) => pos,
2310 None => return false,
2311 };
2312 for stmt in stmts {
2313 match stmt {
2314 Statement::VariableDeclaration(vd) => {
2315 let start = vd.base.start.unwrap_or(0);
2316 let end = vd.base.end.unwrap_or(u32::MAX);
2317 if decl_start >= start && decl_start < end {
2318 return true;
2319 }
2320 }
2321 Statement::FunctionDeclaration(fd) => {
2322 let start = fd.base.start.unwrap_or(0);
2323 let end = fd.base.end.unwrap_or(u32::MAX);
2324 if decl_start >= start && decl_start < end {
2325 return true;
2326 }
2327 }
2328 Statement::ClassDeclaration(cd) => {
2329 let start = cd.base.start.unwrap_or(0);
2330 let end = cd.base.end.unwrap_or(u32::MAX);
2331 if decl_start >= start && decl_start < end {
2332 return true;
2333 }
2334 }
2335 _ => {}
2336 }
2337 }
2338 false
2339 }
2340
2341 #[allow(dead_code)]
2342 fn pattern_declares_name(pattern: &react_compiler_ast::patterns::PatternLike, name: &str) -> bool {
2343 use react_compiler_ast::patterns::PatternLike;
2344 match pattern {
2345 PatternLike::Identifier(id) => id.name == name,
2346 PatternLike::ObjectPattern(op) => op.properties.iter().any(|prop| match prop {
2347 react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
2348 pattern_declares_name(&p.value, name)
2349 }
2350 react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
2351 pattern_declares_name(&r.argument, name)
2352 }
2353 }),
2354 PatternLike::ArrayPattern(ap) => ap.elements.iter().any(|el| {
2355 el.as_ref()
2356 .map_or(false, |e| pattern_declares_name(e, name))
2357 }),
2358 PatternLike::AssignmentPattern(ap) => pattern_declares_name(&ap.left, name),
2359 PatternLike::RestElement(r) => pattern_declares_name(&r.argument, name),
2360 PatternLike::MemberExpression(_) => false,
2361 PatternLike::TSAsExpression(_)
2362 | PatternLike::TSSatisfiesExpression(_)
2363 | PatternLike::TSNonNullExpression(_)
2364 | PatternLike::TSTypeAssertion(_)
2365 | PatternLike::TypeCastExpression(_) => false,
2366 }
2367 }
2368
2369 // =============================================================================
2370 // Statement position helpers
2371 // =============================================================================
2372
2373 fn statement_start(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> {
2374 use react_compiler_ast::statements::Statement;
2375 match stmt {
2376 Statement::BlockStatement(s) => s.base.start,
2377 Statement::ReturnStatement(s) => s.base.start,
2378 Statement::IfStatement(s) => s.base.start,
2379 Statement::ForStatement(s) => s.base.start,
2380 Statement::WhileStatement(s) => s.base.start,
2381 Statement::DoWhileStatement(s) => s.base.start,
2382 Statement::ForInStatement(s) => s.base.start,
2383 Statement::ForOfStatement(s) => s.base.start,
2384 Statement::SwitchStatement(s) => s.base.start,
2385 Statement::ThrowStatement(s) => s.base.start,
2386 Statement::TryStatement(s) => s.base.start,
2387 Statement::BreakStatement(s) => s.base.start,
2388 Statement::ContinueStatement(s) => s.base.start,
2389 Statement::LabeledStatement(s) => s.base.start,
2390 Statement::ExpressionStatement(s) => s.base.start,
2391 Statement::EmptyStatement(s) => s.base.start,
2392 Statement::DebuggerStatement(s) => s.base.start,
2393 Statement::WithStatement(s) => s.base.start,
2394 Statement::VariableDeclaration(s) => s.base.start,
2395 Statement::FunctionDeclaration(s) => s.base.start,
2396 Statement::ClassDeclaration(s) => s.base.start,
2397 Statement::ImportDeclaration(s) => s.base.start,
2398 Statement::ExportNamedDeclaration(s) => s.base.start,
2399 Statement::ExportDefaultDeclaration(s) => s.base.start,
2400 Statement::ExportAllDeclaration(s) => s.base.start,
2401 Statement::TSTypeAliasDeclaration(s) => s.base.start,
2402 Statement::TSInterfaceDeclaration(s) => s.base.start,
2403 Statement::TSEnumDeclaration(s) => s.base.start,
2404 Statement::TSModuleDeclaration(s) => s.base.start,
2405 Statement::TSDeclareFunction(s) => s.base.start,
2406 Statement::TypeAlias(s) => s.base.start,
2407 Statement::OpaqueType(s) => s.base.start,
2408 Statement::InterfaceDeclaration(s) => s.base.start,
2409 Statement::DeclareVariable(s) => s.base.start,
2410 Statement::DeclareFunction(s) => s.base.start,
2411 Statement::DeclareClass(s) => s.base.start,
2412 Statement::DeclareModule(s) => s.base.start,
2413 Statement::DeclareModuleExports(s) => s.base.start,
2414 Statement::DeclareExportDeclaration(s) => s.base.start,
2415 Statement::DeclareExportAllDeclaration(s) => s.base.start,
2416 Statement::DeclareInterface(s) => s.base.start,
2417 Statement::DeclareTypeAlias(s) => s.base.start,
2418 Statement::DeclareOpaqueType(s) => s.base.start,
2419 Statement::EnumDeclaration(s) => s.base.start,
2420 Statement::Unknown(s) => s.base().start,
2421 }
2422 }
2423
2424 fn statement_end(stmt: &react_compiler_ast::statements::Statement) -> Option<u32> {
2425 use react_compiler_ast::statements::Statement;
2426 match stmt {
2427 Statement::BlockStatement(s) => s.base.end,
2428 Statement::ReturnStatement(s) => s.base.end,
2429 Statement::IfStatement(s) => s.base.end,
2430 Statement::ForStatement(s) => s.base.end,
2431 Statement::WhileStatement(s) => s.base.end,
2432 Statement::DoWhileStatement(s) => s.base.end,
2433 Statement::ForInStatement(s) => s.base.end,
2434 Statement::ForOfStatement(s) => s.base.end,
2435 Statement::SwitchStatement(s) => s.base.end,
2436 Statement::ThrowStatement(s) => s.base.end,
2437 Statement::TryStatement(s) => s.base.end,
2438 Statement::BreakStatement(s) => s.base.end,
2439 Statement::ContinueStatement(s) => s.base.end,
2440 Statement::LabeledStatement(s) => s.base.end,
2441 Statement::ExpressionStatement(s) => s.base.end,
2442 Statement::EmptyStatement(s) => s.base.end,
2443 Statement::DebuggerStatement(s) => s.base.end,
2444 Statement::WithStatement(s) => s.base.end,
2445 Statement::VariableDeclaration(s) => s.base.end,
2446 Statement::FunctionDeclaration(s) => s.base.end,
2447 Statement::ClassDeclaration(s) => s.base.end,
2448 Statement::ImportDeclaration(s) => s.base.end,
2449 Statement::ExportNamedDeclaration(s) => s.base.end,
2450 Statement::ExportDefaultDeclaration(s) => s.base.end,
2451 Statement::ExportAllDeclaration(s) => s.base.end,
2452 Statement::TSTypeAliasDeclaration(s) => s.base.end,
2453 Statement::TSInterfaceDeclaration(s) => s.base.end,
2454 Statement::TSEnumDeclaration(s) => s.base.end,
2455 Statement::TSModuleDeclaration(s) => s.base.end,
2456 Statement::TSDeclareFunction(s) => s.base.end,
2457 Statement::TypeAlias(s) => s.base.end,
2458 Statement::OpaqueType(s) => s.base.end,
2459 Statement::InterfaceDeclaration(s) => s.base.end,
2460 Statement::DeclareVariable(s) => s.base.end,
2461 Statement::DeclareFunction(s) => s.base.end,
2462 Statement::DeclareClass(s) => s.base.end,
2463 Statement::DeclareModule(s) => s.base.end,
2464 Statement::DeclareModuleExports(s) => s.base.end,
2465 Statement::DeclareExportDeclaration(s) => s.base.end,
2466 Statement::DeclareExportAllDeclaration(s) => s.base.end,
2467 Statement::DeclareInterface(s) => s.base.end,
2468 Statement::DeclareTypeAlias(s) => s.base.end,
2469 Statement::DeclareOpaqueType(s) => s.base.end,
2470 Statement::EnumDeclaration(s) => s.base.end,
2471 Statement::Unknown(s) => s.base().end,
2472 }
2473 }
2474
2475 /// Extract the HIR SourceLocation from a Statement AST node.
2476 fn statement_loc(stmt: &react_compiler_ast::statements::Statement) -> Option<SourceLocation> {
2477 use react_compiler_ast::statements::Statement;
2478 let loc = match stmt {
2479 Statement::BlockStatement(s) => s.base.loc.clone(),
2480 Statement::ReturnStatement(s) => s.base.loc.clone(),
2481 Statement::IfStatement(s) => s.base.loc.clone(),
2482 Statement::ForStatement(s) => s.base.loc.clone(),
2483 Statement::WhileStatement(s) => s.base.loc.clone(),
2484 Statement::DoWhileStatement(s) => s.base.loc.clone(),
2485 Statement::ForInStatement(s) => s.base.loc.clone(),
2486 Statement::ForOfStatement(s) => s.base.loc.clone(),
2487 Statement::SwitchStatement(s) => s.base.loc.clone(),
2488 Statement::ThrowStatement(s) => s.base.loc.clone(),
2489 Statement::TryStatement(s) => s.base.loc.clone(),
2490 Statement::BreakStatement(s) => s.base.loc.clone(),
2491 Statement::ContinueStatement(s) => s.base.loc.clone(),
2492 Statement::LabeledStatement(s) => s.base.loc.clone(),
2493 Statement::ExpressionStatement(s) => s.base.loc.clone(),
2494 Statement::EmptyStatement(s) => s.base.loc.clone(),
2495 Statement::DebuggerStatement(s) => s.base.loc.clone(),
2496 Statement::WithStatement(s) => s.base.loc.clone(),
2497 Statement::VariableDeclaration(s) => s.base.loc.clone(),
2498 Statement::FunctionDeclaration(s) => s.base.loc.clone(),
2499 Statement::ClassDeclaration(s) => s.base.loc.clone(),
2500 Statement::ImportDeclaration(s) => s.base.loc.clone(),
2501 Statement::ExportNamedDeclaration(s) => s.base.loc.clone(),
2502 Statement::ExportDefaultDeclaration(s) => s.base.loc.clone(),
2503 Statement::ExportAllDeclaration(s) => s.base.loc.clone(),
2504 Statement::TSTypeAliasDeclaration(s) => s.base.loc.clone(),
2505 Statement::TSInterfaceDeclaration(s) => s.base.loc.clone(),
2506 Statement::TSEnumDeclaration(s) => s.base.loc.clone(),
2507 Statement::TSModuleDeclaration(s) => s.base.loc.clone(),
2508 Statement::TSDeclareFunction(s) => s.base.loc.clone(),
2509 Statement::TypeAlias(s) => s.base.loc.clone(),
2510 Statement::OpaqueType(s) => s.base.loc.clone(),
2511 Statement::InterfaceDeclaration(s) => s.base.loc.clone(),
2512 Statement::DeclareVariable(s) => s.base.loc.clone(),
2513 Statement::DeclareFunction(s) => s.base.loc.clone(),
2514 Statement::DeclareClass(s) => s.base.loc.clone(),
2515 Statement::DeclareModule(s) => s.base.loc.clone(),
2516 Statement::DeclareModuleExports(s) => s.base.loc.clone(),
2517 Statement::DeclareExportDeclaration(s) => s.base.loc.clone(),
2518 Statement::DeclareExportAllDeclaration(s) => s.base.loc.clone(),
2519 Statement::DeclareInterface(s) => s.base.loc.clone(),
2520 Statement::DeclareTypeAlias(s) => s.base.loc.clone(),
2521 Statement::DeclareOpaqueType(s) => s.base.loc.clone(),
2522 Statement::EnumDeclaration(s) => s.base.loc.clone(),
2523 Statement::Unknown(s) => s.base().loc.clone(),
2524 };
2525 convert_opt_loc(&loc)
2526 }
2527
2528 /// Collect binding names from a pattern that are declared in the given scope.
2529 fn collect_binding_names_from_pattern(
2530 pattern: &react_compiler_ast::patterns::PatternLike,
2531 scope_id: react_compiler_ast::scope::ScopeId,
2532 scope_info: &ScopeInfo,
2533 out: &mut FxHashSet<BindingId>,
2534 ) {
2535 use react_compiler_ast::patterns::PatternLike;
2536 match pattern {
2537 PatternLike::Identifier(id) => {
2538 if let Some(&binding_id) = scope_info.scopes[scope_id.0 as usize]
2539 .bindings
2540 .get(&id.name)
2541 {
2542 out.insert(binding_id);
2543 }
2544 }
2545 PatternLike::ObjectPattern(obj) => {
2546 for prop in &obj.properties {
2547 match prop {
2548 react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
2549 collect_binding_names_from_pattern(&p.value, scope_id, scope_info, out);
2550 }
2551 react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
2552 collect_binding_names_from_pattern(&r.argument, scope_id, scope_info, out);
2553 }
2554 }
2555 }
2556 }
2557 PatternLike::ArrayPattern(arr) => {
2558 for elem in &arr.elements {
2559 if let Some(e) = elem {
2560 collect_binding_names_from_pattern(e, scope_id, scope_info, out);
2561 }
2562 }
2563 }
2564 PatternLike::AssignmentPattern(assign) => {
2565 collect_binding_names_from_pattern(&assign.left, scope_id, scope_info, out);
2566 }
2567 PatternLike::RestElement(rest) => {
2568 collect_binding_names_from_pattern(&rest.argument, scope_id, scope_info, out);
2569 }
2570 PatternLike::MemberExpression(_) => {}
2571 PatternLike::TSAsExpression(_)
2572 | PatternLike::TSSatisfiesExpression(_)
2573 | PatternLike::TSNonNullExpression(_)
2574 | PatternLike::TSTypeAssertion(_)
2575 | PatternLike::TypeCastExpression(_) => {}
2576 }
2577 }
2578
2579 // =============================================================================
2580 // lower_block_statement (with hoisting)
2581 // =============================================================================
2582
2583 /// Lower a BlockStatement with hoisting support.
2584 ///
2585 /// Implements the TS BlockStatement hoisting pass: identifies forward references to
2586 /// block-scoped bindings and emits DeclareContext instructions to hoist them.
2587 fn lower_block_statement(
2588 builder: &mut HirBuilder,
2589 block: &react_compiler_ast::statements::BlockStatement,
2590 parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2591 ) -> Result<(), CompilerError> {
2592 let _ = lower_block_statement_inner(builder, block, None, parent_scope);
2593 Ok(())
2594 }
2595
2596 fn lower_block_statement_with_scope(
2597 builder: &mut HirBuilder,
2598 block: &react_compiler_ast::statements::BlockStatement,
2599 scope_override: react_compiler_ast::scope::ScopeId,
2600 ) -> Result<(), CompilerError> {
2601 let _ = lower_block_statement_inner(builder, block, Some(scope_override), None);
2602 Ok(())
2603 }
2604
2605 fn lower_block_statement_inner(
2606 builder: &mut HirBuilder,
2607 block: &react_compiler_ast::statements::BlockStatement,
2608 scope_override: Option<react_compiler_ast::scope::ScopeId>,
2609 parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2610 ) -> Result<(), CompilerDiagnostic> {
2611 use react_compiler_ast::scope::BindingKind as AstBindingKind;
2612 use react_compiler_ast::statements::Statement;
2613
2614 // Look up the block's scope to identify hoistable bindings.
2615 // Use the scope override if provided (for function body blocks that share the function's scope).
2616 let block_scope_id = scope_override.or_else(|| {
2617 let found = builder
2618 .scope_info()
2619 .resolve_scope_for_node(block.base.node_id);
2620 if found.is_some() {
2621 return found;
2622 }
2623 // Fallback for synthetic blocks (start=0 from Hermes match desugar):
2624 // find a descendant scope of the parent that contains the block's declarations.
2625 let mut decl_names = Vec::new();
2626 for stmt in &block.body {
2627 if let Statement::VariableDeclaration(vd) = stmt {
2628 for d in &vd.declarations {
2629 if let react_compiler_ast::patterns::PatternLike::Identifier(id) = &d.id {
2630 decl_names.push(id.name.as_str());
2631 }
2632 }
2633 }
2634 }
2635 if decl_names.is_empty() {
2636 return None;
2637 }
2638 let search_parent = parent_scope.unwrap_or_else(|| builder.function_scope());
2639 let found =
2640 builder
2641 .scope_info()
2642 .find_block_scope_by_bindings(&decl_names, search_parent, |sid| {
2643 builder.is_synthetic_scope_claimed(sid)
2644 });
2645 if let Some(sid) = found {
2646 builder.claim_synthetic_scope(sid);
2647 }
2648 found
2649 });
2650
2651 let scope_id = match block_scope_id {
2652 Some(id) => id,
2653 None => {
2654 for body_stmt in &block.body {
2655 lower_statement(builder, body_stmt, None, parent_scope)?;
2656 }
2657 return Ok(());
2658 }
2659 };
2660
2661 // Collect hoistable bindings from this scope AND direct child block scopes.
2662 // In Babel, a function body BlockStatement shares the function's scope, so
2663 // all bindings (var, const, let) are in one scope. But our scope extraction
2664 // may split them: function scope has params/var, child block scope has const/let.
2665 // Including child block scope bindings matches TS behavior where
2666 // stmt.scope.bindings includes all bindings accessible in the block.
2667 //
2668 // IMPORTANT: Only include bindings whose declaration falls within THIS block's
2669 // statement range. Bindings declared in nested blocks (e.g., inside an `if`
2670 // branch) should NOT be hoisted at the parent level — they'll be handled when
2671 // that nested block is recursively lowered. This prevents DeclareContext from
2672 // being emitted before an `if` terminal for variables declared within the branch.
2673 let hoistable: Vec<(
2674 BindingId,
2675 String,
2676 AstBindingKind,
2677 String,
2678 Option<u32>,
2679 Option<u32>,
2680 )> = builder
2681 .scope_info()
2682 .scope_bindings_with_children(scope_id)
2683 .filter(|b| {
2684 !matches!(b.kind, AstBindingKind::Param | AstBindingKind::Module)
2685 && b.declaration_type != "FunctionExpression"
2686 && b.declaration_type != "TypeAlias"
2687 && b.declaration_type != "OpaqueType"
2688 && b.declaration_type != "InterfaceDeclaration"
2689 && b.declaration_type != "TSTypeAliasDeclaration"
2690 && b.declaration_type != "TSInterfaceDeclaration"
2691 && b.declaration_type != "TSEnumDeclaration"
2692 })
2693 .map(|b| {
2694 (
2695 b.id,
2696 b.name.clone(),
2697 b.kind.clone(),
2698 b.declaration_type.clone(),
2699 b.declaration_start,
2700 b.declaration_node_id,
2701 )
2702 })
2703 .collect();
2704
2705 if hoistable.is_empty() {
2706 // No hoistable bindings, just lower statements normally
2707 for body_stmt in &block.body {
2708 lower_statement(builder, body_stmt, None, Some(scope_id))?;
2709 }
2710 return Ok(());
2711 }
2712
2713 // Track which bindings have been "declared" (their declaration statement has been seen)
2714 let mut declared: FxHashSet<BindingId> = FxHashSet::default();
2715
2716 for body_stmt in &block.body {
2717 let stmt_start = statement_start(body_stmt).unwrap_or(0);
2718 let stmt_end = statement_end(body_stmt).unwrap_or(u32::MAX);
2719 let is_function_decl = matches!(body_stmt, Statement::FunctionDeclaration(_));
2720
2721 // Collect ranges of nested function scopes within this statement.
2722 // Used to check per-reference whether a reference is inside a nested function,
2723 // rather than checking once per-statement.
2724 let nested_function_ranges: Vec<(u32, u32)> = if is_function_decl {
2725 // For function declarations, fnDepth starts at 1 (all refs are inside)
2726 vec![(stmt_start, stmt_end)]
2727 } else {
2728 let scope_info = builder.scope_info();
2729 scope_info
2730 .node_to_scope
2731 .iter()
2732 .filter(|&(&pos, &sid)| {
2733 pos > stmt_start
2734 && pos < stmt_end
2735 && matches!(scope_info.scopes[sid.0 as usize].kind, ScopeKind::Function)
2736 })
2737 .filter_map(|(&pos, _)| {
2738 scope_info
2739 .node_to_scope_end
2740 .get(&pos)
2741 .map(|&end| (pos, end))
2742 })
2743 .collect()
2744 };
2745
2746 // Find references to not-yet-declared hoistable bindings within this statement
2747 struct HoistInfo {
2748 binding_id: BindingId,
2749 name: String,
2750 kind: AstBindingKind,
2751 declaration_type: String,
2752 first_ref_pos: u32,
2753 first_ref_nid: u32,
2754 }
2755 let mut will_hoist: Vec<HoistInfo> = Vec::new();
2756
2757 for (binding_id, name, kind, decl_type, _decl_start, decl_node_id) in &hoistable {
2758 if declared.contains(binding_id) {
2759 continue;
2760 }
2761
2762 // Find the first reference (not declaration) to this binding in the statement's range.
2763 // Exclude JSX identifier references: while Babel's scope system links JSX
2764 // tag names to local bindings (and the context capture pass includes them),
2765 // the TS hoisting analysis does NOT traverse JSX elements. This mismatch
2766 // is intentional — it matches the TS behavior where <colgroup> adds
2767 // "colgroup" to the context but does NOT trigger hoisting, causing
2768 // EnterSSA to error with "Expected identifier to be defined before use".
2769 //
2770 // The decl_start filter excludes the binding's own declaration position from
2771 // counting as a reference. For hoisted bindings (function declarations), this
2772 // filter is only applied when the current statement IS a FunctionDeclaration,
2773 // since that's the only statement type where decl_start is a declaration, not
2774 // a reference.
2775 let apply_decl_filter = !matches!(kind, AstBindingKind::Hoisted) || is_function_decl;
2776 let refs_in_stmt: Vec<(u32, u32)> = builder
2777 .scope_info()
2778 .ref_node_id_to_binding
2779 .iter()
2780 .filter_map(|(&ref_nid, &ref_bid)| {
2781 if ref_bid != *binding_id {
2782 return None;
2783 }
2784 let entry = builder.identifier_locs().get(&ref_nid)?;
2785 let ref_start = entry.start;
2786 if ref_start < stmt_start || ref_start >= stmt_end {
2787 return None;
2788 }
2789 if apply_decl_filter && *decl_node_id == Some(ref_nid) {
2790 return None;
2791 }
2792 if entry.is_jsx {
2793 return None;
2794 }
2795 Some((ref_start, ref_nid))
2796 })
2797 .collect();
2798
2799 if refs_in_stmt.is_empty() {
2800 continue;
2801 }
2802
2803 let (first_ref_pos, first_ref_nid) =
2804 *refs_in_stmt.iter().min_by_key(|(pos, _)| *pos).unwrap();
2805
2806 // Hoist if: (1) binding is "hoisted" kind (function declaration), or
2807 // (2) any reference to this binding is inside a nested function scope.
2808 // Check per-reference rather than per-statement to correctly handle
2809 // statements that contain both nested functions and top-level code.
2810 let is_hoisted_kind = matches!(kind, AstBindingKind::Hoisted);
2811 let refs_in_nested_fn: Vec<(u32, u32)> = refs_in_stmt
2812 .iter()
2813 .copied()
2814 .filter(|&(ref_pos, _)| {
2815 nested_function_ranges
2816 .iter()
2817 .any(|&(fn_start, fn_end)| ref_pos >= fn_start && ref_pos < fn_end)
2818 })
2819 .collect();
2820 let should_hoist = is_hoisted_kind || !refs_in_nested_fn.is_empty();
2821 if should_hoist {
2822 // Bindings pulled in from CHILD block scopes (the
2823 // scope_bindings_with_children descent compensates for scope
2824 // splitting) only hoist when declared as a direct statement of
2825 // THIS block; ones declared inside nested control-flow blocks
2826 // are handled when those blocks are recursively lowered. TS
2827 // never sees child-block bindings here (Babel's
2828 // stmt.scope.bindings holds only the block's own scope), so the
2829 // guard must NOT apply to own-scope bindings: catch params and
2830 // for-in/for-of head vars belong to the block's scope without
2831 // being declared by any direct statement, and TS hoists them.
2832 let binding_data = &builder.scope_info().bindings[binding_id.0 as usize];
2833 if binding_data.scope != scope_id
2834 && !is_binding_in_block_direct_statements(binding_data, &block.body)
2835 {
2836 continue;
2837 }
2838 // For hoisted bindings (function declarations), use the first reference
2839 // overall. For non-hoisted bindings, use the first reference inside a
2840 // nested function.
2841 let (hoist_ref_pos, hoist_ref_nid) = if is_hoisted_kind {
2842 (first_ref_pos, first_ref_nid)
2843 } else {
2844 *refs_in_nested_fn
2845 .iter()
2846 .min_by_key(|(pos, _)| *pos)
2847 .unwrap()
2848 };
2849 will_hoist.push(HoistInfo {
2850 binding_id: *binding_id,
2851 name: name.clone(),
2852 kind: kind.clone(),
2853 declaration_type: decl_type.clone(),
2854 first_ref_pos: hoist_ref_pos,
2855 first_ref_nid: hoist_ref_nid,
2856 });
2857 }
2858 }
2859
2860 // Sort by first reference position to match TS traversal order
2861 will_hoist.sort_by_key(|h| h.first_ref_pos);
2862
2863 // Emit DeclareContext for hoisted bindings
2864 for info in &will_hoist {
2865 if builder
2866 .environment()
2867 .is_hoisted_identifier(info.binding_id.0)
2868 {
2869 continue;
2870 }
2871
2872 let hoist_kind = match info.kind {
2873 AstBindingKind::Const | AstBindingKind::Var => InstructionKind::HoistedConst,
2874 AstBindingKind::Let => InstructionKind::HoistedLet,
2875 AstBindingKind::Hoisted => InstructionKind::HoistedFunction,
2876 _ => {
2877 if info.declaration_type == "FunctionDeclaration" {
2878 InstructionKind::HoistedFunction
2879 } else if info.declaration_type == "VariableDeclarator" {
2880 // Unsupported hoisting for this declaration kind
2881 builder.record_error(CompilerErrorDetail {
2882 category: ErrorCategory::Todo,
2883 reason: "Handle non-const declarations for hoisting".to_string(),
2884 description: Some(format!(
2885 "variable \"{}\" declared with {:?}",
2886 info.name, info.kind
2887 )),
2888 loc: None,
2889 suggestions: None,
2890 })?;
2891 continue;
2892 } else {
2893 builder.record_error(CompilerErrorDetail {
2894 category: ErrorCategory::Todo,
2895 reason: "Unsupported declaration type for hoisting".to_string(),
2896 description: Some(format!(
2897 "variable \"{}\" declared with {}",
2898 info.name, info.declaration_type
2899 )),
2900 loc: None,
2901 suggestions: None,
2902 })?;
2903 continue;
2904 }
2905 }
2906 };
2907
2908 // Look up the reference location for the DeclareContext instruction.
2909 let ref_loc = builder
2910 .identifier_locs()
2911 .get(&info.first_ref_nid)
2912 .map(|e| e.loc.clone());
2913 let identifier = builder.resolve_binding(&info.name, info.binding_id)?;
2914 let place = Place {
2915 effect: Effect::Unknown,
2916 identifier,
2917 reactive: false,
2918 loc: ref_loc.clone(),
2919 };
2920 lower_value_to_temporary(
2921 builder,
2922 InstructionValue::DeclareContext {
2923 lvalue: LValue {
2924 kind: hoist_kind,
2925 place,
2926 },
2927 loc: ref_loc,
2928 },
2929 )?;
2930 builder
2931 .environment_mut()
2932 .add_hoisted_identifier(info.binding_id.0);
2933 // Hoisted identifiers also become context identifiers (matching TS addHoistedIdentifier)
2934 builder.add_context_identifier(info.binding_id);
2935 }
2936
2937 // After processing the statement, mark any bindings it declares as "seen".
2938 // This must cover all statement types that can introduce bindings.
2939 match body_stmt {
2940 Statement::FunctionDeclaration(func) => {
2941 if let Some(id) = &func.id {
2942 if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize]
2943 .bindings
2944 .get(&id.name)
2945 {
2946 declared.insert(binding_id);
2947 }
2948 }
2949 }
2950 Statement::VariableDeclaration(var_decl) => {
2951 for decl in &var_decl.declarations {
2952 collect_binding_names_from_pattern(
2953 &decl.id,
2954 scope_id,
2955 builder.scope_info(),
2956 &mut declared,
2957 );
2958 }
2959 }
2960 Statement::ClassDeclaration(cls) => {
2961 if let Some(id) = &cls.id {
2962 if let Some(&binding_id) = builder.scope_info().scopes[scope_id.0 as usize]
2963 .bindings
2964 .get(&id.name)
2965 {
2966 declared.insert(binding_id);
2967 }
2968 }
2969 }
2970 _ => {
2971 // For other statement types (e.g. ForStatement with VariableDeclaration in init),
2972 // we rely on the reference_to_binding check for forward references.
2973 // Any bindings declared by child scopes won't be in this block's scope anyway.
2974 }
2975 }
2976
2977 lower_statement(builder, body_stmt, None, Some(scope_id))?;
2978 }
2979 Ok(())
2980 }
2981
2982 // =============================================================================
2983 // lower_statement
2984 // =============================================================================
2985
2986 fn lower_statement(
2987 builder: &mut HirBuilder,
2988 stmt: &react_compiler_ast::statements::Statement,
2989 label: Option<&str>,
2990 parent_scope: Option<react_compiler_ast::scope::ScopeId>,
2991 ) -> Result<(), CompilerDiagnostic> {
2992 use react_compiler_ast::statements::Statement;
2993
2994 match stmt {
2995 Statement::EmptyStatement(_) => {
2996 // no-op
2997 }
2998 Statement::DebuggerStatement(dbg) => {
2999 let loc = convert_opt_loc(&dbg.base.loc);
3000 let value = InstructionValue::Debugger { loc };
3001 lower_value_to_temporary(builder, value)?;
3002 }
3003 Statement::ExpressionStatement(expr_stmt) => {
3004 lower_expression_to_temporary(builder, &expr_stmt.expression)?;
3005 }
3006 Statement::ReturnStatement(ret) => {
3007 let loc = convert_opt_loc(&ret.base.loc);
3008 let value = if let Some(arg) = &ret.argument {
3009 lower_expression_to_temporary(builder, arg)?
3010 } else {
3011 let undefined_value = InstructionValue::Primitive {
3012 value: PrimitiveValue::Undefined,
3013 loc: None,
3014 };
3015 lower_value_to_temporary(builder, undefined_value)?
3016 };
3017 let fallthrough = builder.reserve(BlockKind::Block);
3018 builder.terminate_with_continuation(
3019 Terminal::Return {
3020 value,
3021 return_variant: ReturnVariant::Explicit,
3022 id: EvaluationOrder(0),
3023 loc,
3024 effects: None,
3025 },
3026 fallthrough,
3027 );
3028 }
3029 Statement::ThrowStatement(throw) => {
3030 let loc = convert_opt_loc(&throw.base.loc);
3031 let value = lower_expression_to_temporary(builder, &throw.argument)?;
3032
3033 // Check for throw handler (try/catch)
3034 if let Some(_handler) = builder.resolve_throw_handler() {
3035 builder.record_error(CompilerErrorDetail {
3036 category: ErrorCategory::Todo,
3037 reason: "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch"
3038 .to_string(),
3039 description: None,
3040 loc: loc.clone(),
3041 suggestions: None,
3042 })?;
3043 }
3044
3045 let fallthrough = builder.reserve(BlockKind::Block);
3046 builder.terminate_with_continuation(
3047 Terminal::Throw {
3048 value,
3049 id: EvaluationOrder(0),
3050 loc,
3051 },
3052 fallthrough,
3053 );
3054 }
3055 Statement::BlockStatement(block) => {
3056 lower_block_statement(builder, block, parent_scope)?;
3057 }
3058 Statement::VariableDeclaration(var_decl) => {
3059 use react_compiler_ast::patterns::PatternLike;
3060 use react_compiler_ast::statements::VariableDeclarationKind;
3061 let unsupported_node_kind = match var_decl.kind {
3062 VariableDeclarationKind::Var => Some("var"),
3063 VariableDeclarationKind::Using => Some("using"),
3064 VariableDeclarationKind::AwaitUsing => Some("await using"),
3065 VariableDeclarationKind::Let | VariableDeclarationKind::Const => None,
3066 };
3067 if let Some(node_kind) = unsupported_node_kind {
3068 builder.record_error(CompilerErrorDetail {
3069 reason: format!(
3070 "(BuildHIR::lowerStatement) Handle {node_kind} kinds in VariableDeclaration"
3071 ),
3072 category: ErrorCategory::Todo,
3073 loc: convert_opt_loc(&var_decl.base.loc),
3074 description: None,
3075 suggestions: None,
3076 })?;
3077 // Treat `var` as `let` and `using`/`await using` as `const` so
3078 // references to the variable don't break while the error unwinds
3079 }
3080 let kind = match var_decl.kind {
3081 VariableDeclarationKind::Let | VariableDeclarationKind::Var => InstructionKind::Let,
3082 VariableDeclarationKind::Const
3083 | VariableDeclarationKind::Using
3084 | VariableDeclarationKind::AwaitUsing => InstructionKind::Const,
3085 };
3086 for declarator in &var_decl.declarations {
3087 let stmt_loc = convert_opt_loc(&var_decl.base.loc);
3088 if let Some(init) = &declarator.init {
3089 let value = lower_expression_to_temporary(builder, init)?;
3090 let assign_style = match &declarator.id {
3091 PatternLike::ObjectPattern(_) | PatternLike::ArrayPattern(_) => {
3092 AssignmentStyle::Destructure
3093 }
3094 _ => AssignmentStyle::Assignment,
3095 };
3096 lower_assignment(builder, stmt_loc, kind, &declarator.id, value, assign_style)?;
3097 } else if let PatternLike::Identifier(id) = &declarator.id {
3098 // No init: emit DeclareLocal or DeclareContext
3099 let id_loc = convert_opt_loc(&id.base.loc);
3100 let mut binding = builder.resolve_identifier(
3101 &id.name,
3102 id.base.start.unwrap_or(0),
3103 id_loc.clone(),
3104 id.base.node_id,
3105 )?;
3106 if !matches!(binding, VariableBinding::Identifier { .. }) {
3107 // Position-based resolution failed (synthetic $$gen vars
3108 // at position 0). Try scope lookup including descendants.
3109 if let Some((binding_id, binding_data)) = builder
3110 .scope_info()
3111 .find_binding_id_in_descendants(&id.name, builder.function_scope())
3112 {
3113 let binding_kind = crate::convert_binding_kind(&binding_data.kind);
3114 let identifier = builder.resolve_binding_with_loc(
3115 &id.name,
3116 binding_id,
3117 id_loc.clone(),
3118 )?;
3119 binding = VariableBinding::Identifier {
3120 identifier,
3121 binding_kind,
3122 };
3123 }
3124 }
3125 match binding {
3126 VariableBinding::Identifier { identifier, .. } => {
3127 // Update the identifier's loc to the declaration site
3128 // (it may have been first created at a reference site during hoisting)
3129 builder.set_identifier_declaration_loc(identifier, &id_loc);
3130 let place = Place {
3131 identifier,
3132 effect: Effect::Unknown,
3133 reactive: false,
3134 loc: id_loc.clone(),
3135 };
3136 if builder.is_context_identifier(
3137 &id.name,
3138 id.base.start.unwrap_or(0),
3139 id.base.node_id,
3140 ) {
3141 if kind == InstructionKind::Const {
3142 builder.record_error(CompilerErrorDetail {
3143 reason: "Expect `const` declaration not to be reassigned"
3144 .to_string(),
3145 category: ErrorCategory::Syntax,
3146 loc: id_loc.clone(),
3147 description: None,
3148 suggestions: None,
3149 })?;
3150 }
3151 lower_value_to_temporary(
3152 builder,
3153 InstructionValue::DeclareContext {
3154 lvalue: LValue {
3155 kind: InstructionKind::Let,
3156 place,
3157 },
3158 loc: id_loc,
3159 },
3160 )?;
3161 } else {
3162 let type_annotation =
3163 extract_type_annotation_name(&id.type_annotation);
3164 lower_value_to_temporary(
3165 builder,
3166 InstructionValue::DeclareLocal {
3167 lvalue: LValue { kind, place },
3168 type_annotation,
3169 loc: id_loc,
3170 },
3171 )?;
3172 }
3173 }
3174 _ => {
3175 builder.record_error(CompilerErrorDetail {
3176 reason: "Could not find binding for declaration".to_string(),
3177 category: ErrorCategory::Invariant,
3178 loc: id_loc,
3179 description: None,
3180 suggestions: None,
3181 })?;
3182 }
3183 }
3184 } else {
3185 builder.record_error(CompilerErrorDetail {
3186 reason: "Expected variable declaration to be an identifier if no initializer was provided".to_string(),
3187 category: ErrorCategory::Syntax,
3188 loc: convert_opt_loc(&declarator.base.loc),
3189 description: None,
3190 suggestions: None,
3191 })?;
3192 }
3193 }
3194 }
3195 Statement::BreakStatement(brk) => {
3196 let loc = convert_opt_loc(&brk.base.loc);
3197 let label_name = brk.label.as_ref().map(|l| l.name.as_str());
3198 let target = builder.lookup_break(label_name)?;
3199 let fallthrough = builder.reserve(BlockKind::Block);
3200 builder.terminate_with_continuation(
3201 Terminal::Goto {
3202 block: target,
3203 variant: GotoVariant::Break,
3204 id: EvaluationOrder(0),
3205 loc,
3206 },
3207 fallthrough,
3208 );
3209 }
3210 Statement::ContinueStatement(cont) => {
3211 let loc = convert_opt_loc(&cont.base.loc);
3212 let label_name = cont.label.as_ref().map(|l| l.name.as_str());
3213 let target = builder.lookup_continue(label_name)?;
3214 let fallthrough = builder.reserve(BlockKind::Block);
3215 builder.terminate_with_continuation(
3216 Terminal::Goto {
3217 block: target,
3218 variant: GotoVariant::Continue,
3219 id: EvaluationOrder(0),
3220 loc,
3221 },
3222 fallthrough,
3223 );
3224 }
3225 Statement::IfStatement(if_stmt) => {
3226 let loc = convert_opt_loc(&if_stmt.base.loc);
3227 // Block for code following the if
3228 let continuation_block = builder.reserve(BlockKind::Block);
3229 let continuation_id = continuation_block.id;
3230
3231 // Block for the consequent (if the test is truthy)
3232 let consequent_loc = statement_loc(&if_stmt.consequent);
3233 let consequent_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3234 lower_statement(builder, &if_stmt.consequent, None, parent_scope)?;
3235 Ok(Terminal::Goto {
3236 block: continuation_id,
3237 variant: GotoVariant::Break,
3238 id: EvaluationOrder(0),
3239 loc: consequent_loc,
3240 })
3241 })?;
3242
3243 // Block for the alternate (if the test is not truthy)
3244 let alternate_block = if let Some(alternate) = &if_stmt.alternate {
3245 let alternate_loc = statement_loc(alternate);
3246 builder.try_enter(BlockKind::Block, |builder, _block_id| {
3247 lower_statement(builder, alternate, None, parent_scope)?;
3248 Ok(Terminal::Goto {
3249 block: continuation_id,
3250 variant: GotoVariant::Break,
3251 id: EvaluationOrder(0),
3252 loc: alternate_loc,
3253 })
3254 })?
3255 } else {
3256 // If there is no else clause, use the continuation directly
3257 continuation_id
3258 };
3259
3260 let test = lower_expression_to_temporary(builder, &if_stmt.test)?;
3261 builder.terminate_with_continuation(
3262 Terminal::If {
3263 test,
3264 consequent: consequent_block,
3265 alternate: alternate_block,
3266 fallthrough: continuation_id,
3267 id: EvaluationOrder(0),
3268 loc,
3269 },
3270 continuation_block,
3271 );
3272 }
3273 Statement::ForStatement(for_stmt) => {
3274 let loc = convert_opt_loc(&for_stmt.base.loc);
3275
3276 let test_block = builder.reserve(BlockKind::Loop);
3277 let test_block_id = test_block.id;
3278 // Block for code following the loop
3279 let continuation_block = builder.reserve(BlockKind::Block);
3280 let continuation_id = continuation_block.id;
3281
3282 // Init block: lower init expression/declaration, then goto test
3283 let init_block = builder.try_enter(BlockKind::Loop, |builder, _block_id| {
3284 let init_loc = match &for_stmt.init {
3285 None => {
3286 // No init expression (e.g., `for (; ...)`), add a placeholder
3287 let placeholder = InstructionValue::Primitive {
3288 value: PrimitiveValue::Undefined,
3289 loc: loc.clone(),
3290 };
3291 lower_value_to_temporary(builder, placeholder)?;
3292 loc.clone()
3293 }
3294 Some(init) => {
3295 match init.as_ref() {
3296 react_compiler_ast::statements::ForInit::VariableDeclaration(var_decl) => {
3297 let init_loc = convert_opt_loc(&var_decl.base.loc);
3298 lower_statement(builder, &Statement::VariableDeclaration(var_decl.clone()), None, parent_scope)?;
3299 init_loc
3300 }
3301 react_compiler_ast::statements::ForInit::Expression(expr) => {
3302 let init_loc = expression_loc(expr);
3303 builder.record_error(CompilerErrorDetail {
3304 category: ErrorCategory::Todo,
3305 reason: "(BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement".to_string(),
3306 description: None,
3307 loc: loc.clone(),
3308 suggestions: None,
3309 })?;
3310 lower_expression_to_temporary(builder, expr)?;
3311 init_loc
3312 }
3313 }
3314 }
3315 };
3316 Ok(Terminal::Goto {
3317 block: test_block_id,
3318 variant: GotoVariant::Break,
3319 id: EvaluationOrder(0),
3320 loc: init_loc,
3321 })
3322 })?;
3323
3324 // Update block (optional)
3325 let update_block_id = if let Some(update) = &for_stmt.update {
3326 let update_loc = expression_loc(update);
3327 Some(builder.try_enter(BlockKind::Loop, |builder, _block_id| {
3328 lower_expression_to_temporary(builder, update)?;
3329 Ok(Terminal::Goto {
3330 block: test_block_id,
3331 variant: GotoVariant::Break,
3332 id: EvaluationOrder(0),
3333 loc: update_loc,
3334 })
3335 })?)
3336 } else {
3337 None
3338 };
3339
3340 // Loop body block
3341 let continue_target = update_block_id.unwrap_or(test_block_id);
3342 let body_loc = statement_loc(&for_stmt.body);
3343 let body_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3344 builder.loop_scope(
3345 label.map(|s| s.to_string()),
3346 continue_target,
3347 continuation_id,
3348 |builder| {
3349 lower_statement(builder, &for_stmt.body, None, parent_scope)?;
3350 Ok(Terminal::Goto {
3351 block: continue_target,
3352 variant: GotoVariant::Continue,
3353 id: EvaluationOrder(0),
3354 loc: body_loc,
3355 })
3356 },
3357 )
3358 })?;
3359
3360 // Emit For terminal, then fill in the test block
3361 builder.terminate_with_continuation(
3362 Terminal::For {
3363 init: init_block,
3364 test: test_block_id,
3365 update: update_block_id,
3366 loop_block: body_block,
3367 fallthrough: continuation_id,
3368 id: EvaluationOrder(0),
3369 loc: loc.clone(),
3370 },
3371 test_block,
3372 );
3373
3374 // Fill in the test block
3375 if let Some(test_expr) = &for_stmt.test {
3376 let test = lower_expression_to_temporary(builder, test_expr)?;
3377 builder.terminate_with_continuation(
3378 Terminal::Branch {
3379 test,
3380 consequent: body_block,
3381 alternate: continuation_id,
3382 fallthrough: continuation_id,
3383 id: EvaluationOrder(0),
3384 loc: loc.clone(),
3385 },
3386 continuation_block,
3387 );
3388 } else {
3389 builder.record_error(CompilerErrorDetail {
3390 category: ErrorCategory::Todo,
3391 reason: "(BuildHIR::lowerStatement) Handle empty test in ForStatement"
3392 .to_string(),
3393 description: None,
3394 loc: loc.clone(),
3395 suggestions: None,
3396 })?;
3397 // Treat `for(;;)` as `while(true)` to keep the builder state consistent
3398 let true_val = InstructionValue::Primitive {
3399 value: PrimitiveValue::Boolean(true),
3400 loc: loc.clone(),
3401 };
3402 let test = lower_value_to_temporary(builder, true_val)?;
3403 builder.terminate_with_continuation(
3404 Terminal::Branch {
3405 test,
3406 consequent: body_block,
3407 alternate: continuation_id,
3408 fallthrough: continuation_id,
3409 id: EvaluationOrder(0),
3410 loc,
3411 },
3412 continuation_block,
3413 );
3414 }
3415 }
3416 Statement::WhileStatement(while_stmt) => {
3417 let loc = convert_opt_loc(&while_stmt.base.loc);
3418 // Block used to evaluate whether to (re)enter or exit the loop
3419 let conditional_block = builder.reserve(BlockKind::Loop);
3420 let conditional_id = conditional_block.id;
3421 // Block for code following the loop
3422 let continuation_block = builder.reserve(BlockKind::Block);
3423 let continuation_id = continuation_block.id;
3424
3425 // Loop body
3426 let body_loc = statement_loc(&while_stmt.body);
3427 let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3428 builder.loop_scope(
3429 label.map(|s| s.to_string()),
3430 conditional_id,
3431 continuation_id,
3432 |builder| {
3433 lower_statement(builder, &while_stmt.body, None, parent_scope)?;
3434 Ok(Terminal::Goto {
3435 block: conditional_id,
3436 variant: GotoVariant::Continue,
3437 id: EvaluationOrder(0),
3438 loc: body_loc,
3439 })
3440 },
3441 )
3442 })?;
3443
3444 // Emit While terminal, jumping to the conditional block
3445 builder.terminate_with_continuation(
3446 Terminal::While {
3447 test: conditional_id,
3448 loop_block,
3449 fallthrough: continuation_id,
3450 id: EvaluationOrder(0),
3451 loc: loc.clone(),
3452 },
3453 conditional_block,
3454 );
3455
3456 // Fill in the conditional block: lower test, branch
3457 let test = lower_expression_to_temporary(builder, &while_stmt.test)?;
3458 builder.terminate_with_continuation(
3459 Terminal::Branch {
3460 test,
3461 consequent: loop_block,
3462 alternate: continuation_id,
3463 fallthrough: conditional_id,
3464 id: EvaluationOrder(0),
3465 loc,
3466 },
3467 continuation_block,
3468 );
3469 }
3470 Statement::DoWhileStatement(do_while_stmt) => {
3471 let loc = convert_opt_loc(&do_while_stmt.base.loc);
3472 // Block used to evaluate whether to (re)enter or exit the loop
3473 let conditional_block = builder.reserve(BlockKind::Loop);
3474 let conditional_id = conditional_block.id;
3475 // Block for code following the loop
3476 let continuation_block = builder.reserve(BlockKind::Block);
3477 let continuation_id = continuation_block.id;
3478
3479 // Loop body, executed at least once unconditionally prior to exit
3480 let body_loc = statement_loc(&do_while_stmt.body);
3481 let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3482 builder.loop_scope(
3483 label.map(|s| s.to_string()),
3484 conditional_id,
3485 continuation_id,
3486 |builder| {
3487 lower_statement(builder, &do_while_stmt.body, None, parent_scope)?;
3488 Ok(Terminal::Goto {
3489 block: conditional_id,
3490 variant: GotoVariant::Continue,
3491 id: EvaluationOrder(0),
3492 loc: body_loc,
3493 })
3494 },
3495 )
3496 })?;
3497
3498 // Jump to the conditional block
3499 builder.terminate_with_continuation(
3500 Terminal::DoWhile {
3501 loop_block,
3502 test: conditional_id,
3503 fallthrough: continuation_id,
3504 id: EvaluationOrder(0),
3505 loc: loc.clone(),
3506 },
3507 conditional_block,
3508 );
3509
3510 // Fill in the conditional block: lower test, branch
3511 let test = lower_expression_to_temporary(builder, &do_while_stmt.test)?;
3512 builder.terminate_with_continuation(
3513 Terminal::Branch {
3514 test,
3515 consequent: loop_block,
3516 alternate: continuation_id,
3517 fallthrough: conditional_id,
3518 id: EvaluationOrder(0),
3519 loc,
3520 },
3521 continuation_block,
3522 );
3523 }
3524 Statement::ForInStatement(for_in) => {
3525 let loc = convert_opt_loc(&for_in.base.loc);
3526 let continuation_block = builder.reserve(BlockKind::Block);
3527 let continuation_id = continuation_block.id;
3528 let init_block = builder.reserve(BlockKind::Loop);
3529 let init_block_id = init_block.id;
3530
3531 let body_loc = statement_loc(&for_in.body);
3532 let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3533 builder.loop_scope(
3534 label.map(|s| s.to_string()),
3535 init_block_id,
3536 continuation_id,
3537 |builder| {
3538 lower_statement(builder, &for_in.body, None, parent_scope)?;
3539 Ok(Terminal::Goto {
3540 block: init_block_id,
3541 variant: GotoVariant::Continue,
3542 id: EvaluationOrder(0),
3543 loc: body_loc,
3544 })
3545 },
3546 )
3547 })?;
3548
3549 let value = lower_expression_to_temporary(builder, &for_in.right)?;
3550 builder.terminate_with_continuation(
3551 Terminal::ForIn {
3552 init: init_block_id,
3553 loop_block,
3554 fallthrough: continuation_id,
3555 id: EvaluationOrder(0),
3556 loc: loc.clone(),
3557 },
3558 init_block,
3559 );
3560
3561 // Lower the init: NextPropertyOf + assignment
3562 let left_loc = match for_in.left.as_ref() {
3563 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3564 convert_opt_loc(&var_decl.base.loc).or(loc.clone())
3565 }
3566 react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => {
3567 pattern_like_hir_loc(pat).or(loc.clone())
3568 }
3569 };
3570 let next_property = lower_value_to_temporary(
3571 builder,
3572 InstructionValue::NextPropertyOf {
3573 value,
3574 loc: left_loc.clone(),
3575 },
3576 )?;
3577
3578 let assign_result = match for_in.left.as_ref() {
3579 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3580 if var_decl.declarations.len() != 1 {
3581 builder.record_error(CompilerErrorDetail {
3582 category: ErrorCategory::Invariant,
3583 reason: format!(
3584 "Expected only one declaration in ForInStatement init, got {}",
3585 var_decl.declarations.len()
3586 ),
3587 description: None,
3588 loc: left_loc.clone(),
3589 suggestions: None,
3590 })?;
3591 }
3592 if let Some(declarator) = var_decl.declarations.first() {
3593 lower_assignment(
3594 builder,
3595 left_loc.clone(),
3596 InstructionKind::Let,
3597 &declarator.id,
3598 next_property.clone(),
3599 AssignmentStyle::Assignment,
3600 )?
3601 } else {
3602 None
3603 }
3604 }
3605 react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment(
3606 builder,
3607 left_loc.clone(),
3608 InstructionKind::Reassign,
3609 pattern,
3610 next_property.clone(),
3611 AssignmentStyle::Assignment,
3612 )?,
3613 };
3614 // Use the assign result (StoreLocal temp) as the test, matching TS behavior
3615 let test_value = assign_result.unwrap_or(next_property);
3616 let test = lower_value_to_temporary(
3617 builder,
3618 InstructionValue::LoadLocal {
3619 place: test_value,
3620 loc: left_loc.clone(),
3621 },
3622 )?;
3623 builder.terminate_with_continuation(
3624 Terminal::Branch {
3625 test,
3626 consequent: loop_block,
3627 alternate: continuation_id,
3628 fallthrough: continuation_id,
3629 id: EvaluationOrder(0),
3630 loc: loc.clone(),
3631 },
3632 continuation_block,
3633 );
3634 }
3635 Statement::ForOfStatement(for_of) => {
3636 let loc = convert_opt_loc(&for_of.base.loc);
3637 let continuation_block = builder.reserve(BlockKind::Block);
3638 let continuation_id = continuation_block.id;
3639 let init_block = builder.reserve(BlockKind::Loop);
3640 let init_block_id = init_block.id;
3641 let test_block = builder.reserve(BlockKind::Loop);
3642 let test_block_id = test_block.id;
3643
3644 if for_of.is_await {
3645 builder.record_error(CompilerErrorDetail {
3646 category: ErrorCategory::Todo,
3647 reason: "(BuildHIR::lowerStatement) Handle for-await loops".to_string(),
3648 description: None,
3649 loc: loc.clone(),
3650 suggestions: None,
3651 })?;
3652 return Ok(());
3653 }
3654
3655 let body_loc = statement_loc(&for_of.body);
3656 let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3657 builder.loop_scope(
3658 label.map(|s| s.to_string()),
3659 init_block_id,
3660 continuation_id,
3661 |builder| {
3662 lower_statement(builder, &for_of.body, None, parent_scope)?;
3663 Ok(Terminal::Goto {
3664 block: init_block_id,
3665 variant: GotoVariant::Continue,
3666 id: EvaluationOrder(0),
3667 loc: body_loc,
3668 })
3669 },
3670 )
3671 })?;
3672
3673 let value = lower_expression_to_temporary(builder, &for_of.right)?;
3674 builder.terminate_with_continuation(
3675 Terminal::ForOf {
3676 init: init_block_id,
3677 test: test_block_id,
3678 loop_block,
3679 fallthrough: continuation_id,
3680 id: EvaluationOrder(0),
3681 loc: loc.clone(),
3682 },
3683 init_block,
3684 );
3685
3686 // Init block: GetIterator, goto test
3687 let iterator = lower_value_to_temporary(
3688 builder,
3689 InstructionValue::GetIterator {
3690 collection: value.clone(),
3691 loc: value.loc.clone(),
3692 },
3693 )?;
3694 builder.terminate_with_continuation(
3695 Terminal::Goto {
3696 block: test_block_id,
3697 variant: GotoVariant::Break,
3698 id: EvaluationOrder(0),
3699 loc: loc.clone(),
3700 },
3701 test_block,
3702 );
3703
3704 // Test block: IteratorNext, assign, branch
3705 let left_loc = match for_of.left.as_ref() {
3706 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3707 convert_opt_loc(&var_decl.base.loc).or(loc.clone())
3708 }
3709 react_compiler_ast::statements::ForInOfLeft::Pattern(pat) => {
3710 pattern_like_hir_loc(pat).or(loc.clone())
3711 }
3712 };
3713 let advance_iterator = lower_value_to_temporary(
3714 builder,
3715 InstructionValue::IteratorNext {
3716 iterator: iterator.clone(),
3717 collection: value.clone(),
3718 loc: left_loc.clone(),
3719 },
3720 )?;
3721
3722 let assign_result = match for_of.left.as_ref() {
3723 react_compiler_ast::statements::ForInOfLeft::VariableDeclaration(var_decl) => {
3724 if var_decl.declarations.len() != 1 {
3725 builder.record_error(CompilerErrorDetail {
3726 category: ErrorCategory::Invariant,
3727 reason: format!(
3728 "Expected only one declaration in ForOfStatement init, got {}",
3729 var_decl.declarations.len()
3730 ),
3731 description: None,
3732 loc: left_loc.clone(),
3733 suggestions: None,
3734 })?;
3735 }
3736 if let Some(declarator) = var_decl.declarations.first() {
3737 lower_assignment(
3738 builder,
3739 left_loc.clone(),
3740 InstructionKind::Let,
3741 &declarator.id,
3742 advance_iterator.clone(),
3743 AssignmentStyle::Assignment,
3744 )?
3745 } else {
3746 None
3747 }
3748 }
3749 react_compiler_ast::statements::ForInOfLeft::Pattern(pattern) => lower_assignment(
3750 builder,
3751 left_loc.clone(),
3752 InstructionKind::Reassign,
3753 pattern,
3754 advance_iterator.clone(),
3755 AssignmentStyle::Assignment,
3756 )?,
3757 };
3758 // Use the assign result (StoreLocal temp) as the test, matching TS behavior
3759 let test_value = assign_result.unwrap_or(advance_iterator);
3760 let test = lower_value_to_temporary(
3761 builder,
3762 InstructionValue::LoadLocal {
3763 place: test_value,
3764 loc: left_loc.clone(),
3765 },
3766 )?;
3767 builder.terminate_with_continuation(
3768 Terminal::Branch {
3769 test,
3770 consequent: loop_block,
3771 alternate: continuation_id,
3772 fallthrough: continuation_id,
3773 id: EvaluationOrder(0),
3774 loc: loc.clone(),
3775 },
3776 continuation_block,
3777 );
3778 }
3779 Statement::SwitchStatement(switch_stmt) => {
3780 let loc = convert_opt_loc(&switch_stmt.base.loc);
3781 let continuation_block = builder.reserve(BlockKind::Block);
3782 let continuation_id = continuation_block.id;
3783
3784 // Iterate through cases in reverse order so that previous blocks can
3785 // fallthrough to successors
3786 let mut fallthrough = continuation_id;
3787 let mut cases: Vec<Case> = Vec::new();
3788 let mut has_default = false;
3789
3790 for ii in (0..switch_stmt.cases.len()).rev() {
3791 let case = &switch_stmt.cases[ii];
3792 let case_loc = convert_opt_loc(&case.base.loc);
3793
3794 if case.test.is_none() {
3795 if has_default {
3796 builder.record_error(CompilerErrorDetail {
3797 category: ErrorCategory::Syntax,
3798 reason: "Expected at most one `default` branch in a switch statement"
3799 .to_string(),
3800 description: None,
3801 loc: case_loc.clone(),
3802 suggestions: None,
3803 })?;
3804 break;
3805 }
3806 has_default = true;
3807 }
3808
3809 let fallthrough_target = fallthrough;
3810 let block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
3811 builder.switch_scope(label.map(|s| s.to_string()), continuation_id, |builder| {
3812 for consequent in &case.consequent {
3813 lower_statement(builder, consequent, None, parent_scope)?;
3814 }
3815 Ok(Terminal::Goto {
3816 block: fallthrough_target,
3817 variant: GotoVariant::Break,
3818 id: EvaluationOrder(0),
3819 loc: case_loc.clone(),
3820 })
3821 })
3822 })?;
3823
3824 let test = if let Some(test_expr) = &case.test {
3825 Some(lower_reorderable_expression(builder, test_expr)?)
3826 } else {
3827 None
3828 };
3829
3830 cases.push(Case { test, block });
3831 fallthrough = block;
3832 }
3833
3834 // Reverse back to original order
3835 cases.reverse();
3836
3837 // If no default case, add one that jumps to continuation
3838 if !has_default {
3839 cases.push(Case {
3840 test: None,
3841 block: continuation_id,
3842 });
3843 }
3844
3845 let test = lower_expression_to_temporary(builder, &switch_stmt.discriminant)?;
3846 builder.terminate_with_continuation(
3847 Terminal::Switch {
3848 test,
3849 cases,
3850 fallthrough: continuation_id,
3851 id: EvaluationOrder(0),
3852 loc,
3853 },
3854 continuation_block,
3855 );
3856 }
3857 Statement::TryStatement(try_stmt) => {
3858 let loc = convert_opt_loc(&try_stmt.base.loc);
3859 let continuation_block = builder.reserve(BlockKind::Block);
3860 let continuation_id = continuation_block.id;
3861
3862 let handler_clause = match &try_stmt.handler {
3863 Some(h) => h,
3864 None => {
3865 builder.record_error(CompilerErrorDetail {
3866 category: ErrorCategory::Todo,
3867 reason:
3868 "(BuildHIR::lowerStatement) Handle TryStatement without a catch clause"
3869 .to_string(),
3870 description: None,
3871 loc: loc.clone(),
3872 suggestions: None,
3873 })?;
3874 return Ok(());
3875 }
3876 };
3877
3878 if try_stmt.finalizer.is_some() {
3879 builder.record_error(CompilerErrorDetail {
3880 category: ErrorCategory::Todo,
3881 reason: "(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause".to_string(),
3882 description: None,
3883 loc: loc.clone(),
3884 suggestions: None,
3885 })?;
3886 }
3887
3888 // Set up handler binding if catch has a param
3889 let handler_binding_info: Option<(Place, react_compiler_ast::patterns::PatternLike)> =
3890 if let Some(param) = &handler_clause.param {
3891 // Check for destructuring in catch clause params.
3892 // Match TS behavior: Babel doesn't register destructured catch bindings
3893 // in its scope, so resolveIdentifier fails and records an invariant error.
3894 let is_destructuring = matches!(
3895 param,
3896 react_compiler_ast::patterns::PatternLike::ObjectPattern(_)
3897 | react_compiler_ast::patterns::PatternLike::ArrayPattern(_)
3898 );
3899 if is_destructuring {
3900 // Iterate the pattern to find all identifier locs for error reporting
3901 fn collect_identifier_locs(
3902 pat: &react_compiler_ast::patterns::PatternLike,
3903 locs: &mut Vec<Option<SourceLocation>>,
3904 ) {
3905 match pat {
3906 react_compiler_ast::patterns::PatternLike::Identifier(id) => {
3907 locs.push(convert_opt_loc(&id.base.loc));
3908 }
3909 react_compiler_ast::patterns::PatternLike::ObjectPattern(obj) => {
3910 for prop in &obj.properties {
3911 match prop {
3912 react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
3913 collect_identifier_locs(&p.value, locs);
3914 }
3915 react_compiler_ast::patterns::ObjectPatternProperty::RestElement(r) => {
3916 collect_identifier_locs(&r.argument, locs);
3917 }
3918 }
3919 }
3920 }
3921 react_compiler_ast::patterns::PatternLike::ArrayPattern(arr) => {
3922 for elem in &arr.elements {
3923 if let Some(e) = elem {
3924 collect_identifier_locs(e, locs);
3925 }
3926 }
3927 }
3928 _ => {}
3929 }
3930 }
3931 let mut id_locs = Vec::new();
3932 collect_identifier_locs(param, &mut id_locs);
3933 for id_loc in id_locs {
3934 builder.record_error(CompilerErrorDetail {
3935 reason: "(BuildHIR::lowerAssignment) Could not find binding for declaration.".to_string(),
3936 category: ErrorCategory::Invariant,
3937 loc: id_loc,
3938 description: None,
3939 suggestions: None,
3940 })?;
3941 }
3942 None
3943 } else {
3944 let param_loc = convert_opt_loc(&pattern_like_loc(param));
3945 let id = builder.make_temporary(param_loc.clone());
3946 promote_temporary(builder, id);
3947 let place = Place {
3948 identifier: id,
3949 effect: Effect::Unknown,
3950 reactive: false,
3951 loc: param_loc.clone(),
3952 };
3953 // Emit DeclareLocal for the catch binding
3954 lower_value_to_temporary(
3955 builder,
3956 InstructionValue::DeclareLocal {
3957 lvalue: LValue {
3958 kind: InstructionKind::Catch,
3959 place: place.clone(),
3960 },
3961 type_annotation: None,
3962 loc: param_loc,
3963 },
3964 )?;
3965 Some((place, param.clone()))
3966 }
3967 } else {
3968 None
3969 };
3970
3971 // Create the handler (catch) block
3972 let handler_binding_for_block = handler_binding_info.clone();
3973 let handler_loc = convert_opt_loc(&handler_clause.base.loc);
3974 // Use the catch param's loc for the assignment, matching TS: handlerBinding.path.node.loc
3975 let handler_param_loc = handler_clause
3976 .param
3977 .as_ref()
3978 .and_then(|p| convert_opt_loc(&pattern_like_loc(p)));
3979 let handler_block = builder.try_enter(BlockKind::Catch, |builder, _block_id| {
3980 if let Some((ref place, ref pattern)) = handler_binding_for_block {
3981 lower_assignment(
3982 builder,
3983 handler_param_loc.clone().or_else(|| handler_loc.clone()),
3984 InstructionKind::Catch,
3985 pattern,
3986 place.clone(),
3987 AssignmentStyle::Assignment,
3988 )?;
3989 }
3990 // Lower the catch body using lower_block_statement to get hoisting support.
3991 // Match TS behavior where `lowerStatement(builder, handlerPath.get('body'))`
3992 // processes the catch body as a BlockStatement (with hoisting).
3993 // Use the catch clause's scope since the catch body block shares
3994 // the CatchClause scope in Babel (contains the catch param binding).
3995 // Use the catch clause's scope (which contains the catch param binding).
3996 // Fall back to the body block's own scope if the catch clause scope is missing.
3997 let catch_scope = builder
3998 .scope_info()
3999 .resolve_scope_for_node(handler_clause.base.node_id)
4000 .or_else(|| {
4001 builder
4002 .scope_info()
4003 .resolve_scope_for_node(handler_clause.body.base.node_id)
4004 });
4005 if let Some(scope_id) = catch_scope {
4006 lower_block_statement_with_scope(builder, &handler_clause.body, scope_id)?;
4007 } else {
4008 // No scope found — this shouldn't happen with well-formed Babel output.
4009 // Fall back to plain block lowering (no hoisting) rather than panicking,
4010 // since this is a non-critical degradation.
4011 lower_block_statement(builder, &handler_clause.body, parent_scope)?;
4012 }
4013 Ok(Terminal::Goto {
4014 block: continuation_id,
4015 variant: GotoVariant::Break,
4016 id: EvaluationOrder(0),
4017 loc: handler_loc.clone(),
4018 })
4019 })?;
4020
4021 // Create the try block
4022 // Use lower_block_statement to get hoisting support for bindings
4023 // declared inside the try body. This matches the catch block's use of
4024 // lower_block_statement_with_scope and ensures self-referencing function
4025 // declarations (e.g., `const loop = () => { loop(); }`) inside try blocks
4026 // are correctly promoted to context variables.
4027 let try_body_loc = convert_opt_loc(&try_stmt.block.base.loc);
4028 let try_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
4029 builder.try_enter_try_catch(handler_block, |builder| {
4030 lower_block_statement(builder, &try_stmt.block, parent_scope)?;
4031 Ok(())
4032 })?;
4033 Ok(Terminal::Goto {
4034 block: continuation_id,
4035 variant: GotoVariant::Try,
4036 id: EvaluationOrder(0),
4037 loc: try_body_loc.clone(),
4038 })
4039 })?;
4040
4041 builder.terminate_with_continuation(
4042 Terminal::Try {
4043 block: try_block,
4044 handler_binding: handler_binding_info.map(|(place, _)| place),
4045 handler: handler_block,
4046 fallthrough: continuation_id,
4047 id: EvaluationOrder(0),
4048 loc,
4049 },
4050 continuation_block,
4051 );
4052 }
4053 Statement::LabeledStatement(labeled_stmt) => {
4054 let label_name = &labeled_stmt.label.name;
4055 let loc = convert_opt_loc(&labeled_stmt.base.loc);
4056
4057 // Check if the body is a loop statement - if so, delegate with label
4058 match labeled_stmt.body.as_ref() {
4059 Statement::ForStatement(_)
4060 | Statement::WhileStatement(_)
4061 | Statement::DoWhileStatement(_)
4062 | Statement::ForInStatement(_)
4063 | Statement::ForOfStatement(_) => {
4064 // Labeled loops are special because of continue, push the label down
4065 lower_statement(builder, &labeled_stmt.body, Some(label_name), parent_scope)?;
4066 }
4067 _ => {
4068 // All other statements create a continuation block to allow `break`
4069 let continuation_block = builder.reserve(BlockKind::Block);
4070 let continuation_id = continuation_block.id;
4071 let body_loc = statement_loc(&labeled_stmt.body);
4072
4073 let block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
4074 builder.label_scope(label_name.clone(), continuation_id, |builder| {
4075 lower_statement(builder, &labeled_stmt.body, None, parent_scope)?;
4076 Ok(())
4077 })?;
4078 Ok(Terminal::Goto {
4079 block: continuation_id,
4080 variant: GotoVariant::Break,
4081 id: EvaluationOrder(0),
4082 loc: body_loc,
4083 })
4084 })?;
4085
4086 builder.terminate_with_continuation(
4087 Terminal::Label {
4088 block,
4089 fallthrough: continuation_id,
4090 id: EvaluationOrder(0),
4091 loc,
4092 },
4093 continuation_block,
4094 );
4095 }
4096 }
4097 }
4098 Statement::WithStatement(with_stmt) => {
4099 let loc = convert_opt_loc(&with_stmt.base.loc);
4100 builder.record_error(CompilerErrorDetail {
4101 category: ErrorCategory::UnsupportedSyntax,
4102 reason: "JavaScript 'with' syntax is not supported".to_string(),
4103 description: Some("'with' syntax is considered deprecated and removed from JavaScript standards, consider alternatives".to_string()),
4104 loc: loc.clone(),
4105 suggestions: None,
4106 })?;
4107 lower_value_to_temporary(
4108 builder,
4109 InstructionValue::UnsupportedNode {
4110 node_type: Some("WithStatement".to_string()),
4111 original_node: serialize_statement(stmt),
4112 loc,
4113 },
4114 )?;
4115 }
4116 Statement::FunctionDeclaration(func_decl) => {
4117 lower_function_declaration(builder, func_decl)?;
4118 }
4119 Statement::ClassDeclaration(cls) => {
4120 let loc = convert_opt_loc(&cls.base.loc);
4121 builder.record_error(CompilerErrorDetail {
4122 category: ErrorCategory::UnsupportedSyntax,
4123 reason: "Inline `class` declarations are not supported".to_string(),
4124 description: Some(
4125 "Move class declarations outside of components/hooks".to_string(),
4126 ),
4127 loc: loc.clone(),
4128 suggestions: None,
4129 })?;
4130 lower_value_to_temporary(
4131 builder,
4132 InstructionValue::UnsupportedNode {
4133 node_type: Some("ClassDeclaration".to_string()),
4134 original_node: serialize_statement(stmt),
4135 loc,
4136 },
4137 )?;
4138 }
4139 Statement::ImportDeclaration(_)
4140 | Statement::ExportNamedDeclaration(_)
4141 | Statement::ExportDefaultDeclaration(_)
4142 | Statement::ExportAllDeclaration(_) => {
4143 let (loc, node_type_name) = match stmt {
4144 Statement::ImportDeclaration(s) => {
4145 (convert_opt_loc(&s.base.loc), "ImportDeclaration")
4146 }
4147 Statement::ExportNamedDeclaration(s) => {
4148 (convert_opt_loc(&s.base.loc), "ExportNamedDeclaration")
4149 }
4150 Statement::ExportDefaultDeclaration(s) => {
4151 (convert_opt_loc(&s.base.loc), "ExportDefaultDeclaration")
4152 }
4153 Statement::ExportAllDeclaration(s) => {
4154 (convert_opt_loc(&s.base.loc), "ExportAllDeclaration")
4155 }
4156 _ => unreachable!(),
4157 };
4158 builder.record_error(CompilerErrorDetail {
4159 category: ErrorCategory::Syntax,
4160 reason: "JavaScript `import` and `export` statements may only appear at the top level of a module".to_string(),
4161 description: None,
4162 loc: loc.clone(),
4163 suggestions: None,
4164 })?;
4165 lower_value_to_temporary(
4166 builder,
4167 InstructionValue::UnsupportedNode {
4168 node_type: Some(node_type_name.to_string()),
4169 original_node: serialize_statement(stmt),
4170 loc,
4171 },
4172 )?;
4173 }
4174 // TypeScript/Flow declarations are type-only, skip them
4175 Statement::TSEnumDeclaration(e) => {
4176 let loc = convert_opt_loc(&e.base.loc);
4177 let original_node = serde_json::to_value(
4178 &react_compiler_ast::statements::Statement::TSEnumDeclaration(e.clone()),
4179 )
4180 .ok();
4181 lower_value_to_temporary(
4182 builder,
4183 InstructionValue::UnsupportedNode {
4184 node_type: Some("TSEnumDeclaration".to_string()),
4185 original_node,
4186 loc,
4187 },
4188 )?;
4189 }
4190 Statement::EnumDeclaration(e) => {
4191 let loc = convert_opt_loc(&e.base.loc);
4192 let original_node = serde_json::to_value(
4193 &react_compiler_ast::statements::Statement::EnumDeclaration(e.clone()),
4194 )
4195 .ok();
4196 lower_value_to_temporary(
4197 builder,
4198 InstructionValue::UnsupportedNode {
4199 node_type: Some("EnumDeclaration".to_string()),
4200 original_node,
4201 loc,
4202 },
4203 )?;
4204 }
4205 // TypeScript/Flow type declarations are type-only, skip them
4206 Statement::TSTypeAliasDeclaration(_)
4207 | Statement::TSInterfaceDeclaration(_)
4208 | Statement::TSModuleDeclaration(_)
4209 | Statement::TSDeclareFunction(_)
4210 | Statement::TypeAlias(_)
4211 | Statement::OpaqueType(_)
4212 | Statement::InterfaceDeclaration(_)
4213 | Statement::DeclareVariable(_)
4214 | Statement::DeclareFunction(_)
4215 | Statement::DeclareClass(_)
4216 | Statement::DeclareModule(_)
4217 | Statement::DeclareModuleExports(_)
4218 | Statement::DeclareExportDeclaration(_)
4219 | Statement::DeclareExportAllDeclaration(_)
4220 | Statement::DeclareInterface(_)
4221 | Statement::DeclareTypeAlias(_)
4222 | Statement::DeclareOpaqueType(_) => {}
4223 // The TS reference can only reach its equivalent default case via
4224 // assertExhaustive (Babel's closed Statement type), so it crashes;
4225 // here unmodeled syntax is reachable by construction and degrades
4226 // like the other unsupported-statement arms instead.
4227 Statement::Unknown(unknown) => {
4228 let loc = convert_opt_loc(&unknown.base().loc);
4229 let node_type = unknown.node_type().to_string();
4230 builder.record_error(CompilerErrorDetail {
4231 category: ErrorCategory::UnsupportedSyntax,
4232 reason: format!("Unsupported statement kind '{node_type}'"),
4233 description: None,
4234 loc: loc.clone(),
4235 suggestions: None,
4236 })?;
4237 lower_value_to_temporary(
4238 builder,
4239 InstructionValue::UnsupportedNode {
4240 node_type: Some(node_type),
4241 original_node: Some(unknown.raw().parse_value()),
4242 loc,
4243 },
4244 )?;
4245 }
4246 }
4247 Ok(())
4248 }
4249
4250 // =============================================================================
4251 // lower() entry point
4252 // =============================================================================
4253
4254 enum FunctionBody<'a> {
4255 Block(&'a react_compiler_ast::statements::BlockStatement),
4256 Expression(&'a react_compiler_ast::expressions::Expression),
4257 }
4258
4259 /// Main entry point: lower a function AST node into HIR.
4260 ///
4261 /// Receives a `FunctionNode` (discovered by the entrypoint) and lowers it to HIR.
4262 /// The `id` parameter provides the function name (which may come from the variable
4263 /// declarator rather than the function node itself, e.g. `const Foo = () => {}`).
4264 pub fn lower(
4265 func: &FunctionNode<'_>,
4266 _id: Option<&str>,
4267 scope_info: &ScopeInfo,
4268 env: &mut Environment,
4269 ) -> Result<HirFunction, CompilerError> {
4270 // Extract params, body, generator, is_async, loc, scope_id, and the AST function's own id
4271 // Note: `id` param may include inferred names (e.g., from `const Foo = () => {}`),
4272 // but the HIR function's `id` field should only include the function's own AST id
4273 // (FunctionDeclaration.id or FunctionExpression.id, NOT arrow functions).
4274 let (params, body, generator, is_async, loc, start, end, ast_id) = match func {
4275 FunctionNode::FunctionDeclaration(decl) => (
4276 &decl.params[..],
4277 FunctionBody::Block(&decl.body),
4278 decl.generator,
4279 decl.is_async,
4280 convert_opt_loc(&decl.base.loc),
4281 decl.base.start.unwrap_or(0),
4282 decl.base.end.unwrap_or(0),
4283 decl.id.as_ref().map(|id| id.name.as_str()),
4284 ),
4285 FunctionNode::FunctionExpression(expr) => (
4286 &expr.params[..],
4287 FunctionBody::Block(&expr.body),
4288 expr.generator,
4289 expr.is_async,
4290 convert_opt_loc(&expr.base.loc),
4291 expr.base.start.unwrap_or(0),
4292 expr.base.end.unwrap_or(0),
4293 expr.id.as_ref().map(|id| id.name.as_str()),
4294 ),
4295 FunctionNode::ArrowFunctionExpression(arrow) => {
4296 let body = match arrow.body.as_ref() {
4297 react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
4298 FunctionBody::Block(block)
4299 }
4300 react_compiler_ast::expressions::ArrowFunctionBody::Expression(expr) => {
4301 FunctionBody::Expression(expr)
4302 }
4303 };
4304 (
4305 &arrow.params[..],
4306 body,
4307 arrow.generator,
4308 arrow.is_async,
4309 convert_opt_loc(&arrow.base.loc),
4310 arrow.base.start.unwrap_or(0),
4311 arrow.base.end.unwrap_or(0),
4312 None, // Arrow functions never have an AST id
4313 )
4314 }
4315 };
4316
4317 let scope_id = scope_info
4318 .resolve_scope_for_node(func.node_id())
4319 .unwrap_or(scope_info.program_scope);
4320
4321 validate_ts_this_parameters_in_function_range(scope_info, start, end)?;
4322
4323 // Build identifier location index from the AST (replaces serialized referenceLocs/jsxReferencePositions)
4324 let identifier_locs = build_identifier_loc_index(func, scope_info);
4325
4326 // Pre-compute context identifiers: variables captured across function boundaries
4327 let context_identifiers = find_context_identifiers(func, scope_info, env, &identifier_locs)?;
4328
4329 // For top-level functions, context is empty (no captured refs)
4330 let context_map: IndexMap<
4331 react_compiler_ast::scope::BindingId,
4332 Option<SourceLocation>,
4333 FxBuildHasher,
4334 > = IndexMap::default();
4335
4336 let (hir_func, _used_names, _child_bindings) = lower_inner(
4337 params,
4338 body,
4339 ast_id,
4340 generator,
4341 is_async,
4342 loc,
4343 scope_info,
4344 env,
4345 None, // no pre-existing bindings for top-level
4346 None, // no pre-existing used_names for top-level
4347 context_map,
4348 scope_id,
4349 scope_id, // component_scope = function_scope for top-level
4350 &context_identifiers,
4351 true, // is_top_level
4352 &identifier_locs,
4353 )?;
4354
4355 Ok(hir_func)
4356 }
4357
4358 // =============================================================================
4359 // Stubs for future milestones
4360 // =============================================================================
4361
4362 /// Result of resolving an identifier for assignment.
4363 enum IdentifierForAssignment {
4364 /// A local place (identifier binding)
4365 Place(Place),
4366 /// A global variable (non-local, non-import)
4367 Global { name: String },
4368 }
4369
4370 /// Resolve an identifier for use as an assignment target.
4371 /// Returns None if the binding could not be found (error recorded).
4372 fn lower_identifier_for_assignment(
4373 builder: &mut HirBuilder,
4374 loc: Option<SourceLocation>,
4375 ident_loc: Option<SourceLocation>,
4376 kind: InstructionKind,
4377 name: &str,
4378 start: u32,
4379 node_id: Option<u32>,
4380 ) -> Result<Option<IdentifierForAssignment>, CompilerError> {
4381 let mut binding = builder.resolve_identifier(name, start, ident_loc.clone(), node_id)?;
4382 if !matches!(binding, VariableBinding::Identifier { .. }) && kind != InstructionKind::Reassign {
4383 if let Some((binding_id, binding_data)) = builder
4384 .scope_info()
4385 .find_binding_id_in_descendants(name, builder.function_scope())
4386 {
4387 let bk = crate::convert_binding_kind(&binding_data.kind);
4388 let identifier =
4389 builder.resolve_binding_with_loc(name, binding_id, ident_loc.clone())?;
4390 binding = VariableBinding::Identifier {
4391 identifier,
4392 binding_kind: bk,
4393 };
4394 }
4395 }
4396 match binding {
4397 VariableBinding::Identifier {
4398 identifier,
4399 binding_kind,
4400 ..
4401 } => {
4402 // Set the identifier's loc from the declaration site (not for reassignments,
4403 // which should keep the original declaration loc)
4404 if kind != InstructionKind::Reassign {
4405 builder.set_identifier_declaration_loc(identifier, &ident_loc);
4406 }
4407 if binding_kind == BindingKind::Const && kind == InstructionKind::Reassign {
4408 builder.record_error(CompilerErrorDetail {
4409 reason: "Cannot reassign a `const` variable".to_string(),
4410 category: ErrorCategory::Syntax,
4411 loc: loc.clone(),
4412 description: Some(format!("`{}` is declared as const", name)),
4413 suggestions: None,
4414 })?;
4415 return Ok(None);
4416 }
4417 Ok(Some(IdentifierForAssignment::Place(Place {
4418 identifier,
4419 effect: Effect::Unknown,
4420 reactive: false,
4421 loc,
4422 })))
4423 }
4424 VariableBinding::Global { name: gname } => {
4425 if kind == InstructionKind::Reassign {
4426 Ok(Some(IdentifierForAssignment::Global { name: gname }))
4427 } else {
4428 builder.record_error(CompilerErrorDetail {
4429 reason: "Could not find binding for declaration".to_string(),
4430 category: ErrorCategory::Invariant,
4431 loc,
4432 description: None,
4433 suggestions: None,
4434 })?;
4435 Ok(None)
4436 }
4437 }
4438 _ => {
4439 // Import bindings can't be assigned to
4440 if kind == InstructionKind::Reassign {
4441 Ok(Some(IdentifierForAssignment::Global {
4442 name: name.to_string(),
4443 }))
4444 } else {
4445 builder.record_error(CompilerErrorDetail {
4446 reason: "Could not find binding for declaration".to_string(),
4447 category: ErrorCategory::Invariant,
4448 loc,
4449 description: None,
4450 suggestions: None,
4451 })?;
4452 Ok(None)
4453 }
4454 }
4455 }
4456 }
4457
4458 fn lower_assignment(
4459 builder: &mut HirBuilder,
4460 loc: Option<SourceLocation>,
4461 kind: InstructionKind,
4462 target: &react_compiler_ast::patterns::PatternLike,
4463 value: Place,
4464 assignment_style: AssignmentStyle,
4465 ) -> Result<Option<Place>, CompilerError> {
4466 use react_compiler_ast::patterns::PatternLike;
4467
4468 match target {
4469 PatternLike::Identifier(id) => {
4470 let id_loc = convert_opt_loc(&id.base.loc);
4471 let result = lower_identifier_for_assignment(
4472 builder,
4473 loc.clone(),
4474 id_loc,
4475 kind,
4476 &id.name,
4477 id.base.start.unwrap_or(0),
4478 id.base.node_id,
4479 )?;
4480 match result {
4481 None => {
4482 // Error already recorded
4483 return Ok(None);
4484 }
4485 Some(IdentifierForAssignment::Global { name }) => {
4486 let temp = lower_value_to_temporary(
4487 builder,
4488 InstructionValue::StoreGlobal { name, value, loc },
4489 )?;
4490 return Ok(Some(temp));
4491 }
4492 Some(IdentifierForAssignment::Place(place)) => {
4493 let start = id.base.start.unwrap_or(0);
4494 if builder.is_context_identifier(&id.name, start, id.base.node_id) {
4495 // Check if the binding is hoisted before flagging const reassignment
4496 let is_hoisted = builder
4497 .scope_info()
4498 .resolve_reference_for_node(id.base.node_id)
4499 .map(|b| builder.environment().is_hoisted_identifier(b.id.0))
4500 .unwrap_or(false);
4501 if kind == InstructionKind::Const && !is_hoisted {
4502 builder.record_error(CompilerErrorDetail {
4503 reason: "Expected `const` declaration not to be reassigned"
4504 .to_string(),
4505 category: ErrorCategory::Syntax,
4506 loc: loc.clone(),
4507 suggestions: None,
4508 description: None,
4509 })?;
4510 }
4511 if kind != InstructionKind::Const
4512 && kind != InstructionKind::Reassign
4513 && kind != InstructionKind::Let
4514 && kind != InstructionKind::Function
4515 {
4516 builder.record_error(CompilerErrorDetail {
4517 reason: "Unexpected context variable kind".to_string(),
4518 category: ErrorCategory::Syntax,
4519 loc: loc.clone(),
4520 suggestions: None,
4521 description: None,
4522 })?;
4523 let temp = lower_value_to_temporary(
4524 builder,
4525 InstructionValue::UnsupportedNode {
4526 node_type: Some("Identifier".to_string()),
4527 original_node: serialize_pattern(target),
4528 loc,
4529 },
4530 )?;
4531 return Ok(Some(temp));
4532 }
4533 let temp = lower_value_to_temporary(
4534 builder,
4535 InstructionValue::StoreContext {
4536 lvalue: LValue { place, kind },
4537 value,
4538 loc,
4539 },
4540 )?;
4541 return Ok(Some(temp));
4542 } else {
4543 let type_annotation = extract_type_annotation_name(&id.type_annotation);
4544 let temp = lower_value_to_temporary(
4545 builder,
4546 InstructionValue::StoreLocal {
4547 lvalue: LValue { place, kind },
4548 value,
4549 type_annotation,
4550 loc,
4551 },
4552 )?;
4553 return Ok(Some(temp));
4554 }
4555 }
4556 }
4557 }
4558
4559 PatternLike::MemberExpression(member) => {
4560 // MemberExpression may only appear in an assignment expression (Reassign)
4561 if kind != InstructionKind::Reassign {
4562 builder.record_error(CompilerErrorDetail {
4563 category: ErrorCategory::Invariant,
4564 reason: "MemberExpression may only appear in an assignment expression"
4565 .to_string(),
4566 description: None,
4567 loc: loc.clone(),
4568 suggestions: None,
4569 })?;
4570 return Ok(None);
4571 }
4572 let object = lower_expression_to_temporary(builder, &member.object)?;
4573 let temp = if !member.computed
4574 || matches!(
4575 &*member.property,
4576 react_compiler_ast::expressions::Expression::NumericLiteral(_)
4577 ) {
4578 match &*member.property {
4579 react_compiler_ast::expressions::Expression::Identifier(prop_id) => {
4580 lower_value_to_temporary(
4581 builder,
4582 InstructionValue::PropertyStore {
4583 object,
4584 property: PropertyLiteral::String(prop_id.name.clone()),
4585 value,
4586 loc,
4587 },
4588 )?
4589 }
4590 react_compiler_ast::expressions::Expression::NumericLiteral(num) => {
4591 lower_value_to_temporary(
4592 builder,
4593 InstructionValue::PropertyStore {
4594 object,
4595 property: PropertyLiteral::Number(FloatValue::new(
4596 num.precise_value(),
4597 )),
4598 value,
4599 loc,
4600 },
4601 )?
4602 }
4603 _ => {
4604 builder.record_error(CompilerErrorDetail {
4605 reason: format!("(BuildHIR::lowerAssignment) Handle {} properties in MemberExpression", expression_type_name(&member.property)),
4606 category: ErrorCategory::Todo,
4607 loc: expression_loc(&member.property),
4608 description: None,
4609 suggestions: None,
4610 })?;
4611 lower_value_to_temporary(
4612 builder,
4613 InstructionValue::UnsupportedNode {
4614 node_type: Some("MemberExpression".to_string()),
4615 original_node: serialize_pattern(target),
4616 loc,
4617 },
4618 )?
4619 }
4620 }
4621 } else {
4622 if matches!(
4623 &*member.property,
4624 react_compiler_ast::expressions::Expression::PrivateName(_)
4625 ) {
4626 builder.record_error(CompilerErrorDetail {
4627 reason: "(BuildHIR::lowerAssignment) Expected private name to appear as a non-computed property".to_string(),
4628 category: ErrorCategory::Todo,
4629 loc: expression_loc(&member.property),
4630 description: None,
4631 suggestions: None,
4632 })?;
4633 lower_value_to_temporary(
4634 builder,
4635 InstructionValue::UnsupportedNode {
4636 node_type: Some("MemberExpression".to_string()),
4637 original_node: serialize_pattern(target),
4638 loc,
4639 },
4640 )?
4641 } else {
4642 let property_place = lower_expression_to_temporary(builder, &member.property)?;
4643 lower_value_to_temporary(
4644 builder,
4645 InstructionValue::ComputedStore {
4646 object,
4647 property: property_place,
4648 value,
4649 loc,
4650 },
4651 )?
4652 }
4653 };
4654 Ok(Some(temp))
4655 }
4656
4657 PatternLike::ArrayPattern(pattern) => {
4658 let mut items: Vec<ArrayPatternElement> = Vec::new();
4659 let mut followups: Vec<(Place, &PatternLike)> = Vec::new();
4660
4661 // Compute forceTemporaries: when kind is Reassign and any element is
4662 // non-identifier, a context variable, or a non-local binding
4663 let force_temporaries = if kind == InstructionKind::Reassign {
4664 let mut found = false;
4665 for elem in &pattern.elements {
4666 match elem {
4667 Some(PatternLike::Identifier(id)) => {
4668 let start = id.base.start.unwrap_or(0);
4669 if builder.is_context_identifier(&id.name, start, id.base.node_id) {
4670 found = true;
4671 break;
4672 }
4673 let ident_loc = convert_opt_loc(&id.base.loc);
4674 match builder.resolve_identifier(
4675 &id.name,
4676 start,
4677 ident_loc,
4678 id.base.node_id,
4679 )? {
4680 VariableBinding::Identifier { .. } => {}
4681 _ => {
4682 found = true;
4683 break;
4684 }
4685 }
4686 }
4687 _ => {
4688 // Non-identifier elements (including None/holes and RestElements)
4689 // trigger forceTemporaries, matching TS where `!element.isIdentifier()`
4690 // returns true for null elements
4691 found = true;
4692 break;
4693 }
4694 }
4695 }
4696 found
4697 } else {
4698 false
4699 };
4700
4701 for element in &pattern.elements {
4702 match element {
4703 None => {
4704 items.push(ArrayPatternElement::Hole);
4705 }
4706 Some(PatternLike::RestElement(rest)) => {
4707 match &*rest.argument {
4708 PatternLike::Identifier(id) => {
4709 let start = id.base.start.unwrap_or(0);
4710 let is_context =
4711 builder.is_context_identifier(&id.name, start, id.base.node_id);
4712 let can_use_direct = !force_temporaries
4713 && (matches!(assignment_style, AssignmentStyle::Assignment)
4714 || !is_context);
4715 if can_use_direct {
4716 match lower_identifier_for_assignment(
4717 builder,
4718 convert_opt_loc(&rest.base.loc),
4719 convert_opt_loc(&id.base.loc),
4720 kind,
4721 &id.name,
4722 start,
4723 id.base.node_id,
4724 )? {
4725 Some(IdentifierForAssignment::Place(place)) => {
4726 items.push(ArrayPatternElement::Spread(
4727 SpreadPattern { place },
4728 ));
4729 }
4730 Some(IdentifierForAssignment::Global { .. }) => {
4731 let temp = build_temporary_place(
4732 builder,
4733 convert_opt_loc(&rest.base.loc),
4734 );
4735 promote_temporary(builder, temp.identifier);
4736 items.push(ArrayPatternElement::Spread(
4737 SpreadPattern {
4738 place: temp.clone(),
4739 },
4740 ));
4741 followups.push((temp, &rest.argument));
4742 }
4743 None => {
4744 // Error already recorded
4745 }
4746 }
4747 } else {
4748 let temp = build_temporary_place(
4749 builder,
4750 convert_opt_loc(&rest.base.loc),
4751 );
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 let temp =
4761 build_temporary_place(builder, convert_opt_loc(&rest.base.loc));
4762 promote_temporary(builder, temp.identifier);
4763 items.push(ArrayPatternElement::Spread(SpreadPattern {
4764 place: temp.clone(),
4765 }));
4766 followups.push((temp, &rest.argument));
4767 }
4768 }
4769 }
4770 Some(PatternLike::Identifier(id)) => {
4771 let start = id.base.start.unwrap_or(0);
4772 let is_context =
4773 builder.is_context_identifier(&id.name, start, id.base.node_id);
4774 let can_use_direct = !force_temporaries
4775 && (matches!(assignment_style, AssignmentStyle::Assignment)
4776 || !is_context);
4777 if can_use_direct {
4778 match lower_identifier_for_assignment(
4779 builder,
4780 convert_opt_loc(&id.base.loc),
4781 convert_opt_loc(&id.base.loc),
4782 kind,
4783 &id.name,
4784 start,
4785 id.base.node_id,
4786 )? {
4787 Some(IdentifierForAssignment::Place(place)) => {
4788 items.push(ArrayPatternElement::Place(place));
4789 }
4790 Some(IdentifierForAssignment::Global { .. }) => {
4791 let temp = build_temporary_place(
4792 builder,
4793 convert_opt_loc(&id.base.loc),
4794 );
4795 promote_temporary(builder, temp.identifier);
4796 items.push(ArrayPatternElement::Place(temp.clone()));
4797 followups.push((temp, element.as_ref().unwrap()));
4798 }
4799 None => {
4800 items.push(ArrayPatternElement::Hole);
4801 }
4802 }
4803 } else {
4804 // Context variable or force_temporaries: use promoted temporary
4805 let temp =
4806 build_temporary_place(builder, convert_opt_loc(&id.base.loc));
4807 promote_temporary(builder, temp.identifier);
4808 items.push(ArrayPatternElement::Place(temp.clone()));
4809 followups.push((temp, element.as_ref().unwrap()));
4810 }
4811 }
4812 Some(other) => {
4813 // Nested pattern: use temporary + followup
4814 let elem_loc = pattern_like_hir_loc(other);
4815 let temp = build_temporary_place(builder, elem_loc);
4816 promote_temporary(builder, temp.identifier);
4817 items.push(ArrayPatternElement::Place(temp.clone()));
4818 followups.push((temp, other));
4819 }
4820 }
4821 }
4822
4823 let temporary = lower_value_to_temporary(
4824 builder,
4825 InstructionValue::Destructure {
4826 lvalue: LValuePattern {
4827 pattern: Pattern::Array(ArrayPattern {
4828 items,
4829 loc: convert_opt_loc(&pattern.base.loc),
4830 }),
4831 kind,
4832 },
4833 value: value.clone(),
4834 loc: loc.clone(),
4835 },
4836 )?;
4837
4838 for (place, path) in followups {
4839 let followup_loc = pattern_like_hir_loc(path).or(loc.clone());
4840 lower_assignment(builder, followup_loc, kind, path, place, assignment_style)?;
4841 }
4842 Ok(Some(temporary))
4843 }
4844
4845 PatternLike::ObjectPattern(pattern) => {
4846 let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new();
4847 let mut followups: Vec<(Place, &PatternLike)> = Vec::new();
4848
4849 // Compute forceTemporaries for ObjectPattern
4850 let force_temporaries = if kind == InstructionKind::Reassign {
4851 use react_compiler_ast::patterns::ObjectPatternProperty;
4852 let mut found = false;
4853 for prop in &pattern.properties {
4854 match prop {
4855 ObjectPatternProperty::RestElement(_) => {
4856 found = true;
4857 break;
4858 }
4859 ObjectPatternProperty::ObjectProperty(obj_prop) => match &*obj_prop.value {
4860 PatternLike::Identifier(id) => {
4861 let start = id.base.start.unwrap_or(0);
4862 let ident_loc = convert_opt_loc(&id.base.loc);
4863 match builder.resolve_identifier(
4864 &id.name,
4865 start,
4866 ident_loc,
4867 id.base.node_id,
4868 )? {
4869 VariableBinding::Identifier { .. } => {}
4870 _ => {
4871 found = true;
4872 break;
4873 }
4874 }
4875 }
4876 _ => {
4877 found = true;
4878 break;
4879 }
4880 },
4881 }
4882 }
4883 found
4884 } else {
4885 false
4886 };
4887
4888 for prop in &pattern.properties {
4889 match prop {
4890 react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => {
4891 match &*rest.argument {
4892 PatternLike::Identifier(id) => {
4893 let start = id.base.start.unwrap_or(0);
4894 let is_context =
4895 builder.is_context_identifier(&id.name, start, id.base.node_id);
4896 let can_use_direct = !force_temporaries
4897 && (matches!(assignment_style, AssignmentStyle::Assignment)
4898 || !is_context);
4899 if can_use_direct {
4900 match lower_identifier_for_assignment(
4901 builder,
4902 convert_opt_loc(&rest.base.loc),
4903 convert_opt_loc(&id.base.loc),
4904 kind,
4905 &id.name,
4906 start,
4907 id.base.node_id,
4908 )? {
4909 Some(IdentifierForAssignment::Place(place)) => {
4910 properties.push(ObjectPropertyOrSpread::Spread(
4911 SpreadPattern { place },
4912 ));
4913 }
4914 Some(IdentifierForAssignment::Global { .. }) => {
4915 builder.record_error(CompilerErrorDetail {
4916 reason: "Expected reassignment of globals to enable forceTemporaries".to_string(),
4917 category: ErrorCategory::Todo,
4918 loc: convert_opt_loc(&rest.base.loc),
4919 description: None,
4920 suggestions: None,
4921 })?;
4922 }
4923 None => {}
4924 }
4925 } else {
4926 let temp = build_temporary_place(
4927 builder,
4928 convert_opt_loc(&rest.base.loc),
4929 );
4930 promote_temporary(builder, temp.identifier);
4931 properties.push(ObjectPropertyOrSpread::Spread(
4932 SpreadPattern {
4933 place: temp.clone(),
4934 },
4935 ));
4936 followups.push((temp, &rest.argument));
4937 }
4938 }
4939 _ => {
4940 builder.record_error(CompilerErrorDetail {
4941 reason: format!("(BuildHIR::lowerAssignment) Handle {} rest element in ObjectPattern",
4942 match &*rest.argument {
4943 PatternLike::ObjectPattern(_) => "ObjectPattern",
4944 PatternLike::ArrayPattern(_) => "ArrayPattern",
4945 PatternLike::AssignmentPattern(_) => "AssignmentPattern",
4946 PatternLike::MemberExpression(_) => "MemberExpression",
4947 _ => "unknown",
4948 }),
4949 category: ErrorCategory::Todo,
4950 loc: convert_opt_loc(&rest.base.loc),
4951 description: None,
4952 suggestions: None,
4953 })?;
4954 }
4955 }
4956 }
4957 react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(
4958 obj_prop,
4959 ) => {
4960 if obj_prop.computed {
4961 builder.record_error(CompilerErrorDetail {
4962 reason: "(BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern".to_string(),
4963 category: ErrorCategory::Todo,
4964 loc: convert_opt_loc(&obj_prop.base.loc),
4965 description: None,
4966 suggestions: None,
4967 })?;
4968 continue;
4969 }
4970
4971 let key = match lower_object_property_key(builder, &obj_prop.key, false)? {
4972 Some(k) => k,
4973 None => continue,
4974 };
4975
4976 match &*obj_prop.value {
4977 PatternLike::Identifier(id) => {
4978 let start = id.base.start.unwrap_or(0);
4979 let is_context =
4980 builder.is_context_identifier(&id.name, start, id.base.node_id);
4981 let can_use_direct = !force_temporaries
4982 && (matches!(assignment_style, AssignmentStyle::Assignment)
4983 || !is_context);
4984 if can_use_direct {
4985 match lower_identifier_for_assignment(
4986 builder,
4987 convert_opt_loc(&id.base.loc),
4988 convert_opt_loc(&id.base.loc),
4989 kind,
4990 &id.name,
4991 start,
4992 id.base.node_id,
4993 )? {
4994 Some(IdentifierForAssignment::Place(place)) => {
4995 properties.push(ObjectPropertyOrSpread::Property(
4996 ObjectProperty {
4997 key,
4998 property_type: ObjectPropertyType::Property,
4999 place,
5000 },
Showing first 5,000 of 7,390 lines. View raw