From a176a3c3050795b55345082c3c747ad16fc66fd3 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 18:06:56 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20if=20=E6=94=AF?= =?UTF-8?q?=E8=87=82=E5=87=BD=E6=95=B0=E7=BB=91=E5=AE=9A=E9=97=A8=E6=8E=A7?= =?UTF-8?q?=E7=9A=84=E5=B5=8C=E5=A5=97=E4=B8=8E=E5=86=85=E7=BD=AE=E5=90=8D?= =?UTF-8?q?=E5=88=A4=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_emit/src/prepass/declare.rs | 63 ++++++++++++------- .../tests/block_fn_if_arm_var_shadow_tests.rs | 40 +++++++++++- 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index af25ee10..60658170 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -175,10 +175,11 @@ impl Emitter { /// - 只处理块内函数声明;switch 不推 scope(其 case 内函数声明随 switch /// 作用域修复一并处理);export 声明不可能出现在块内。 /// - `if` 支臂的裸函数声明属 Annex B 隐式支臂块:名已有外层 var 绑定承载 - /// (非 eval 且未被形参/词法同名抑制)时不建外层块绑定,声明点物化进该 - /// var 槽(建外层块 Let 会遮蔽同名 var);名无外层 var 绑定(形参/词法抑制 - /// 或 eval 脚本顶层)时保留块级绑定承载支臂闭包,避免声明点覆写外层绑定。 - /// 支臂为块时由该块自身的块入口流程处理。 + /// (非 eval 脚本顶层、未被形参/词法同名抑制、且非脚本顶层只读三常量)时 + /// 不建外层块绑定,声明点物化进该 var 槽(建外层块 Let 会遮蔽同名 var); + /// 名无外层 var 绑定(形参/词法抑制、eval 脚本顶层或只读三常量)时保留 + /// 块级绑定承载支臂闭包,避免声明点覆写外层绑定。支臂为块时由该块自身的 + /// 块入口流程处理。 /// - 与 lexical 预声明的顺序:本函数在前,`{ let g; function g(){} }` 时 /// lexical 的 `declare_predeclared` 命中已存在的函数绑定自然报重复声明错, /// 避免函数预声明被 lexical 占位静默覆盖而破坏 let 的 TDZ。 @@ -253,34 +254,54 @@ impl Emitter { } } - /// 预声明 `if` 支臂裸函数声明的块级绑定(展开标签链后取函数声明):仅当该 - /// 名没有外层 var 绑定承载时才在当前块建块 Let,承载支臂闭包,避免声明点 - /// 覆写外层形参/词法绑定;名已有外层 var 绑定(非 eval 且未被形参/词法同名 - /// 抑制)时不建块级绑定,声明点物化进该 var 槽。 + /// 预声明 `if` 支臂裸函数声明的块级绑定(展开标签链并下钻嵌套 `if` 后取 + /// 函数声明):仅当该名没有外层 var 绑定承载时才在当前块建块 Let,承载支臂 + /// 闭包,避免声明点覆写外层形参/词法绑定;名已有外层 var 绑定(非 eval 脚本 + /// 顶层、未被形参/词法同名抑制、且非全局只读内置三常量)时不建块级绑定, + /// 声明点物化进该 var 槽。 /// /// # 边界与前提 /// - 支臂为块时返回:该块自身的块入口流程预声明其函数声明。 - /// - 支臂为其它语句(含嵌套 `if`)时返回:嵌套 `if` 由 emit 到达时经本函数 - /// 的 `IfStatement` 臂处理。 + /// - 支臂为嵌套 `if`(含 `else if` 链)时沿 consequent/alternate 下钻至叶子, + /// 叶子按同一门控判定——嵌套支臂与单层支臂同属 Annex B 隐式支臂块。 + /// - 支臂为其它语句时返回:其中的函数声明不属于 `if` 支臂隐式块。 /// - 须在 `block_fn_suppressed` 已构建之后调用(函数体/程序声明实例化阶段 /// 先于任何块 emit);`is_eval_script` 仅标记 eval 脚本顶层程序,其顶层 - /// 不建块级函数外层 var 绑定。 + /// 不建块级函数外层 var 绑定;脚本顶层的只读三常量全局属性不可配置, + /// 声明实例化不建外层 var 绑定,须保留块级绑定承载支臂闭包。 fn predeclare_if_arm_block_function(&self, stmt: &Statement, ctx: &mut CompileCtx) { let mut cur = stmt; while let Statement::LabeledStatement(ls) = cur { cur = &ls.body; } - let Statement::FunctionDeclaration(f) = cur else { - return; - }; - let Some(identifier) = &f.id else { - return; - }; - if !ctx.is_eval_script && !ctx.block_fn_suppressed.contains(identifier.name.as_str()) { - return; + match cur { + Statement::FunctionDeclaration(f) => { + let Some(identifier) = &f.id else { + return; + }; + if Self::if_arm_has_outer_var_carrier(ctx, identifier.name.as_str()) { + return; + } + let reg = ctx.alloc_reg(); + let _ = ctx.declare_initialized(identifier.name.as_str(), reg, VariableDeclarationKind::Let, false); + } + Statement::IfStatement(is) => { + self.predeclare_if_arm_block_function(&is.consequent, ctx); + if let Some(alt) = &is.alternate { + self.predeclare_if_arm_block_function(alt, ctx); + } + } + _ => {} } - let reg = ctx.alloc_reg(); - let _ = ctx.declare_initialized(identifier.name.as_str(), reg, VariableDeclarationKind::Let, false); + } + + /// `if` 支臂裸函数声明的名是否已有外层 var/函数绑定承载:非 eval 脚本顶层的 + /// 非抑制名由实例化阶段建外层 var 绑定,唯一例外是脚本顶层的只读三常量 + /// (全局属性不可配置,声明实例化刻意不建外层绑定),其名须退回块级绑定。 + fn if_arm_has_outer_var_carrier(ctx: &CompileCtx, name: &str) -> bool { + !ctx.is_eval_script + && !ctx.block_fn_suppressed.contains(name) + && !(ctx.is_global_scope && CompileCtx::is_non_writable_global_builtin(name)) } /// 预声明当前作用域直接子语句中的 `let`/`const`/`class` 绑定(未初始化, diff --git a/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs b/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs index ae3fdea4..694ca619 100644 --- a/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs +++ b/crates/oxide_vm/tests/block_fn_if_arm_var_shadow_tests.rs @@ -3,7 +3,8 @@ //! 遮蔽该 `var`;块内前读须见 `var` 值、外层读须见支臂求值写回。 //! //! 覆盖:块内前读/typeof 前读/后读、`if(false)` 未求值支、标签包裹支臂、支臂 -//! 前 `var` 重赋值、块外后读、外层词法/形参同名不覆写、strict 早期错误。 +//! 前 `var` 重赋值、块外后读、外层词法/形参同名不覆写、嵌套 `if`/`else if` 支臂 +//! (eval 顶层与形参抑制面)、只读三常量名支臂、strict 早期错误。 use std::sync::Arc; @@ -96,6 +97,43 @@ fn if_arm_fn_keeps_outer_param_binding() { assert_eq!(eval(source).unwrap().as_int(), 42); } +#[test] +fn nested_else_if_arm_fn_eval_top_level_keeps_block_binding() { + // 嵌套 `else if` 支臂在 eval 顶层无外层 var 面承载:须下钻到叶子并建块级 + // 绑定,否则声明点无绑定可物化而报 `Identifier is not defined`。 + let source = "eval(\"{ if(false) {} else if(true) function g(){}; typeof g }\")"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn nested_if_if_arm_fn_eval_top_level_keeps_block_binding() { + // 嵌套 `if` 支臂(不带 `else`)同形:逐层下钻后叶子建块级绑定。 + let source = "eval(\"{ if(true) if(true) function g(){}; typeof g }\")"; + assert_eq!(eval_str(source).unwrap(), "function"); +} + +#[test] +fn nested_else_if_arm_fn_keeps_outer_param_binding() { + // 形参抑制面嵌套 `else if`:支臂闭包写块槽,块外读仍见形参 42。 + let source = "function f(g){ { if(false){} else if(true) function g(){} } return g } f(42)"; + assert_eq!(eval(source).unwrap().as_int(), 42); +} + +#[test] +fn if_arm_fn_named_undefined_at_script_top_keeps_block_binding() { + // 脚本顶层只读三常量无外层 var 绑定承载:块内支臂须建块级绑定, + // 否则声明点无绑定可物化而编译错。 + let source = "{ if(true) function undefined(){} }"; + assert!(eval(source).unwrap().is_undefined()); +} + +#[test] +fn if_arm_fn_named_nan_at_script_top_keeps_block_binding() { + // 同只读三常量面:`NaN` 名为只读全局内置,块内支臂须建块级绑定。 + let source = "{ if(true) function NaN(){} }"; + assert!(eval(source).unwrap().is_undefined()); +} + #[test] fn if_arm_fn_strict_is_early_error() { // strict 代码中 if 子句内的函数声明是早期错误(Annex B 仅 sloppy)。