fix: 编译缓存键递归纳入解构形态绑定名

This commit is contained in:
2026-10-10 07:17:35 +00:00
parent 3cc811e730
commit 17e5992dcc
7 changed files with 217 additions and 38 deletions
+2 -3
View File
@@ -58,9 +58,8 @@ pub(super) fn hash_expression(expr: &Expression, h: &mut rustc_hash::FxHasher, i
}
Expression::AssignmentExpression(assign) => {
std::mem::discriminant(&assign.operator).hash(h);
if let Some(target) = assign.left.as_simple_assignment_target() {
target::hash_simple_assignment_target(target, h, include_binding_names);
}
// 全量分派:简单目标与数组/对象解构目标都计入,左值整体不得漏哈希。
target::hash_assignment_target(&assign.left, h, include_binding_names);
hash_expression(&assign.right, h, include_binding_names);
}
Expression::UpdateExpression(update) => {
+53 -10
View File
@@ -2,15 +2,18 @@
//! `Program` 的稳定哈希,用作编译缓存键(code cache)。
//!
//! `structural_hash` 为粗粒度键,忽略变量声明名、标识符读、import 本地名与
//! 函数参数等大部分绑定名;`compiled_module_hash` 为精确键,额外纳入标识符形态的绑定名
//! (变量声明、参数与 catch 的解构形态均不贡献哈希)。
//! 函数参数等大部分绑定名(含声明/参数位置的解构形态);`compiled_module_hash`
//! 为精确键,额外纳入绑定名:标识符形态直接计入,解构形态(数组/对象/默认值
//! pattern)递归计入键、名、默认值表达式与 rest 的完整结构。
//! 函数声明名、类名与 catch 参数标识名是编译产物物化名,两种粒度下恒计入哈希;
//! 变量声明、函数/箭头/方法参数与 catch 参数为解构形态时均不贡献哈希。
//! 解构赋值目标的完整结构(元素数、键、rest、默认值表达式)两种粒度下均计入,
//! 其内部绑定名仅精确键计入。
use oxide_parser::{
ArrayExpressionElement, BindingPattern, ChainElement, Class, ClassElement, Declaration,
ArrayAssignmentTarget, ArrayExpressionElement, AssignmentTarget, AssignmentTargetMaybeDefault,
AssignmentTargetProperty, BindingPattern, ChainElement, Class, ClassElement, Declaration,
ExportDefaultDeclarationKind, Expression, ForStatementInit, ForStatementLeft, Function, ImportDeclarationSpecifier,
ModuleExportName, ObjectPropertyKind, PropertyKey, SimpleAssignmentTarget, Statement,
ModuleExportName, ObjectAssignmentTarget, ObjectPropertyKind, PropertyKey, SimpleAssignmentTarget, Statement,
};
use std::hash::Hash;
@@ -75,12 +78,52 @@ fn hash_program(program: &oxide_parser::Program, include_binding_names: bool) ->
h.finish()
}
/// 哈希 `BindingIdentifier` 的绑定名;只由 `include_binding_names` 为真的调用点
/// 使用(变量声明、函数与箭头参数、方法参数、catch 参数),非标识符模式
/// (数组/对象解构)不贡献哈希。
/// 哈希 `BindingPattern` 的完整结构:标识符形态计绑定名,解构形态(数组/对象/
/// 默认值 pattern)递归计入键、名、默认值表达式与 rest 的完整结构。
///
/// # 边界与前提
/// - 只由 `include_binding_names` 为真的调用点使用(变量声明、函数与箭头参数、
/// 方法参数、catch 参数),故嵌套调用对 `hash_expression` / `hash_property_key`
/// 硬编码传 `true`。
///
/// # 注意事项
/// - 数组省略位与 rest 有无须以哨兵区分:位置敏感,`[a, , b]` 与 `[a, b, ]`
/// 同长不同位,`[a, b]` 与 `[a, b, ...r]` 结构不同。
fn hash_binding_pattern(pattern: &BindingPattern, h: &mut rustc_hash::FxHasher) {
if let BindingPattern::BindingIdentifier(ident) = pattern {
ident.name.as_str().hash(h);
match pattern {
BindingPattern::BindingIdentifier(ident) => {
ident.name.as_str().hash(h);
}
BindingPattern::ArrayPattern(ap) => {
(ap.elements.len() as u32).hash(h);
for elem in &ap.elements {
match elem {
Some(bp) => hash_binding_pattern(bp, h),
// 省略位(如 `[a, , b]`)与有值位须区分:位置敏感。
None => 0u8.hash(h),
}
}
if let Some(rest) = &ap.rest {
1u8.hash(h);
hash_binding_pattern(&rest.argument, h);
}
}
BindingPattern::ObjectPattern(op) => {
(op.properties.len() as u32).hash(h);
for prop in &op.properties {
property::hash_property_key(&prop.key, h, true);
hash_binding_pattern(&prop.value, h);
}
if let Some(rest) = &op.rest {
1u8.hash(h);
hash_binding_pattern(&rest.argument, h);
}
}
BindingPattern::AssignmentPattern(ap) => {
hash_binding_pattern(&ap.left, h);
// 默认值表达式是字节码依赖(发射时求值入池),须计入,不能只哈希左值名。
expression::hash_expression(&ap.right, h, true);
}
}
}
+17 -4
View File
@@ -252,10 +252,23 @@ fn hash_module_export_name(name: &ModuleExportName<'_>, h: &mut rustc_hash::FxHa
fn hash_for_statement_left(left: &ForStatementLeft<'_>, h: &mut rustc_hash::FxHasher, include_binding_names: bool) {
std::mem::discriminant(left).hash(h);
if let ForStatementLeft::VariableDeclaration(decl) = left {
hash_variable_declaration(decl, h, include_binding_names);
} else if let Some(target) = left.as_simple_assignment_target() {
target::hash_simple_assignment_target(target, h, include_binding_names);
match left {
ForStatementLeft::VariableDeclaration(decl) => {
hash_variable_declaration(decl, h, include_binding_names);
}
// 解构赋值头与赋值目标同构,分派到共享助手。
ForStatementLeft::ArrayAssignmentTarget(ap) => {
target::hash_array_assignment_target(ap, h, include_binding_names);
}
ForStatementLeft::ObjectAssignmentTarget(op) => {
target::hash_object_assignment_target(op, h, include_binding_names);
}
// 简单目标(标识符/成员/TS 断言)走既有分派。
_ => {
if let Some(simple) = left.as_simple_assignment_target() {
target::hash_simple_assignment_target(simple, h, include_binding_names);
}
}
}
}
+95 -1
View File
@@ -1,4 +1,4 @@
//! 赋值目标(SimpleAssignmentTarget)的结构哈希。
//! 赋值目标(SimpleAssignmentTarget / AssignmentTarget)的结构哈希。
use super::*;
@@ -36,3 +36,97 @@ pub(super) fn hash_simple_assignment_target(
}
});
}
/// 哈希全量 `AssignmentTarget`:简单目标(标识符/成员/TS 断言)复用既有分派,
/// 数组/对象解构目标递归计入元素、键、默认值表达式与 rest 的完整结构。
///
/// # 边界与前提
/// - `include_binding_names` 透传至简单目标、默认值表达式与解构目标内部绑定名;
/// 解构目标的结构(元素数、属性键、rest)两种粒度下恒计入。
pub(super) fn hash_assignment_target(
target: &AssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool,
) {
if let Some(simple) = target.as_simple_assignment_target() {
hash_simple_assignment_target(simple, h, include_binding_names);
return;
}
match target {
AssignmentTarget::ArrayAssignmentTarget(ap) => {
hash_array_assignment_target(ap, h, include_binding_names);
}
AssignmentTarget::ObjectAssignmentTarget(op) => {
hash_object_assignment_target(op, h, include_binding_names);
}
_ => {}
}
}
/// 哈希数组赋值目标:元素逐个哈希(省略位以哨兵区分),rest 有无以哨兵区分。
pub(super) fn hash_array_assignment_target(
target: &ArrayAssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool,
) {
(target.elements.len() as u32).hash(h);
for elem in &target.elements {
match elem {
Some(td) => hash_assignment_target_maybe_default(td, h, include_binding_names),
// 省略位与有值位须区分:位置敏感。
None => 0u8.hash(h),
}
}
if let Some(rest) = &target.rest {
1u8.hash(h);
hash_assignment_target(&rest.target, h, include_binding_names);
}
}
/// 哈希对象赋值目标:属性逐个哈希(键与绑定),rest 有无以哨兵区分。
pub(super) fn hash_object_assignment_target(
target: &ObjectAssignmentTarget, h: &mut rustc_hash::FxHasher, include_binding_names: bool,
) {
(target.properties.len() as u32).hash(h);
for prop in &target.properties {
hash_assignment_target_property(prop, h, include_binding_names);
}
if let Some(rest) = &target.rest {
1u8.hash(h);
hash_assignment_target(&rest.target, h, include_binding_names);
}
}
/// 哈希 `AssignmentTargetMaybeDefault`:带默认值形态计绑定递归加默认值表达式,
/// 其余变体(继承自 `AssignmentTarget`)转全量分派。
fn hash_assignment_target_maybe_default(
target: &AssignmentTargetMaybeDefault, h: &mut rustc_hash::FxHasher, include_binding_names: bool,
) {
match target {
AssignmentTargetMaybeDefault::AssignmentTargetWithDefault(d) => {
hash_assignment_target(&d.binding, h, include_binding_names);
// 默认值表达式是字节码依赖(发射时求值入池),须计入。
expression::hash_expression(&d.init, h, include_binding_names);
}
other => {
if let Some(t) = other.as_assignment_target() {
hash_assignment_target(t, h, include_binding_names);
}
}
}
}
/// 哈希对象赋值目标属性:标识符形态计绑定名与默认值(若有),
/// 属性形态计键与绑定递归。
fn hash_assignment_target_property(
prop: &AssignmentTargetProperty, h: &mut rustc_hash::FxHasher, include_binding_names: bool,
) {
match prop {
AssignmentTargetProperty::AssignmentTargetPropertyIdentifier(id) => {
id.binding.name.as_str().hash(h);
if let Some(init) = &id.init {
expression::hash_expression(init, h, include_binding_names);
}
}
AssignmentTargetProperty::AssignmentTargetPropertyProperty(pp) => {
property::hash_property_key(&pp.name, h, include_binding_names);
hash_assignment_target_maybe_default(&pp.binding, h, include_binding_names);
}
}
}
@@ -107,6 +107,36 @@ fn import_defer_distinguished() {
);
}
#[test]
fn destructuring_binding_names_distinguished() {
// 声明侧:解构绑定名、键、rest 与嵌套结构全量计入精确键。
assert_ne!(compiled("var [a, b] = [1, 2]"), compiled("var [p, q] = [1, 2]"));
assert_ne!(compiled("var {x: a} = o"), compiled("var {y: b} = o"));
assert_ne!(compiled("var [a, ...r] = o"), compiled("var [a, ...s] = o"));
assert_ne!(
compiled("var [{p: a}, b] = o"),
compiled("var [a, b] = o"),
"nested pattern structure must be part of the hash"
);
// 默认值侧:默认值表达式计入哈希。
assert_ne!(compiled("var [a = f()] = o"), compiled("var [a = g()] = o"));
// 赋值目标侧:解构赋值左值整体不得漏哈希。
assert_ne!(compiled("[a, b] = [1, 2]"), compiled("[p, q] = [1, 2]"));
assert_ne!(compiled("({a} = o)"), compiled("({b} = o)"));
// for 头侧:声明形态与赋值形态的解构头都计入哈希。
assert_ne!(compiled("for (var [a, b] of xs) {}"), compiled("for (var [p, q] of xs) {}"));
assert_ne!(compiled("for ({a} of xs) {}"), compiled("for ({b} of xs) {}"));
// 函数参数与 catch 侧:解构参数名计入哈希。
assert_ne!(compiled("function f([a, b]) {}"), compiled("function f([p, q]) {}"));
assert_ne!(compiled("try {} catch ([a]) {}"), compiled("try {} catch ([p]) {}"));
// 同源等价回归。
assert_eq!(compiled("var [a, b] = [1, 2]"), compiled("var [a, b] = [1, 2]"));
}
#[test]
fn structural_hash_ignores_binding_names() {
// 结构哈希忽略绑定名:仅参数/变量改名后哈希相等。