diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 65e11ca0..ebfde39a 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -128,7 +128,7 @@ impl Emitter { /// # 步骤 /// 1. 标识符:with 体动态解析、可删全局内置镜像槽先行;其余按 DeleteBinding /// 三分类(见标识符臂注释)——局部绑定与脚本自身顶层已声明名发 false - /// 常数,未声明名/隐式全局槽/eval 程序自身顶层已声明名发全局对象运行期 + /// 常数,未声明名/隐式全局槽/eval 程序自身顶层 var/函数名发全局对象运行期 /// 探针。严格模式的 delete 标识符是早期错误,由语义分析阶段拦截,不在此出现。 /// 2. 静态/计算成员与链式成员:从对象真删属性,结果为删除返回值。 /// 3. 其余形态(字面量/调用/new/this/一元形/序列/嵌套 delete 等):先求值 @@ -188,16 +188,18 @@ impl Emitter { return Ok(reg); } // DeleteBinding 三分类(13.5.1.2 步5 绑定引用走 base.DeleteBinding): - // - 局部绑定(捕获 cell/upvalue/lookup 命中函数或块作用域槽)与 - // 非 eval 脚本自身顶层已声明名(脚本 var/函数名 c:false)→ false 常数; + // - 局部绑定(捕获 cell/upvalue/lookup 命中函数或块作用域槽)、 + // 非 eval 脚本自身顶层已声明名(脚本 var/函数名 c:false)、eval + // 程序自身顶层 let/const(落 eval 自身 lexical 环境,declarative + // 环境记录 DeleteBinding 恒 false 且不物化全局属性)→ false 常数; // - 其余——未声明名(引用不可解析)、隐式全局槽、eval 程序自身顶层 - // 已声明名(c:true)——发全局对象运行期探针(与 Reflect.deleteProperty - // 同源 DeleteBinding 语义:缺失 → true;不可配置 → false 且保留; - // 可配置 → 真删且 true)。三类全局属性的 c 位已由各自写点物化, - // 探针取值即规范值;独立编译的 eval 程序见不到调用方域变量,静态 - // "当前程序内是否声明"粗于规范动态判定,故不可解析面一律运行期定值。 - // 严格模式的 delete 标识符由语义分析提前拦截为早期错误,本臂在 - // strict 代码不可达。 + // var/函数名(物化 c:true 全局属性)——发全局对象运行期探针(与 + // Reflect.deleteProperty 同源 DeleteBinding 语义:缺失 → true; + // 不可配置 → false 且保留;可配置 → 真删且 true)。三类全局属性的 + // c 位已由各自写点物化,探针取值即规范值;独立编译的 eval 程序见 + // 不到调用方域变量,静态"当前程序内是否声明"粗于规范动态判定,故 + // 不可解析面一律运行期定值。严格模式的 delete 标识符由语义分析 + // 提前拦截为早期错误,本臂在 strict 代码不可达。 let local = ctx.captured_bindings.contains_key(name) || ctx.current_upvalue_captures.iter().any(|u| u.name == name); let probe = if local { @@ -209,9 +211,13 @@ impl Emitter { Some((binding, scope_idx)) => { if scope_idx == 0 { // 全局作用域:隐式全局槽(未声明名读写登记,属性 - // c:true)与 eval 程序自身顶层名(c:true)可删; - // 非 eval 脚本自身顶层 var/函数名(c:false)保留 false。 - ctx.is_implicit_global_reg(binding.reg) || ctx.is_eval_script + // c:true)与 eval 程序自身顶层 var/函数名(顶层 + // var 名集 ∪ 顶层函数声明名,物化 c:true 全局属性) + // 可删;eval 顶层 let/const 不在名集内(自身 lexical + // 环境不可删),与非 eval 脚本自身顶层 var/函数名 + // (c:false)同保留 false。 + ctx.is_implicit_global_reg(binding.reg) + || (ctx.is_eval_script && ctx.global_tier_names.contains(name)) } else { // 函数/块作用域局部绑定:非属性引用,恒 false。 false diff --git a/crates/oxide_vm/tests/delete_eval_var_env_tests.rs b/crates/oxide_vm/tests/delete_eval_var_env_tests.rs index 761a6238..8f8d388b 100644 --- a/crates/oxide_vm/tests/delete_eval_var_env_tests.rs +++ b/crates/oxide_vm/tests/delete_eval_var_env_tests.rs @@ -1,11 +1,13 @@ //! delete 标识符 DeleteBinding 面:eval 程序独立编译见不到调用方域变量,静态 //! "当前程序内是否声明"粗于规范动态判定,未声明名/隐式全局槽/eval 程序自身 -//! 顶层已声明名一律发全局对象运行期探针(缺失 → true;不可配置 → false 且保留; -//! 可配置 → 真删且 true)。局部绑定与非 eval 脚本自身顶层已声明名保留 false -//! 常数。覆盖:direct eval 对调用方脚本 var、eval 自身 var 真删、跨 eval 真删、 -//! 函数内隐式全局真删、读侧真删可见(删后裸读抛 ReferenceError)、typeof 对 -//! 删后缺失名不抛、删后重写同步、绿基线(脚本 var/顶层函数名/未声明缺失/builtin -//! 槽优先级/strict 调用方 indirect eval)。 +//! 顶层 var/函数名(物化 c:true 全局属性)一律发全局对象运行期探针(缺失 → +//! true;不可配置 → false 且保留;可配置 → 真删且 true)。局部绑定、非 eval +//! 脚本自身顶层已声明名、eval 程序自身顶层 let/const(落 eval 自身 lexical +//! 环境,declarative 环境不可删)保留 false 常数。覆盖:direct eval 对调用方 +//! 脚本 var、eval 自身 var 真删、跨 eval 真删、函数内隐式全局真删、读侧真删 +//! 可见(删后裸读抛 ReferenceError)、typeof 对删后缺失名不抛、删后重写同步、 +//! 绿基线(脚本 var/顶层函数名/未声明缺失/builtin 槽优先级/strict 调用方 +//! indirect eval)、eval 自身顶层 let/const false 且无全局属性副作用。 use std::sync::Arc; @@ -106,3 +108,21 @@ fn reassign_after_real_delete_resyncs_both_sides() { fn read_before_delete_unchanged() { eval_truthy("x = 1; var r = x; delete x; r === 1"); } + +// ── eval 自身顶层 let(lexical 环境不可删):false 且不物化全局属性 ── +#[test] +fn eval_delete_own_let_returns_false_without_global_property() { + eval_truthy("eval('let l = 1; delete l') === false && typeof l === 'undefined' && !('l' in globalThis)"); +} + +// ── eval 自身顶层 const(lexical 环境不可删):false 且不物化全局属性 ── +#[test] +fn eval_delete_own_const_returns_false_without_global_property() { + eval_truthy("eval('const c = 1; delete c') === false && typeof c === 'undefined' && !('c' in globalThis)"); +} + +// ── 同名单元:调用方脚本 var(c:false)+ eval 自身顶层 let,false 且调用方值保留 ── +#[test] +fn eval_delete_own_let_shadowing_caller_var_returns_false_and_kept() { + eval_truthy("var l = 5; eval('let l = 1; delete l') === false && l === 5 && globalThis.l === 5"); +}