fix: 计算成员复合赋值保规范求值序

This commit is contained in:
2026-08-14 11:41:50 +08:00
parent c2aea3f8e9
commit 02dabbb845
3 changed files with 85 additions and 18 deletions
+48
View File
@@ -65,6 +65,54 @@ fn eval_computed_const_key_dynamic_keys_stay_dynamic() {
assert_eq!(eval("var arr=[1,2]; arr[0]=7, arr[0]"), "7");
}
#[test]
fn eval_computed_compound_assign_preserves_read_before_rhs() {
// 复合赋值保规范求值序:先读属性再求值 RHS,RHS 副作用改写属性不影响被加的旧值。
assert_eq!(eval("var o={a:1}; function f(){o.a=100; return 1;} o[\"a\"]+=f()"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]-=1, o.a"), "4");
assert_eq!(eval("var o={a:5}; o[\"a\"]*=2, o.a"), "10");
assert_eq!(eval("var o={a:5}; o[\"a\"]/=2, o.a"), "2.5");
assert_eq!(eval("var o={a:5}; o[\"a\"]%=3, o.a"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]**=2, o.a"), "25");
assert_eq!(eval("var o={a:5}; o[\"a\"]&=3, o.a"), "1");
assert_eq!(eval("var o={a:5}; o[\"a\"]|=8, o.a"), "13");
assert_eq!(eval("var o={a:5}; o[\"a\"]^=1, o.a"), "4");
assert_eq!(eval("var o={a:5}; o[\"a\"]<<=1, o.a"), "10");
assert_eq!(eval("var o={a:5}; o[\"a\"]>>=1, o.a"), "2");
assert_eq!(eval("var o={a:5}; o[\"a\"]>>>=1, o.a"), "2");
// 逻辑赋值同为读→短路→RHS→写:truthy 时写 RHS 值,falsy 短路 RHS 不求值。
assert_eq!(eval("var o={a:5}; function g(){o.a=7; return 2;} o[\"a\"]&&=g(), o.a"), "2");
assert_eq!(eval("var o={a:0}; o[\"a\"]&&=99, o.a"), "0");
assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]||=(hit=1), hit"), "0");
assert_eq!(eval("var o={a:5}; var hit=0; o[\"a\"]??=(hit=1), hit"), "0");
assert_eq!(eval("var p={a:null}; p[\"a\"]??=5, p.a"), "5");
assert_eq!(eval("var q={a:0}; q[\"a\"]||=9, q.a"), "9");
// 静态成员复合赋值行为不变(pre-existing 求值序偏差不在本次修复范围)。
assert_eq!(eval("var o={a:5}; o.a += 1, o.a"), "6");
}
#[test]
fn eval_computed_compound_assign_getter_order() {
// getter 场景:先触发 getter 读取,再求值 RHS。
assert_eq!(eval("var g={get a(){return 2}, set a(v){}}; g[\"a\"] += 1"), "3");
// 求值序严格验证:RHS 观察到 getter 已先行(seen=1),否则 RHS 取 100。
// 结果 v*10+seen:规范序 101,RHS 先行 1001。
assert_eq!(
eval("var seen=0, v=0; var g={get a(){seen=1; return v}, set a(x){v=x}}; g[\"a\"] += (seen===0?100:10); v*10+seen"),
"101"
);
}
#[test]
fn eval_computed_compound_assign_read_error_skips_rhs() {
// 读取抛错时 RHS 不求值,其副作用不泄漏。
assert_eq!(eval("var h={get a(){throw 1}}; try{h[\"a\"]+=1}catch(e){e}"), "1");
assert_eq!(
eval("var side=0; var h={get a(){throw 1}}; try{h[\"a\"]+=(side=99)}catch(e){} side"),
"0"
);
}
#[test]
fn eval_computed_const_key_method_call_keeps_this() {
// 方法调用接收者折叠:this 绑定保留。