fix(react_compiler): preserve source spans (#25462)

React Compiler rebuilt source-derived AST nodes with empty spans, so compiled function bodies had no useful source mappings. Preserve retained HIR and reactive spans during codegen while keeping synthetic memoization scaffolding unmapped.

AI-assisted: implementation used Codex; the author remains responsible for review.
This commit is contained in:
Boshen committed 2026-08-11 02:10:30 +00:00
1 parent fed2b900eb
commit 1645d93cfb
25 files changed
+1478 -550

No files matched your search

+3
View File
@@ -2269,6 +2269,7 @@ impl Gen for AssignmentTargetPropertyProperty<'_> {
impl Gen for AssignmentTargetRest<'_> {
fn r#gen(&self, p: &mut Codegen, ctx: Context) {
p.add_source_mapping(self.span);
p.print_ellipsis();
self.target.print(p, ctx);
}
@@ -2733,12 +2734,14 @@ impl Gen for JSXAttributeValue<'_> {
impl Gen for JSXSpreadAttribute<'_> {
fn r#gen(&self, p: &mut Codegen, _ctx: Context) {
p.add_source_mapping(self.span);
p.print_ascii_byte(b'{');
if p.print_comments_in_range(self.span.start, self.argument.span().start) {
p.print_indent();
}
p.print_str("...");
self.argument.print_expr(p, Precedence::Comma, Context::empty());
p.add_source_mapping_end(self.span);
p.print_ascii_byte(b'}');
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ smallvec = { workspace = true }
[dev-dependencies]
# The example and integration tests parse source and print the compiled program; the
# library itself takes an already-parsed `Program` and never parses or codegens.
oxc_codegen = { workspace = true }
oxc_codegen = { workspace = true, features = ["sourcemap"] }
oxc_parser = { workspace = true }
# Snapshot testing of the compiler over the upstream fixture corpus (tests/snapshot.rs).
@@ -2106,10 +2106,7 @@ impl<'a> CompileOutput<'a> {
/// Drop comments left dangling by compilation.
///
/// The compiled functions were rebuilt with fresh spans, so a comment that
/// pointed inside one no longer lines up with any statement and codegen would
/// re-emit it at a stale position. Keep only the comments still anchored to a
/// top-level statement.
/// Rewritten functions may no longer contain the statements comments were attached to.
fn prune_inner_comments(program: &mut Program<'_>) {
if program.comments.is_empty() {
return;
@@ -2157,7 +2154,7 @@ fn ox_build_function<'a>(
fn_type: FunctionType,
) -> ArenaBox<'a, Function<'a>> {
Function::boxed(
SPAN,
codegen.span.unwrap_or_default(),
fn_type,
codegen.id.clone_in_with_semantic_ids(ast.allocator()),
codegen.generator,
@@ -2181,7 +2178,7 @@ fn ox_build_compiled_expression<'a>(
) -> Expression<'a> {
match original_kind {
OriginalFnKind::ArrowFunctionExpression => Expression::new_arrow_function_expression(
SPAN,
codegen.span.unwrap_or_default(),
codegen.is_async,
None,
codegen.params.clone_in_with_semantic_ids(ast.allocator()),
@@ -2220,7 +2217,11 @@ fn ox_replace_function<'a>(
func.return_type = None;
func.this_param = None;
}
let source_id_span = func.id.as_ref().map(|id| id.span);
func.id = codegen.id.clone_in_with_semantic_ids(ast.allocator());
if let (Some(id), Some(span)) = (&mut func.id, source_id_span) {
id.span = span;
}
func.params = params;
func.body = Some(codegen.body.clone_in_with_semantic_ids(ast.allocator()));
func.generator = codegen.generator;
@@ -2255,17 +2256,19 @@ fn ox_build_gated_const_decl<'a>(
ast: &AstBuilder<'a>,
gating_expression: &Expression<'a>,
name: &str,
name_span: Span,
declaration_span: Span,
) -> Statement<'a> {
let declarator = VariableDeclarator::new(
SPAN,
BindingPattern::new_binding_identifier(SPAN, ox_atom(ast, name), ast),
declaration_span,
BindingPattern::new_binding_identifier(name_span, ox_atom(ast, name), ast),
None,
Some(gating_expression.clone_in_with_semantic_ids(ast.allocator())),
false,
ast,
);
Statement::new_variable_declaration(
SPAN,
declaration_span,
VariableDeclarationKind::Const,
[declarator],
false,
@@ -2316,6 +2319,8 @@ enum OxcVisitMode<'a, 'b> {
FindOriginalFn { scope_id: ScopeId, found: Option<Expression<'a>> },
}
type GatedStatementMatch<'a> = (Option<(Ident<'a>, Span)>, Option<Span>);
impl<'a> OxcVisitor<'a, '_> {
/// In [`OxcVisitMode::ReplaceWithGated`]: if `stmt` is the target function
/// declaration (bare, `export`-wrapped, or `export default`), substitute
@@ -2334,28 +2339,29 @@ impl<'a> OxcVisitor<'a, '_> {
let scope_id = *scope_id;
let gating_expression = *gating_expression;
// FunctionDeclaration → `const Foo = gating() ? ... : ...;`
let replace_name: Option<Option<Ident<'a>>> = match &*stmt {
let replacement: Option<GatedStatementMatch<'a>> = match &*stmt {
Statement::FunctionDeclaration(f) if f.scope_id.get() == Some(scope_id) => {
Some(f.id.as_ref().map(|id| id.name))
Some((f.id.as_ref().map(|id| (id.name, id.span)), None))
}
Statement::ExportDeclaration(e) => match &e.declaration {
Declaration::FunctionDeclaration(f) if f.scope_id.get() == Some(scope_id) => {
Some(f.id.as_ref().map(|id| id.name))
Some((f.id.as_ref().map(|id| (id.name, id.span)), Some(e.span)))
}
_ => None,
},
_ => None,
};
if let Some(name) = replace_name {
let name = name.as_deref().unwrap_or("anonymous");
let is_export = matches!(stmt, Statement::ExportDeclaration(_));
let const_decl = ox_build_gated_const_decl(ast, gating_expression, name);
if is_export {
if let Some((name, export_span)) = replacement {
let (name, name_span) =
name.map_or(("anonymous", SPAN), |(name, span)| (name.as_str(), span));
let const_decl =
ox_build_gated_const_decl(ast, gating_expression, name, name_span, SPAN);
if let Some(export_span) = export_span {
let decl = match const_decl {
Statement::VariableDeclaration(d) => Declaration::VariableDeclaration(d),
_ => unreachable!(),
};
*stmt = Statement::new_export_declaration(SPAN, decl, ast);
*stmt = Statement::new_export_declaration(export_span, decl, ast);
} else {
*stmt = const_decl;
}
@@ -2367,12 +2373,20 @@ impl<'a> OxcVisitor<'a, '_> {
&& let ExportDefaultDeclarationKind::FunctionDeclaration(f) = &e.declaration
&& f.scope_id.get() == Some(scope_id)
{
if let Some(id) = f.id.as_ref().map(|id| id.name) {
*stmt = ox_build_gated_const_decl(ast, gating_expression, id.as_str());
*export_default_name = Some(id);
let export_span = e.span;
let id = f.id.as_ref().map(|id| (id.name, id.span));
if let Some((name, name_span)) = id {
*stmt = ox_build_gated_const_decl(
ast,
gating_expression,
name.as_str(),
name_span,
export_span,
);
*export_default_name = Some(name);
} else {
*stmt = Statement::new_export_default_declaration(
SPAN,
export_span,
ExportDefaultDeclarationKind::from(
gating_expression.clone_in_with_semantic_ids(ast.allocator()),
),
@@ -2409,7 +2423,7 @@ impl<'a> oxc_ast_visit::VisitMut<'a> for OxcVisitor<'a, '_> {
}
if func.scope_id.get() == Some(*scope_id) {
let f = Function::boxed(
SPAN,
func.span,
FunctionType::FunctionExpression,
func.id.clone_in_with_semantic_ids(ast.allocator()),
func.generator,
@@ -174,7 +174,9 @@ impl std::fmt::Display for FloatValue {
#[derive(Debug)]
pub struct HirFunction<'a> {
pub span: Option<Span>,
pub body_span: Option<Span>,
pub id: Option<Ident<'a>>,
pub id_span: Option<Span>,
pub name_hint: Option<Ident<'a>>,
pub fn_type: ReactFunctionType,
pub params: ArenaVec<'a, ParamPattern>,
@@ -184,10 +186,17 @@ pub struct HirFunction<'a> {
pub instructions: ArenaVec<'a, Instruction<'a>>,
pub generator: bool,
pub is_async: bool,
pub directives: ArenaVec<'a, Str<'a>>,
pub directives: ArenaVec<'a, FunctionDirective<'a>>,
pub aliasing_effects: Option<ArenaVec<'a, AliasingEffect<'a>>>,
}
#[derive(Debug, Clone, Copy)]
pub struct FunctionDirective<'a> {
pub value: Str<'a>,
pub span: Span,
pub expression_span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReactFunctionType {
Component,
@@ -280,7 +289,9 @@ pub enum Terminal<'a> {
If {
test: Place,
consequent: BlockId,
consequent_span: Option<Span>,
alternate: BlockId,
alternate_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -302,6 +313,7 @@ pub enum Terminal<'a> {
},
DoWhile {
loop_block: BlockId,
loop_block_span: Option<Span>,
test: BlockId,
fallthrough: BlockId,
id: EvaluationOrder,
@@ -310,6 +322,7 @@ pub enum Terminal<'a> {
While {
test: BlockId,
loop_block: BlockId,
loop_block_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -319,6 +332,7 @@ pub enum Terminal<'a> {
test: BlockId,
update: Option<BlockId>,
loop_block: BlockId,
loop_block_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -327,6 +341,8 @@ pub enum Terminal<'a> {
init: BlockId,
test: BlockId,
loop_block: BlockId,
loop_block_span: Option<Span>,
left_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -334,6 +350,8 @@ pub enum Terminal<'a> {
ForIn {
init: BlockId,
loop_block: BlockId,
loop_block_span: Option<Span>,
left_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -360,6 +378,7 @@ pub enum Terminal<'a> {
},
Label {
block: BlockId,
block_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -379,8 +398,10 @@ pub enum Terminal<'a> {
},
Try {
block: BlockId,
block_span: Option<Span>,
handler_binding: Option<Place>,
handler: BlockId,
handler_span: Option<Span>,
fallthrough: BlockId,
id: EvaluationOrder,
span: Option<Span>,
@@ -502,6 +523,7 @@ pub enum GotoVariant {
pub struct Case {
pub test: Option<Place>,
pub block: BlockId,
pub span: Option<Span>,
}
// =============================================================================
@@ -630,7 +652,9 @@ pub enum InstructionValue<'a> {
children: Option<ArenaVec<'a, Place>>,
span: Option<Span>,
opening_span: Option<Span>,
opening_name_span: Option<Span>,
closing_span: Option<Span>,
closing_name_span: Option<Span>,
},
ObjectExpression {
properties: ArenaVec<'a, ObjectPropertyOrSpread<'a>>,
@@ -647,6 +671,8 @@ pub enum InstructionValue<'a> {
JsxFragment {
children: ArenaVec<'a, Place>,
span: Option<Span>,
opening_span: Option<Span>,
closing_span: Option<Span>,
},
RegExpLiteral {
pattern: Str<'a>,
@@ -661,17 +687,20 @@ pub enum InstructionValue<'a> {
PropertyStore {
object: Place,
property: PropertyLiteral<'a>,
property_span: Option<Span>,
value: Place,
span: Option<Span>,
},
PropertyLoad {
object: Place,
property: PropertyLiteral<'a>,
property_span: Option<Span>,
span: Option<Span>,
},
PropertyDelete {
object: Place,
property: PropertyLiteral<'a>,
property_span: Option<Span>,
span: Option<Span>,
},
ComputedStore {
@@ -701,6 +730,7 @@ pub enum InstructionValue<'a> {
},
FunctionExpression {
name: Option<Ident<'a>>,
name_span: Option<Span>,
name_hint: Option<Ident<'a>>,
lowered_func: LoweredFunction,
expr_type: FunctionExpressionType,
@@ -714,6 +744,7 @@ pub enum InstructionValue<'a> {
// interpolations (a deliberate divergence from the TS reference).
quasis: ArenaVec<'a, TemplateQuasi<'a>>,
subexprs: ArenaVec<'a, Place>,
quasi_span: Option<Span>,
span: Option<Span>,
},
TemplateLiteral {
@@ -875,6 +906,7 @@ pub enum FunctionExpressionType {
pub struct TemplateQuasi<'a> {
pub raw: Str<'a>,
pub cooked: Option<Str<'a>>,
pub span: Span,
}
#[derive(Debug)]
@@ -1005,6 +1037,7 @@ impl std::fmt::Display for Effect {
#[derive(Debug, Clone, Copy)]
pub struct SpreadPattern {
pub place: Place,
pub span: Option<Span>,
}
#[derive(Debug)]
@@ -1041,9 +1074,19 @@ pub struct ObjectProperty<'a> {
#[derive(Debug, Clone, Copy)]
pub enum ObjectPropertyKey<'a> {
String { name: Ident<'a> },
Identifier { name: Ident<'a> },
Computed { name: Place },
String { name: Ident<'a>, span: Option<Span> },
Identifier { name: Ident<'a>, span: Option<Span> },
Computed { name: Place, span: Option<Span> },
}
impl ObjectPropertyKey<'_> {
pub fn span(&self) -> Option<Span> {
match self {
Self::String { span, .. }
| Self::Identifier { span, .. }
| Self::Computed { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -1113,8 +1156,8 @@ pub enum JsxTag<'a> {
#[derive(Debug, Clone, Copy)]
pub enum JsxAttribute<'a> {
SpreadAttribute { argument: Place },
Attribute { name: Ident<'a>, place: Place },
SpreadAttribute { argument: Place, span: Option<Span> },
Attribute { name: Ident<'a>, name_span: Option<Span>, place: Place },
}
// =============================================================================
@@ -1412,7 +1455,9 @@ impl<'a> CloneIn<'a> for HirFunction<'a> {
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
HirFunction {
span: self.span,
body_span: self.body_span,
id: self.id,
id_span: self.id_span,
name_hint: self.name_hint,
fn_type: self.fn_type,
params: self.params.clone_in_impl(sem, alloc),
@@ -1428,6 +1473,17 @@ impl<'a> CloneIn<'a> for HirFunction<'a> {
}
}
impl<'a> CloneIn<'a> for FunctionDirective<'a> {
type Cloned = FunctionDirective<'a>;
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
FunctionDirective {
value: self.value.clone_in_impl(sem, alloc),
span: self.span,
expression_span: self.expression_span,
}
}
}
impl<'a> CloneIn<'a> for HIR<'a> {
type Cloned = HIR<'a>;
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
@@ -1478,10 +1534,21 @@ impl<'a> CloneIn<'a> for Terminal<'a> {
Terminal::Goto { block, variant, id, span } => {
Terminal::Goto { block: *block, variant: *variant, id: *id, span: *span }
}
Terminal::If { test, consequent, alternate, fallthrough, id, span } => Terminal::If {
Terminal::If {
test,
consequent,
consequent_span,
alternate,
alternate_span,
fallthrough,
id,
span,
} => Terminal::If {
test: *test,
consequent: *consequent,
consequent_span: *consequent_span,
alternate: *alternate,
alternate_span: *alternate_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
@@ -1503,42 +1570,77 @@ impl<'a> CloneIn<'a> for Terminal<'a> {
id: *id,
span: *span,
},
Terminal::DoWhile { loop_block, test, fallthrough, id, span } => Terminal::DoWhile {
loop_block: *loop_block,
test: *test,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::While { test, loop_block, fallthrough, id, span } => Terminal::While {
test: *test,
loop_block: *loop_block,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::For { init, test, update, loop_block, fallthrough, id, span } => {
Terminal::For {
init: *init,
test: *test,
update: *update,
Terminal::DoWhile { loop_block, loop_block_span, test, fallthrough, id, span } => {
Terminal::DoWhile {
loop_block: *loop_block,
loop_block_span: *loop_block_span,
test: *test,
fallthrough: *fallthrough,
id: *id,
span: *span,
}
}
Terminal::ForOf { init, test, loop_block, fallthrough, id, span } => Terminal::ForOf {
Terminal::While { test, loop_block, loop_block_span, fallthrough, id, span } => {
Terminal::While {
test: *test,
loop_block: *loop_block,
loop_block_span: *loop_block_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
}
}
Terminal::For {
init,
test,
update,
loop_block,
loop_block_span,
fallthrough,
id,
span,
} => Terminal::For {
init: *init,
test: *test,
update: *update,
loop_block: *loop_block,
loop_block_span: *loop_block_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::ForIn { init, loop_block, fallthrough, id, span } => Terminal::ForIn {
Terminal::ForOf {
init,
test,
loop_block,
loop_block_span,
left_span,
fallthrough,
id,
span,
} => Terminal::ForOf {
init: *init,
test: *test,
loop_block: *loop_block,
loop_block_span: *loop_block_span,
left_span: *left_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::ForIn {
init,
loop_block,
loop_block_span,
left_span,
fallthrough,
id,
span,
} => Terminal::ForIn {
init: *init,
loop_block: *loop_block,
loop_block_span: *loop_block_span,
left_span: *left_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
@@ -1560,9 +1662,13 @@ impl<'a> CloneIn<'a> for Terminal<'a> {
id: *id,
span: *span,
},
Terminal::Label { block, fallthrough, id, span } => {
Terminal::Label { block: *block, fallthrough: *fallthrough, id: *id, span: *span }
}
Terminal::Label { block, block_span, fallthrough, id, span } => Terminal::Label {
block: *block,
block_span: *block_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::Sequence { block, fallthrough, id, span } => Terminal::Sequence {
block: *block,
fallthrough: *fallthrough,
@@ -1578,16 +1684,25 @@ impl<'a> CloneIn<'a> for Terminal<'a> {
effects: effects.as_ref().map(|v| v.clone_in_impl(sem, alloc)),
}
}
Terminal::Try { block, handler_binding, handler, fallthrough, id, span } => {
Terminal::Try {
block: *block,
handler_binding: *handler_binding,
handler: *handler,
fallthrough: *fallthrough,
id: *id,
span: *span,
}
}
Terminal::Try {
block,
block_span,
handler_binding,
handler,
handler_span,
fallthrough,
id,
span,
} => Terminal::Try {
block: *block,
block_span: *block_span,
handler_binding: *handler_binding,
handler: *handler,
handler_span: *handler_span,
fallthrough: *fallthrough,
id: *id,
span: *span,
},
Terminal::Scope { fallthrough, block, scope, id, span } => Terminal::Scope {
fallthrough: *fallthrough,
block: *block,
@@ -1677,21 +1792,28 @@ impl<'a> CloneIn<'a> for InstructionValue<'a> {
InstructionValue::MetaProperty { meta, property, span } => {
InstructionValue::MetaProperty { meta: *meta, property: *property, span: *span }
}
InstructionValue::PropertyStore { object, property, value, span } => {
InstructionValue::PropertyStore { object, property, property_span, value, span } => {
InstructionValue::PropertyStore {
object: *object,
property: *property,
property_span: *property_span,
value: *value,
span: *span,
}
}
InstructionValue::PropertyLoad { object, property, span } => {
InstructionValue::PropertyLoad { object: *object, property: *property, span: *span }
InstructionValue::PropertyLoad { object, property, property_span, span } => {
InstructionValue::PropertyLoad {
object: *object,
property: *property,
property_span: *property_span,
span: *span,
}
}
InstructionValue::PropertyDelete { object, property, span } => {
InstructionValue::PropertyDelete { object, property, property_span, span } => {
InstructionValue::PropertyDelete {
object: *object,
property: *property,
property_span: *property_span,
span: *span,
}
}
@@ -1721,12 +1843,14 @@ impl<'a> CloneIn<'a> for InstructionValue<'a> {
}
InstructionValue::FunctionExpression {
name,
name_span,
name_hint,
lowered_func,
expr_type,
span,
} => InstructionValue::FunctionExpression {
name: *name,
name_span: *name_span,
name_hint: *name_hint,
lowered_func: *lowered_func,
expr_type: *expr_type,
@@ -1809,14 +1933,18 @@ impl<'a> CloneIn<'a> for InstructionValue<'a> {
children,
span,
opening_span,
opening_name_span,
closing_span,
closing_name_span,
} => InstructionValue::JsxExpression {
tag: *tag,
props: props.clone_in_impl(sem, alloc),
children: children.as_ref().map(|v| v.clone_in_impl(sem, alloc)),
span: *span,
opening_span: *opening_span,
opening_name_span: *opening_name_span,
closing_span: *closing_span,
closing_name_span: *closing_name_span,
},
InstructionValue::ObjectExpression { properties, span } => {
InstructionValue::ObjectExpression {
@@ -1830,18 +1958,27 @@ impl<'a> CloneIn<'a> for InstructionValue<'a> {
span: *span,
}
}
InstructionValue::JsxFragment { children, span } => InstructionValue::JsxFragment {
children: children.clone_in_impl(sem, alloc),
span: *span,
},
InstructionValue::TaggedTemplateExpression { tag, quasis, subexprs, span } => {
InstructionValue::TaggedTemplateExpression {
tag: *tag,
quasis: quasis.clone_in_impl(sem, alloc),
subexprs: subexprs.clone_in_impl(sem, alloc),
InstructionValue::JsxFragment { children, span, opening_span, closing_span } => {
InstructionValue::JsxFragment {
children: children.clone_in_impl(sem, alloc),
span: *span,
opening_span: *opening_span,
closing_span: *closing_span,
}
}
InstructionValue::TaggedTemplateExpression {
tag,
quasis,
subexprs,
quasi_span,
span,
} => InstructionValue::TaggedTemplateExpression {
tag: *tag,
quasis: quasis.clone_in_impl(sem, alloc),
subexprs: subexprs.clone_in_impl(sem, alloc),
quasi_span: *quasi_span,
span: *span,
},
InstructionValue::TemplateLiteral { subexprs, quasis, span } => {
InstructionValue::TemplateLiteral {
subexprs: subexprs.clone_in_impl(sem, alloc),
@@ -15,10 +15,10 @@ use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use oxc_str::{Ident, Str};
use oxc_str::Ident;
use crate::react_compiler_hir::{
BlockId, EvaluationOrder, InstructionValue, ParamPattern, Place, ScopeId,
BlockId, EvaluationOrder, FunctionDirective, InstructionValue, ParamPattern, Place, ScopeId,
};
use oxc_allocator::{Allocator, Box as ArenaBox, CloneIn, CloneInSemanticIds, Vec as ArenaVec};
use oxc_ast::ast::LogicalOperator;
@@ -33,13 +33,15 @@ use oxc_span::Span;
#[derive(Debug)]
pub struct ReactiveFunction<'a> {
pub span: Option<Span>,
pub body_span: Option<Span>,
pub id: Option<Ident<'a>>,
pub id_span: Option<Span>,
pub name_hint: Option<Ident<'a>>,
pub params: Vec<ParamPattern>,
pub generator: bool,
pub is_async: bool,
pub body: ReactiveBlock<'a>,
pub directives: Vec<Str<'a>>,
pub directives: Vec<FunctionDirective<'a>>,
// No env field — passed separately per established Rust convention
}
@@ -113,6 +115,7 @@ pub enum ReactiveValue<'a> {
pub struct ReactiveTerminalStatement<'a> {
pub terminal: ReactiveTerminal<'a>,
pub label: Option<ReactiveLabel>,
pub span: Span,
}
#[derive(Debug, Clone)]
@@ -165,12 +168,14 @@ pub enum ReactiveTerminal<'a> {
},
DoWhile {
loop_block: ReactiveBlock<'a>,
loop_block_span: Option<Span>,
test: ReactiveValue<'a>,
id: EvaluationOrder,
},
While {
test: ReactiveValue<'a>,
loop_block: ReactiveBlock<'a>,
loop_block_span: Option<Span>,
id: EvaluationOrder,
},
For {
@@ -178,35 +183,45 @@ pub enum ReactiveTerminal<'a> {
test: ReactiveValue<'a>,
update: Option<ReactiveValue<'a>>,
loop_block: ReactiveBlock<'a>,
loop_block_span: Option<Span>,
id: EvaluationOrder,
},
ForOf {
init: ReactiveValue<'a>,
test: ReactiveValue<'a>,
loop_block: ReactiveBlock<'a>,
loop_block_span: Option<Span>,
left_span: Option<Span>,
id: EvaluationOrder,
span: Option<Span>,
},
ForIn {
init: ReactiveValue<'a>,
loop_block: ReactiveBlock<'a>,
loop_block_span: Option<Span>,
left_span: Option<Span>,
id: EvaluationOrder,
span: Option<Span>,
},
If {
test: Place,
consequent: ReactiveBlock<'a>,
consequent_span: Option<Span>,
alternate: Option<ReactiveBlock<'a>>,
alternate_span: Option<Span>,
id: EvaluationOrder,
},
Label {
block: ReactiveBlock<'a>,
block_span: Option<Span>,
id: EvaluationOrder,
},
Try {
block: ReactiveBlock<'a>,
block_span: Option<Span>,
handler_binding: Option<Place>,
handler: ReactiveBlock<'a>,
handler_span: Option<Span>,
id: EvaluationOrder,
},
}
@@ -215,6 +230,7 @@ pub enum ReactiveTerminal<'a> {
pub struct ReactiveSwitchCase<'a> {
pub test: Option<Place>,
pub block: Option<ReactiveBlock<'a>>,
pub span: Option<Span>,
}
// =============================================================================
@@ -264,7 +280,9 @@ impl<'a> CloneIn<'a> for ReactiveFunction<'a> {
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
ReactiveFunction {
span: self.span,
body_span: self.body_span,
id: self.id,
id_span: self.id_span,
name_hint: self.name_hint,
params: self.params.clone(),
generator: self.generator,
@@ -358,6 +376,7 @@ impl<'a> CloneIn<'a> for ReactiveTerminalStatement<'a> {
ReactiveTerminalStatement {
terminal: self.terminal.clone_in_impl(sem, alloc),
label: self.label.clone(),
span: self.span,
}
}
}
@@ -390,58 +409,96 @@ impl<'a> CloneIn<'a> for ReactiveTerminal<'a> {
),
id: *id,
},
ReactiveTerminal::DoWhile { loop_block, test, id } => ReactiveTerminal::DoWhile {
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
test: test.clone_in_impl(sem, alloc),
id: *id,
},
ReactiveTerminal::While { test, loop_block, id } => ReactiveTerminal::While {
test: test.clone_in_impl(sem, alloc),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
id: *id,
},
ReactiveTerminal::For { init, test, update, loop_block, id } => ReactiveTerminal::For {
init: init.clone_in_impl(sem, alloc),
test: test.clone_in_impl(sem, alloc),
update: update.as_ref().map(|value| value.clone_in_impl(sem, alloc)),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
id: *id,
},
ReactiveTerminal::ForOf { init, test, loop_block, id, span } => {
ReactiveTerminal::ForOf {
init: init.clone_in_impl(sem, alloc),
ReactiveTerminal::DoWhile { loop_block, loop_block_span, test, id } => {
ReactiveTerminal::DoWhile {
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
loop_block_span: *loop_block_span,
test: test.clone_in_impl(sem, alloc),
id: *id,
}
}
ReactiveTerminal::While { test, loop_block, loop_block_span, id } => {
ReactiveTerminal::While {
test: test.clone_in_impl(sem, alloc),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
loop_block_span: *loop_block_span,
id: *id,
}
}
ReactiveTerminal::For { init, test, update, loop_block, loop_block_span, id } => {
ReactiveTerminal::For {
init: init.clone_in_impl(sem, alloc),
test: test.clone_in_impl(sem, alloc),
update: update.as_ref().map(|value| value.clone_in_impl(sem, alloc)),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
loop_block_span: *loop_block_span,
id: *id,
}
}
ReactiveTerminal::ForOf {
init,
test,
loop_block,
loop_block_span,
left_span,
id,
span,
} => ReactiveTerminal::ForOf {
init: init.clone_in_impl(sem, alloc),
test: test.clone_in_impl(sem, alloc),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
loop_block_span: *loop_block_span,
left_span: *left_span,
id: *id,
span: *span,
},
ReactiveTerminal::ForIn { init, loop_block, loop_block_span, left_span, id, span } => {
ReactiveTerminal::ForIn {
init: init.clone_in_impl(sem, alloc),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
loop_block_span: *loop_block_span,
left_span: *left_span,
id: *id,
span: *span,
}
}
ReactiveTerminal::ForIn { init, loop_block, id, span } => ReactiveTerminal::ForIn {
init: init.clone_in_impl(sem, alloc),
loop_block: clone_reactive_block_in(loop_block, sem, alloc),
id: *id,
span: *span,
},
ReactiveTerminal::If { test, consequent, alternate, id } => ReactiveTerminal::If {
ReactiveTerminal::If {
test,
consequent,
consequent_span,
alternate,
alternate_span,
id,
} => ReactiveTerminal::If {
test: *test,
consequent: clone_reactive_block_in(consequent, sem, alloc),
consequent_span: *consequent_span,
alternate: alternate
.as_ref()
.map(|block| clone_reactive_block_in(block, sem, alloc)),
alternate_span: *alternate_span,
id: *id,
},
ReactiveTerminal::Label { block, id } => ReactiveTerminal::Label {
ReactiveTerminal::Label { block, block_span, id } => ReactiveTerminal::Label {
block: clone_reactive_block_in(block, sem, alloc),
block_span: *block_span,
id: *id,
},
ReactiveTerminal::Try {
block,
block_span,
handler_binding,
handler,
handler_span,
id,
} => ReactiveTerminal::Try {
block: clone_reactive_block_in(block, sem, alloc),
block_span: *block_span,
handler_binding: *handler_binding,
handler: clone_reactive_block_in(handler, sem, alloc),
handler_span: *handler_span,
id: *id,
},
ReactiveTerminal::Try { block, handler_binding, handler, id } => {
ReactiveTerminal::Try {
block: clone_reactive_block_in(block, sem, alloc),
handler_binding: *handler_binding,
handler: clone_reactive_block_in(handler, sem, alloc),
id: *id,
}
}
}
}
}
@@ -452,6 +509,7 @@ impl<'a> CloneIn<'a> for ReactiveSwitchCase<'a> {
ReactiveSwitchCase {
test: self.test,
block: self.block.as_ref().map(|block| clone_reactive_block_in(block, sem, alloc)),
span: self.span,
}
}
}
@@ -199,7 +199,7 @@ pub fn each_instruction_value_operand_with_functions(
for property in properties {
match property {
ObjectPropertyOrSpread::Property(prop) => {
if let ObjectPropertyKey::Computed { name } = &prop.key {
if let ObjectPropertyKey::Computed { name, .. } = &prop.key {
result.push(*name);
}
result.push(prop.place);
@@ -942,7 +942,7 @@ pub fn for_each_instruction_value_operand_mut(
for property in properties.iter_mut() {
match property {
ObjectPropertyOrSpread::Property(prop) => {
if let ObjectPropertyKey::Computed { name } = &mut prop.key {
if let ObjectPropertyKey::Computed { name, .. } = &mut prop.key {
f(name);
}
f(&mut prop.place);
@@ -108,7 +108,7 @@ pub fn flatten_scopes_with_hooks_or_use_hir(
{
// This was a scope just for a hook call, which doesn't need memoization.
// Flatten it away. We rely on PruneUnusedLabels to do the actual flattening.
Terminal::Label { block: scope_block, fallthrough, id: eval_id, span }
Terminal::Label { block: scope_block, block_span: None, fallthrough, id: eval_id, span }
} else {
Terminal::PrunedScope { block: scope_block, fallthrough, scope, id: eval_id, span }
};
@@ -2481,7 +2481,7 @@ fn compute_effects_for_legacy_signature<'a>(
match arg {
PlaceOrSpreadOrHole::Hole => continue,
PlaceOrSpreadOrHole::Place(place)
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place }) => {
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place, .. }) => {
effects.push(AliasingEffect::ImmutableCapture { from: *place, into: *lvalue });
}
}
@@ -2532,7 +2532,7 @@ fn compute_effects_for_legacy_signature<'a>(
match arg {
PlaceOrSpreadOrHole::Hole => continue,
PlaceOrSpreadOrHole::Place(place)
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place }) => {
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place, .. }) => {
let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_));
let sig_effect = if !is_spread && i < signature.positional_params.len() {
signature.positional_params[i]
@@ -2602,7 +2602,7 @@ fn are_arguments_immutable_and_non_mutating(
match arg {
PlaceOrSpreadOrHole::Hole => continue,
PlaceOrSpreadOrHole::Place(place)
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place }) => {
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place, .. }) => {
// Check if it's a function type with a known signature
let is_place = matches!(arg, PlaceOrSpreadOrHole::Place(_));
if is_place {
@@ -2693,7 +2693,7 @@ fn compute_effects_for_aliasing_signature_config<'a>(
match arg {
PlaceOrSpreadOrHole::Hole => continue,
PlaceOrSpreadOrHole::Place(place)
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place }) => {
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place, .. }) => {
if i < config.params.len() && !matches!(arg, PlaceOrSpreadOrHole::Spread(_)) {
substitutions.insert(config.params[i].to_string(), vec![*place]);
} else if let Some(rest) = config.rest {
@@ -2935,7 +2935,7 @@ fn compute_effects_for_aliasing_signature<'a>(
match arg {
PlaceOrSpreadOrHole::Hole => continue,
PlaceOrSpreadOrHole::Place(place)
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place }) => {
| PlaceOrSpreadOrHole::Spread(SpreadPattern { place, .. }) => {
let is_spread = matches!(arg, PlaceOrSpreadOrHole::Spread(_));
if !is_spread && i < signature.params.len() {
substitutions.insert(signature.params[i], vec![*place]);
@@ -3104,6 +3104,7 @@ fn compute_effects_for_aliasing_signature<'a>(
{
apply_args.push(PlaceOrSpreadOrHole::Spread(SpreadPattern {
place: *place,
span: sp.span,
}));
}
}
@@ -278,13 +278,16 @@ fn collect_temporaries_sidemap_impl<'a>(
let used_outside = used_outside_declaring_scope.contains(&lvalue_decl_id);
match &instr.value {
InstructionValue::PropertyLoad { object, property, span, .. } if !used_outside => {
InstructionValue::PropertyLoad { object, property, property_span, span }
if !used_outside =>
{
if inner_fn_context.is_none() || temporaries[object.identifier].is_some() {
let prop = get_property(
object,
property,
false,
*span,
*property_span,
temporaries,
env.allocator,
);
@@ -348,13 +351,15 @@ fn get_property<'a>(
property_name: &PropertyLiteral<'a>,
optional: bool,
span: Option<Span>,
property_span: Option<Span>,
temporaries: &TemporariesMap<'a>,
alloc: &'a Allocator,
) -> ReactiveScopeDependency<'a> {
let property_span = property_span.or(span);
let resolved = temporaries[object.identifier].as_ref();
if let Some(resolved) = resolved {
let mut path = resolved.path.clone_in(alloc);
path.push(DependencyPathEntry { property: *property_name, optional, span });
path.push(DependencyPathEntry { property: *property_name, optional, span: property_span });
ReactiveScopeDependency {
identifier: resolved.identifier,
reactive: resolved.reactive,
@@ -366,7 +371,7 @@ fn get_property<'a>(
identifier: object.identifier,
reactive: object.reactive,
path: ArenaVec::from_array_in(
[DependencyPathEntry { property: *property_name, optional, span }],
[DependencyPathEntry { property: *property_name, optional, span: property_span }],
&alloc,
),
span,
@@ -458,6 +463,7 @@ struct MatchConsequentResult<'a> {
store_local_lvalue_id: IdentifierId,
consequent_goto: BlockId,
property_load_span: Option<Span>,
property_span: Option<Span>,
}
fn match_optional_test_block<'a>(
@@ -477,8 +483,10 @@ fn match_optional_test_block<'a>(
let instr0 = &func.instructions[consequent_block.instructions[0].index()];
let instr1 = &func.instructions[consequent_block.instructions[1].index()];
let (property_load_object, property, property_load_span) = match &instr0.value {
InstructionValue::PropertyLoad { object, property, span } => (object, property, span),
let (property_load_object, property, property_load_span, property_span) = match &instr0.value {
InstructionValue::PropertyLoad { object, property, property_span, span } => {
(object, property, span, property_span)
}
_ => return None,
};
@@ -520,6 +528,7 @@ fn match_optional_test_block<'a>(
store_local_lvalue_id: instr1.lvalue.identifier,
consequent_goto: *goto_block,
property_load_span: *property_load_span,
property_span: *property_span,
})
}
_ => None,
@@ -561,13 +570,13 @@ fn traverse_optional_block<'a>(
let curr_instr = &func.instructions[maybe_test_block.instructions[i].index()];
let prev_instr = &func.instructions[maybe_test_block.instructions[i - 1].index()];
match &curr_instr.value {
InstructionValue::PropertyLoad { object, property, span, .. }
InstructionValue::PropertyLoad { object, property, property_span, span }
if object.identifier == prev_instr.lvalue.identifier =>
{
path.push(DependencyPathEntry {
property: *property,
optional: false,
span: *span,
span: property_span.or(*span),
});
}
_ => return None,
@@ -669,7 +678,7 @@ fn traverse_optional_block<'a>(
p.push(DependencyPathEntry {
property: match_result.property,
optional: is_optional,
span: match_result.property_load_span,
span: match_result.property_span.or(match_result.property_load_span),
});
p
},
@@ -1743,9 +1752,18 @@ impl<'a, 'e> DependencyCollectionContext<'a, 'e> {
property: &PropertyLiteral<'a>,
optional: bool,
span: Option<Span>,
property_span: Option<Span>,
env: &mut Environment<'a>,
) {
let dep = get_property(object, property, optional, span, self.temporaries, env.allocator);
let dep = get_property(
object,
property,
optional,
span,
property_span,
self.temporaries,
env.allocator,
);
self.visit_dependency(dep, env);
}
@@ -1916,8 +1934,8 @@ fn handle_instruction<'a>(
}
match &instr.value {
InstructionValue::PropertyLoad { object, property, span, .. } => {
ctx.visit_property(object, property, false, *span, env);
InstructionValue::PropertyLoad { object, property, property_span, span } => {
ctx.visit_property(object, property, false, *span, *property_span, env);
}
InstructionValue::StoreLocal { value: val, lvalue, .. } => {
ctx.visit_operand(val, env);
@@ -550,7 +550,7 @@ pub fn lower<'a>(
// Note: `id` param may include inferred names (e.g., from `const Foo = () => {}`),
// but the HIR function's `id` field should only include the function's own AST id
// (FunctionDeclaration.id or FunctionExpression.id, NOT arrow functions).
let (params, body, generator, is_async, span, ast_id) = match func {
let (params, body, generator, is_async, span, ast_id, ast_id_span) = match func {
FunctionNode::Function(f) => {
let body_ref = f.body.as_deref().expect("component function has a body");
(
@@ -560,6 +560,7 @@ pub fn lower<'a>(
f.r#async,
f.span,
f.id.as_ref().map(|id| id.name),
f.id.as_ref().map(|id| id.span),
)
}
FunctionNode::Arrow(arrow) => {
@@ -567,7 +568,7 @@ pub fn lower<'a>(
|| FunctionBody::Block(arrow.get_function_body().unwrap()),
FunctionBody::Expression,
);
(arrow.params.as_ref(), body, false, arrow.r#async, arrow.span, None)
(arrow.params.as_ref(), body, false, arrow.r#async, arrow.span, None, None)
}
};
@@ -588,6 +589,7 @@ pub fn lower<'a>(
params,
body,
ast_id,
ast_id_span,
generator,
is_async,
span,
@@ -616,6 +618,7 @@ fn lower_inner<'a>(
params: &oxc::FormalParameters<'a>,
body: FunctionBody<'_, 'a>,
id: Option<Ident<'a>>,
id_span: Option<Span>,
generator: bool,
is_async: bool,
span: Span,
@@ -721,7 +724,7 @@ fn lower_inner<'a>(
let param_span = param.span;
let place = build_temporary_place(&mut builder, Some(param_span));
promote_temporary(&mut builder, place.identifier);
hir_params.push(ParamPattern::Place(place));
hir_params.push(ParamPattern::Place(Place { span: None, ..place }));
let value = if let Some(initializer) = &param.initializer {
lower_default_to_temp(&mut builder, param_span, initializer, place)?
} else {
@@ -743,7 +746,7 @@ fn lower_inner<'a>(
if let Some(rest) = &params.rest {
let rest_span = rest.span;
let place = build_temporary_place(&mut builder, Some(rest_span));
hir_params.push(ParamPattern::Spread(SpreadPattern { place }));
hir_params.push(ParamPattern::Spread(SpreadPattern { place, span: Some(rest_span) }));
lower_binding_assignment(
&mut builder,
rest_span,
@@ -755,6 +758,7 @@ fn lower_inner<'a>(
}
// Lower the body
let mut body_span = None;
let mut directives = ArenaVec::new_in(&alloc);
match body {
FunctionBody::Expression(expr) => {
@@ -772,8 +776,15 @@ fn lower_inner<'a>(
);
}
FunctionBody::Block(block) => {
directives =
ArenaVec::from_iter_in(block.directives.iter().map(|d| d.expression.value), &alloc);
body_span = Some(block.span);
directives = ArenaVec::from_iter_in(
block.directives.iter().map(|d| FunctionDirective {
value: d.expression.value,
span: d.span,
expression_span: d.expression.span,
}),
&alloc,
);
// A function body shares the function's scope (the scope cell lives on
// the function node, not the block), so pass it as the scope override.
lower_block_statement_with_scope(&mut builder, &block.statements, function_scope)?;
@@ -806,7 +817,9 @@ fn lower_inner<'a>(
Ok((
HirFunction {
span: Some(span),
body_span,
id,
id_span,
name_hint: None,
fn_type: if is_top_level { env.fn_type } else { ReactFunctionType::Other },
params: hir_params,
@@ -902,6 +915,7 @@ enum MemberProperty<'a> {
struct LoweredMemberExpression<'a> {
object: Place,
property: MemberProperty<'a>,
property_span: Option<Span>,
value: InstructionValue<'a>,
}
@@ -922,15 +936,22 @@ fn lower_member_expression_impl<'a>(
match member {
oxc::MemberExpression::StaticMemberExpression(m) => {
let span = Some(m.span);
let property_span = Some(m.property.span);
let object = match lowered_object {
Some(obj) => obj,
None => lower_expression_to_temporary(builder, &m.object)?,
};
let prop_literal = PropertyLiteral::String(m.property.name);
let value = InstructionValue::PropertyLoad { object, property: prop_literal, span };
let value = InstructionValue::PropertyLoad {
object,
property: prop_literal,
property_span,
span,
};
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(prop_literal),
property_span,
value,
})
}
@@ -942,11 +963,18 @@ fn lower_member_expression_impl<'a>(
};
// A numeric computed index is treated as a PropertyLoad (matches TS).
if let oxc::Expression::NumericLiteral(lit) = &m.expression {
let property_span = Some(lit.span);
let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.value));
let value = InstructionValue::PropertyLoad { object, property: prop_literal, span };
let value = InstructionValue::PropertyLoad {
object,
property: prop_literal,
property_span,
span,
};
return Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(prop_literal),
property_span,
value,
});
}
@@ -955,6 +983,7 @@ fn lower_member_expression_impl<'a>(
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Computed(property),
property_span: property.span,
value,
})
}
@@ -974,6 +1003,7 @@ fn lower_member_expression_impl<'a>(
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(PropertyLiteral::String(Ident::empty())),
property_span: None,
value: InstructionValue::Primitive { value: PrimitiveValue::Undefined, span },
})
}
@@ -982,7 +1012,7 @@ fn lower_member_expression_impl<'a>(
/// Build a HIR `TemplateQuasi` from an oxc `TemplateElement`.
fn template_quasi_from_oxc<'a>(q: &oxc::TemplateElement<'a>) -> TemplateQuasi<'a> {
TemplateQuasi { raw: q.value.raw, cooked: q.value.cooked }
TemplateQuasi { raw: q.value.raw, cooked: q.value.cooked, span: q.span }
}
/// Lower the `import` keyword callee of an `ImportExpression`. The original Babel
@@ -1091,12 +1121,19 @@ fn lower_member_expression_from_simple_target<'a>(
match target {
oxc::SimpleAssignmentTarget::StaticMemberExpression(m) => {
let span = Some(m.span);
let property_span = Some(m.property.span);
let object = lower_expression_to_temporary(builder, &m.object)?;
let prop_literal = PropertyLiteral::String(m.property.name);
let value = InstructionValue::PropertyLoad { object, property: prop_literal, span };
let value = InstructionValue::PropertyLoad {
object,
property: prop_literal,
property_span,
span,
};
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(prop_literal),
property_span,
value,
})
}
@@ -1104,11 +1141,18 @@ fn lower_member_expression_from_simple_target<'a>(
let span = Some(m.span);
let object = lower_expression_to_temporary(builder, &m.object)?;
if let oxc::Expression::NumericLiteral(lit) = &m.expression {
let property_span = Some(lit.span);
let prop_literal = PropertyLiteral::Number(FloatValue::new(lit.value));
let value = InstructionValue::PropertyLoad { object, property: prop_literal, span };
let value = InstructionValue::PropertyLoad {
object,
property: prop_literal,
property_span,
span,
};
return Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(prop_literal),
property_span,
value,
});
}
@@ -1117,6 +1161,7 @@ fn lower_member_expression_from_simple_target<'a>(
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Computed(property),
property_span: property.span,
value,
})
}
@@ -1131,6 +1176,7 @@ fn lower_member_expression_from_simple_target<'a>(
Ok(LoweredMemberExpression {
object,
property: MemberProperty::Literal(PropertyLiteral::String(Ident::empty())),
property_span: None,
value: InstructionValue::Primitive { value: PrimitiveValue::Undefined, span },
})
}
@@ -1150,7 +1196,8 @@ fn lower_arguments<'a>(
match arg {
oxc::Argument::SpreadElement(spread) => {
let place = lower_expression_to_temporary(builder, &spread.argument)?;
result.push(PlaceOrSpread::Spread(SpreadPattern { place }));
result
.push(PlaceOrSpread::Spread(SpreadPattern { place, span: Some(spread.span) }));
}
_ => {
let expr = arg.as_expression().expect("non-spread argument is an expression");
@@ -1390,14 +1437,17 @@ fn lower_binding_assignment<'a>(
builder.scope().resolve_binding_identifier(id),
)? {
Some(IdentifierForAssignment::Place(place)) => {
items
.push(ArrayPatternElement::Spread(SpreadPattern { place }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place,
span: Some(rest.span),
}));
}
Some(IdentifierForAssignment::Global { .. }) => {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, &rest.argument));
}
@@ -1406,14 +1456,20 @@ fn lower_binding_assignment<'a>(
} else {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern { place: temp }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, &rest.argument));
}
}
_ => {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern { place: temp }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, &rest.argument));
}
}
@@ -1538,7 +1594,7 @@ fn lower_binding_assignment<'a>(
)? {
Some(IdentifierForAssignment::Place(place)) => {
properties.push(ObjectPropertyOrSpread::Spread(
SpreadPattern { place },
SpreadPattern { place, span: Some(rest.span) },
));
}
Some(IdentifierForAssignment::Global { .. }) => {
@@ -1554,6 +1610,7 @@ fn lower_binding_assignment<'a>(
promote_temporary(builder, temp.identifier);
properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, &rest.argument));
}
@@ -1738,6 +1795,7 @@ fn lower_member_assignment_target<'a>(
InstructionValue::PropertyStore {
object,
property: PropertyLiteral::String(member.property.name),
property_span: Some(member.property.span),
value,
span: Some(span),
},
@@ -1754,6 +1812,7 @@ fn lower_member_assignment_target<'a>(
InstructionValue::PropertyStore {
object,
property: PropertyLiteral::Number(FloatValue::new(num.value)),
property_span: Some(num.span),
value,
span: Some(span),
},
@@ -1992,14 +2051,17 @@ fn lower_assignment_target<'a>(
builder.scope().resolve_reference(id),
)? {
Some(IdentifierForAssignment::Place(place)) => {
items
.push(ArrayPatternElement::Spread(SpreadPattern { place }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place,
span: Some(rest.span),
}));
}
Some(IdentifierForAssignment::Global { .. }) => {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, FollowupTarget::Target(&rest.target)));
}
@@ -2008,14 +2070,20 @@ fn lower_assignment_target<'a>(
} else {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern { place: temp }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, FollowupTarget::Target(&rest.target)));
}
}
_ => {
let temp = build_temporary_place(builder, Some(rest.span));
promote_temporary(builder, temp.identifier);
items.push(ArrayPatternElement::Spread(SpreadPattern { place: temp }));
items.push(ArrayPatternElement::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, FollowupTarget::Target(&rest.target)));
}
}
@@ -2090,7 +2158,10 @@ fn lower_assignment_target<'a>(
for prop in &pattern.properties {
match prop {
oxc::AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(p) => {
let key = ObjectPropertyKey::Identifier { name: p.binding.name };
let key = ObjectPropertyKey::Identifier {
name: p.binding.name,
span: Some(p.binding.span),
};
let id = &p.binding;
if let Some(default) = &p.init {
// `{foo = d}` — Babel shorthand AssignmentPattern. Lower
@@ -2251,7 +2322,7 @@ fn lower_assignment_target<'a>(
)? {
Some(IdentifierForAssignment::Place(place)) => {
properties.push(ObjectPropertyOrSpread::Spread(
SpreadPattern { place },
SpreadPattern { place, span: Some(rest.span) },
));
}
Some(IdentifierForAssignment::Global { .. }) => {
@@ -2267,6 +2338,7 @@ fn lower_assignment_target<'a>(
promote_temporary(builder, temp.identifier);
properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern {
place: temp,
span: Some(rest.span),
}));
followups.push((temp, FollowupTarget::Target(&rest.target)));
}
@@ -2928,17 +3000,16 @@ fn lower_function_to_value<'a>(
func: FunctionNode<'_, 'a>,
expr_type: FunctionExpressionType,
) -> Result<InstructionValue<'a>, OxcDiagnostic> {
let span = match func {
FunctionNode::Arrow(arrow) => Some(arrow.span),
FunctionNode::Function(f) => Some(f.span),
};
let name = match func {
FunctionNode::Function(f) => f.id.as_ref().map(|id| id.name),
FunctionNode::Arrow(_) => None,
let (span, name, name_span) = match func {
FunctionNode::Arrow(arrow) => (Some(arrow.span), None, None),
FunctionNode::Function(f) => {
(Some(f.span), f.id.as_ref().map(|id| id.name), f.id.as_ref().map(|id| id.span))
}
};
let lowered_func = lower_function(builder, func)?;
Ok(InstructionValue::FunctionExpression {
name,
name_span,
name_hint: None,
lowered_func,
expr_type,
@@ -2953,13 +3024,13 @@ fn lower_function<'a>(
func: FunctionNode<'_, 'a>,
) -> Result<LoweredFunction, OxcDiagnostic> {
// Extract function parts from the AST node
let (params, body, id, generator, is_async, func_span) = match func {
let (params, body, id, id_span, generator, is_async, func_span) = match func {
FunctionNode::Arrow(arrow) => {
let body = arrow.get_expression().map_or_else(
|| FunctionBody::Block(arrow.get_function_body().unwrap()),
FunctionBody::Expression,
);
(arrow.params.as_ref(), body, None, false, arrow.r#async, arrow.span)
(arrow.params.as_ref(), body, None, None, false, arrow.r#async, arrow.span)
}
FunctionNode::Function(f) => {
let body_ref = f.body.as_deref().expect("function expression has a body");
@@ -2967,6 +3038,7 @@ fn lower_function<'a>(
f.params.as_ref(),
FunctionBody::Block(body_ref),
f.id.as_ref().map(|id| id.name),
f.id.as_ref().map(|id| id.span),
f.generator,
f.r#async,
f.span,
@@ -3001,6 +3073,7 @@ fn lower_function<'a>(
params,
body,
id,
id_span,
generator,
is_async,
func_span,
@@ -3062,6 +3135,7 @@ fn lower_function_declaration<'a>(
func_decl.params.as_ref(),
FunctionBody::Block(body_ref),
func_decl.id.as_ref().map(|id| id.name),
func_decl.id.as_ref().map(|id| id.span),
func_decl.generator,
func_decl.r#async,
span,
@@ -3086,6 +3160,7 @@ fn lower_function_declaration<'a>(
// Emit FunctionExpression instruction
let fn_value = InstructionValue::FunctionExpression {
name: func_name,
name_span: func_decl.id.as_ref().map(|id| id.span),
name_hint: None,
lowered_func,
expr_type: FunctionExpressionType::FunctionDeclaration,
@@ -3235,6 +3310,7 @@ fn lower_function_for_object_method<'a>(
params,
FunctionBody::Block(body),
None,
None,
generator,
is_async,
func_span,
@@ -3413,11 +3489,13 @@ fn lower_expression<'a>(
unary.argument.without_parentheses().as_member_expression()
{
let lowered = lower_member_expression(builder, member)?;
let property_span = lowered.property_span;
match lowered.property {
MemberProperty::Literal(property) => {
Ok(InstructionValue::PropertyDelete {
object: lowered.object,
property,
property_span,
span,
})
}
@@ -3698,6 +3776,7 @@ fn lower_expression<'a>(
}
oxc::Expression::TaggedTemplateExpression(tagged) => {
let span = Some(tagged.span);
let quasi_span = Some(tagged.quasi.span);
// Upstream React Compiler bails on any interpolation here; the oxc port
// instead lowers the tag plus every quasi and every `${...}`
// subexpression (mirroring `TemplateLiteral`). This is a deliberate
@@ -3731,7 +3810,13 @@ fn lower_expression<'a>(
tagged.quasi.quasis.iter().map(template_quasi_from_oxc),
&alloc,
);
Ok(InstructionValue::TaggedTemplateExpression { tag, quasis, subexprs, span })
Ok(InstructionValue::TaggedTemplateExpression {
tag,
quasis,
subexprs,
quasi_span,
span,
})
}
oxc::Expression::AwaitExpression(await_expr) => {
let span = Some(await_expr.span);
@@ -3842,6 +3927,7 @@ fn lower_expression<'a>(
lower_member_expression_from_simple_target(builder, &update.argument)?;
let object = lowered.object;
let lowered_property = lowered.property;
let property_span = lowered.property_span;
let prev_value = lower_value_to_temporary(builder, lowered.value)?;
let one = lower_value_to_temporary(
@@ -3870,6 +3956,7 @@ fn lower_expression<'a>(
InstructionValue::PropertyStore {
object,
property: prop_literal,
property_span,
value: updated,
span: member_span,
},
@@ -4046,7 +4133,10 @@ fn lower_expression<'a>(
}
oxc::ObjectPropertyKind::SpreadProperty(spread) => {
let place = lower_expression_to_temporary(builder, &spread.argument)?;
properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern { place }));
properties.push(ObjectPropertyOrSpread::Spread(SpreadPattern {
place,
span: Some(spread.span),
}));
}
}
}
@@ -4063,7 +4153,10 @@ fn lower_expression<'a>(
}
oxc::ArrayExpressionElement::SpreadElement(spread) => {
let place = lower_expression_to_temporary(builder, &spread.argument)?;
elements.push(ArrayElement::Spread(SpreadPattern { place }));
elements.push(ArrayElement::Spread(SpreadPattern {
place,
span: Some(spread.span),
}));
}
_ => {
let expr = element.to_expression();
@@ -4190,6 +4283,7 @@ fn lower_assignment_expression<'a>(
InstructionValue::PropertyStore {
object,
property: PropertyLiteral::String(member.property.name),
property_span: Some(member.property.span),
value: right,
span: left_span,
},
@@ -4203,6 +4297,7 @@ fn lower_assignment_expression<'a>(
InstructionValue::PropertyStore {
object,
property: PropertyLiteral::Number(FloatValue::new(num.value)),
property_span: Some(num.span),
value: right,
span: left_span,
},
@@ -4367,6 +4462,7 @@ fn lower_assignment_expression<'a>(
let lowered = lower_member_expression_from_simple_target(builder, simple)?;
let object = lowered.object;
let lowered_property = lowered.property;
let property_span = lowered.property_span;
let current_value = lower_value_to_temporary(builder, lowered.value)?;
let right = lower_expression_to_temporary(builder, &assign.right)?;
let result = lower_value_to_temporary(
@@ -4382,6 +4478,7 @@ fn lower_assignment_expression<'a>(
MemberProperty::Literal(prop_literal) => Ok(InstructionValue::PropertyStore {
object,
property: prop_literal,
property_span,
value: result,
span: member_span,
}),
@@ -4418,7 +4515,9 @@ fn lower_jsx_element_expr<'a>(
) -> Result<InstructionValue<'a>, OxcDiagnostic> {
let span = Some(jsx_element.span);
let opening_span = Some(jsx_element.opening_element.span);
let opening_name_span = Some(jsx_element.opening_element.name.span());
let closing_span = jsx_element.closing_element.as_ref().map(|c| c.span);
let closing_name_span = jsx_element.closing_element.as_ref().map(|c| c.name.span());
// Lower the tag name
let tag = lower_jsx_element_name(builder, &jsx_element.opening_element.name)?;
@@ -4430,7 +4529,7 @@ fn lower_jsx_element_expr<'a>(
match attr_item {
oxc::JSXAttributeItem::SpreadAttribute(spread) => {
let argument = lower_expression_to_temporary(builder, &spread.argument)?;
props.push(JsxAttribute::SpreadAttribute { argument });
props.push(JsxAttribute::SpreadAttribute { argument, span: Some(spread.span) });
}
oxc::JSXAttributeItem::Attribute(attr) => {
// Get the attribute name
@@ -4509,7 +4608,11 @@ fn lower_jsx_element_expr<'a>(
}
};
props.push(JsxAttribute::Attribute { name: prop_name, place: value });
props.push(JsxAttribute::Attribute {
name: prop_name,
name_span: Some(attr.name.span()),
place: value,
});
}
}
}
@@ -4611,7 +4714,9 @@ fn lower_jsx_element_expr<'a>(
children: if children.is_empty() { None } else { Some(children) },
span,
opening_span,
opening_name_span,
closing_span,
closing_name_span,
})
}
@@ -4622,6 +4727,8 @@ fn lower_jsx_fragment_expr<'a>(
jsx_fragment: &oxc::JSXFragment<'a>,
) -> Result<InstructionValue<'a>, OxcDiagnostic> {
let span = Some(jsx_fragment.span);
let opening_span = Some(jsx_fragment.opening_fragment.span);
let closing_span = Some(jsx_fragment.closing_fragment.span);
// Lower children
let alloc = builder.environment().allocator;
@@ -4636,7 +4743,7 @@ fn lower_jsx_fragment_expr<'a>(
&alloc,
);
Ok(InstructionValue::JsxFragment { children, span })
Ok(InstructionValue::JsxFragment { children, span, opening_span, closing_span })
}
/// Lower a JSX element name into a `JsxTag`. Faithful translation of the original
@@ -4746,6 +4853,7 @@ fn lower_jsx_member_expression<'a>(
let value = InstructionValue::PropertyLoad {
object,
property: PropertyLiteral::String(Ident::from(prop_name)),
property_span: Some(expr.property.span),
span: expr_span,
};
lower_value_to_temporary(builder, value)
@@ -4785,23 +4893,29 @@ fn lower_jsx_element<'a>(
// Since the fbt transform runs after, preserve all whitespace
// in FBT subtrees as is.
let value = if builder.fbt_depth > 0 {
Some(match decoded {
Cow::Borrowed(text) => Str::from(text),
Cow::Owned(ref text) => {
Str::from_str_in(text, &builder.environment().allocator)
}
})
Some((
match decoded {
Cow::Borrowed(text) => Str::from(text),
Cow::Owned(ref text) => {
Str::from_str_in(text, &builder.environment().allocator)
}
},
0,
))
} else {
trim_jsx_text(&decoded)
.map(|text| Str::from_str_in(&text, &builder.environment().allocator))
trim_jsx_text(&decoded).map(|(text, start)| {
(Str::from_str_in(&text, &builder.environment().allocator), start)
})
};
match value {
None => Ok(None),
Some(value) => {
let span = Some(text.span);
Some((value, start)) => {
let mut span = text.span;
span.start +=
source_offset_for_decoded_jsx_text(text.value.as_str(), start) as u32;
let place = lower_value_to_temporary(
builder,
InstructionValue::JSXText { value, span },
InstructionValue::JSXText { value, span: Some(span) },
)?;
Ok(Some(place))
}
@@ -5111,7 +5225,7 @@ fn split_line_endings(s: &str) -> Vec<&str> {
/// Trims whitespace according to the JSX spec.
/// Implementation ported from Babel's cleanJSXElementLiteralChild.
fn trim_jsx_text(original: &str) -> Option<String> {
fn trim_jsx_text(original: &str) -> Option<(String, usize)> {
// Split on \r\n, \n, or \r to handle all line ending styles (matching TS split(/\r\n|\n|\r/))
let lines: Vec<&str> = split_line_endings(original);
@@ -5126,6 +5240,8 @@ fn trim_jsx_text(original: &str) -> Option<String> {
}
let mut str = String::new();
let mut first_retained_offset = None;
let mut line_offset = 0;
for (i, line) in lines.iter().enumerate() {
let is_first_line = i == 0;
@@ -5134,10 +5250,13 @@ fn trim_jsx_text(original: &str) -> Option<String> {
// Replace rendered whitespace tabs with spaces
let mut trimmed_line = line.cow_replace('\t', " ").into_owned();
let mut leading_trimmed = 0;
// Trim whitespace touching a newline (leading whitespace on non-first lines)
if !is_first_line {
let original_len = trimmed_line.len();
trimmed_line = trimmed_line.trim_start_matches(' ').to_string();
leading_trimmed = original_len - trimmed_line.len();
}
// Trim whitespace touching an endline (trailing whitespace on non-last lines)
@@ -5146,14 +5265,63 @@ fn trim_jsx_text(original: &str) -> Option<String> {
}
if !trimmed_line.is_empty() {
first_retained_offset.get_or_insert(line_offset + leading_trimmed);
if !is_last_non_empty_line {
trimmed_line.push(' ');
}
str.push_str(&trimmed_line);
}
line_offset += line.len();
if !is_last_line {
line_offset += if original.as_bytes().get(line_offset) == Some(&b'\r')
&& original.as_bytes().get(line_offset + 1) == Some(&b'\n')
{
2
} else {
1
};
}
}
if str.is_empty() { None } else { Some(str) }
first_retained_offset.map(|offset| (str, offset))
}
fn source_offset_for_decoded_jsx_text(source: &str, target_offset: usize) -> usize {
// Decoded entities can make a byte offset in the normalized value differ
// from the corresponding offset in the raw JSX source.
if target_offset == 0 || !source.contains('&') {
return target_offset;
}
let mut source_offset = 0;
let mut decoded_offset = 0;
while decoded_offset < target_offset && source_offset < source.len() {
let remaining = &source[source_offset..];
if let Some(entity) = remaining.strip_prefix('&')
&& let Some(end) = entity.find(';')
{
let word = &entity[..end];
if !word.contains('&')
&& let Some(decoded) = decode_jsx_entity(word)
{
let decoded_len = decoded.len_utf8();
if decoded_offset + decoded_len > target_offset {
return source_offset;
}
decoded_offset += decoded_len;
source_offset += end + 2;
continue;
}
}
let char_len = remaining.chars().next().unwrap().len_utf8();
decoded_offset += char_len;
source_offset += char_len;
}
source_offset
}
/// Decode XML/HTML entities in JSX text (`&amp;` → `&`, `&gt;` → `>`, `&#123;`
@@ -5186,16 +5354,7 @@ fn decode_jsx_entities(s: &str) -> Cow<'_, str> {
out.push_str(&s[prev..start]);
prev = end + 1;
let word = &s[start + 1..end];
let decoded = if let Some(num) = word.strip_prefix('#') {
if let Some(hex) = num.strip_prefix(['x', 'X']) {
u32::from_str_radix(hex, 16).ok().and_then(char::from_u32)
} else {
num.parse::<u32>().ok().and_then(char::from_u32)
}
} else {
oxc_syntax::xml_entities::XML_ENTITIES.get(word).copied()
};
match decoded {
match decode_jsx_entity(word) {
Some(c) => out.push(c),
// Not a recognized entity — keep the `&…;` literal.
None => {
@@ -5209,6 +5368,18 @@ fn decode_jsx_entities(s: &str) -> Cow<'_, str> {
Cow::Owned(out)
}
fn decode_jsx_entity(word: &str) -> Option<char> {
if let Some(num) = word.strip_prefix('#') {
if let Some(hex) = num.strip_prefix(['x', 'X']) {
u32::from_str_radix(hex, 16).ok().and_then(char::from_u32)
} else {
num.parse::<u32>().ok().and_then(char::from_u32)
}
} else {
oxc_syntax::xml_entities::XML_ENTITIES.get(word).copied()
}
}
/// Get the Babel-style type name of an oxc `Expression` node. Mirrors the
/// original `expression_type_name` (which read Babel-shaped variants), mapping
/// oxc's split member/chain shapes back to the Babel names the original emitted
@@ -5292,7 +5463,7 @@ fn lower_object_method<'a>(
}
let key = lower_object_property_key(builder, &method.key, method.computed)?
.unwrap_or(ObjectPropertyKey::String { name: Ident::empty() });
.unwrap_or(ObjectPropertyKey::String { name: Ident::empty(), span: None });
let func = match &method.value {
oxc::Expression::FunctionExpression(func) => func,
@@ -5323,23 +5494,25 @@ fn lower_object_property_key<'a>(
computed: bool,
) -> Result<Option<ObjectPropertyKey<'a>>, OxcDiagnostic> {
match key {
oxc::PropertyKey::StringLiteral(lit) => {
Ok(Some(ObjectPropertyKey::String { name: Ident::from(lit.value.as_str()) }))
}
oxc::PropertyKey::StringLiteral(lit) => Ok(Some(ObjectPropertyKey::String {
name: Ident::from(lit.value.as_str()),
span: Some(lit.span),
})),
oxc::PropertyKey::StaticIdentifier(ident) if !computed => {
Ok(Some(ObjectPropertyKey::Identifier { name: ident.name }))
Ok(Some(ObjectPropertyKey::Identifier { name: ident.name, span: Some(ident.span) }))
}
oxc::PropertyKey::Identifier(ident) if !computed => {
Ok(Some(ObjectPropertyKey::Identifier { name: ident.name }))
Ok(Some(ObjectPropertyKey::Identifier { name: ident.name, span: Some(ident.span) }))
}
oxc::PropertyKey::NumericLiteral(lit) if !computed => {
Ok(Some(ObjectPropertyKey::Identifier {
name: format_ident!(builder.environment().allocator, "{}", lit.value),
span: Some(lit.span),
}))
}
_ if computed => {
let place = lower_expression_to_temporary(builder, key.to_expression())?;
Ok(Some(ObjectPropertyKey::Computed { name: place }))
Ok(Some(ObjectPropertyKey::Computed { name: place, span: Some(key.span()) }))
}
_ => {
let span = match key {
@@ -5525,6 +5698,13 @@ fn is_reorderable_expression(
}
}
fn source_block_span(stmt: &oxc::Statement<'_>) -> Option<Span> {
match stmt {
oxc::Statement::BlockStatement(block) => Some(block.span),
_ => None,
}
}
fn lower_statement<'a>(
builder: &mut HirBuilder<'a, '_>,
stmt: &oxc::Statement<'a>,
@@ -5596,7 +5776,7 @@ fn lower_statement<'a>(
let continuation_id = continuation_block.id;
// Block for the consequent (if the test is truthy)
let consequent_span = Some(if_stmt.consequent.span());
let consequent_span = source_block_span(&if_stmt.consequent);
let consequent_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
lower_statement(builder, &if_stmt.consequent, None, parent_scope)?;
Ok(Terminal::Goto {
@@ -5608,8 +5788,8 @@ fn lower_statement<'a>(
})?;
// Block for the alternate (if the test is not truthy)
let alternate_span = if_stmt.alternate.as_ref().and_then(source_block_span);
let alternate_block = if let Some(alternate) = &if_stmt.alternate {
let alternate_span = Some(alternate.span());
builder.try_enter(BlockKind::Block, |builder, _block_id| {
lower_statement(builder, alternate, None, parent_scope)?;
Ok(Terminal::Goto {
@@ -5629,7 +5809,9 @@ fn lower_statement<'a>(
Terminal::If {
test,
consequent: consequent_block,
consequent_span,
alternate: alternate_block,
alternate_span,
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -5701,7 +5883,7 @@ fn lower_statement<'a>(
// Loop body block
let continue_target = update_block_id.unwrap_or(test_block_id);
let body_span = Some(for_stmt.body.span());
let body_span = source_block_span(&for_stmt.body);
let body_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.loop_scope(label, continue_target, continuation_id, |builder| {
lower_statement(builder, &for_stmt.body, None, parent_scope)?;
@@ -5721,6 +5903,7 @@ fn lower_statement<'a>(
test: test_block_id,
update: update_block_id,
loop_block: body_block,
loop_block_span: body_span,
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -5775,7 +5958,7 @@ fn lower_statement<'a>(
let continuation_id = continuation_block.id;
// Loop body
let body_span = Some(while_stmt.body.span());
let body_span = source_block_span(&while_stmt.body);
let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.loop_scope(label, conditional_id, continuation_id, |builder| {
lower_statement(builder, &while_stmt.body, None, parent_scope)?;
@@ -5793,6 +5976,7 @@ fn lower_statement<'a>(
Terminal::While {
test: conditional_id,
loop_block,
loop_block_span: body_span,
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -5824,7 +6008,7 @@ fn lower_statement<'a>(
let continuation_id = continuation_block.id;
// Loop body, executed at least once unconditionally prior to exit
let body_span = Some(do_while_stmt.body.span());
let body_span = source_block_span(&do_while_stmt.body);
let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.loop_scope(label, conditional_id, continuation_id, |builder| {
lower_statement(builder, &do_while_stmt.body, None, parent_scope)?;
@@ -5841,6 +6025,7 @@ fn lower_statement<'a>(
builder.terminate_with_continuation(
Terminal::DoWhile {
loop_block,
loop_block_span: body_span,
test: conditional_id,
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
@@ -5865,12 +6050,13 @@ fn lower_statement<'a>(
}
oxc::Statement::ForInStatement(for_in) => {
let span = Some(for_in.span);
let left_span = for_in.left.span();
let continuation_block = builder.reserve(BlockKind::Block);
let continuation_id = continuation_block.id;
let init_block = builder.reserve(BlockKind::Loop);
let init_block_id = init_block.id;
let body_span = Some(for_in.body.span());
let body_span = source_block_span(&for_in.body);
let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.loop_scope(label, init_block_id, continuation_id, |builder| {
lower_statement(builder, &for_in.body, None, parent_scope)?;
@@ -5888,6 +6074,8 @@ fn lower_statement<'a>(
Terminal::ForIn {
init: init_block_id,
loop_block,
loop_block_span: body_span,
left_span: Some(left_span),
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -5896,7 +6084,6 @@ fn lower_statement<'a>(
);
// Lower the init: NextPropertyOf + assignment
let left_span = for_in.left.span();
let next_property = lower_value_to_temporary(
builder,
InstructionValue::NextPropertyOf { value, span: Some(left_span) },
@@ -5924,6 +6111,7 @@ fn lower_statement<'a>(
}
oxc::Statement::ForOfStatement(for_of) => {
let span = Some(for_of.span);
let left_span = for_of.left.span();
let continuation_block = builder.reserve(BlockKind::Block);
let continuation_id = continuation_block.id;
let init_block = builder.reserve(BlockKind::Loop);
@@ -5940,7 +6128,7 @@ fn lower_statement<'a>(
return Ok(());
}
let body_span = Some(for_of.body.span());
let body_span = source_block_span(&for_of.body);
let loop_block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.loop_scope(label, init_block_id, continuation_id, |builder| {
lower_statement(builder, &for_of.body, None, parent_scope)?;
@@ -5959,6 +6147,8 @@ fn lower_statement<'a>(
init: init_block_id,
test: test_block_id,
loop_block,
loop_block_span: body_span,
left_span: Some(left_span),
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -5982,7 +6172,6 @@ fn lower_statement<'a>(
);
// Test block: IteratorNext, assign, branch
let left_span = for_of.left.span();
let advance_iterator = lower_value_to_temporary(
builder,
InstructionValue::IteratorNext {
@@ -6063,7 +6252,7 @@ fn lower_statement<'a>(
None
};
cases.push(Case { test, block });
cases.push(Case { test, block, span: case_span });
fallthrough = block;
}
@@ -6072,7 +6261,7 @@ fn lower_statement<'a>(
// If no default case, add one that jumps to continuation
if !has_default {
cases.push(Case { test: None, block: continuation_id });
cases.push(Case { test: None, block: continuation_id, span: None });
}
let test = lower_expression_to_temporary(builder, &switch_stmt.discriminant)?;
@@ -6220,8 +6409,10 @@ fn lower_statement<'a>(
builder.terminate_with_continuation(
Terminal::Try {
block: try_block,
block_span: try_body_span,
handler_binding: handler_binding_info.map(|(place, _)| place),
handler: handler_block,
handler_span: Some(handler_clause.body.span),
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -6277,7 +6468,7 @@ fn lower_statement<'a>(
// All other statements create a continuation block to allow `break`
let continuation_block = builder.reserve(BlockKind::Block);
let continuation_id = continuation_block.id;
let body_span = Some(labeled_stmt.body.span());
let body_span = source_block_span(&labeled_stmt.body);
let block = builder.try_enter(BlockKind::Block, |builder, _block_id| {
builder.label_scope(label_name, continuation_id, |builder| {
lower_statement(builder, &labeled_stmt.body, None, parent_scope)?;
@@ -6294,6 +6485,7 @@ fn lower_statement<'a>(
builder.terminate_with_continuation(
Terminal::Label {
block,
block_span: body_span,
fallthrough: continuation_id,
id: EvaluationOrder::UNSET,
span,
@@ -288,17 +288,29 @@ fn evaluate_instruction<'a>(
match prim {
PrimitiveValue::String(s) if is_valid_identifier(s.as_str()) => {
let object = *object;
let property_span = property.span;
let span = *span;
let new_property = PropertyLiteral::String(Ident::from(s.as_str()));
func.instructions[instr_id.index()].value =
InstructionValue::PropertyLoad { object, property: new_property, span };
InstructionValue::PropertyLoad {
object,
property: new_property,
property_span,
span,
};
}
PrimitiveValue::Number(n) => {
let object = *object;
let property_span = property.span;
let span = *span;
let new_property = PropertyLiteral::Number(*n);
func.instructions[instr_id.index()].value =
InstructionValue::PropertyLoad { object, property: new_property, span };
InstructionValue::PropertyLoad {
object,
property: new_property,
property_span,
span,
};
}
PrimitiveValue::Null
| PrimitiveValue::Undefined
@@ -314,6 +326,7 @@ fn evaluate_instruction<'a>(
match prim {
PrimitiveValue::String(s) if is_valid_identifier(s.as_str()) => {
let object = *object;
let property_span = property.span;
let store_value = *value;
let span = *span;
let new_property = PropertyLiteral::String(Ident::from(s.as_str()));
@@ -321,12 +334,14 @@ fn evaluate_instruction<'a>(
InstructionValue::PropertyStore {
object,
property: new_property,
property_span,
value: store_value,
span,
};
}
PrimitiveValue::Number(n) => {
let object = *object;
let property_span = property.span;
let store_value = *value;
let span = *span;
let new_property = PropertyLiteral::Number(*n);
@@ -334,6 +349,7 @@ fn evaluate_instruction<'a>(
InstructionValue::PropertyStore {
object,
property: new_property,
property_span,
value: store_value,
span,
};
@@ -449,7 +465,7 @@ fn evaluate_instruction<'a>(
}
None
}
InstructionValue::PropertyLoad { object, property, span } => {
InstructionValue::PropertyLoad { object, property, span, .. } => {
let object_value = read(constants, object);
if let Some(Constant::Primitive { value: PrimitiveValue::String(ref s), .. }) =
object_value
@@ -384,12 +384,16 @@ fn collect_maybe_memo_dependencies<'a>(
path: ArenaVec::new_in(&env.allocator),
span: *span,
}),
InstructionValue::PropertyLoad { object, property, span, .. } => {
InstructionValue::PropertyLoad { object, property, property_span, span } => {
maybe_deps.get(&object.identifier).map(|object_dep| ManualMemoDependency {
root: object_dep.root,
path: {
let mut path = object_dep.path.clone_in(env.allocator);
path.push(DependencyPathEntry { property: *property, optional, span: *span });
path.push(DependencyPathEntry {
property: *property,
optional,
span: property_span.or(*span),
});
path
},
span: *span,
@@ -224,6 +224,7 @@ pub fn inline_immediately_invoked_function_expressions<'a>(
// Set block terminal to Label
func.body.blocks.get_mut(&block_id).unwrap().terminal = Terminal::Label {
block: inner_entry,
block_span: None,
id: EvaluationOrder::UNSET,
fallthrough: continuation_block_id,
span: block_terminal_span,
@@ -189,7 +189,7 @@ fn name_anonymous_functions_impl<'a>(
for attr in props {
match attr {
JsxAttribute::SpreadAttribute { .. } => continue,
JsxAttribute::Attribute { name: attr_name, place } => {
JsxAttribute::Attribute { name: attr_name, place, .. } => {
if let Some(&node_idx) = functions.get(&place.identifier) {
let node = &mut nodes[node_idx];
if node.generated_name.is_none() {
@@ -13,6 +13,7 @@ use crate::react_compiler_utils::ordered_map::ArenaOrderedSet;
use oxc_allocator::CloneIn;
use oxc_allocator::Vec as ArenaVec;
use oxc_index::IndexVec;
use oxc_span::Span;
use oxc_str::{Ident, IdentHashSet, format_ident};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -51,6 +52,7 @@ struct JsxInstrInfo {
struct OutlinedJsxAttribute<'a> {
original_name: Ident<'a>,
new_name: Ident<'a>,
name_span: Option<Span>,
place: Place,
}
@@ -274,11 +276,12 @@ fn collect_props<'a>(
for attr in props {
match attr {
JsxAttribute::SpreadAttribute { .. } => return None,
JsxAttribute::Attribute { name, place } => {
JsxAttribute::Attribute { name, name_span, place } => {
let new_name = generate_name(*name);
attributes.push(OutlinedJsxAttribute {
original_name: *name,
new_name,
name_span: *name_span,
place: *place,
});
}
@@ -308,6 +311,7 @@ fn collect_props<'a>(
attributes.push(OutlinedJsxAttribute {
original_name: child_name,
new_name,
name_span: None,
place: *child,
});
}
@@ -326,7 +330,11 @@ fn emit_outlined_jsx<'a>(
outlined_tag: Ident<'a>,
) -> Option<Vec<Instruction<'a>>> {
let props = ArenaVec::from_iter_in(
outlined_props.iter().map(|p| JsxAttribute::Attribute { name: p.new_name, place: p.place }),
outlined_props.iter().map(|p| JsxAttribute::Attribute {
name: p.new_name,
name_span: None,
place: p.place,
}),
&env.allocator,
);
@@ -363,7 +371,9 @@ fn emit_outlined_jsx<'a>(
children: None,
span: None,
opening_span: None,
opening_name_span: None,
closing_span: None,
closing_name_span: None,
},
span: None,
effects: None,
@@ -440,7 +450,9 @@ fn emit_outlined_fn<'a>(
blocks.insert(BlockId::ENTRY, block);
let outlined_fn = HirFunction {
body_span: None,
id: None,
id_span: None,
name_hint: None,
fn_type: ReactFunctionType::Other,
params: ArenaVec::from_array_in([ParamPattern::Place(props_obj)], &env.allocator),
@@ -493,7 +505,9 @@ fn emit_updated_jsx<'a>(
children,
span,
opening_span,
opening_name_span,
closing_span,
closing_name_span,
} = &instr.value
{
let mut new_props = ArenaVec::new_in(&alloc);
@@ -501,7 +515,7 @@ fn emit_updated_jsx<'a>(
// TS: invariant(prop.kind === 'JsxAttribute', ...)
// Spread attributes would have caused collectProps to return null earlier
let (name, place) = match prop {
JsxAttribute::Attribute { name, place } => (name, place),
JsxAttribute::Attribute { name, place, .. } => (name, place),
JsxAttribute::SpreadAttribute { .. } => {
unreachable!("Expected only JsxAttribute, not spread")
}
@@ -515,6 +529,7 @@ fn emit_updated_jsx<'a>(
.expect("Expected a new property for identifier");
new_props.push(JsxAttribute::Attribute {
name: new_prop.original_name,
name_span: new_prop.name_span,
place: new_prop.place,
});
}
@@ -545,7 +560,9 @@ fn emit_updated_jsx<'a>(
children: new_children,
span: *span,
opening_span: *opening_span,
opening_name_span: *opening_name_span,
closing_span: *closing_span,
closing_name_span: *closing_name_span,
},
span: instr.span,
effects: instr.effects.as_ref().map(|v| v.clone_in(alloc)),
@@ -578,6 +595,7 @@ fn create_old_to_new_props_mapping<'a>(
OutlinedJsxAttribute {
original_name: old_prop.original_name,
new_name: old_prop.new_name,
name_span: old_prop.name_span,
place: new_place,
},
);
@@ -594,7 +612,7 @@ fn emit_destructure_props<'a>(
let mut properties = ArenaVec::new_in(&env.allocator);
for prop in old_to_new_props.values() {
properties.push(ObjectPropertyOrSpread::Property(ObjectProperty {
key: ObjectPropertyKey::String { name: prop.new_name },
key: ObjectPropertyKey::String { name: prop.new_name, span: None },
property_type: ObjectPropertyType::Property,
place: prop.place,
}));
@@ -39,7 +39,9 @@ pub fn build_reactive_function<'a>(
Ok(ReactiveFunction {
span: hir.span,
body_span: hir.body_span,
id: hir.id,
id_span: hir.id_span,
name_hint: hir.name_hint,
params: hir.params.iter().copied().collect(),
generator: hir.generator,
@@ -312,6 +314,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
let block_id_val = block.id;
let instructions: Vec<_> = block.instructions.iter().copied().collect();
let terminal = block.terminal.clone_in(self.env.allocator);
let terminal_source_span = terminal.span().copied().unwrap_or_default();
if !self.cx.emitted.insert(block_id_val) {
return Err(ErrorCategory::Invariant
@@ -334,7 +337,16 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
let mut next_block: Option<BlockId> = None;
match &terminal {
Terminal::If { test, consequent, alternate, fallthrough, id, .. } => {
Terminal::If {
test,
consequent,
consequent_span,
alternate,
alternate_span,
fallthrough,
id,
..
} => {
// TS: reachable(fallthrough) && !isScheduled(fallthrough)
let fallthrough_id =
if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
@@ -378,11 +390,14 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
terminal: ReactiveTerminal::If {
test: *test,
consequent: consequent_block,
consequent_span: *consequent_span,
alternate: alternate_block,
alternate_span: *alternate_span,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
@@ -420,8 +435,11 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
let case_schedule_id = self.cx.schedule(case_block_id, "case")?;
schedule_ids.push(case_schedule_id);
reactive_cases
.push(ReactiveSwitchCase { test: case.test, block: Some(consequent) });
reactive_cases.push(ReactiveSwitchCase {
test: case.test,
block: Some(consequent),
span: case.span,
});
}
reactive_cases.reverse();
@@ -435,13 +453,14 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::DoWhile { loop_block, test, fallthrough, id, span } => {
Terminal::DoWhile { loop_block, loop_block_span, test, fallthrough, id, span } => {
let fallthrough_id =
if !self.cx.is_scheduled(*fallthrough) { Some(*fallthrough) } else { None };
let loop_id =
@@ -471,18 +490,20 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
ReactiveTerminalStatement {
terminal: ReactiveTerminal::DoWhile {
loop_block: loop_body,
loop_block_span: *loop_block_span,
test: test_result.value,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::While { test, loop_block, fallthrough, id, span } => {
Terminal::While { test, loop_block, loop_block_span, fallthrough, id, span } => {
// TS: reachable(fallthrough) && !isScheduled(fallthrough)
let fallthrough_id =
if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
@@ -518,17 +539,28 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
terminal: ReactiveTerminal::While {
test: test_result.value,
loop_block: loop_body,
loop_block_span: *loop_block_span,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::For { init, test, update, loop_block, fallthrough, id, span } => {
Terminal::For {
init,
test,
update,
loop_block,
loop_block_span,
fallthrough,
id,
span,
} => {
let loop_id =
if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
Some(*loop_block)
@@ -572,17 +604,28 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
test: test_result.value,
update: update_result.map(|r| r.value),
loop_block: loop_body,
loop_block_span: *loop_block_span,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::ForOf { init, test, loop_block, fallthrough, id, span } => {
Terminal::ForOf {
init,
test,
loop_block,
loop_block_span,
left_span,
fallthrough,
id,
span,
} => {
let loop_id =
if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
Some(*loop_block)
@@ -621,18 +664,29 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
init: init_value,
test: test_value,
loop_block: loop_body,
loop_block_span: *loop_block_span,
left_span: *left_span,
id: *id,
span: *span,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::ForIn { init, loop_block, fallthrough, id, span } => {
Terminal::ForIn {
init,
loop_block,
loop_block_span,
left_span,
fallthrough,
id,
span,
} => {
let loop_id =
if !self.cx.is_scheduled(*loop_block) && *loop_block != *fallthrough {
Some(*loop_block)
@@ -666,18 +720,21 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
terminal: ReactiveTerminal::ForIn {
init: init_value,
loop_block: loop_body,
loop_block_span: *loop_block_span,
left_span: *left_span,
id: *id,
span: *span,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
next_block = fallthrough_id;
}
Terminal::Label { block: label_block, fallthrough, id, .. } => {
Terminal::Label { block: label_block, block_span, fallthrough, id, .. } => {
// TS: reachable(fallthrough) && !isScheduled(fallthrough)
let fallthrough_id =
if self.cx.reachable(*fallthrough) && !self.cx.is_scheduled(*fallthrough) {
@@ -699,9 +756,14 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
self.cx.unschedule_all(&schedule_ids)?;
block_value.push(ReactiveStatement::Terminal(self.box_in(
ReactiveTerminalStatement {
terminal: ReactiveTerminal::Label { block: label_body, id: *id },
terminal: ReactiveTerminal::Label {
block: label_body,
block_span: *block_span,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
@@ -740,12 +802,15 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
Terminal::Goto { block: goto_block, variant, id, .. } => {
match variant {
GotoVariant::Break => {
if let Some(stmt) = self.visit_break(*goto_block, *id)? {
if let Some(stmt) =
self.visit_break(*goto_block, *id, terminal_source_span)?
{
block_value.push(stmt);
}
}
GotoVariant::Continue => {
let stmt = self.visit_continue(*goto_block, *id)?;
let stmt =
self.visit_continue(*goto_block, *id, terminal_source_span)?;
block_value.push(stmt);
}
GotoVariant::Try => {
@@ -762,8 +827,10 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
Terminal::Try {
block: try_block,
block_span,
handler_binding,
handler,
handler_span,
fallthrough,
id,
..
@@ -787,12 +854,15 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
ReactiveTerminalStatement {
terminal: ReactiveTerminal::Try {
block: try_body,
block_span: *block_span,
handler_binding: *handler_binding,
handler: handler_body,
handler_span: *handler_span,
id: *id,
},
label:
fallthrough_id.map(|ft| ReactiveLabel { id: ft, implicit: false }),
span: terminal_source_span,
},
)));
@@ -852,6 +922,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
ReactiveTerminalStatement {
terminal: ReactiveTerminal::Return { value: *value, id: *id },
label: None,
span: terminal_source_span,
},
)));
}
@@ -861,6 +932,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
ReactiveTerminalStatement {
terminal: ReactiveTerminal::Throw { value: *value, id: *id },
label: None,
span: terminal_source_span,
},
)));
}
@@ -872,7 +944,9 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
Terminal::Branch { test, consequent, alternate, id, .. } => {
let alloc = self.env.allocator;
let consequent_block = if self.cx.is_scheduled(*consequent) {
if let Some(stmt) = self.visit_break(*consequent, *id)? {
if let Some(stmt) =
self.visit_break(*consequent, *id, terminal_source_span)?
{
ArenaVec::from_iter_in([stmt], &alloc)
} else {
ArenaVec::new_in(&alloc)
@@ -894,9 +968,12 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
test: *test,
consequent: consequent_block,
alternate: Some(alternate_block),
consequent_span: None,
alternate_span: None,
id: *id,
},
label: None,
span: terminal_source_span,
},
)));
}
@@ -1305,6 +1382,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
&self,
block: BlockId,
id: EvaluationOrder,
span: Span,
) -> Result<Option<ReactiveStatement<'a>>, OxcDiagnostic> {
let (target_block, target_kind) = self.cx.get_break_target(block)?;
if self.cx.scope_fallthroughs.contains(&target_block) {
@@ -1317,6 +1395,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
Ok(Some(ReactiveStatement::Terminal(self.box_in(ReactiveTerminalStatement {
terminal: ReactiveTerminal::Break { target: target_block, id, target_kind },
label: None,
span,
}))))
}
@@ -1324,6 +1403,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
&self,
block: BlockId,
id: EvaluationOrder,
span: Span,
) -> Result<ReactiveStatement<'a>, OxcDiagnostic> {
let (target_block, target_kind) = match self.cx.get_continue_target(block) {
Some(result) => result,
@@ -1338,6 +1418,7 @@ impl<'a, 'b, 'h> Driver<'a, 'b, 'h> {
Ok(ReactiveStatement::Terminal(self.box_in(ReactiveTerminalStatement {
terminal: ReactiveTerminal::Continue { target: target_block, id, target_kind },
label: None,
span,
})))
}
}
@@ -124,7 +124,7 @@ impl<'a, 'e> ReactiveFunctionTransform<'a> for Transform<'a, 'e> {
existing.clone()
} else {
// Create a new early return identifier
let identifier_id = create_temporary_place_id(self.env, span);
let identifier_id = create_generated_temporary_place_id(self.env);
promote_temporary(self.env, identifier_id);
let label = self.env.next_block_id();
EarlyReturnInfo { value: identifier_id, span, label }
@@ -164,6 +164,7 @@ impl<'a, 'e> ReactiveFunctionTransform<'a> for Transform<'a, 'e> {
target_kind: ReactiveTerminalTargetKind::Labeled,
},
label: None,
span: span.unwrap_or_default(),
},
&alloc,
)),
@@ -186,7 +187,6 @@ fn apply_early_return_to_scope<'a>(
early_return: &EarlyReturnInfo,
) {
let scope_id = scope_block.scope;
let span = early_return.span;
// Set early return value on the scope
env.scopes[scope_id].early_return_value = Some(ReactiveScopeEarlyReturn {
@@ -202,10 +202,10 @@ fn apply_early_return_to_scope<'a>(
));
// Create temporary places for the sentinel initialization
let sentinel_temp = create_temporary_place_id(env, span);
let symbol_temp = create_temporary_place_id(env, span);
let for_temp = create_temporary_place_id(env, span);
let arg_temp = create_temporary_place_id(env, span);
let sentinel_temp = create_generated_temporary_place_id(env);
let symbol_temp = create_generated_temporary_place_id(env);
let for_temp = create_generated_temporary_place_id(env);
let arg_temp = create_generated_temporary_place_id(env);
let alloc = env.allocator;
let original_instructions = replace(&mut scope_block.instructions, ArenaVec::new_in(&alloc));
@@ -223,9 +223,9 @@ fn apply_early_return_to_scope<'a>(
}),
value: ReactiveValue::Instruction(InstructionValue::LoadGlobal {
binding: NonLocalBinding::Global { name: Ident::from("Symbol") },
span,
span: None,
}),
span,
span: None,
}),
// PropertyLoad Symbol.for
ReactiveStatement::Instruction(ReactiveInstruction {
@@ -244,9 +244,10 @@ fn apply_early_return_to_scope<'a>(
span: None, // GeneratedSource
},
property: PropertyLiteral::String(Ident::from("for")),
span,
property_span: None,
span: None,
}),
span,
span: None,
}),
// Primitive: the sentinel string
ReactiveStatement::Instruction(ReactiveInstruction {
@@ -259,9 +260,9 @@ fn apply_early_return_to_scope<'a>(
}),
value: ReactiveValue::Instruction(InstructionValue::Primitive {
value: PrimitiveValue::String(EARLY_RETURN_SENTINEL.into()),
span,
span: None,
}),
span,
span: None,
}),
// MethodCall: Symbol.for("react.early_return_sentinel")
ReactiveStatement::Instruction(ReactiveInstruction {
@@ -294,9 +295,9 @@ fn apply_early_return_to_scope<'a>(
})],
&alloc,
),
span,
span: None,
}),
span,
span: None,
}),
// StoreLocal: let earlyReturnValue = sentinel
ReactiveStatement::Instruction(ReactiveInstruction {
@@ -309,7 +310,7 @@ fn apply_early_return_to_scope<'a>(
identifier: early_return.value,
effect: Effect::ConditionallyMutate,
reactive: true,
span,
span: None,
},
},
value: Place {
@@ -318,9 +319,9 @@ fn apply_early_return_to_scope<'a>(
reactive: false,
span: None, // GeneratedSource
},
span,
span: None,
}),
span,
span: None,
}),
// Label terminal wrapping the original instructions
ReactiveStatement::Terminal(ArenaBox::new_in(
@@ -328,8 +329,10 @@ fn apply_early_return_to_scope<'a>(
label: Some(ReactiveLabel { id: early_return.label, implicit: false }),
terminal: ReactiveTerminal::Label {
block: original_instructions,
block_span: None,
id: EvaluationOrder::UNSET,
},
span: Span::default(),
},
&alloc,
)),
@@ -342,10 +345,8 @@ fn apply_early_return_to_scope<'a>(
// Helper: create a temporary place identifier
// =============================================================================
fn create_temporary_place_id(env: &mut Environment, span: Option<Span>) -> IdentifierId {
let id = env.next_identifier_id();
env.identifiers[id].span = span;
id
fn create_generated_temporary_place_id(env: &mut Environment) -> IdentifierId {
env.next_identifier_id()
}
fn promote_temporary<'a>(env: &mut Environment<'a>, identifier_id: IdentifierId) {
@@ -308,6 +308,7 @@ fn enter_ssa_impl<'a>(
ParamPattern::Place(p) => ParamPattern::Place(builder.define_place(&p, env)?),
ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern {
place: builder.define_place(&s.place, env)?,
span: s.span,
}),
});
}
@@ -399,6 +400,7 @@ fn enter_ssa_impl<'a>(
}
ParamPattern::Spread(s) => ParamPattern::Spread(SpreadPattern {
place: builder.define_place(&s.place, env)?,
span: s.span,
}),
});
}
@@ -456,7 +458,9 @@ fn enter_ssa_impl<'a>(
pub fn placeholder_function<'a>(alloc: &'a Allocator) -> HirFunction<'a> {
HirFunction {
span: None,
body_span: None,
id: None,
id_span: None,
name_hint: None,
fn_type: ReactFunctionType::Other,
params: ArenaVec::new_in(&alloc),
@@ -575,7 +575,7 @@ fn generate_instruction_types<'a>(
InstructionValue::ObjectExpression { properties, .. } => {
for prop in properties {
if let ObjectPropertyOrSpread::Property(obj_prop) = prop
&& let ObjectPropertyKey::Computed { name } = &obj_prop.key
&& let ObjectPropertyKey::Computed { name, .. } = &obj_prop.key
{
let name_type = get_type(name.identifier, identifiers);
unifier.unify(name_type, Type::Primitive, shapes)?;
@@ -671,8 +671,8 @@ fn generate_instruction_types<'a>(
for prop in &object_pattern.properties {
if let ObjectPropertyOrSpread::Property(obj_prop) = prop {
match &obj_prop.key {
ObjectPropertyKey::Identifier { name }
| ObjectPropertyKey::String { name } => {
ObjectPropertyKey::Identifier { name, .. }
| ObjectPropertyKey::String { name, .. } => {
let prop_place_type =
get_type(obj_prop.place.identifier, identifiers);
let value_type = get_type(value.identifier, identifiers);
@@ -757,7 +757,7 @@ fn generate_instruction_types<'a>(
InstructionValue::JsxExpression { props, .. } => {
if unifier.enable_treat_ref_like_identifiers_as_refs {
for prop in props {
if let JsxAttribute::Attribute { name, place } = prop
if let JsxAttribute::Attribute { name, place, .. } = prop
&& name == "ref"
{
let ref_type = get_type(place.identifier, identifiers);
@@ -1062,7 +1062,7 @@ fn apply_instruction_operands<'a>(
match prop {
ObjectPropertyOrSpread::Property(obj_prop) => {
resolve_identifier(obj_prop.place.identifier, identifiers, types, unifier);
if let ObjectPropertyKey::Computed { name } = &obj_prop.key {
if let ObjectPropertyKey::Computed { name, .. } = &obj_prop.key {
resolve_identifier(name.identifier, identifiers, types, unifier);
}
}
@@ -1094,7 +1094,7 @@ fn apply_instruction_operands<'a>(
JsxAttribute::Attribute { place, .. } => {
resolve_identifier(place.identifier, identifiers, types, unifier);
}
JsxAttribute::SpreadAttribute { argument } => {
JsxAttribute::SpreadAttribute { argument, .. } => {
resolve_identifier(argument.identifier, identifiers, types, unifier);
}
}
@@ -664,7 +664,7 @@ fn collect_dependencies<'a>(
}
}
}
InstructionValue::PropertyLoad { object, property, .. } => {
InstructionValue::PropertyLoad { object, property, property_span, .. } => {
// Number properties or ref.current: visit the object directly
let is_numeric = matches!(property, PropertyLiteral::Number(_));
let is_ref_current =
@@ -689,7 +689,7 @@ fn collect_dependencies<'a>(
new_path.push(DependencyPathEntry {
optional,
property: *property,
span: instr.value.span().copied(),
span: property_span.or_else(|| instr.value.span().copied()),
});
temporaries.insert(
lvalue_id,
@@ -4,6 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/gating/gating-preserves-function-
---
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
// @gating
const Component = isForgetEnabled_Fixtures() ? function Component() {
const $ = _c(1);
let t0;
@@ -4,6 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/gating/gating-test-export-default
---
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
// @gating @compilationMode:"annotation"
const Bar = isForgetEnabled_Fixtures() ? function Bar(props) {
"use forget";
const $ = _c(2);
@@ -4,6 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/gating/gating-test-export-functio
---
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
// @gating @compilationMode:"annotation"
const Bar = isForgetEnabled_Fixtures() ? function Bar(props) {
"use forget";
const $ = _c(2);
@@ -4,6 +4,7 @@ input_file: crates/oxc_react_compiler/fixtures/gating/gating-test-export-functio
---
import { c as _c } from "react/compiler-runtime";
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
// @gating @compilationMode:"annotation"
export const Bar = isForgetEnabled_Fixtures() ? function Bar(props) {
"use forget";
const $ = _c(2);