fix: capture object keys and scope loop bindings

This commit is contained in:
爱特每羊队
2026-08-11 14:17:45 +08:00
parent 925ca358e9
commit ec2efa7b6b
8 changed files with 77 additions and 10 deletions
@@ -405,6 +405,14 @@ fn compile_closure_capture_arrow_emits_make_cell() {
); );
} }
#[test]
fn compile_adjacent_for_of_lexical_bindings_in_distinct_scopes() {
let module = compile_source(
"for (const [arg, descr] of [[1, 'a']]) { descr; } for (const [arg, descr] of [[2, 'b']]) { descr; }",
);
assert!(!module.bytecode.is_empty());
}
#[test] #[test]
fn compile_compound_assignment_complement_ops() { fn compile_compound_assignment_complement_ops() {
let module = compile_source("let x = 10; x -= 1; x *= 2; x /= 3; x %= 4;"); let module = compile_source("let x = 10; x -= 1; x *= 2; x /= 3; x %= 4;");
+20 -4
View File
@@ -589,8 +589,16 @@ impl Emitter {
} }
Expression::ObjectExpression(o) => { Expression::ObjectExpression(o) => {
for prop in &o.properties { for prop in &o.properties {
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { match prop {
self.collect_capture_names_expr(&p.value, ref_set, shadow, out); oxide_parser::ObjectPropertyKind::ObjectProperty(p) => {
if p.computed {
self.collect_capture_names_expr(p.key.to_expression(), ref_set, shadow, out);
}
self.collect_capture_names_expr(&p.value, ref_set, shadow, out);
}
oxide_parser::ObjectPropertyKind::SpreadProperty(spread) => {
self.collect_capture_names_expr(&spread.argument, ref_set, shadow, out);
}
} }
} }
} }
@@ -884,8 +892,16 @@ impl Emitter {
} }
Expression::ObjectExpression(o) => { Expression::ObjectExpression(o) => {
for prop in &o.properties { for prop in &o.properties {
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { match prop {
self.collect_captured_expr(&p.value, own, out); oxide_parser::ObjectPropertyKind::ObjectProperty(p) => {
if p.computed {
self.collect_captured_expr(p.key.to_expression(), own, out);
}
self.collect_captured_expr(&p.value, own, out);
}
oxide_parser::ObjectPropertyKind::SpreadProperty(spread) => {
self.collect_captured_expr(&spread.argument, own, out);
}
} }
} }
} }
+9 -4
View File
@@ -187,11 +187,16 @@ impl Emitter {
} }
Expression::ObjectExpression(obj) => { Expression::ObjectExpression(obj) => {
for prop in &obj.properties { for prop in &obj.properties {
if let oxide_parser::ObjectPropertyKind::ObjectProperty(p) = prop { match prop {
if p.computed { oxide_parser::ObjectPropertyKind::ObjectProperty(p) => {
self.pre_scan_builtin_expr(p.key.to_expression(), ctx); if p.computed {
self.pre_scan_builtin_expr(p.key.to_expression(), ctx);
}
self.pre_scan_builtin_expr(&p.value, ctx);
}
oxide_parser::ObjectPropertyKind::SpreadProperty(spread) => {
self.pre_scan_builtin_expr(&spread.argument, ctx);
} }
self.pre_scan_builtin_expr(&p.value, ctx);
} }
} }
} }
@@ -36,6 +36,7 @@ impl Emitter {
let Statement::ForStatement(fr) = stmt else { let Statement::ForStatement(fr) = stmt else {
return Ok(None); return Ok(None);
}; };
ctx.push_scope();
let start_label = ctx.next_label_id(); let start_label = ctx.next_label_id();
let update_label = ctx.next_label_id(); let update_label = ctx.next_label_id();
let end_label = ctx.next_label_id(); let end_label = ctx.next_label_id();
@@ -135,6 +136,7 @@ impl Emitter {
ctx.labels.set_label_pos(end_label, ctx.insts.len()); ctx.labels.set_label_pos(end_label, ctx.insts.len());
ctx.pop_label_scopes(n_labeled); ctx.pop_label_scopes(n_labeled);
ctx.pop_loop(); ctx.pop_loop();
ctx.pop_scope();
Ok(None) Ok(None)
} }
} }
@@ -11,6 +11,7 @@ impl Emitter {
let Statement::ForInStatement(fi) = stmt else { let Statement::ForInStatement(fi) = stmt else {
return Ok(None); return Ok(None);
}; };
ctx.push_scope();
let start_label = ctx.next_label_id(); let start_label = ctx.next_label_id();
let end_label = ctx.next_label_id(); let end_label = ctx.next_label_id();
let obj_reg = self.emit_expression(&fi.right, ctx)?; let obj_reg = self.emit_expression(&fi.right, ctx)?;
@@ -40,7 +41,12 @@ impl Emitter {
ctx.declare(name, var_reg, decl.kind, is_const)?; ctx.declare(name, var_reg, decl.kind, is_const)?;
if let Some(&cell_idx) = ctx.captured_bindings.get(name) { if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL };
ctx.inst(Inst::new(op, Operand::Reg(key_reg), Operand::Imm(cell_idx as u16), Operand::None)); ctx.inst(Inst::new(
op,
Operand::Reg(key_reg),
Operand::Imm(cell_idx as u16),
Operand::None,
));
} else { } else {
ctx.inst(Inst::new( ctx.inst(Inst::new(
OpCode::STORE_VAR, OpCode::STORE_VAR,
@@ -68,6 +74,7 @@ impl Emitter {
ctx.inst(Inst::new(OpCode::FOR_IN_CLEANUP, Operand::None, Operand::None, Operand::None)); ctx.inst(Inst::new(OpCode::FOR_IN_CLEANUP, Operand::None, Operand::None, Operand::None));
ctx.pop_label_scopes(n_labeled); ctx.pop_label_scopes(n_labeled);
ctx.pop_loop(); ctx.pop_loop();
ctx.pop_scope();
Ok(None) Ok(None)
} }
} }
+10 -1
View File
@@ -23,6 +23,7 @@ impl Emitter {
} }
fn emit_sync_for_of_statement(&self, fo: &ForOfStatement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> { fn emit_sync_for_of_statement(&self, fo: &ForOfStatement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
ctx.push_scope();
let start_label = ctx.next_label_id(); let start_label = ctx.next_label_id();
let end_label = ctx.next_label_id(); let end_label = ctx.next_label_id();
let iter_src_reg = self.emit_expression(&fo.right, ctx)?; let iter_src_reg = self.emit_expression(&fo.right, ctx)?;
@@ -42,6 +43,7 @@ impl Emitter {
ctx.inst(Inst::new(OpCode::FOR_OF_CLOSE, Operand::None, Operand::None, Operand::None)); ctx.inst(Inst::new(OpCode::FOR_OF_CLOSE, Operand::None, Operand::None, Operand::None));
ctx.pop_label_scopes(n_labeled); ctx.pop_label_scopes(n_labeled);
ctx.pop_loop(); ctx.pop_loop();
ctx.pop_scope();
Ok(None) Ok(None)
} }
@@ -51,10 +53,16 @@ impl Emitter {
/// 挂起(恢复值经 reg 0 交付)→ LOAD_VAR 拷贝为结果对象 → DONE 检查 done → /// 挂起(恢复值经 reg 0 交付)→ LOAD_VAR 拷贝为结果对象 → DONE 检查 done →
/// FOR_OF_NEXT 读 value → 绑定左侧 → 循环体 → CLOSE 异步收尾。 /// FOR_OF_NEXT 读 value → 绑定左侧 → 循环体 → CLOSE 异步收尾。
fn emit_for_await_of_statement(&self, fo: &ForOfStatement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> { fn emit_for_await_of_statement(&self, fo: &ForOfStatement, ctx: &mut CompileCtx) -> Result<Option<u32>, String> {
ctx.push_scope();
let start_label = ctx.next_label_id(); let start_label = ctx.next_label_id();
let end_label = ctx.next_label_id(); let end_label = ctx.next_label_id();
let iter_src_reg = self.emit_expression(&fo.right, ctx)?; let iter_src_reg = self.emit_expression(&fo.right, ctx)?;
ctx.inst(Inst::new(OpCode::FOR_AWAIT_OF_INIT, Operand::None, Operand::Reg(iter_src_reg), Operand::None)); ctx.inst(Inst::new(
OpCode::FOR_AWAIT_OF_INIT,
Operand::None,
Operand::Reg(iter_src_reg),
Operand::None,
));
ctx.labels.set_label_pos(start_label, ctx.insts.len()); ctx.labels.set_label_pos(start_label, ctx.insts.len());
ctx.push_loop(end_label, start_label); ctx.push_loop(end_label, start_label);
let n_labeled = ctx.take_pending_loop_labels(end_label, start_label); let n_labeled = ctx.take_pending_loop_labels(end_label, start_label);
@@ -80,6 +88,7 @@ impl Emitter {
ctx.inst(Inst::new(OpCode::FOR_AWAIT_OF_CLOSE, Operand::None, Operand::None, Operand::None)); ctx.inst(Inst::new(OpCode::FOR_AWAIT_OF_CLOSE, Operand::None, Operand::None, Operand::None));
ctx.pop_label_scopes(n_labeled); ctx.pop_label_scopes(n_labeled);
ctx.pop_loop(); ctx.pop_loop();
ctx.pop_scope();
Ok(None) Ok(None)
} }
@@ -194,6 +194,14 @@ fn eval_for_in_let_scoping() {
assert_eq!(result, "1"); assert_eq!(result, "1");
} }
#[test]
fn eval_adjacent_for_of_lexical_bindings_do_not_conflict() {
assert_eq!(
eval("let out=0; for (const [arg, descr] of [[1, 'a']]) out += arg; for (const [arg, descr] of [[2, 'b']]) out += arg; out"),
"3"
);
}
#[test] #[test]
fn eval_switch_basic_match() { fn eval_switch_basic_match() {
assert_eq!(eval("var x=0;switch(2){case 1:x=10;case 2:x=20;break;default:x=30;}x"), "20"); assert_eq!(eval("var x=0;switch(2){case 1:x=10;case 2:x=20;break;default:x=30;}x"), "20");
+12
View File
@@ -92,6 +92,18 @@ fn closure_nested_expr_capture() {
assert_eq!(result.as_double(), 2.0); assert_eq!(result.as_double(), 2.0);
} }
#[test]
fn closure_captures_computed_object_key_across_caller_frame() {
let result = eval("let key='answer'; const call=fn=>fn(); const make=()=>({[key]:42}); call(make).answer").unwrap();
assert_eq!(result.as_int(), 42);
}
#[test]
fn closure_captures_object_spread_source_across_caller_frame() {
let result = eval("let source={x:7}; const call=fn=>fn(); const make=()=>({...source}); call(make).x").unwrap();
assert_eq!(result.as_int(), 7);
}
#[test] #[test]
fn for_let_per_iteration_independent() { fn for_let_per_iteration_independent() {
let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()"); let _ = eval("var fns=[]; for(let i=0;i<3;i++){ fns.push(function(){return i}); } fns[0]()+fns[1]()+fns[2]()");