From 43d25e58b9de7884ad0c920b28eda38dcdb974db Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 2 Oct 2026 22:33:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(match):=20case=20=E5=86=85=20break=20?= =?UTF-8?q?=E8=B7=B3=E5=87=BA=20match=EF=BC=8C=E5=B9=B6=E8=90=BD=E5=9C=B0?= =?UTF-8?q?=20fallthrough?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Translator:新增 _in_match_case(FallThrough 合法性判定用);_break_bb 注释补充 case 内 break 语义 - StmtGenDeclMixin:新增 translate_fallthrough(本身不产出 IR;非 case 体内 fail-fast) - StmtGenMixin:FallThrough 语句分派 - MatchGenMixin:进出 case 体(含 default 臂)时暂存/覆盖 _break_bb = match end_bb, 使 case 内 break 跳出 match 而非外层循环;_has_no_break 兼容 FallThrough 标记; REnum / simple 两条 switch 路径的 fallthrough 由"只留无终结符块"改为显式 br 下一个 case body(原实现产出非法 IR) --- App/lib/core/Gen/MatchGenMixin.vp | 90 +++++++++++++++++++++-- App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp | 14 ++++ App/lib/core/Gen/Stmt/StmtGenMixin.vp | 2 + App/lib/core/Gen/Translator.vp | 9 ++- 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/App/lib/core/Gen/MatchGenMixin.vp b/App/lib/core/Gen/MatchGenMixin.vp index 6cc8985..af523d2 100644 --- a/App/lib/core/Gen/MatchGenMixin.vp +++ b/App/lib/core/Gen/MatchGenMixin.vp @@ -215,9 +215,11 @@ novirtual class MatchGenMixin: return self.Gen.ClassGenEnum._lookup_renum_variant(variant_name) # ============================================================ - # _has_no_break - 检测 case body 是否含 c.NoBreak() 调用 + # _has_no_break - 检测 case body 是否要求 fallthrough(落到下一个 case) # - # 遍历 case body 顶层语句,查找 Expr(Call(Attribute(Name('c'), 'NoBreak'))) + # 两种写法等价: + # ① 软关键字语句 `fallthrough`(ASTKind.FallThrough) + # ② 兼容写法 `c.NoBreak()`:Expr(Call(Attribute(Name('c'), 'NoBreak'))) # ============================================================ def _has_no_break(self, body_list: list[ast.AST*]*) -> int: if body_list is None: @@ -226,6 +228,8 @@ novirtual class MatchGenMixin: i: int = 0 while i < n: stmt: ast.AST* = body_list.get(i) + if stmt is not None and stmt.kind() == ast.ASTKind.FallThrough: + return 1 if stmt is not None and stmt.kind() == ast.ASTKind.Expr: expr_node: rawptr[ast.Expr] = rawptr[ast.Expr](stmt) val: ast.AST* = expr_node.value @@ -560,6 +564,13 @@ novirtual class MatchGenMixin: # 定位到 case BB llvmlite.position_at_end(builder, case_bb) + # case 体内 break = 跳出 match(C switch 语义):暂存外层循环的 + # break 目标并覆盖为 match 的 end_bb,出 case 时恢复。 + rmb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + rmb_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 + # 进入 case 作用域(变体字段绑定隔离) self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) @@ -606,15 +617,29 @@ novirtual class MatchGenMixin: self.Gen.StmtGen.translate_stmt(trans, stmt) bi = bi + 1 - # 退出 case 作用域 + # 退出 case 作用域,并恢复 break 目标 / match-case 标记 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = rmb_old_break + trans._in_match_case = rmb_old_inmc - # 若未终止且无 NoBreak,跳到 end + # 若未终止:默认跳 end;含 fallthrough(`fallthrough` 标记或兼容的 + # c.NoBreak())则落到下一个 case 的 body(跳过其模式检查)。 # 末句控制流全 return(结构性不可达)时补 unreachable,不 br end_bb if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ - and nb == 0 \ and self._case_tail_unreachable(trans, builder, body_list) == 0: - llvmlite.build_br(builder, end_bb) + if nb == 0: + llvmlite.build_br(builder, end_bb) + else: + rmb_ft_nj: int = i + 1 + while rmb_ft_nj < collected and def_arr[rmb_ft_nj] != 0: + rmb_ft_nj = rmb_ft_nj + 1 + if rmb_ft_nj < collected: + rmb_ft_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](bb_arr[rmb_ft_nj]) + llvmlite.build_br(builder, rmb_ft_bb) + elif has_default != 0: + llvmlite.build_br(builder, default_bb) + else: + llvmlite.build_br(builder, end_bb) trans._ctrl_merge_reachable = 1 rm_merge_reach = 1 @@ -633,6 +658,11 @@ novirtual class MatchGenMixin: # (不再经 def_arr 槽位反查 cases_list:collected 下标与原始下标 # 在"有臂被跳过"时会错位,default_bb 随之空转) dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node) + # default 臂同样按 match-case 语义:break = 跳出 match + rmd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + rmd_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) # dbody 预声明+重赋值会使 .get 误分派成 list[int].get # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。 @@ -647,6 +677,8 @@ novirtual class MatchGenMixin: self.Gen.StmtGen.translate_stmt(trans, stmt3) dj3 = dj3 + 1 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = rmd_old_break + trans._in_match_case = rmd_old_inmc # 末句控制流全 return → 补 unreachable(不 br end_bb) if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ and self._case_tail_unreachable(trans, builder, dbody3) == 0: @@ -1024,6 +1056,11 @@ novirtual class MatchGenMixin: llvmlite.position_at_end(builder, bbb3) cnode2: ast.AST* = cases_list.get(ci) mc3: rawptr[ast.MatchCase] = (rawptr[ast.MatchCase])(cnode2) + # case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb + sqb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + sqb_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) body_list2: list[ast.AST*]* = mc3.children if body_list2 is not None: @@ -1035,6 +1072,8 @@ novirtual class MatchGenMixin: self.Gen.StmtGen.translate_stmt(trans, stmt) bi = bi + 1 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = sqb_old_break + trans._in_match_case = sqb_old_inmc # 末句控制流全 return → 补 unreachable(不 br end,无 fallthrough) if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ and self._case_tail_unreachable(trans, builder, body_list2) == 0: @@ -1063,6 +1102,11 @@ novirtual class MatchGenMixin: # 直接翻译 pass1 记录的 `case _` 臂(不再经 def_arr 槽位反查 cases_list: # collected 下标与原始下标在"有臂被跳过"时会错位,default_bb 随之空转) dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node) + # default 臂同样按 match-case 语义:break = 跳出 match + sqd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + sqd_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) # dbody 预声明+重赋值会使 .get 误分派成 list[int].get # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。 @@ -1078,6 +1122,8 @@ novirtual class MatchGenMixin: self.Gen.StmtGen.translate_stmt(trans, stmt3) dj3 = dj3 + 1 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = sqd_old_break + trans._in_match_case = sqd_old_inmc # 末句控制流全 return → 补 unreachable(不 br end_bb) if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ and self._case_tail_unreachable(trans, builder, dbody) == 0: @@ -1267,6 +1313,12 @@ novirtual class MatchGenMixin: llvmlite.position_at_end(builder, case_bb) + # case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb + smb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + smb_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 + self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) body_list: list[ast.AST*]* = mc.children @@ -1280,12 +1332,27 @@ novirtual class MatchGenMixin: bi = bi + 1 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = smb_old_break + trans._in_match_case = smb_old_inmc + # 若未终止:默认跳 end;含 fallthrough(`fallthrough` 标记或兼容的 + # c.NoBreak())则落到下一个 case 的 body(跳过其模式检查)。 # 末句控制流全 return → 补 unreachable(不 br end_bb) if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ - and nb == 0 \ and self._case_tail_unreachable(trans, builder, body_list) == 0: - llvmlite.build_br(builder, end_bb) + if nb == 0: + llvmlite.build_br(builder, end_bb) + else: + sm_ft_nj: int = i + 1 + while sm_ft_nj < collected and def_arr[sm_ft_nj] != 0: + sm_ft_nj = sm_ft_nj + 1 + if sm_ft_nj < collected: + sm_ft_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](bb_arr[sm_ft_nj]) + llvmlite.build_br(builder, sm_ft_bb) + elif has_default != 0: + llvmlite.build_br(builder, default_bb) + else: + llvmlite.build_br(builder, end_bb) trans._ctrl_merge_reachable = 1 sm_merge_reach = 1 @@ -1303,6 +1370,11 @@ novirtual class MatchGenMixin: # 直接翻译 pass1 记录的 `case _` 臂(不再经 def_arr 槽位反查 cases_list: # collected 下标与原始下标在"有臂被跳过"时会错位,default_bb 随之空转) dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node) + # default 臂同样按 match-case 语义:break = 跳出 match + smd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb + trans._break_bb = end_bb + smd_old_inmc: int = trans._in_match_case + trans._in_match_case = 1 self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH) # dbody 预声明+重赋值会使 .get 误分派成 list[int].get # (i32→inttoptr 截断指针 → translate_stmt 读垃圾节点崩溃,gen1 IR 证据)。 @@ -1317,6 +1389,8 @@ novirtual class MatchGenMixin: self.Gen.StmtGen.translate_stmt(trans, stmt3) dj3 = dj3 + 1 self.Gen.VarGen.exit_scope(trans.SymTab) + trans._break_bb = smd_old_break + trans._in_match_case = smd_old_inmc # 末句控制流全 return → 补 unreachable(不 br end_bb) if llvmlite.builder_cur_block_is_terminated(builder) == 0 \ and self._case_tail_unreachable(trans, builder, dbody3) == 0: diff --git a/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp b/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp index fcc7026..5a28340 100644 --- a/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenDeclMixin.vp @@ -280,6 +280,20 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin): llvmlite.position_at_end(builder, dead_bb) return 0 + def translate_fallthrough(self, trans: HT.Translator*, node: ast.AST*) -> int: + """翻译 `fallthrough` 标记语句:本身不产出任何 IR + + 语义由 match 生成侧消费——case 体含该标记时末尾不自动 break,直接落到 + 下一个 case 体(对齐 C 的 [[fallthrough]])。合法位置只有 match-case + 语句体;出现在别处只会静默无效果,故 fail-fast 暴露。 + """ + if trans is None: + return 0 + if trans._in_match_case == 0: + self.Gen.TypeGenErr.fatal_error(node, + "fallthrough 只能用在 match-case 语句体内(语义是落到下一个 case)", "SyntaxError") + return 0 + def translate_continue(self, trans: HT.Translator*) -> int: """翻译 continue 语句:跳转到循环 cond/incr 块""" builder: llvmlite.IRBuilder* = trans._cur_builder diff --git a/App/lib/core/Gen/Stmt/StmtGenMixin.vp b/App/lib/core/Gen/Stmt/StmtGenMixin.vp index 4ae1163..e0a61ec 100644 --- a/App/lib/core/Gen/Stmt/StmtGenMixin.vp +++ b/App/lib/core/Gen/Stmt/StmtGenMixin.vp @@ -90,6 +90,8 @@ novirtual class StmtGenMixin(StmtGenDeclMixin.StmtGenDeclMixin): return self.translate_break(trans) elif k == ast.ASTKind.Continue: return self.translate_continue(trans) + elif k == ast.ASTKind.FallThrough: + return self.translate_fallthrough(trans, node) elif k == ast.ASTKind.Match: return self.Gen.MatchGen.translate_match(trans, node) elif k == ast.ASTKind.Delete: diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index 3f7278f..be24d68 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -170,10 +170,16 @@ novirtual class Translator: # 的直接值」时才用 CurFuncRetAnnot(嵌套调用如 return f(Stack()) 不推断)。 CurRetNode: ast.AST* - # 循环控制流目标(break/continue) + # 循环控制流目标(break/continue)。注意 match-case 体内的 break 语义是 + # "跳出 match":MatchGenMixin 进出 case 体时暂存并把 _break_bb 覆盖为 match + # 的 end_bb(出 case 恢复),故 case 内 break 不会误跳外层循环。 _break_bb: rawptr[llvmlite.BasicBlock] _continue_bb: rawptr[llvmlite.BasicBlock] + # 是否正处于 match-case 语句体内(1=是)。供 `fallthrough` 语句判定合法性 + # (只在 case 体内合法,别处 fail-fast),由 MatchGenMixin 进出 case 时置位。 + _in_match_case: int + # 独立标签计数器(不与 builder.Counter 共享,避免 SSA 编号非单调) _label_counter: int @@ -291,6 +297,7 @@ novirtual class Translator: self._cur_builder = None self._break_bb = None self._continue_bb = None + self._in_match_case = 0 self._label_counter = 0 self._imported_modules = None self._from_imports = None