emit/kernel/vm/builtins: 常量池键编码 + 键物化真实串值面 + 16 回归钉——emit 侧字面量/键/模板统一改存无标志键编码:lone_surrogates 字面量与模板 cooked 按 oxc marker 解码还原真单元序列(FFFD+hex4≠fffd→surrogate 单元、FFFD+fffd→FFFD、FFFD+非 hex 防御臂),无标志串 encode_key(encode_utf16()) 入池(BMP 无 FFFD 键恒等 = 现值,池条目零漂移),string_pool 集中收口 9 处入池点;interner string_ptr 由单元物化(含孤立→FlatU16、良形→Flat);键物化面改真实 JS 串值(key_si_to_js_value 经 decode_key,Object.keys/ownKeys/entries/Reflect.ownKeys/json reviver 键同源;整数键反解数字串);批内修复三处子集 A/B 差分撞出回归:replace $n 展开按 spec GetSubstitution 重写(digitCount 2/1、越界回退 1 位、越界含 $0 原文输出,node 13 用例对照钉住)、String 构造器 Symbol 路径按 new.target 分流(String(sym)→描述串、new String(sym)→TypeError,恢复 to_string_for_string_constructor)、padStart/padEnd 未给 fill 时按单空格补(原 lossy 串取 'un' 杂质,pre-existing 一并归零);迭代器 dispose 钉改回真值断言(String(Symbol.dispose) 现合法返回描述串);property-escapes 613/0/0(B167 基线 582/31/0,31 归零)、String 子集 963/250/10 vs BASE 949/264/10(14 修 0 回归)、for-of +1(string-astral-truncated)、五 crate 113 套件 2680/0、clippy/fmt 零新增(JSON 面与全量 test262 收尾在 131.2 后续 commit 闭合)

