1use rustc_hash::{FxBuildHasher, FxHashSet};23use indexmap::{IndexMap, IndexSet};4use react_compiler_ast::scope::BindingId;5use react_compiler_ast::scope::BindingKind as AstBindingKind;6use react_compiler_ast::scope::ScopeId;7use react_compiler_ast::scope::ScopeInfo;8use react_compiler_ast::scope::ScopeKind;9use react_compiler_diagnostics::CompilerDiagnostic;10use react_compiler_diagnostics::CompilerDiagnosticDetail;11use react_compiler_diagnostics::CompilerError;12use react_compiler_diagnostics::CompilerErrorDetail;13use react_compiler_diagnostics::ErrorCategory;14use react_compiler_hir::environment::Environment;15use react_compiler_hir::*;1617use crate::FunctionNode;18use crate::find_context_identifiers::find_context_identifiers;19use crate::hir_builder::HirBuilder;20use crate::hir_builder::is_always_reserved_word;21use crate::hir_builder::reserved_identifier_diagnostic;22use crate::identifier_loc_index::IdentifierLocIndex;23use crate::identifier_loc_index::build_identifier_loc_index;2425// =============================================================================26// Source location conversion27// =============================================================================2829/// Convert an AST SourceLocation to an HIR SourceLocation.30fn 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}4445/// Convert an optional AST SourceLocation to an optional HIR SourceLocation.46fn convert_opt_loc(47 loc: &Option<react_compiler_ast::common::SourceLocation>,48) -> Option<SourceLocation> {49 loc.as_ref().map(convert_loc)50}5152/// 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 deciding55/// to create an UnsupportedNode.56fn serialize_expression(57 expr: &react_compiler_ast::expressions::Expression,58) -> Option<serde_json::Value> {59 serde_json::to_value(expr).ok()60}6162/// Serialize a statement to a serde_json::Value for UnsupportedNode's original_node.63fn serialize_statement(64 stmt: &react_compiler_ast::statements::Statement,65) -> Option<serde_json::Value> {66 serde_json::to_value(stmt).ok()67}6869/// Serialize a pattern to a serde_json::Value for UnsupportedNode's original_node.70fn serialize_pattern(pat: &react_compiler_ast::patterns::PatternLike) -> Option<serde_json::Value> {71 serde_json::to_value(pat).ok()72}7374fn 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}9293/// Extract the HIR SourceLocation from an Expression AST node.94fn 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}144145fn 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}163164fn 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 false178}179180fn 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}204205/// Get the Babel-style type name of an Expression node (e.g. "Identifier", "NumericLiteral").206fn 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}255256/// 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.261fn 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.type266 let inner = val.get("typeAnnotation")?;267 let type_name = inner.get("type")?.as_str()?;268 Some(type_name.to_string())269}270271// =============================================================================272// Helper functions273// =============================================================================274275fn 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}284285/// Promote a temporary identifier to a named identifier (for destructuring).286/// Corresponds to TS `promoteTemporary(identifier)`.287fn 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}293294fn 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 instruction299 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}316317fn 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}324325// =============================================================================326// Operator conversion327// =============================================================================328329fn 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}359360fn 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}372373// =============================================================================374// lower_identifier375// =============================================================================376377/// 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.381fn 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 if name == "arguments" {410 builder.record_error(CompilerErrorDetail {411 category: ErrorCategory::UnsupportedSyntax,412 reason: "Implicit 'arguments' is not supported".to_string(),413 description: Some(414 "React Compiler does not support compiling functions that reference the implicit arguments object"415 .to_string(),416 ),417 loc: loc.clone(),418 suggestions: None,419 })?;420 }421 }422 let non_local_binding = match binding {423 VariableBinding::Global { name } => NonLocalBinding::Global { name },424 VariableBinding::ImportDefault { name, module } => {425 NonLocalBinding::ImportDefault { name, module }426 }427 VariableBinding::ImportSpecifier {428 name,429 module,430 imported,431 } => NonLocalBinding::ImportSpecifier {432 name,433 module,434 imported,435 },436 VariableBinding::ImportNamespace { name, module } => {437 NonLocalBinding::ImportNamespace { name, module }438 }439 VariableBinding::ModuleLocal { name } => NonLocalBinding::ModuleLocal { name },440 VariableBinding::Identifier { .. } => unreachable!(),441 };442 let instr_value = InstructionValue::LoadGlobal {443 binding: non_local_binding,444 loc: loc.clone(),445 };446 Ok(lower_value_to_temporary(builder, instr_value)?)447 }448 }449}450451// =============================================================================452// lower_arguments453// =============================================================================454455fn lower_arguments(456 builder: &mut HirBuilder,457 args: &[react_compiler_ast::expressions::Expression],458) -> Result<Vec<PlaceOrSpread>, CompilerError> {459 use react_compiler_ast::expressions::Expression;460 let mut result = Vec::new();461 for arg in args {462 match arg {463 Expression::SpreadElement(spread) => {464 let place = lower_expression_to_temporary(builder, &spread.argument)?;465 result.push(PlaceOrSpread::Spread(SpreadPattern { place }));466 }467 _ => {468 let place = lower_expression_to_temporary(builder, arg)?;469 result.push(PlaceOrSpread::Place(place));470 }471 }472 }473 Ok(result)474}475476fn convert_update_operator(op: &react_compiler_ast::operators::UpdateOperator) -> UpdateOperator {477 match op {478 react_compiler_ast::operators::UpdateOperator::Increment => UpdateOperator::Increment,479 react_compiler_ast::operators::UpdateOperator::Decrement => UpdateOperator::Decrement,480 }481}482483// =============================================================================484// lower_member_expression485// =============================================================================486487enum MemberProperty {488 Literal(PropertyLiteral),489 Computed(Place),490}491492struct LoweredMemberExpression {493 object: Place,494 property: MemberProperty,495 value: InstructionValue,496}497498fn lower_member_expression(499 builder: &mut HirBuilder,500 member: &react_compiler_ast::expressions::MemberExpression,501) -> Result<LoweredMemberExpression, CompilerError> {502 Ok(lower_member_expression_impl(builder, member, None)?)503}504505fn lower_member_expression_with_object(506 builder: &mut HirBuilder,507 member: &react_compiler_ast::expressions::OptionalMemberExpression,508 lowered_object: Place,509) -> Result<LoweredMemberExpression, CompilerError> {510 // OptionalMemberExpression has the same shape as MemberExpression for property access511 use react_compiler_ast::expressions::Expression;512 let loc = convert_opt_loc(&member.base.loc);513 let object = lowered_object;514515 if !member.computed {516 let prop_literal = match member.property.as_ref() {517 Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),518 Expression::NumericLiteral(lit) => {519 PropertyLiteral::Number(FloatValue::new(lit.precise_value()))520 }521 _ => {522 builder.record_error(CompilerErrorDetail {523 category: ErrorCategory::Todo,524 reason: format!(525 "(BuildHIR::lowerMemberExpression) Handle {:?} property",526 member.property527 ),528 description: None,529 loc: loc.clone(),530 suggestions: None,531 })?;532 return Ok(LoweredMemberExpression {533 object,534 property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),535 value: InstructionValue::UnsupportedNode {536 node_type: Some("OptionalMemberExpression".to_string()),537 original_node: serialize_expression(538 &react_compiler_ast::expressions::Expression::OptionalMemberExpression(539 member.clone(),540 ),541 ),542 loc,543 },544 });545 }546 };547 let value = InstructionValue::PropertyLoad {548 object: object.clone(),549 property: prop_literal.clone(),550 loc,551 };552 Ok(LoweredMemberExpression {553 object,554 property: MemberProperty::Literal(prop_literal),555 value,556 })557 } else {558 if let Expression::NumericLiteral(lit) = member.property.as_ref() {559 let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));560 let value = InstructionValue::PropertyLoad {561 object: object.clone(),562 property: prop_literal.clone(),563 loc,564 };565 return Ok(LoweredMemberExpression {566 object,567 property: MemberProperty::Literal(prop_literal),568 value,569 });570 }571 let property = lower_expression_to_temporary(builder, &member.property)?;572 let value = InstructionValue::ComputedLoad {573 object: object.clone(),574 property: property.clone(),575 loc,576 };577 Ok(LoweredMemberExpression {578 object,579 property: MemberProperty::Computed(property),580 value,581 })582 }583}584585fn lower_member_expression_impl(586 builder: &mut HirBuilder,587 member: &react_compiler_ast::expressions::MemberExpression,588 lowered_object: Option<Place>,589) -> Result<LoweredMemberExpression, CompilerError> {590 use react_compiler_ast::expressions::Expression;591 let loc = convert_opt_loc(&member.base.loc);592 let object = match lowered_object {593 Some(obj) => obj,594 None => lower_expression_to_temporary(builder, &member.object)?,595 };596597 if !member.computed {598 // Non-computed: property must be an identifier or numeric literal599 let prop_literal = match member.property.as_ref() {600 Expression::Identifier(id) => PropertyLiteral::String(id.name.clone()),601 Expression::NumericLiteral(lit) => {602 PropertyLiteral::Number(FloatValue::new(lit.precise_value()))603 }604 _ => {605 builder.record_error(CompilerErrorDetail {606 category: ErrorCategory::Todo,607 reason: format!(608 "(BuildHIR::lowerMemberExpression) Handle {:?} property",609 member.property610 ),611 description: None,612 loc: loc.clone(),613 suggestions: None,614 })?;615 return Ok(LoweredMemberExpression {616 object,617 property: MemberProperty::Literal(PropertyLiteral::String("".to_string())),618 value: InstructionValue::UnsupportedNode {619 node_type: Some("MemberExpression".to_string()),620 original_node: serialize_expression(621 &react_compiler_ast::expressions::Expression::MemberExpression(622 member.clone(),623 ),624 ),625 loc,626 },627 });628 }629 };630 let value = InstructionValue::PropertyLoad {631 object: object.clone(),632 property: prop_literal.clone(),633 loc,634 };635 Ok(LoweredMemberExpression {636 object,637 property: MemberProperty::Literal(prop_literal),638 value,639 })640 } else {641 // Computed: check for numeric literal first (treated as PropertyLoad in TS)642 if let Expression::NumericLiteral(lit) = member.property.as_ref() {643 let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.precise_value()));644 let value = InstructionValue::PropertyLoad {645 object: object.clone(),646 property: prop_literal.clone(),647 loc,648 };649 return Ok(LoweredMemberExpression {650 object,651 property: MemberProperty::Literal(prop_literal),652 value,653 });654 }655 // Otherwise lower property to temporary for ComputedLoad656 let property = lower_expression_to_temporary(builder, &member.property)?;657 let value = InstructionValue::ComputedLoad {658 object: object.clone(),659 property: property.clone(),660 loc,661 };662 Ok(LoweredMemberExpression {663 object,664 property: MemberProperty::Computed(property),665 value,666 })667 }668}669670// =============================================================================671// lower_expression672// =============================================================================673674fn lower_expression(675 builder: &mut HirBuilder,676 expr: &react_compiler_ast::expressions::Expression,677) -> Result<InstructionValue, CompilerError> {678 use react_compiler_ast::expressions::Expression;679680 match expr {681 Expression::Identifier(ident) => {682 let loc = convert_opt_loc(&ident.base.loc);683 let start = ident.base.start.unwrap_or(0);684 let place =685 lower_identifier(builder, &ident.name, start, loc.clone(), ident.base.node_id)?;686 // Determine LoadLocal vs LoadContext based on context identifier check687 if builder.is_context_identifier(&ident.name, start, ident.base.node_id) {688 Ok(InstructionValue::LoadContext { place, loc })689 } else {690 Ok(InstructionValue::LoadLocal { place, loc })691 }692 }693 Expression::NullLiteral(lit) => {694 let loc = convert_opt_loc(&lit.base.loc);695 Ok(InstructionValue::Primitive {696 value: PrimitiveValue::Null,697 loc,698 })699 }700 Expression::BooleanLiteral(lit) => {701 let loc = convert_opt_loc(&lit.base.loc);702 Ok(InstructionValue::Primitive {703 value: PrimitiveValue::Boolean(lit.value),704 loc,705 })706 }707 Expression::NumericLiteral(lit) => {708 let loc = convert_opt_loc(&lit.base.loc);709 Ok(InstructionValue::Primitive {710 value: PrimitiveValue::Number(FloatValue::new(lit.precise_value())),711 loc,712 })713 }714 Expression::StringLiteral(lit) => {715 let loc = convert_opt_loc(&lit.base.loc);716 Ok(InstructionValue::Primitive {717 value: PrimitiveValue::String(lit.value.clone()),718 loc,719 })720 }721 Expression::BinaryExpression(bin) => {722 let loc = convert_opt_loc(&bin.base.loc);723 // Check for pipeline operator before lowering operands724 if matches!(725 bin.operator,726 react_compiler_ast::operators::BinaryOperator::Pipeline727 ) {728 builder.record_error(CompilerErrorDetail {729 category: ErrorCategory::Todo,730 reason: "(BuildHIR::lowerExpression) Pipe operator not supported".to_string(),731 description: None,732 loc: loc.clone(),733 suggestions: None,734 })?;735 return Ok(InstructionValue::UnsupportedNode {736 node_type: Some("BinaryExpression".to_string()),737 original_node: serialize_expression(expr),738 loc,739 });740 }741 let left = lower_expression_to_temporary(builder, &bin.left)?;742 let right = lower_expression_to_temporary(builder, &bin.right)?;743 let operator = convert_binary_operator(&bin.operator);744 Ok(InstructionValue::BinaryExpression {745 operator,746 left,747 right,748 loc,749 })750 }751 Expression::UnaryExpression(unary) => {752 let loc = convert_opt_loc(&unary.base.loc);753 match &unary.operator {754 react_compiler_ast::operators::UnaryOperator::Delete => {755 // Delete can be on member expressions or identifiers756 let loc = convert_opt_loc(&unary.base.loc);757 match &*unary.argument {758 Expression::MemberExpression(member) => {759 let object = lower_expression_to_temporary(builder, &member.object)?;760 if !member.computed {761 match &*member.property {762 Expression::Identifier(prop_id) => {763 Ok(InstructionValue::PropertyDelete {764 object,765 property: PropertyLiteral::String(prop_id.name.clone()),766 loc,767 })768 }769 _ => {770 builder.record_error(CompilerErrorDetail {771 reason: "Unsupported delete target".to_string(),772 category: ErrorCategory::Todo,773 loc: loc.clone(),774 description: None,775 suggestions: None,776 })?;777 Ok(InstructionValue::UnsupportedNode {778 node_type: Some("UnaryExpression".to_string()),779 original_node: serialize_expression(expr),780 loc,781 })782 }783 }784 } else {785 let property =786 lower_expression_to_temporary(builder, &member.property)?;787 Ok(InstructionValue::ComputedDelete {788 object,789 property,790 loc,791 })792 }793 }794 _ => {795 // delete on non-member expression (e.g., optional chain, identifier)796 builder.record_error(CompilerErrorDetail {797 reason: "Only object properties can be deleted".to_string(),798 category: ErrorCategory::Syntax,799 loc: loc.clone(),800 description: None,801 suggestions: None,802 })?;803 Ok(InstructionValue::UnsupportedNode {804 node_type: Some("UnaryExpression".to_string()),805 original_node: serialize_expression(expr),806 loc,807 })808 }809 }810 }811 react_compiler_ast::operators::UnaryOperator::Throw => {812 // throw as unary operator (Babel-specific)813 let loc = convert_opt_loc(&unary.base.loc);814 builder.record_error(CompilerErrorDetail {815 reason: "throw expressions are not supported".to_string(),816 category: ErrorCategory::Todo,817 loc: loc.clone(),818 description: None,819 suggestions: None,820 })?;821 Ok(InstructionValue::UnsupportedNode {822 node_type: Some("UnaryExpression".to_string()),823 original_node: serialize_expression(expr),824 loc,825 })826 }827 op => {828 let value = lower_expression_to_temporary(builder, &unary.argument)?;829 let operator = convert_unary_operator(op);830 Ok(InstructionValue::UnaryExpression {831 operator,832 value,833 loc,834 })835 }836 }837 }838 Expression::CallExpression(call) => {839 let loc = convert_opt_loc(&call.base.loc);840 // Check if callee is a MemberExpression => MethodCall841 if let Expression::MemberExpression(member) = call.callee.as_ref() {842 let lowered = lower_member_expression(builder, member)?;843 let property = lower_value_to_temporary(builder, lowered.value)?;844 let args = lower_arguments(builder, &call.arguments)?;845 Ok(InstructionValue::MethodCall {846 receiver: lowered.object,847 property,848 args,849 loc,850 })851 } else {852 let callee = lower_expression_to_temporary(builder, &call.callee)?;853 let args = lower_arguments(builder, &call.arguments)?;854 Ok(InstructionValue::CallExpression { callee, args, loc })855 }856 }857 Expression::MemberExpression(member) => {858 let lowered = lower_member_expression(builder, member)?;859 Ok(lowered.value)860 }861 Expression::OptionalCallExpression(opt_call) => {862 Ok(lower_optional_call_expression(builder, opt_call)?)863 }864 Expression::OptionalMemberExpression(opt_member) => {865 Ok(lower_optional_member_expression(builder, opt_member)?)866 }867 Expression::LogicalExpression(expr) => {868 let loc = convert_opt_loc(&expr.base.loc);869 let continuation_block = builder.reserve(builder.current_block_kind());870 let continuation_id = continuation_block.id;871 let test_block = builder.reserve(BlockKind::Value);872 let test_block_id = test_block.id;873 let place = build_temporary_place(builder, loc.clone());874 let left_loc = expression_loc(&expr.left);875 let left_place = build_temporary_place(builder, left_loc);876877 // Block for short-circuit case: store left value as result, goto continuation878 let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {879 lower_value_to_temporary(880 builder,881 InstructionValue::StoreLocal {882 lvalue: LValue {883 kind: InstructionKind::Const,884 place: place.clone(),885 },886 value: left_place.clone(),887 type_annotation: None,888 loc: left_place.loc.clone(),889 },890 )?;891 Ok(Terminal::Goto {892 block: continuation_id,893 variant: GotoVariant::Break,894 id: EvaluationOrder(0),895 loc: left_place.loc.clone(),896 })897 });898899 // Block for evaluating right side900 let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {901 let right = lower_expression_to_temporary(builder, &expr.right)?;902 let right_loc = right.loc.clone();903 lower_value_to_temporary(904 builder,905 InstructionValue::StoreLocal {906 lvalue: LValue {907 kind: InstructionKind::Const,908 place: place.clone(),909 },910 value: right,911 type_annotation: None,912 loc: right_loc.clone(),913 },914 )?;915 Ok(Terminal::Goto {916 block: continuation_id,917 variant: GotoVariant::Break,918 id: EvaluationOrder(0),919 loc: right_loc,920 })921 });922923 let hir_op = match expr.operator {924 react_compiler_ast::operators::LogicalOperator::And => LogicalOperator::And,925 react_compiler_ast::operators::LogicalOperator::Or => LogicalOperator::Or,926 react_compiler_ast::operators::LogicalOperator::NullishCoalescing => {927 LogicalOperator::NullishCoalescing928 }929 };930931 builder.terminate_with_continuation(932 Terminal::Logical {933 operator: hir_op,934 test: test_block_id,935 fallthrough: continuation_id,936 id: EvaluationOrder(0),937 loc: loc.clone(),938 },939 test_block,940 );941942 // Now in test block: lower left expression, copy to left_place943 let left_value = lower_expression_to_temporary(builder, &expr.left)?;944 builder.push(Instruction {945 id: EvaluationOrder(0),946 lvalue: left_place.clone(),947 value: InstructionValue::LoadLocal {948 place: left_value,949 loc: loc.clone(),950 },951 effects: None,952 loc: loc.clone(),953 });954955 builder.terminate_with_continuation(956 Terminal::Branch {957 test: left_place,958 consequent: consequent_block?,959 alternate: alternate_block?,960 fallthrough: continuation_id,961 id: EvaluationOrder(0),962 loc: loc.clone(),963 },964 continuation_block,965 );966967 Ok(InstructionValue::LoadLocal {968 place: place.clone(),969 loc: place.loc.clone(),970 })971 }972 Expression::UpdateExpression(update) => {973 let loc = convert_opt_loc(&update.base.loc);974 match update.argument.as_ref() {975 Expression::MemberExpression(member) => {976 let binary_op = match &update.operator {977 react_compiler_ast::operators::UpdateOperator::Increment => {978 BinaryOperator::Add979 }980 react_compiler_ast::operators::UpdateOperator::Decrement => {981 BinaryOperator::Subtract982 }983 };984 // Use the member expression's loc (not the update expression's)985 // to match TS behavior where the inner operations use leftExpr.node.loc986 let member_loc = convert_opt_loc(&member.base.loc);987 let lowered = lower_member_expression(builder, member)?;988 let object = lowered.object;989 let lowered_property = lowered.property;990 let prev_value = lower_value_to_temporary(builder, lowered.value)?;991992 let one = lower_value_to_temporary(993 builder,994 InstructionValue::Primitive {995 value: PrimitiveValue::Number(FloatValue::new(1.0)),996 loc: None,997 },998 )?;999 let updated = lower_value_to_temporary(1000 builder,1001 InstructionValue::BinaryExpression {1002 operator: binary_op,1003 left: prev_value.clone(),1004 right: one,1005 loc: member_loc.clone(),1006 },1007 )?;10081009 // Store back using the property from the lowered member expression.1010 // For prefix, the result is the PropertyStore/ComputedStore lvalue1011 // (matching TS which uses newValuePlace). For postfix, it's prev_value.1012 let new_value_place = match lowered_property {1013 MemberProperty::Literal(prop_literal) => lower_value_to_temporary(1014 builder,1015 InstructionValue::PropertyStore {1016 object,1017 property: prop_literal,1018 value: updated.clone(),1019 loc: member_loc,1020 },1021 )?,1022 MemberProperty::Computed(prop_place) => lower_value_to_temporary(1023 builder,1024 InstructionValue::ComputedStore {1025 object,1026 property: prop_place,1027 value: updated.clone(),1028 loc: member_loc,1029 },1030 )?,1031 };10321033 // Return previous for postfix, newValuePlace for prefix1034 let result_place = if update.prefix {1035 new_value_place1036 } else {1037 prev_value1038 };1039 Ok(InstructionValue::LoadLocal {1040 place: result_place.clone(),1041 loc: result_place.loc.clone(),1042 })1043 }1044 Expression::Identifier(ident) => {1045 let start = ident.base.start.unwrap_or(0);1046 let ident_loc = convert_opt_loc(&ident.base.loc);1047 let binding = builder.resolve_identifier(1048 &ident.name,1049 start,1050 ident_loc.clone(),1051 ident.base.node_id,1052 )?;1053 match &binding {1054 VariableBinding::Global { .. } => {1055 builder.record_error(CompilerErrorDetail {1056 category: ErrorCategory::Todo,1057 reason: "UpdateExpression where argument is a global is not yet supported".to_string(),1058 description: None,1059 loc: loc.clone(),1060 suggestions: None,1061 })?;1062 return Ok(InstructionValue::UnsupportedNode {1063 node_type: Some("UpdateExpression".to_string()),1064 original_node: serialize_expression(expr),1065 loc,1066 });1067 }1068 _ => {}1069 }1070 let identifier = match binding {1071 VariableBinding::Identifier { identifier, .. } => identifier,1072 _ => {1073 builder.record_error(CompilerErrorDetail {1074 category: ErrorCategory::Todo,1075 reason: "(BuildHIR::lowerExpression) Support UpdateExpression where argument is a global".to_string(),1076 description: None,1077 loc: loc.clone(),1078 suggestions: None,1079 })?;1080 return Ok(InstructionValue::UnsupportedNode {1081 node_type: Some("UpdateExpression".to_string()),1082 original_node: serialize_expression(expr),1083 loc,1084 });1085 }1086 };1087 let lvalue_place = Place {1088 identifier,1089 effect: Effect::Unknown,1090 reactive: false,1091 loc: ident_loc.clone(),1092 };10931094 // Load the current value1095 let value = lower_identifier(1096 builder,1097 &ident.name,1098 start,1099 ident_loc,1100 ident.base.node_id,1101 )?;11021103 let operation = convert_update_operator(&update.operator);1104 let is_context =1105 builder.is_context_identifier(&ident.name, start, ident.base.node_id);11061107 if update.prefix {1108 let value = if is_context {1109 InstructionValue::PrefixUpdateContext {1110 lvalue: lvalue_place,1111 operation,1112 value,1113 loc,1114 }1115 } else {1116 InstructionValue::PrefixUpdateLocal {1117 lvalue: lvalue_place,1118 operation,1119 value,1120 loc,1121 }1122 };1123 Ok(value)1124 } else {1125 let value = if is_context {1126 InstructionValue::PostfixUpdateContext {1127 lvalue: lvalue_place,1128 operation,1129 value,1130 loc,1131 }1132 } else {1133 InstructionValue::PostfixUpdateLocal {1134 lvalue: lvalue_place,1135 operation,1136 value,1137 loc,1138 }1139 };1140 Ok(value)1141 }1142 }1143 _ => {1144 builder.record_error(CompilerErrorDetail {1145 category: ErrorCategory::Todo,1146 reason: format!("UpdateExpression with unsupported argument type"),1147 description: None,1148 loc: loc.clone(),1149 suggestions: None,1150 })?;1151 Ok(InstructionValue::UnsupportedNode {1152 node_type: Some("UpdateExpression".to_string()),1153 original_node: serialize_expression(expr),1154 loc,1155 })1156 }1157 }1158 }1159 Expression::ConditionalExpression(expr) => {1160 let loc = convert_opt_loc(&expr.base.loc);1161 let continuation_block = builder.reserve(builder.current_block_kind());1162 let continuation_id = continuation_block.id;1163 let test_block = builder.reserve(BlockKind::Value);1164 let test_block_id = test_block.id;1165 let place = build_temporary_place(builder, loc.clone());11661167 // Block for the consequent (test is truthy)1168 let consequent_ast_loc = expression_loc(&expr.consequent);1169 let consequent_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {1170 let consequent = lower_expression_to_temporary(builder, &expr.consequent)?;1171 lower_value_to_temporary(1172 builder,1173 InstructionValue::StoreLocal {1174 lvalue: LValue {1175 kind: InstructionKind::Const,1176 place: place.clone(),1177 },1178 value: consequent,1179 type_annotation: None,1180 loc: loc.clone(),1181 },1182 )?;1183 Ok(Terminal::Goto {1184 block: continuation_id,1185 variant: GotoVariant::Break,1186 id: EvaluationOrder(0),1187 loc: consequent_ast_loc,1188 })1189 });11901191 // Block for the alternate (test is falsy)1192 let alternate_ast_loc = expression_loc(&expr.alternate);1193 let alternate_block = builder.try_enter(BlockKind::Value, |builder, _block_id| {1194 let alternate = lower_expression_to_temporary(builder, &expr.alternate)?;1195 lower_value_to_temporary(1196 builder,1197 InstructionValue::StoreLocal {1198 lvalue: LValue {1199 kind: InstructionKind::Const,1200 place: place.clone(),1201 },1202 value: alternate,1203 type_annotation: None,1204 loc: loc.clone(),1205 },1206 )?;1207 Ok(Terminal::Goto {1208 block: continuation_id,1209 variant: GotoVariant::Break,1210 id: EvaluationOrder(0),1211 loc: alternate_ast_loc,1212 })1213 });12141215 builder.terminate_with_continuation(1216 Terminal::Ternary {1217 test: test_block_id,1218 fallthrough: continuation_id,1219 id: EvaluationOrder(0),1220 loc: loc.clone(),1221 },1222 test_block,1223 );12241225 // Now in test block: lower test expression1226 let test_place = lower_expression_to_temporary(builder, &expr.test)?;1227 builder.terminate_with_continuation(1228 Terminal::Branch {1229 test: test_place,1230 consequent: consequent_block?,1231 alternate: alternate_block?,1232 fallthrough: continuation_id,1233 id: EvaluationOrder(0),1234 loc: loc.clone(),1235 },1236 continuation_block,1237 );12381239 Ok(InstructionValue::LoadLocal {1240 place: place.clone(),1241 loc: place.loc.clone(),1242 })1243 }1244 Expression::AssignmentExpression(expr) => {1245 use react_compiler_ast::operators::AssignmentOperator;1246 let loc = convert_opt_loc(&expr.base.loc);12471248 if matches!(expr.operator, AssignmentOperator::Assign) {1249 // Simple `=` assignment1250 match &*expr.left {1251 react_compiler_ast::patterns::PatternLike::Identifier(ident) => {1252 // Handle simple identifier assignment directly1253 let start = ident.base.start.unwrap_or(0);1254 let right = lower_expression_to_temporary(builder, &expr.right)?;1255 let ident_loc = convert_opt_loc(&ident.base.loc);1256 let binding = builder.resolve_identifier(1257 &ident.name,1258 start,1259 ident_loc.clone(),1260 ident.base.node_id,1261 )?;1262 match binding {1263 VariableBinding::Identifier {1264 identifier,1265 binding_kind,1266 } => {1267 // Check for const reassignment1268 if binding_kind == BindingKind::Const {1269 builder.record_error(CompilerErrorDetail {1270 reason: "Cannot reassign a `const` variable".to_string(),1271 category: ErrorCategory::Syntax,1272 loc: ident_loc.clone(),1273 description: Some(format!(1274 "`{}` is declared as const",1275 &ident.name1276 )),1277 suggestions: None,1278 })?;1279 return Ok(InstructionValue::UnsupportedNode {1280 node_type: Some("Identifier".to_string()),1281 original_node: serialize_expression(1282 &Expression::AssignmentExpression(expr.clone()),1283 ),1284 loc: ident_loc,1285 });1286 }1287 let place = Place {1288 identifier,1289 reactive: false,1290 effect: Effect::Unknown,1291 loc: ident_loc,1292 };1293 if builder.is_context_identifier(1294 &ident.name,1295 start,1296 ident.base.node_id,1297 ) {1298 let temp = lower_value_to_temporary(1299 builder,1300 InstructionValue::StoreContext {1301 lvalue: LValue {1302 kind: InstructionKind::Reassign,1303 place: place.clone(),1304 },1305 value: right,1306 loc: place.loc.clone(),1307 },1308 )?;1309 Ok(InstructionValue::LoadLocal {1310 place: temp.clone(),1311 loc: temp.loc.clone(),1312 })1313 } else {1314 let temp = lower_value_to_temporary(1315 builder,1316 InstructionValue::StoreLocal {1317 lvalue: LValue {1318 kind: InstructionKind::Reassign,1319 place: place.clone(),1320 },1321 value: right,1322 type_annotation: None,1323 loc: place.loc.clone(),1324 },1325 )?;1326 Ok(InstructionValue::LoadLocal {1327 place: temp.clone(),1328 loc: temp.loc.clone(),1329 })1330 }1331 }1332 _ => {1333 // Global or import assignment1334 let name = ident.name.clone();1335 let temp = lower_value_to_temporary(1336 builder,1337 InstructionValue::StoreGlobal {1338 name,1339 value: right,1340 loc: ident_loc,1341 },1342 )?;1343 Ok(InstructionValue::LoadLocal {1344 place: temp.clone(),1345 loc: temp.loc.clone(),1346 })1347 }1348 }1349 }1350 react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {1351 // Member expression assignment: a.b = value or a[b] = value1352 let right = lower_expression_to_temporary(builder, &expr.right)?;1353 let left_loc = convert_opt_loc(&member.base.loc);1354 let object = lower_expression_to_temporary(builder, &member.object)?;1355 let temp = if !member.computed1356 || matches!(1357 &*member.property,1358 react_compiler_ast::expressions::Expression::NumericLiteral(_)1359 ) {1360 match &*member.property {1361 react_compiler_ast::expressions::Expression::Identifier(1362 prop_id,1363 ) => lower_value_to_temporary(1364 builder,1365 InstructionValue::PropertyStore {1366 object,1367 property: PropertyLiteral::String(prop_id.name.clone()),1368 value: right,1369 loc: left_loc,1370 },1371 )?,1372 react_compiler_ast::expressions::Expression::NumericLiteral(1373 num,1374 ) => lower_value_to_temporary(1375 builder,1376 InstructionValue::PropertyStore {1377 object,1378 property: PropertyLiteral::Number(FloatValue::new(1379 num.precise_value(),1380 )),1381 value: right,1382 loc: left_loc,1383 },1384 )?,1385 _ => {1386 let prop =1387 lower_expression_to_temporary(builder, &member.property)?;1388 lower_value_to_temporary(1389 builder,1390 InstructionValue::ComputedStore {1391 object,1392 property: prop,1393 value: right,1394 loc: left_loc,1395 },1396 )?1397 }1398 }1399 } else {1400 let prop = lower_expression_to_temporary(builder, &member.property)?;1401 lower_value_to_temporary(1402 builder,1403 InstructionValue::ComputedStore {1404 object,1405 property: prop,1406 value: right,1407 loc: left_loc,1408 },1409 )?1410 };1411 Ok(InstructionValue::LoadLocal {1412 place: temp.clone(),1413 loc: temp.loc.clone(),1414 })1415 }1416 _ => {1417 // Destructuring assignment1418 let right = lower_expression_to_temporary(builder, &expr.right)?;1419 let left_loc = pattern_like_hir_loc(&expr.left);1420 let result = lower_assignment(1421 builder,1422 left_loc,1423 InstructionKind::Reassign,1424 &expr.left,1425 right.clone(),1426 AssignmentStyle::Destructure,1427 )?;1428 match result {1429 Some(place) => Ok(InstructionValue::LoadLocal {1430 place: place.clone(),1431 loc: place.loc.clone(),1432 }),1433 None => Ok(InstructionValue::LoadLocal { place: right, loc }),1434 }1435 }1436 }1437 } else {1438 // Compound assignment operators1439 let binary_op = match expr.operator {1440 AssignmentOperator::AddAssign => Some(BinaryOperator::Add),1441 AssignmentOperator::SubAssign => Some(BinaryOperator::Subtract),1442 AssignmentOperator::MulAssign => Some(BinaryOperator::Multiply),1443 AssignmentOperator::DivAssign => Some(BinaryOperator::Divide),1444 AssignmentOperator::RemAssign => Some(BinaryOperator::Modulo),1445 AssignmentOperator::ExpAssign => Some(BinaryOperator::Exponent),1446 AssignmentOperator::ShlAssign => Some(BinaryOperator::ShiftLeft),1447 AssignmentOperator::ShrAssign => Some(BinaryOperator::ShiftRight),1448 AssignmentOperator::UShrAssign => Some(BinaryOperator::UnsignedShiftRight),1449 AssignmentOperator::BitOrAssign => Some(BinaryOperator::BitwiseOr),1450 AssignmentOperator::BitXorAssign => Some(BinaryOperator::BitwiseXor),1451 AssignmentOperator::BitAndAssign => Some(BinaryOperator::BitwiseAnd),1452 AssignmentOperator::OrAssign1453 | AssignmentOperator::AndAssign1454 | AssignmentOperator::NullishAssign => {1455 // Logical assignment operators (||=, &&=, ??=) - not yet supported1456 builder.record_error(CompilerErrorDetail {1457 reason:1458 "Logical assignment operators (||=, &&=, ??=) are not yet supported"1459 .to_string(),1460 category: ErrorCategory::Todo,1461 loc: loc.clone(),1462 description: None,1463 suggestions: None,1464 })?;1465 return Ok(InstructionValue::UnsupportedNode {1466 node_type: Some("AssignmentExpression".to_string()),1467 original_node: serialize_expression(&Expression::AssignmentExpression(1468 expr.clone(),1469 )),1470 loc,1471 });1472 }1473 AssignmentOperator::Assign => unreachable!(),1474 };1475 let binary_op = match binary_op {1476 Some(op) => op,1477 None => {1478 return Ok(InstructionValue::UnsupportedNode {1479 node_type: Some("AssignmentExpression".to_string()),1480 original_node: serialize_expression(&Expression::AssignmentExpression(1481 expr.clone(),1482 )),1483 loc,1484 });1485 }1486 };14871488 match &*expr.left {1489 react_compiler_ast::patterns::PatternLike::Identifier(ident) => {1490 let start = ident.base.start.unwrap_or(0);1491 let left_place = lower_expression_to_temporary(1492 builder,1493 &react_compiler_ast::expressions::Expression::Identifier(ident.clone()),1494 )?;1495 let right = lower_expression_to_temporary(builder, &expr.right)?;1496 let binary_place = lower_value_to_temporary(1497 builder,1498 InstructionValue::BinaryExpression {1499 operator: binary_op,1500 left: left_place,1501 right,1502 loc: loc.clone(),1503 },1504 )?;1505 let ident_loc = convert_opt_loc(&ident.base.loc);1506 let binding = builder.resolve_identifier(1507 &ident.name,1508 start,1509 ident_loc.clone(),1510 ident.base.node_id,1511 )?;1512 match binding {1513 VariableBinding::Identifier { identifier, .. } => {1514 let place = Place {1515 identifier,1516 reactive: false,1517 effect: Effect::Unknown,1518 loc: ident_loc,1519 };1520 if builder.is_context_identifier(1521 &ident.name,1522 start,1523 ident.base.node_id,1524 ) {1525 lower_value_to_temporary(1526 builder,1527 InstructionValue::StoreContext {1528 lvalue: LValue {1529 kind: InstructionKind::Reassign,1530 place: place.clone(),1531 },1532 value: binary_place,1533 loc: loc.clone(),1534 },1535 )?;1536 Ok(InstructionValue::LoadContext { place, loc })1537 } else {1538 lower_value_to_temporary(1539 builder,1540 InstructionValue::StoreLocal {1541 lvalue: LValue {1542 kind: InstructionKind::Reassign,1543 place: place.clone(),1544 },1545 value: binary_place,1546 type_annotation: None,1547 loc: loc.clone(),1548 },1549 )?;1550 Ok(InstructionValue::LoadLocal { place, loc })1551 }1552 }1553 _ => {1554 // Global assignment1555 let name = ident.name.clone();1556 let temp = lower_value_to_temporary(1557 builder,1558 InstructionValue::StoreGlobal {1559 name,1560 value: binary_place,1561 loc: loc.clone(),1562 },1563 )?;1564 Ok(InstructionValue::LoadLocal {1565 place: temp.clone(),1566 loc: temp.loc.clone(),1567 })1568 }1569 }1570 }1571 react_compiler_ast::patterns::PatternLike::MemberExpression(member) => {1572 // a.b += right: read, compute, store1573 // Match TS behavior: return the PropertyStore/ComputedStore value1574 // directly (let the caller lower it to a temporary)1575 let member_loc = convert_opt_loc(&member.base.loc);1576 let lowered = lower_member_expression(builder, member)?;1577 let object = lowered.object;1578 let lowered_property = lowered.property;1579 let current_value = lower_value_to_temporary(builder, lowered.value)?;1580 let right = lower_expression_to_temporary(builder, &expr.right)?;1581 let result = lower_value_to_temporary(1582 builder,1583 InstructionValue::BinaryExpression {1584 operator: binary_op,1585 left: current_value,1586 right,1587 loc: member_loc.clone(),1588 },1589 )?;1590 // Return the store instruction value directly (matching TS behavior)1591 match lowered_property {1592 MemberProperty::Literal(prop_literal) => {1593 Ok(InstructionValue::PropertyStore {1594 object,1595 property: prop_literal,1596 value: result,1597 loc: member_loc,1598 })1599 }1600 MemberProperty::Computed(prop_place) => {1601 Ok(InstructionValue::ComputedStore {1602 object,1603 property: prop_place,1604 value: result,1605 loc: member_loc,1606 })1607 }1608 }1609 }1610 _ => {1611 builder.record_error(CompilerErrorDetail {1612 reason: "Compound assignment to complex pattern is not yet supported"1613 .to_string(),1614 category: ErrorCategory::Todo,1615 loc: loc.clone(),1616 description: None,1617 suggestions: None,1618 })?;1619 Ok(InstructionValue::UnsupportedNode {1620 node_type: Some("AssignmentExpression".to_string()),1621 original_node: serialize_expression(&Expression::AssignmentExpression(1622 expr.clone(),1623 )),1624 loc,1625 })1626 }1627 }1628 }1629 }1630 Expression::SequenceExpression(seq) => {1631 let loc = convert_opt_loc(&seq.base.loc);16321633 if seq.expressions.is_empty() {1634 builder.record_error(CompilerErrorDetail {1635 category: ErrorCategory::Syntax,1636 reason: "Expected sequence expression to have at least one expression"1637 .to_string(),1638 description: None,1639 loc: loc.clone(),1640 suggestions: None,1641 })?;1642 return Ok(InstructionValue::UnsupportedNode {1643 node_type: Some("SequenceExpression".to_string()),1644 original_node: serialize_expression(expr),1645 loc,1646 });1647 }16481649 let continuation_block = builder.reserve(builder.current_block_kind());1650 let continuation_id = continuation_block.id;1651 let place = build_temporary_place(builder, loc.clone());16521653 let sequence_block = builder.try_enter(BlockKind::Sequence, |builder, _block_id| {1654 let mut last: Option<Place> = None;1655 for item in &seq.expressions {1656 last = Some(lower_expression_to_temporary(builder, item)?);1657 }1658 if let Some(last) = last {1659 lower_value_to_temporary(1660 builder,1661 InstructionValue::StoreLocal {1662 lvalue: LValue {1663 kind: InstructionKind::Const,1664 place: place.clone(),1665 },1666 value: last,1667 type_annotation: None,1668 loc: loc.clone(),1669 },1670 )?;1671 }1672 Ok(Terminal::Goto {1673 block: continuation_id,1674 variant: GotoVariant::Break,1675 id: EvaluationOrder(0),1676 loc: loc.clone(),1677 })1678 });16791680 builder.terminate_with_continuation(1681 Terminal::Sequence {1682 block: sequence_block?,1683 fallthrough: continuation_id,1684 id: EvaluationOrder(0),1685 loc: loc.clone(),1686 },1687 continuation_block,1688 );1689 Ok(InstructionValue::LoadLocal { place, loc })1690 }1691 Expression::ArrowFunctionExpression(_) => Ok(lower_function_to_value(1692 builder,1693 expr,1694 FunctionExpressionType::ArrowFunctionExpression,1695 )?),1696 Expression::FunctionExpression(_) => Ok(lower_function_to_value(1697 builder,1698 expr,1699 FunctionExpressionType::FunctionExpression,1700 )?),1701 Expression::ObjectExpression(obj) => {1702 let loc = convert_opt_loc(&obj.base.loc);1703 let mut properties: Vec<ObjectPropertyOrSpread> = Vec::new();1704 for prop in &obj.properties {1705 match prop {1706 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(1707 p,1708 ) => {1709 let key = lower_object_property_key(builder, &p.key, p.computed)?;1710 let key = match key {1711 Some(k) => k,1712 None => continue,1713 };1714 let value = lower_expression_to_temporary(builder, &p.value)?;1715 properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {1716 key,1717 property_type: ObjectPropertyType::Property,1718 place: value,1719 }));1720 }1721 react_compiler_ast::expressions::ObjectExpressionProperty::SpreadElement(1722 spread,1723 ) => {1724 let place = lower_expression_to_temporary(builder, &spread.argument)?;1725 properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern { place }));1726 }1727 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(1728 method,1729 ) => {1730 if let Some(prop) = lower_object_method(builder, method)? {1731 properties.push(ObjectPropertyOrSpread::Property(prop));1732 }1733 }1734 }1735 }1736 Ok(InstructionValue::ObjectExpression { properties, loc })1737 }1738 Expression::ArrayExpression(arr) => {1739 let loc = convert_opt_loc(&arr.base.loc);1740 let mut elements: Vec<ArrayElement> = Vec::new();1741 for element in &arr.elements {1742 match element {1743 None => {1744 elements.push(ArrayElement::Hole);1745 }1746 Some(Expression::SpreadElement(spread)) => {1747 let place = lower_expression_to_temporary(builder, &spread.argument)?;1748 elements.push(ArrayElement::Spread(SpreadPattern { place }));1749 }1750 Some(expr) => {1751 let place = lower_expression_to_temporary(builder, expr)?;1752 elements.push(ArrayElement::Place(place));1753 }1754 }1755 }1756 Ok(InstructionValue::ArrayExpression { elements, loc })1757 }1758 Expression::NewExpression(new_expr) => {1759 let loc = convert_opt_loc(&new_expr.base.loc);1760 let callee = lower_expression_to_temporary(builder, &new_expr.callee)?;1761 let args = lower_arguments(builder, &new_expr.arguments)?;1762 Ok(InstructionValue::NewExpression { callee, args, loc })1763 }1764 Expression::TemplateLiteral(tmpl) => {1765 let loc = convert_opt_loc(&tmpl.base.loc);1766 let subexprs: Vec<Place> = tmpl1767 .expressions1768 .iter()1769 .map(|e| lower_expression_to_temporary(builder, e))1770 .collect::<Result<Vec<_>, _>>()?;1771 let quasis: Vec<TemplateQuasi> = tmpl1772 .quasis1773 .iter()1774 .map(|q| TemplateQuasi {1775 raw: q.value.raw.clone(),1776 cooked: q.value.cooked.clone(),1777 })1778 .collect();1779 Ok(InstructionValue::TemplateLiteral {1780 subexprs,1781 quasis,1782 loc,1783 })1784 }1785 Expression::TaggedTemplateExpression(tagged) => {1786 let loc = convert_opt_loc(&tagged.base.loc);1787 if !tagged.quasi.expressions.is_empty() {1788 builder.record_error(CompilerErrorDetail {1789 category: ErrorCategory::Todo,1790 reason:1791 "(BuildHIR::lowerExpression) Handle tagged template with interpolations"1792 .to_string(),1793 description: None,1794 loc: loc.clone(),1795 suggestions: None,1796 })?;1797 return Ok(InstructionValue::UnsupportedNode {1798 node_type: Some("TaggedTemplateExpression".to_string()),1799 original_node: serialize_expression(expr),1800 loc,1801 });1802 }1803 assert!(1804 tagged.quasi.quasis.len() == 1,1805 "there should be only one quasi as we don't support interpolations yet"1806 );1807 let quasi = &tagged.quasi.quasis[0];1808 // Check if raw and cooked values differ (e.g., graphql tagged templates)1809 if quasi.value.raw != quasi.value.cooked.clone().unwrap_or_default() {1810 builder.record_error(CompilerErrorDetail {1811 category: ErrorCategory::Todo,1812 reason: "(BuildHIR::lowerExpression) Handle tagged template where cooked value is different from raw value".to_string(),1813 description: None,1814 loc: loc.clone(),1815 suggestions: None,1816 })?;1817 return Ok(InstructionValue::UnsupportedNode {1818 node_type: Some("TaggedTemplateExpression".to_string()),1819 original_node: serialize_expression(expr),1820 loc,1821 });1822 }1823 let value = TemplateQuasi {1824 raw: quasi.value.raw.clone(),1825 cooked: quasi.value.cooked.clone(),1826 };1827 let tag = lower_expression_to_temporary(builder, &tagged.tag)?;1828 Ok(InstructionValue::TaggedTemplateExpression { tag, value, loc })1829 }1830 Expression::AwaitExpression(await_expr) => {1831 let loc = convert_opt_loc(&await_expr.base.loc);1832 let value = lower_expression_to_temporary(builder, &await_expr.argument)?;1833 Ok(InstructionValue::Await { value, loc })1834 }1835 Expression::YieldExpression(yld) => {1836 let loc = convert_opt_loc(&yld.base.loc);1837 builder.record_error(CompilerErrorDetail {1838 category: ErrorCategory::Todo,1839 reason: "(BuildHIR::lowerExpression) Handle YieldExpression expressions"1840 .to_string(),1841 description: None,1842 loc: loc.clone(),1843 suggestions: None,1844 })?;1845 Ok(InstructionValue::UnsupportedNode {1846 node_type: Some("YieldExpression".to_string()),1847 original_node: serialize_expression(expr),1848 loc,1849 })1850 }1851 Expression::SpreadElement(spread) => {1852 // SpreadElement should be handled by the parent context (array/object/call)1853 // If we reach here, just lower the argument expression1854 Ok(lower_expression(builder, &spread.argument)?)1855 }1856 Expression::MetaProperty(meta) => {1857 let loc = convert_opt_loc(&meta.base.loc);1858 if meta.meta.name == "import" && meta.property.name == "meta" {1859 Ok(InstructionValue::MetaProperty {1860 meta: meta.meta.name.clone(),1861 property: meta.property.name.clone(),1862 loc,1863 })1864 } else {1865 builder.record_error(CompilerErrorDetail {1866 category: ErrorCategory::Todo,1867 reason: "(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta".to_string(),1868 description: None,1869 loc: loc.clone(),1870 suggestions: None,1871 })?;1872 Ok(InstructionValue::UnsupportedNode {1873 node_type: Some("MetaProperty".to_string()),1874 original_node: serialize_expression(expr),1875 loc,1876 })1877 }1878 }1879 Expression::ClassExpression(cls) => {1880 let loc = convert_opt_loc(&cls.base.loc);1881 builder.record_error(CompilerErrorDetail {1882 category: ErrorCategory::Todo,1883 reason: "(BuildHIR::lowerExpression) Handle ClassExpression expressions"1884 .to_string(),1885 description: None,1886 loc: loc.clone(),1887 suggestions: None,1888 })?;1889 Ok(InstructionValue::UnsupportedNode {1890 node_type: Some("ClassExpression".to_string()),1891 original_node: serialize_expression(expr),1892 loc,1893 })1894 }1895 Expression::PrivateName(pn) => {1896 let loc = convert_opt_loc(&pn.base.loc);1897 builder.record_error(CompilerErrorDetail {1898 category: ErrorCategory::Todo,1899 reason: "(BuildHIR::lowerExpression) Handle PrivateName expressions".to_string(),1900 description: None,1901 loc: loc.clone(),1902 suggestions: None,1903 })?;1904 Ok(InstructionValue::UnsupportedNode {1905 node_type: Some("PrivateName".to_string()),1906 original_node: serialize_expression(expr),1907 loc,1908 })1909 }1910 Expression::Super(sup) => {1911 let loc = convert_opt_loc(&sup.base.loc);1912 builder.record_error(CompilerErrorDetail {1913 category: ErrorCategory::Todo,1914 reason: "(BuildHIR::lowerExpression) Handle Super expressions".to_string(),1915 description: None,1916 loc: loc.clone(),1917 suggestions: None,1918 })?;1919 Ok(InstructionValue::UnsupportedNode {1920 node_type: Some("Super".to_string()),1921 original_node: serialize_expression(expr),1922 loc,1923 })1924 }1925 Expression::Import(imp) => {1926 let loc = convert_opt_loc(&imp.base.loc);1927 builder.record_error(CompilerErrorDetail {1928 category: ErrorCategory::Todo,1929 reason: "(BuildHIR::lowerExpression) Handle Import expressions".to_string(),1930 description: None,1931 loc: loc.clone(),1932 suggestions: None,1933 })?;1934 Ok(InstructionValue::UnsupportedNode {1935 node_type: Some("Import".to_string()),1936 original_node: serialize_expression(expr),1937 loc,1938 })1939 }1940 Expression::ThisExpression(this) => {1941 let loc = convert_opt_loc(&this.base.loc);1942 builder.record_error(CompilerErrorDetail {1943 category: ErrorCategory::Todo,1944 reason: "(BuildHIR::lowerExpression) Handle ThisExpression expressions".to_string(),1945 description: None,1946 loc: loc.clone(),1947 suggestions: None,1948 })?;1949 Ok(InstructionValue::UnsupportedNode {1950 node_type: Some("ThisExpression".to_string()),1951 original_node: serialize_expression(expr),1952 loc,1953 })1954 }1955 Expression::ParenthesizedExpression(paren) => {1956 Ok(lower_expression(builder, &paren.expression)?)1957 }1958 Expression::JSXElement(jsx_element) => {1959 let loc = convert_opt_loc(&jsx_element.base.loc);1960 let opening_loc = convert_opt_loc(&jsx_element.opening_element.base.loc);1961 let closing_loc = jsx_element1962 .closing_element1963 .as_ref()1964 .and_then(|c| convert_opt_loc(&c.base.loc));19651966 // Lower the tag name1967 let tag = lower_jsx_element_name(builder, &jsx_element.opening_element.name)?;19681969 // Lower attributes (props)1970 let mut props: Vec<JsxAttribute> = Vec::new();1971 for attr_item in &jsx_element.opening_element.attributes {1972 use react_compiler_ast::jsx::JSXAttributeItem;1973 use react_compiler_ast::jsx::JSXAttributeName;1974 use react_compiler_ast::jsx::JSXAttributeValue;1975 match attr_item {1976 JSXAttributeItem::JSXSpreadAttribute(spread) => {1977 let argument = lower_expression_to_temporary(builder, &spread.argument)?;1978 props.push(JsxAttribute::SpreadAttribute { argument });1979 }1980 JSXAttributeItem::JSXAttribute(attr) => {1981 // Get the attribute name1982 let prop_name = match &attr.name {1983 JSXAttributeName::JSXIdentifier(id) => {1984 let name = &id.name;1985 if name.contains(':') {1986 builder.record_error(CompilerErrorDetail {1987 category: ErrorCategory::Todo,1988 reason: format!(1989 "(BuildHIR::lowerExpression) Unexpected colon in attribute name `{}`",1990 name1991 ),1992 description: None,1993 loc: convert_opt_loc(&id.base.loc),1994 suggestions: None,1995 })?;1996 }1997 name.clone()1998 }1999 JSXAttributeName::JSXNamespacedName(ns) => {2000 format!("{}:{}", ns.namespace.name, ns.name.name)
Findings
✓ No findings reported for this file.