fix: TDZ 写与 const 赋值检查补全(含闭包捕获路径)

This commit is contained in:
2026-08-15 21:55:43 +08:00
parent 2669967967
commit 07ea7e6d4f
8 changed files with 224 additions and 16 deletions
@@ -50,3 +50,64 @@ fn block_scoped_const_shadowing_independent() {
fn const_destructuring_declaration_works() {
assert_eq!(eval("const { a } = { a: 7 }; a"), "7");
}
// const 初始化为 undefined 时再赋值也必须抛(guard 不能依赖槽值)。
#[test]
fn const_undefined_reassignment_throws() {
let out = eval("const x = undefined; x = 5");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// const 初始化为 undefined 时读取不受影响。
#[test]
fn const_undefined_read_ok() {
assert_eq!(eval("const x = undefined; x"), "undefined");
}
// 闭包捕获 const 赋值抛(cell 写路径编译期拦截)。
#[test]
fn const_captured_write_throws() {
let out = eval("const x = 1; (()=>x=2)()");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// 函数体内 const 被嵌套函数捕获(upvalue 路径)写同样抛。
#[test]
fn const_upvalue_write_throws() {
let out = eval("function outer(){ const x=1; function inner(){ x=2; } inner(); } outer()");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// 自增/自减路径 const 赋值抛。
#[test]
fn const_update_throws() {
let out = eval("const x = 1; x++");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// 复合赋值路径 const 抛。
#[test]
fn const_compound_throws() {
let out = eval("const x = 1; x += 1");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// 解构赋值目标 const 抛。
#[test]
fn const_destructuring_assignment_throws() {
let out = eval("const [a] = [1]; a = 5");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
// 逻辑赋值短路场景:const truthy 时 ||= 不写不抛。
#[test]
fn const_logical_assign_short_circuit_ok() {
assert_eq!(eval("const x = 1; x ||= 5; x"), "1");
}
// 逻辑赋值非短路场景:const falsy 时 ||= 写抛。
#[test]
fn const_logical_assign_write_throws() {
let out = eval("const x = 0; x ||= 5");
assert!(out.contains("Assignment to constant variable"), "got: {out}");
}
+45 -2
View File
@@ -112,6 +112,49 @@ fn for_let_per_iteration_independent() {
#[test]
fn tdz_access_before_init_throws() {
let _ = eval("x; let x=1");
// 未支持:精确 TDZ 尚未实现。
// 块级预声明后声明点前读取:TDZ 读抛 ReferenceError。
let err = eval("x; let x=1").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}
#[test]
fn tdz_write_throws() {
// TDZ 写:赋值引用解析先于 RHS,`x=1; let x;` 抛 ReferenceError。
let err = eval("x=1; let x;").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}
#[test]
fn tdz_compound_write_throws() {
// 复合赋值路径 TDZ:`x += 1` 在读旧值前解析赋值引用。
let err = eval("{ x += 1; let x; }").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}
#[test]
fn tdz_update_throws() {
// 自增/自减路径 TDZ:`x++` 同样抛 ReferenceError。
let err = eval("{ x++; let x; }").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}
#[test]
fn tdz_destructuring_throws() {
// 解构赋值目标 TDZ:`[a]=[1]` 写未初始化 a 抛 ReferenceError。
let err = eval("[a]=[1]; let a;").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}
#[test]
fn tdz_write_rhs_side_effect_not_evaluated() {
// 检查点顺序:TDZ 检查在 RHS 求值之前,RHS 副作用不执行(s 保持 0)。
let result = eval("var s=0; try{ x=(s=1); let x; }catch(e){} s").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn typeof_tdz_throws() {
// typeof 对 TDZ 绑定抛 ReferenceError(非 "undefined")。
let err = eval("typeof x; let x;").unwrap_err();
assert!(err.contains("ReferenceError"), "got: {}", err);
}