This commit is contained in:
2026-09-14 03:04:03 +00:00
parent 3acac721b0
commit 0c750fa74c
20 changed files with 398 additions and 129 deletions
@@ -528,9 +528,10 @@ fn symbol_dispose_and_async_dispose_registered() {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// ToString(Symbol) 按规格抛 TypeError(String 构造器无描述性名称通道)。
let err = eval(&mut vm, "try { String(Symbol.dispose); 'no-throw' } catch (e) { e.name }").unwrap();
assert_eq!(to_str(&vm, err), "TypeError");
// String(Symbol) 走 SymbolDescriptiveString(规范 String 构造器步骤),
// 不经 ToString——well-known 符号对象按描述名产出。
let s = eval(&mut vm, "String(Symbol.dispose)").unwrap();
assert_eq!(to_str(&vm, s), "Symbol(Symbol.dispose)");
}
#[test]
@@ -150,6 +150,9 @@ fn string_pad_end() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hi'.padEnd(4)").unwrap();
assert_eq!(to_str(&vm, s), "hi ");
// 显式 undefined 填充与缺省同义:回落空格(规范 PadString 步骤)。
let s = eval(&mut vm, "'hi'.padEnd(4, undefined)").unwrap();
assert_eq!(to_str(&vm, s), "hi ");
}
#[test]
@@ -264,6 +267,17 @@ fn string_missing_arg_searches_undefined() {
assert_eq!(r.as_int(), 0);
}
#[test]
fn string_ctor_symbol_paths() {
// 函数调用:Symbol → SymbolDescriptiveString(描述串,不抛)。
let mut vm = Vm::new();
let s = eval(&mut vm, "String(Symbol('66'))").unwrap();
assert_eq!(to_str(&vm, s), "Symbol(66)");
// new 语义:构造器路径走 ToString,Symbol 抛 TypeError。
let name = eval(&mut vm, "try { new String(Symbol('66')); 'no-throw' } catch (e) { e.name }").unwrap();
assert_eq!(to_str(&vm, name), "TypeError");
}
#[test]
fn string_symbol_argument_throws_type_error() {
// Symbol 参数经 as_string 抛 TypeError(不静默降级为文本查找)。
@@ -425,6 +439,37 @@ fn string_replace_function_replacer_string_pattern() {
assert_eq!(to_str(&vm, s), "a1c");
}
#[test]
fn string_replace_dollar_group_expansion() {
// `$n` 展开按规范 GetSubstitution:两位数值越界回退一位、仍越界(含 $0)
// 整段 ref 字面、未参与捕获组为空。预言值对照 V8(node)实测。
let mut vm = Vm::new();
let cases = [
// 两位越界回退一位(次位留字面)。
("\"uid=31\".replace(/(uid=)(\\d+)/, \"$11\" + 15)", "uid=115"),
("\"x\".replace(/(x)/, \"$12\")", "x2"),
("\"xy\".replace(/(x)(y)/, \"$12\")", "x2"),
// 越界(含 $0)→ ref 字面;无捕获组全字面。
("\"xy\".replace(/(x)(y)/, \"$3\")", "$3"),
("\"abc\".replace(/b/, \"$0$1\")", "a$0$1c"),
("\"abc\".replace(/(b)/, \"$22\")", "a$22c"),
("\"x\".replace(/x/, \"$10\")", "$10"),
// $& / $` 与组引用混排。
("\"aaaaa,aaaaa\".replace(/(a+),/, \"$1$&$`$1\")", "aaaaaaaaaa,aaaaaaaaaa"),
// $$ 折叠与文本内 $ 无关;$ 序列多位数字逐段消费。
("\"a$b$c\".replace(/b/, \"$$$$\")", "a$$$$c"),
("\"x12y\".replace(/1/, \"$1$12$123\")", "x$1$12$1232y"),
("\"x12y\".replace(/(1)/, \"$1$12$123\")", "x1121232y"),
("\"x12y\".replace(/(1)(2)/, \"$1$12$123\")", "x112123y"),
// 组参与但未匹配 → 空。
("\"x12y\".replace(/(1)(2)(3)?/, \"$3\")", "xy"),
];
for (src, expected) in cases {
let s = eval(&mut vm, src).unwrap();
assert_eq!(to_str(&vm, s), expected, "for {}", src);
}
}
#[test]
fn string_replace_function_replacer_string_arg() {
// 回调第 4 参为原字符串:原始 receiver 直接传值(=== 原串),
@@ -1154,3 +1199,151 @@ fn string_utf16_replace_position_callback() {
let s = eval(&mut vm, "'\\u{1F600}ab'.replaceAll('a', function(m, o){ return o })").unwrap();
assert_eq!(to_str(&vm, s), "\u{1F600}2b");
}
// UTF-16 载荷层回归钉:孤立 surrogate 一等单元(fromCodePoint/fromCharCode 直推单元、
// 字面量/模板 oxc marker 解码、切片/迭代/搜索/键/正则面单元语义)。
#[test]
fn utf16_from_code_point_lone_surrogate() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"String.fromCodePoint(0xD800).length===1 && String.fromCodePoint(0xD800).charCodeAt(0)===0xD800",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_from_char_code_pair_units() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"String.fromCharCode(0xD800,0xDC00).length===2 && String.fromCharCode(0xD800,0xDC00).charCodeAt(1)===0xDC00",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_from_code_point_astral_pair() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"String.fromCodePoint(0x1F600).length===2 && String.fromCodePoint(0x1F600).charCodeAt(0)===0xD83D && String.fromCodePoint(0x1F600).charCodeAt(1)===0xDE00",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_literal_lone_surrogate_emit_decode() {
let mut vm = Vm::new();
let r = eval(&mut vm, "'\\ud800'.length===1 && '\\ud800'.charCodeAt(0)===0xD800").unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_template_cooked_lone_surrogate() {
let mut vm = Vm::new();
let r = eval(&mut vm, "`\\ud800`.length===1").unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_char_at_single_unit_astral() {
// charAt 按单元取:astral 字符首单元为高代理(非整码点)。
let mut vm = Vm::new();
let r = eval(&mut vm, "'\u{1D11E}'.charAt(0).charCodeAt(0)===0xD834").unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_slice_substr_single_unit_astral() {
// slice/substr 单元精确:'\u{1D11E}' = [0xD834, 0xDD1E]。
let mut vm = Vm::new();
let r = eval(
&mut vm,
"'\u{1D11E}'.slice(-1).charCodeAt(0)===0xDD1E && '\u{1D11E}'.substr(1).charCodeAt(0)===0xDD1E && '\u{1D11E}'.slice(0,1).charCodeAt(0)===0xD834",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_iterator_lone_surrogate_units() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ let n=0, ok=true; for (const c of 'a\\ud800') { n++; if (n===2) ok = c.length===1 && c.charCodeAt(0)===0xD800; } return n===2 && ok; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_spread_and_to_object_lone_surrogate() {
// 迭代/spread 按单元产出;rest 解构的 ToObject 字符串下标读得 1 单元串
// (非空串)。字符串包装对象的索引/length 读(new String(...)[0])另有
// 独立既有缺口,不在此钉。
let mut vm = Vm::new();
let r = eval(
&mut vm,
"(function(){ const arr=[...'a\\ud800']; const o={...'a\\ud800'}; const { ...rest } = 'a\\ud800'; return arr.length===2 && arr[1].length===1 && arr[1].charCodeAt(0)===0xD800 && o[1].length===1 && o[1].charCodeAt(0)===0xD800 && rest[1].length===1 && rest[1].charCodeAt(0)===0xD800; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_regex_surrogate_property_class() {
// 属性转义须 u 标志(无 u 为规范 SyntaxError,引擎宽容面不在此钉)。
let mut vm = Vm::new();
let r = eval(
&mut vm,
"/\\p{General_Category=Surrogate}/u.test(String.fromCodePoint(0xD800))===true && /\\P{ASCII}/u.test(String.fromCodePoint(0xD800))===true && /\\p{General_Category=Surrogate}/u.test('a')===false",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_is_well_formed_to_well_formed() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"'a\\ud800b'.isWellFormed()===false && 'a\\ud800b'.toWellFormed().length===3 && 'a\\ud800b'.toWellFormed().charCodeAt(1)===0xFFFD",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_index_of_lone_surrogate() {
let mut vm = Vm::new();
let r = eval(&mut vm, "'a\\ud800\\ud800'.indexOf('\\ud800')===1").unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_property_key_round_trip() {
let mut vm = Vm::new();
// 孤立 surrogate 计算键:写入/读取 round-trip,Object.keys 出 1 单元键。
let r = eval(
&mut vm,
"(function(){ const k='\\ud800'; const o={}; o[k]=1; const keys=Object.keys(o); return o[k]===1 && keys.length===1 && keys[0].length===1 && keys[0].charCodeAt(0)===0xD800; })()",
)
.unwrap();
assert!(r.as_bool());
}
#[test]
fn utf16_normalize_astral_identity() {
let mut vm = Vm::new();
let r = eval(
&mut vm,
"'\u{1F441}'.normalize('NFD')==='\u{1F441}' && '\u{1F441}'.normalize('NFC').length===2",
)
.unwrap();
assert!(r.as_bool());
}