diff --git a/crates/oxide_emit/src/stmt/block.rs b/crates/oxide_emit/src/stmt/block.rs index 981a0260..6552ad6d 100644 --- a/crates/oxide_emit/src/stmt/block.rs +++ b/crates/oxide_emit/src/stmt/block.rs @@ -14,6 +14,11 @@ impl Emitter { self.predeclare_block_function_declarations(&block.body, ctx); // 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。 let _ = self.predeclare_lexical_declarations(&block.body, ctx, false); + // 块级函数声明的块入口初始化:闭包物化入预声明块槽,声明语句前读命中 + // 函数对象(sloppy 与 strict 同形);声明点退化为其守语义。 + for s in &block.body { + self.emit_block_fn_entry_init_stmt(s, ctx)?; + } let mut r = None; for s in &block.body { if let Some(rr) = self.emit_statement(s, ctx)? { @@ -24,6 +29,39 @@ impl Emitter { Ok(r) } + /// 块入口初始化的单语句走法:函数声明就地物化(同名重复声明共享块绑定, + /// 源序物化使末次声明天然成为入口值);if/labeled/while/do-while/with 体 + /// 不推作用域、随本块绑定递归;嵌套块/循环/try 各自压作用域、由其自身块 + /// 入口处理,不进入。 + /// + /// # 前提 + /// - 预声明已为各声明名建立块 Let 绑定;`materialize_function_declaration` + /// 经 `lookup` 解析槽位。 + fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> { + match stmt { + Statement::FunctionDeclaration(fd) => { + let Some(id) = &fd.id else { + return Ok(()); + }; + let name = id.name.to_string(); + self.materialize_function_declaration(fd, &name, ctx)?; + Ok(()) + } + Statement::IfStatement(is) => { + self.emit_block_fn_entry_init_stmt(&is.consequent, ctx)?; + if let Some(alt) = &is.alternate { + self.emit_block_fn_entry_init_stmt(alt, ctx)?; + } + Ok(()) + } + Statement::WhileStatement(wh) => self.emit_block_fn_entry_init_stmt(&wh.body, ctx), + Statement::DoWhileStatement(dw) => self.emit_block_fn_entry_init_stmt(&dw.body, ctx), + Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx), + Statement::WithStatement(ws) => self.emit_block_fn_entry_init_stmt(&ws.body, ctx), + _ => Ok(()), + } + } + pub(crate) fn emit_block_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result, String> { match stmt { Statement::BlockStatement(_) => self.emit_block_statement(stmt, ctx), diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index 8731481b..9c7696bf 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -1,5 +1,6 @@ //! 函数声明语句 emit:`emit_function_declaration_statement` 创建闭包并绑定函数名。 +use crate::symbol_table::ScopeKind; use crate::{CompileCtx, Emitter, ParamSpec}; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; @@ -25,49 +26,19 @@ impl Emitter { } else { return Err("FunctionDeclaration without name".into()); }; - let mut param_names = Vec::new(); - for (idx, param) in fd.params.items.iter().enumerate() { - match ¶m.pattern { - oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier { - name: bi.name.to_string(), - initializer: param.initializer.as_deref(), - }), - pattern => param_names.push(ParamSpec::Pattern { - synthetic_name: format!("@@param_{idx}"), - pattern, - initializer: param.initializer.as_deref(), - }), - } - } - if let Some(rest) = &fd.params.rest { - self.push_rest_param(&rest.rest.argument, &mut param_names)?; - } - let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] }; - let own_strict = fd.has_use_strict_directive(); - let mut sub_module = if fd.generator && fd.r#async { - self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)? - } else if fd.generator { - self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)? - } else if fd.r#async { - self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + // 块级函数声明:闭包已在块入口物化写入预声明块槽(块绑定块入口即持有 + // 函数对象,声明语句前读命中它),声明点不重编闭包,只处理下方外层 + // var 写回。 + let binding_scope_kind = ctx + .scopes + .symbols + .lookup_any_binding(&name) + .map(|(_, scope_idx)| ctx.scopes.symbols.scopes[scope_idx].kind); + let var_reg = if binding_scope_kind == Some(ScopeKind::BlockScope) { + ctx.lookup(&name)? } else { - self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + self.materialize_function_declaration(fd, &name, ctx)? }; - sub_module.function_name = Some(name.clone()); - ctx.nested.push(sub_module); - let var_reg = ctx.lookup(&name)?; - ctx.reserve_reg(var_reg); - ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16)); - if let Some(&cell_idx) = ctx.captured_bindings.get(&name) { - ctx.inst(Inst::new( - OpCode::MAKE_CELL, - Operand::Reg(var_reg), - Operand::Imm(cell_idx as u16), - Operand::None, - )); - } else { - ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None)); - } // 块内函数声明求值写回外层 var 绑定(sloppy web-compat):预声明期为该 // 名实例化的外层 var 绑定(函数作用域)在此写入块槽位里的函数对象, // 求值一次写回一次(循环体内每迭代覆写头绑定)。形参/词法声明同名 @@ -104,4 +75,67 @@ impl Emitter { } Ok(None) } + + /// 函数声明物化:建形参 spec、编译体为子模块、创建闭包并把函数对象写入 + /// 声明绑定槽(名被捕获时同步建 cell)。顶层声明点与块级块入口初始化共用。 + /// + /// # 前提 + /// - 声明名已有已初始化绑定(顶层为 var 预声明、块为 Let 预声明); + /// `lookup` 对未声明名失败。 + /// + /// # 副作用 + /// - 子模块登记进嵌套模块表;当前指令流发 CREATE_CLOSURE(及 MAKE_CELL / + /// STORE_VAR)。 + /// + /// # 注意事项 + /// - 闭包的 upvalue/cell 捕获在函数入口的捕获分析期固定,本调用点的时序 + /// 只决定 CREATE_CLOSURE 在指令流中的落点。 + pub(crate) fn materialize_function_declaration( + &self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx, + ) -> Result { + let mut param_names = Vec::new(); + for (idx, param) in fd.params.items.iter().enumerate() { + match ¶m.pattern { + oxide_parser::BindingPattern::BindingIdentifier(bi) => param_names.push(ParamSpec::Identifier { + name: bi.name.to_string(), + initializer: param.initializer.as_deref(), + }), + pattern => param_names.push(ParamSpec::Pattern { + synthetic_name: format!("@@param_{idx}"), + pattern, + initializer: param.initializer.as_deref(), + }), + } + } + if let Some(rest) = &fd.params.rest { + self.push_rest_param(&rest.rest.argument, &mut param_names)?; + } + let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] }; + let own_strict = fd.has_use_strict_directive(); + let mut sub_module = if fd.generator && fd.r#async { + self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)? + } else if fd.generator { + self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)? + } else if fd.r#async { + self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + } else { + self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + }; + sub_module.function_name = Some(name.to_string()); + ctx.nested.push(sub_module); + let var_reg = ctx.lookup(name)?; + ctx.reserve_reg(var_reg); + ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16)); + if let Some(&cell_idx) = ctx.captured_bindings.get(name) { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(var_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } else { + ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None)); + } + Ok(var_reg) + } } diff --git a/crates/oxide_vm/tests/block_function_decl_tests.rs b/crates/oxide_vm/tests/block_function_decl_tests.rs index 29c1f828..123f1565 100644 --- a/crates/oxide_vm/tests/block_function_decl_tests.rs +++ b/crates/oxide_vm/tests/block_function_decl_tests.rs @@ -32,11 +32,10 @@ fn block_function_decl_call_after_decl() { } #[test] -fn block_function_decl_hoisted_call_before_decl_is_not_callable() { - // 阶段 1:声明点之前调用读到未初始化槽 undefined,报 not callable - // (比修复前的编译错好;完整 hoisting 属后续阶段)。 - let err = eval("{ g(); function g(){return 1;} }").unwrap_err(); - assert!(err.contains("not callable") || err.contains("not an object"), "got: {err}"); +fn block_function_decl_call_before_decl() { + // 声明点之前调用:块绑定块入口即持有函数对象,提前调用正常返回。 + let result = eval("{ g(); function g(){return 1;} }").unwrap(); + assert_eq!(result.as_int(), 1); } #[test] @@ -58,8 +57,8 @@ fn block_function_decl_capture_escape() { #[test] fn block_function_decl_leaks_outer_in_sloppy_script() { // sloppy 块级函数声明名泄漏外层(web-compat 外层 var 绑定 + 求值写回): - // 块后读见函数对象而非未声明名。块内声明前读仍为 undefined(块入口初始化 - // 未建,见 hoisted_call 测试),块内声明后读为函数。 + // 块后读见函数对象而非未声明名;块内声明前读同样为函数对象(块入口 + // 初始化),块内声明后读为函数。 let result = eval("{ { function g(){return 2;} } } typeof g === 'function'").unwrap(); assert!(result.as_bool(), "sloppy 块函数名应泄漏外层,got {:?}", result); }