diff --git a/crates/oxide_emit/src/prepass.rs b/crates/oxide_emit/src/prepass.rs index 897d62dc..c69e11de 100644 --- a/crates/oxide_emit/src/prepass.rs +++ b/crates/oxide_emit/src/prepass.rs @@ -442,6 +442,33 @@ impl Emitter { } self.predeclare_var_declarations(std::slice::from_ref(&fs.body), ctx); } + Statement::ForInStatement(fi) => { + // var 头名提升到函数/全局作用域(与 For 臂同规则),先于提升 + // 函数声明发射预声明,使函数体按既有 var 绑定解析头名; + // let/const 头是迭代级绑定,不入提升面。 + if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fi.left { + if matches!(decl.kind, VariableDeclarationKind::Var) { + for d in &decl.declarations { + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { + self.predeclare_var_name(bi.name.as_str(), ctx); + } + } + } + } + self.predeclare_var_declarations(std::slice::from_ref(&fi.body), ctx); + } + Statement::ForOfStatement(fo) => { + if let oxide_parser::ForStatementLeft::VariableDeclaration(decl) = &fo.left { + if matches!(decl.kind, VariableDeclarationKind::Var) { + for d in &decl.declarations { + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = &d.id { + self.predeclare_var_name(bi.name.as_str(), ctx); + } + } + } + } + self.predeclare_var_declarations(std::slice::from_ref(&fo.body), ctx); + } Statement::SwitchStatement(sw) => { for case in &sw.cases { self.predeclare_var_declarations(&case.consequent, ctx); diff --git a/crates/oxide_vm/tests/for_in_ordering_tests.rs b/crates/oxide_vm/tests/for_in_ordering_tests.rs index 6de7896b..6acc62f1 100644 --- a/crates/oxide_vm/tests/for_in_ordering_tests.rs +++ b/crates/oxide_vm/tests/for_in_ordering_tests.rs @@ -102,3 +102,37 @@ fn for_in_var_head_fresh_name() { "for-in var head binds iteration keys into the hoisted var slot" ); } + +// ── 提升函数引用 for-in/for-of 头 var 名:头名须编译期预声明(hoisting 面)── + +#[test] +fn for_in_var_head_hoisted_fn_read() { + // 顶层提升函数读 for-in 头 var:头名预声明入全局 var 名集,函数体按 + // 全局 var 绑定解析,完成值为迭代键。 + assert_eq!( + eval("function f(){ return x; } for (var x in {a:1}) {} f() === \"a\""), + "true", + "hoisted function reads for-in head var through the global var binding" + ); +} + +#[test] +fn for_in_var_head_hoisted_fn_strict_write() { + // strict 嵌套写:头名预声明为全局 var 绑定后,写落全局属性(A 侧); + // 头名缺预声明时该写按未声明名抛 ReferenceError(本钉判别面)。 + assert_eq!( + eval("'use strict'; function f(){ x = 5; } for (var x in {a:1}) { f(); } x === 5"), + "true", + "strict hoisted-function write to for-in head var hits the global binding" + ); +} + +#[test] +fn for_of_var_head_hoisted_fn_strict_write() { + // for-of 头同形:预声明臂覆盖 for-in 与 for-of 两臂。 + assert_eq!( + eval("'use strict'; function f(){ x = 5; } for (var x of [1,2]) { f(); } x === 5"), + "true", + "strict hoisted-function write to for-of head var hits the global binding" + ); +}