fix: 编译缓存键补模板段值、spread 实参、赋值目标属性名与数组 spread 形态

This commit is contained in:
2026-10-10 07:28:09 +00:00
parent 17e5992dcc
commit 7ef19347c8
6 changed files with 120 additions and 28 deletions
+52 -7
View File
@@ -16,9 +16,19 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
Expression::CallExpression(call) => {
(call.arguments.len() as u32).hash(h);
hash_expression(&call.callee, h, include_binding_names);
// spread 实参改指令形态(CALL 变 CALL_SPREAD)与装载方式,逐位打形态标记。
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
hash_expression(expr, h, include_binding_names);
match arg {
Argument::SpreadElement(sp) => {
1u8.hash(h);
hash_expression(&sp.argument, h, include_binding_names);
}
other => {
0u8.hash(h);
if let Some(expr) = other.as_expression() {
hash_expression(expr, h, include_binding_names);
}
}
}
}
}
@@ -69,6 +79,11 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
}
Expression::TemplateLiteral(tl) => {
(tl.quasis.len() as u32).hash(h);
// 段值是字节码依赖(发射侧把 cooked 写入常量池);cooked 为 None 与
// Some(空串) 的池键都是空串,字节码相同,口径上合并。
for q in &tl.quasis {
q.value.cooked.as_deref().unwrap_or("").hash(h);
}
for expr in &tl.expressions {
hash_expression(expr, h, include_binding_names);
}
@@ -76,6 +91,12 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
Expression::TaggedTemplateExpression(tt) => {
hash_expression(&tt.tag, h, include_binding_names);
(tt.quasi.quasis.len() as u32).hash(h);
// 发射侧同时消费 cooked 与 raw;cooked 为 None 时写标记字,与
// Some(空串) 的常量下标字不同,须保留 None 区分。
for q in &tt.quasi.quasis {
q.value.cooked.hash(h);
q.value.raw.hash(h);
}
for expr in &tt.quasi.expressions {
hash_expression(expr, h, include_binding_names);
}
@@ -128,9 +149,19 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
Expression::NewExpression(ne) => {
hash_expression(&ne.callee, h, include_binding_names);
(ne.arguments.len() as u32).hash(h);
// spread 实参改指令形态(NEW_EXPRESSION 变 NEW_EXPRESSION_SPREAD),逐位打形态标记。
for arg in &ne.arguments {
if let Some(expr) = arg.as_expression() {
hash_expression(expr, h, include_binding_names);
match arg {
Argument::SpreadElement(sp) => {
1u8.hash(h);
hash_expression(&sp.argument, h, include_binding_names);
}
other => {
0u8.hash(h);
if let Some(expr) = other.as_expression() {
hash_expression(expr, h, include_binding_names);
}
}
}
}
}
@@ -169,15 +200,19 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
}
Expression::ArrayExpression(arr) => {
(arr.elements.len() as u32).hash(h);
// 元素形态(spread / 省略 / 值)逐位打标记:spread 源改变装载指令,
// 位置敏感,`[...a]` 与 `[a]` 不得同键。
for element in &arr.elements {
match element {
ArrayExpressionElement::SpreadElement(spread) => {
1u8.hash(h);
hash_expression(&spread.argument, h, include_binding_names);
}
ArrayExpressionElement::Elision(_) => {
1u8.hash(h);
2u8.hash(h);
}
other => {
0u8.hash(h);
if let Some(expr) = other.as_expression() {
hash_expression(expr, h, include_binding_names);
}
@@ -262,9 +297,19 @@ pub(super) fn hash_chain_element(element: &ChainElement, h: &mut rustc_hash::FxH
hash_expression(&call.callee, h, include_binding_names);
call.optional.hash(h);
(call.arguments.len() as u32).hash(h);
// spread 实参改指令形态(CALL_SPREAD),逐位打形态标记。
for arg in &call.arguments {
if let Some(expr) = arg.as_expression() {
hash_expression(expr, h, include_binding_names);
match arg {
Argument::SpreadElement(sp) => {
1u8.hash(h);
hash_expression(&sp.argument, h, include_binding_names);
}
other => {
0u8.hash(h);
if let Some(expr) = other.as_expression() {
hash_expression(expr, h, include_binding_names);
}
}
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
//! 其内部绑定名仅精确键计入。
use oxide_parser::{
ArrayAssignmentTarget, ArrayExpressionElement, AssignmentTarget, AssignmentTargetMaybeDefault,
ArrayAssignmentTarget, ArrayExpressionElement, Argument, AssignmentTarget, AssignmentTargetMaybeDefault,
AssignmentTargetProperty, BindingPattern, ChainElement, Class, ClassElement, Declaration,
ExportDefaultDeclarationKind, Expression, ForStatementInit, ForStatementLeft, Function, ImportDeclarationSpecifier,
ModuleExportName, ObjectAssignmentTarget, ObjectPropertyKind, PropertyKey, SimpleAssignmentTarget, Statement,
+6
View File
@@ -13,14 +13,20 @@ pub(super) fn hash_simple_assignment_target(
}
SimpleAssignmentTarget::StaticMemberExpression(member) => {
expression::hash_expression(&member.object, h, include_binding_names);
// 属性名是字节码依赖(发射侧写入常量池作 IC_SET 等键);optional 与
// 读侧口径对齐(写侧发射当前忽略该标志,防未来写侧开始区分时再漏)。
member.property.name.as_str().hash(h);
member.optional.hash(h);
}
SimpleAssignmentTarget::ComputedMemberExpression(member) => {
expression::hash_expression(&member.object, h, include_binding_names);
expression::hash_expression(&member.expression, h, include_binding_names);
member.optional.hash(h);
}
SimpleAssignmentTarget::PrivateFieldExpression(member) => {
expression::hash_expression(&member.object, h, include_binding_names);
member.field.name.as_str().hash(h);
member.optional.hash(h);
}
SimpleAssignmentTarget::TSAsExpression(ts) => {
expression::hash_expression(&ts.expression, h, include_binding_names);
@@ -227,3 +227,44 @@ fn function_async_generator_flags_distinguished() {
// 同源等价回归
assert_eq!(compiled("async function f(){ return 1 }"), compiled("async function f(){ return 1 }"));
}
#[test]
fn template_segment_values_distinguished() {
// 模板段值是字节码依赖(发射侧把 cooked 写入常量池):段值不入哈希时
// 同键异码,缓存命中复用错误字节码。
assert_ne!(compiled("`${a}x`"), compiled("`${a}y`"));
assert_ne!(compiled("t`${a}x`"), compiled("t`${a}y`"));
// 段值在两种哈希粒度下都计入(非绑定名输入)。
assert_ne!(structural("`${a}x`"), structural("`${a}y`"));
// 同源等价回归。
assert_eq!(compiled("`${a}x`"), compiled("`${a}x`"));
}
#[test]
fn call_spread_arguments_distinguished() {
assert_ne!(compiled("f(...a)"), compiled("f(...b)"));
assert_ne!(compiled("new F(...a)"), compiled("new F(...b)"));
assert_ne!(compiled("o.m(...a)"), compiled("o.m(...b)"));
// spread 与非 spread 同长同表达式须区分(逐位形态标记)。
assert_ne!(compiled("f(a, ...b)"), compiled("f(a, b)"));
// spread 形态标记在两种哈希粒度下都计入(标记本身非绑定名输入)。
assert_ne!(structural("f(...a, 1)"), structural("f(a, 1)"));
}
#[test]
fn array_spread_elements_distinguished() {
// spread 元素与值元素同位置须区分(形态标记逐位计入)。
assert_ne!(compiled("[...a]"), compiled("[a]"), "spread vs value element must differ");
assert_ne!(structural("[...a, 1]"), structural("[a, 1]"), "spread element shape must be in both granularities");
}
#[test]
fn assignment_target_member_names_distinguished() {
// 赋值目标静态成员属性名是字节码依赖(发射侧写入常量池):属性名不入哈希时
// 同键异码。覆盖赋值、自增减与 for 头三个调用点。
assert_ne!(compiled("a.x = 1"), compiled("a.y = 1"));
assert_ne!(compiled("a.x++"), compiled("a.y++"));
assert_ne!(compiled("for (a.x of iter) {}"), compiled("for (a.y of iter) {}"));
// 属性名在两种哈希粒度下都计入。
assert_ne!(structural("a.x = 1"), structural("a.y = 1"));
}