fix: switch 语句补 case 块作用域与函数预声明

This commit is contained in:
2026-09-17 19:09:22 +00:00
parent f73f5b8005
commit 3de3b639d9
6 changed files with 199 additions and 47 deletions
+2 -1
View File
@@ -81,7 +81,8 @@ pub struct CompileCtx {
/// (形参 ∪ 函数作用域树内词法声明名):抑制集内名字退化为纯块作用域;预声明期定稿,实例化与声明点写回两侧同查。
pub(crate) block_fn_suppressed: HashSet<String>,
/// 块入口已物化声明登记表(按块层级压/弹):块直接子与标签直接子体
/// 的函数声明在块入口物化闭包后,按 名 → 声明节点 arena 指针 登记。
/// 的函数声明在块入口物化闭包后,按 名 → 声明节点 arena 指针 登记(同名
/// 重复声明只登记首个节点,任一直接子声明点都复用块槽取入口当前值)。
/// 声明点据此三分:同节点命中(本声明已入口物化)→ 复用块槽保闭包
/// 同一;仅同名命中(支臂声明与直接子同名共享块槽)→ 闭包落 fresh
/// 寄存器只写回外层 var、块槽不动;均未命中 → 执行期自物化入块槽。
+8 -41
View File
@@ -172,8 +172,8 @@ impl Emitter {
/// 与 emit 阶段嵌套块 push_scope 的时机对齐。
///
/// # 边界与前提
/// - 只处理块内函数声明;switch 不推 scope(其 case 内函数声明随 switch
/// 作用域修复一并处理);export 声明不可能出现在块内。
/// - 只处理块内函数声明;switch case 的 CaseBlock 环境由 `emit_switch_statement`
/// 自行建立并预声明,不在此递归;export 声明不可能出现在块内。
/// - `if_arm` 标记当前递归是否位于 `if` 支臂隐式块内。Annex B 下该隐式块的
/// 函数声明名已有外层 var 绑定承载(非 eval 脚本顶层、或 sloppy eval 顶层;
/// 未被形参/词法同名抑制,且非全局只读三常量)时不建当前块绑定,声明点
@@ -280,15 +280,15 @@ impl Emitter {
/// 声明点复用预登记槽位。
///
/// # 边界与前提
/// - 只扫描直接子语句 + 递归 switch case(switch 不推 scope,case 内 lexical
/// 声明属于外层作用域);不递归块/if/for/while body(嵌套块自预声明;
/// 单语句 body 不接受 lexical 声明——lexical 属 Declaration、非 Statement
/// 子产生式,parser 按语法错误直接拒绝,这些形状不会进入 emit)。
/// - 只扫描直接子语句;不递归块/if/for/while/switch case/try 区(嵌套块与
/// switch CaseBlock 各自预声明;单语句 body 不接受 lexical 声明——lexical
/// 属 Declaration、非 Statement 子产生式,parser 按语法错误直接拒绝,这些
/// 形状不会进入 emit)。
/// - 跳过 for 头声明(循环作用域由 for 分支内联 declare)。
/// - `global_lexical` 为 true 时(仅脚本顶层调用点传 `!is_eval_script`):
/// lexical 声明撞受限全局名报 SyntaxError(脚本声明实例化对全局对象受限
/// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/try/模块
/// 调用点传 false,lexical 声明是局部绑定不查。
/// 自有属性名做检查,eval 代码声明实例化无此检查);函数体/块/switch case/
/// try/模块调用点传 false,lexical 声明是局部绑定不查。
pub(crate) fn predeclare_lexical_declarations(
&self, statements: &[Statement], ctx: &mut CompileCtx, global_lexical: bool,
) -> Result<(), String> {
@@ -310,13 +310,6 @@ impl Emitter {
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true);
}
}
Statement::SwitchStatement(sw) => {
for case in &sw.cases {
for s in &case.consequent {
self.predeclare_lexical_stmt(s, ctx, global_lexical)?;
}
}
}
Statement::ExportNamedDeclaration(exp) => {
if let Some(decl) = &exp.declaration {
match decl {
@@ -369,32 +362,6 @@ impl Emitter {
Ok(())
}
/// 预声明单个语句中的 lexical 声明(供 switch case 递归;`let`/`const`/`class` 分支)。
fn predeclare_lexical_stmt(
&self, stmt: &Statement, ctx: &mut CompileCtx, global_lexical: bool,
) -> Result<(), String> {
match stmt {
Statement::VariableDeclaration(decl) => {
if matches!(decl.kind, VariableDeclarationKind::Var) {
return Ok(());
}
let is_const = matches!(decl.kind, VariableDeclarationKind::Const);
for d in &decl.declarations {
self.predeclare_lexical_pattern(&d.id, is_const, ctx, global_lexical)?;
}
}
Statement::ClassDeclaration(cd) => {
if let Some(id) = &cd.id {
check_restricted_global_lexical(id.name.as_str(), global_lexical)?;
let reg = ctx.alloc_reg();
let _ = ctx.declare_predeclared(id.name.as_str(), reg, VariableDeclarationKind::Const, true);
}
}
_ => {}
}
Ok(())
}
/// 递归预声明绑定 pattern 内的全部标识符(含数组/对象/默认值解构)。
fn predeclare_lexical_pattern(
&self, pattern: &BindingPattern, is_const: bool, ctx: &mut CompileCtx, global_lexical: bool,
+9 -2
View File
@@ -48,7 +48,13 @@ impl Emitter {
/// # 副作用
/// - 当前指令流在块入口发 CREATE_CLOSURE(及 MAKE_CELL / STORE_VAR);
/// 物化登记写入 `block_fn_entry_mats` 栈顶帧。
fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> {
///
/// # 注意事项
/// - 同名重复的直接子声明只登记首个节点:该名任一直接子声明点都复用块槽
/// (取其当前值),而非重编本节点闭包——块绑定入口值按源序末次物化,
/// 复用槽才与外层 var 写回后的块槽值一致(仅登记末次会让较早声明点把
/// 本节点闭包写回外层 var,与规范的块绑定写回口径不符)。
pub(crate) fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> {
match stmt {
Statement::FunctionDeclaration(fd) => {
let Some(id) = &fd.id else {
@@ -61,7 +67,8 @@ impl Emitter {
ctx.block_fn_entry_mats
.last_mut()
.unwrap()
.insert(name, &**fd as *const _ as *const ());
.entry(name)
.or_insert_with(|| &**fd as *const _ as *const ());
Ok(())
}
Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx),
+23
View File
@@ -1,4 +1,8 @@
//! switch 语句 emit:case 匹配链 + 穿落(fallthrough)与 default,见 `emit_switch_statement`。
//! CaseBlock 有独立块作用域:case 选择表达式与全部 case 体共享同一环境,case 内
//! 函数/词法声明不泄漏到 switch 外。
use std::collections::HashMap;
use crate::{CompileCtx, Emitter};
use oxide_bytecode::opcode::OpCode;
@@ -13,8 +17,25 @@ impl Emitter {
};
let end_label = ctx.next_label_id();
ctx.push_switch(end_label);
// 判别式在 switch 外层词法环境求值,CaseBlock 环境尚未建立。
let disc_reg = self.emit_expression(&sw.discriminant, ctx)?;
let cases = &sw.cases;
// CaseBlock 环境:case 选择表达式与全部 case 体同处一个块作用域。
// 块函数预声明先于 lexical,使 `case 0: let g; function g(){}` 的 lexical
// 占位命中已存在的函数绑定,报重复声明错。
ctx.push_scope();
for case in cases.iter() {
self.predeclare_block_function_declarations(&case.consequent, ctx, false);
self.predeclare_lexical_declarations(&case.consequent, ctx, false)?;
}
// case 内函数声明的块入口初始化:同名重复声明按源序物化,末次声明成为
// 入口值;声明点复用块槽写回外层 var(见 emit_function_declaration)。
ctx.block_fn_entry_mats.push(HashMap::new());
for case in cases.iter() {
for s in &case.consequent {
self.emit_block_fn_entry_init_stmt(s, ctx)?;
}
}
let mut case_labels = Vec::with_capacity(cases.len());
for case in cases.iter() {
let case_label = ctx.next_label_id();
@@ -45,7 +66,9 @@ impl Emitter {
}
}
}
ctx.block_fn_entry_mats.pop();
ctx.labels.set_label_pos(end_label, ctx.insts.len());
ctx.pop_scope();
ctx.pop_switch();
// switch 完成值 = 已执行 case 链最后一次非空完成值(源序最后非空语句的
// 收敛寄存器:命中后穿落到 break/尾即为其值,无命中则该寄存器不被写入而