feat(switch): 新增 switch 语句并禁止 match 内 fallthrough

- vast/Nodes.py:Match 增加 is_switch 字段(含默认值)
- vast/Parser.py:新增 _ParseSwitch(case <常量|枚举变体> / default:,desugar 为 Match(is_switch=True));match / struct / fallthrough 改为硬关键字
- vast/Unparser.py:round-trip 忠实还原 switch 与 default:
- HandlesMatch / HandlesBody:新增 in_switch_case 门控,fallthrough 只允许出现在 switch 的 case 体内
- HandlesFor:补齐嵌套函数的 saved_loop_break/continue_targets 定义(修 NameError),并隔离 in_switch_case
- LLVMCG/BaseGen:新增 in_switch_case 状态
This commit is contained in:
TermiNexus committed 2026-10-02 23:15:42 +08:00
1 parent 8b6c36d24f
commit 74cfaeb917
7 files changed
+187 -97

No files matched your search

+11 -6
View File
@@ -19,11 +19,16 @@ class BodyHandle(BaseHandle):
Gen._set_node_info(Node)
try:
if isinstance(Node, ast.Expr):
# `fallthrough`(vast 软关键字已脱糖为 c.NoBreak())是 match 的
# 标记语句,本身不产出 IR,语义由 HandlesMatch 消费;后续步骤
# (含 _EmitTempFrees)保持原样执行。
# `fallthrough`(vast 关键字已脱糖为 c.NoBreak())是 switch 的
# 标记语句,本身不产出 IR,语义由 HandlesMatch 消费(case 体因此
# 不自动跳出 switch);合法位置**只有 switch 的 case 体内**,
# match(完全禁止 fallthrough)或 switch 之外一律 fail-fast。
if self._IsNoBreakMarkerStmt(Node):
pass
if not Gen.in_switch_case:
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
"fallthrough 只能用在 switch 的 case 体内"
"(match 完全禁止 fallthrough)")
else:
self.HandleExprLlvm(Node.value)
elif isinstance(Node, ast.Return):
@@ -42,11 +47,11 @@ class BodyHandle(BaseHandle):
self.Trans.WhileHandler._HandleWhileLlvm(Node)
elif isinstance(Node, ast.Break):
if not Gen.loop_break_targets:
# fail-fast:循环 / match-case 之外的 break 无处可跳,
# fail-fast:循环 / switch / match-case 之外的 break 无处可跳,
# 静默忽略只会让"提前退出"变成无声无效果(不兜底)
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
"break 只能用在循环或 match-case 体内(当前没有可跳出的目标)")
"break 只能用在循环或 switch/match 的 case 体内(当前没有可跳出的目标)")
Gen.builder.branch(Gen.loop_break_targets[-1])
elif isinstance(Node, ast.Continue):
if not Gen.loop_continue_targets:
+10
View File
@@ -403,6 +403,15 @@ class ForHandle(BaseHandle):
Gen.nonlocal_params[Node.name] = [(name, _safe_ptr_type(ptr_type)) for name, ptr_type in extra_params]
# Push current variables to scope stack before compiling nested function
Gen._variable_scope_stack.append(Gen.variables.copy())
# 嵌套函数体拥有**独立的**跳转目标:进入前暂存外层循环/switch 目标并清空,
# 退出后恢复。否则嵌套函数里的 break/continue/fallthrough 会误跳外层循环
# 或误认为仍在 switch 的 case 体内。
saved_loop_break_targets: list = Gen.loop_break_targets
saved_loop_continue_targets: list = Gen.loop_continue_targets
saved_in_switch_case: bool = Gen.in_switch_case
Gen.loop_break_targets = []
Gen.loop_continue_targets = []
Gen.in_switch_case = False
saved_builder: ir.IRBuilder = Gen.builder
saved_func: ir.Function = Gen.func
saved_variables: dict[str, ir.Value] = Gen.variables.copy()
@@ -419,6 +428,7 @@ class ForHandle(BaseHandle):
Gen.func = saved_func
Gen.loop_break_targets = saved_loop_break_targets
Gen.loop_continue_targets = saved_loop_continue_targets
Gen.in_switch_case = saved_in_switch_case
Gen.variables = saved_variables
Gen._reg_values = saved_reg_values
Gen.global_vars = saved_global_vars
+50 -52
View File
@@ -37,8 +37,11 @@ class MatchHandle(BaseHandle):
def _HandleMatchLlvm(self, Node: ast.Match) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
# 重名 case 检测(fail-fast):C 的 switch 不允许重复 case 标签——重复标签会让
# 某个臂永远不可达,且通常意味着漏改的分支;`case _`(default)同样只允许一个。
# 在产出任何 IR 之前检查,保证报错即终止。
# 某个臂永远不可达,且通常意味着漏改的分支;通配臂(match 的 `case _` /
# switch 的 `default`)同样只允许一个。
# 在产出任何 IR 之前检查,保证报错即终止。报文前缀按 is_switch 区分。
KindLabel: str = 'switch' if Node.is_switch else 'match'
DefaultLabel: str = '`default`' if Node.is_switch else '`case _`'
SeenLabels: dict[str, int] = {}
DefaultCount: int = 0
for ci, case in enumerate(Node.cases):
@@ -50,13 +53,13 @@ class MatchHandle(BaseHandle):
if Key in SeenLabels:
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"match 出现重名 case:标签 '{Key}' 已在第 {SeenLabels[Key]} 个 case 使用,"
f"{KindLabel} 出现重名 case:标签 '{Key}' 已在第 {SeenLabels[Key]} 个 case 使用,"
f"第 {ci} 个 case 重复(C switch 不允许重复 case 标签)")
SeenLabels[Key] = ci
if DefaultCount > 1:
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"match 出现 {DefaultCount} 个 `case _`(default 只允许一个)")
f"{KindLabel} 出现 {DefaultCount} 个 {DefaultLabel}(通配/default 只允许一个)")
SubjectVal: ir.Value | None = self.HandleExprLlvm(Node.subject)
if not SubjectVal:
return
@@ -230,22 +233,7 @@ class MatchHandle(BaseHandle):
else:
# 变体名未找到,跳过此 case(fast-fail 已在外层处理)
pass
def _HasNoBreak(stmts: list[ast.stmt]) -> bool:
for stmt in stmts:
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
if isinstance(stmt.value.func, ast.Attribute):
if (isinstance(stmt.value.func.value, ast.Name) and
stmt.value.func.value.id == 'c' and
stmt.value.func.attr == 'NoBreak'):
return True
if getattr(stmt, 'body', None) and isinstance(stmt.body, list):
if _HasNoBreak(stmt.body):
return True
if getattr(stmt, 'orelse', None):
if isinstance(stmt.orelse, list) and _HasNoBreak(stmt.orelse):
return True
return False
HasNoBreak.append(_HasNoBreak(case.body) if case.body else False)
HasNoBreak.append(MatchHandle._HasNoBreakMarker(case.body) if case.body else False)
if not HasDefault:
CaseBBs.append(DefaultBB)
SwitchCases: list[tuple[ir.Value, ir.Block]] = [(val, bb) for val, bb in zip(CaseValues, CaseBBs) if bb != DefaultBB]
@@ -260,7 +248,7 @@ class MatchHandle(BaseHandle):
for j in range(NumSubCases):
if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB:
Gen.builder.position_at_start(CaseBBs[CaseIdx])
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -270,7 +258,7 @@ class MatchHandle(BaseHandle):
elif isinstance(pattern, (ast.MatchValue, ast.MatchSingleton)):
if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB:
Gen.builder.position_at_start(CaseBBs[CaseIdx])
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -280,7 +268,7 @@ class MatchHandle(BaseHandle):
elif isinstance(pattern, ast.MatchAs):
if pattern.pattern is None:
Gen.builder.position_at_start(DefaultBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -289,7 +277,7 @@ class MatchHandle(BaseHandle):
CaseIdx += 1
elif isinstance(pattern, ast.MatchSequence):
Gen.builder.position_at_start(DefaultBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -301,7 +289,7 @@ class MatchHandle(BaseHandle):
# case 收集阶段已创建 CaseBB,此处生成 body
if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB:
Gen.builder.position_at_start(CaseBBs[CaseIdx])
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -316,26 +304,51 @@ class MatchHandle(BaseHandle):
Gen.builder.branch(AfterBB)
Gen.builder.position_at_start(AfterBB)
def _HandleMatchCaseBody(self, Gen: "Translator.LlvmGen",
Body: list[ast.stmt], AfterBB: ir.Block) -> None:
"""翻译 match case 体:case 体内的 break 目标是 match 的 AfterBB
@staticmethod
def _HasNoBreakMarker(Stmts: list[ast.stmt]) -> bool:
"""case 体(含其嵌套 body/orelse)是否含 `fallthrough` 标记(c.NoBreak())"""
for stmt in Stmts:
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
if isinstance(stmt.value.func, ast.Attribute):
if (isinstance(stmt.value.func.value, ast.Name) and
stmt.value.func.value.id == 'c' and
stmt.value.func.attr == 'NoBreak'):
return True
NestedBody: Any = getattr(stmt, 'body', None)
if NestedBody and isinstance(NestedBody, list) and MatchHandle._HasNoBreakMarker(NestedBody):
return True
NestedElse: Any = getattr(stmt, 'orelse', None)
if NestedElse and isinstance(NestedElse, list) and MatchHandle._HasNoBreakMarker(NestedElse):
return True
return False
C switch 语义——case 内 break 跳出 **match**(而非外层循环)。故翻译前把
AfterBB 压入 loop_break_targets,翻译完弹出(外层循环目标不受影响)。
def _HandleMatchCaseBody(self, Gen: "Translator.LlvmGen",
Body: list[ast.stmt], AfterBB: ir.Block,
IsSwitch: bool) -> None:
"""翻译一个 case 体:case 体内的 break 目标是 AfterBB(C switch 语义)
- case 内 break 跳出 **match/switch**(而非外层循环):翻译前把 AfterBB
压入 loop_break_targets,翻译完弹出(外层循环目标不受影响)。
- 同时把 Gen.in_switch_case 置为 IsSwitch(进出恢复):供 `fallthrough`
标记语句判定合法性 —— 只有 switch 的 case 体内才允许,写进 match
(完全禁止 fallthrough)或 switch 之外由 HandlesBody fail-fast。
"""
Targets: list = Gen.loop_break_targets
Targets.append(AfterBB)
PrevInSwitch: bool = Gen.in_switch_case
Gen.in_switch_case = IsSwitch
try:
self.HandleBodyLlvm(Body)
finally:
Gen.in_switch_case = PrevInSwitch
Targets.pop()
@staticmethod
def _FallThroughTarget(CaseBBs: list[ir.Block], CaseIdx: int, AfterBB: ir.Block) -> ir.Block:
"""fallthrough 的落点:下一个 case 的 body(无后继槽位时落到 AfterBB)
`fallthrough`(vast 软关键字,脱糖为 c.NoBreak())表示"本 case 不自动
break",直接落到下一个 case 的 body(对齐 C 的 [[fallthrough]])。
`fallthrough`(vast 关键字,脱糖为 c.NoBreak())表示"本 case 不自动
跳出 switch",直接落到下一个 case 的 body(对齐 C 的 [[fallthrough]])。
"""
NextIdx: int = CaseIdx + 1
if 0 <= NextIdx < len(CaseBBs):
@@ -457,22 +470,7 @@ class MatchHandle(BaseHandle):
HasDefault = True
CaseBBs.append(DefaultBB)
CaseBindings.append([])
def _HasNoBreak(stmts: list[ast.stmt]) -> bool:
for stmt in stmts:
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
if isinstance(stmt.value.func, ast.Attribute):
if (isinstance(stmt.value.func.value, ast.Name) and
stmt.value.func.value.id == 'c' and
stmt.value.func.attr == 'NoBreak'):
return True
if getattr(stmt, 'body', None) and isinstance(stmt.body, list):
if _HasNoBreak(stmt.body):
return True
if getattr(stmt, 'orelse', None):
if isinstance(stmt.orelse, list) and _HasNoBreak(stmt.orelse):
return True
return False
HasNoBreak.append(_HasNoBreak(case.body) if case.body else False)
HasNoBreak.append(MatchHandle._HasNoBreakMarker(case.body) if case.body else False)
if not HasDefault:
CaseBBs.append(DefaultBB)
SwitchCases: list[tuple[ir.Value, ir.Block]] = [(val, bb) for val, bb in zip(CaseValues, CaseBBs) if bb != DefaultBB]
@@ -512,7 +510,7 @@ class MatchHandle(BaseHandle):
if isinstance(elem_ptr.type, ir.PointerType) and elem_ptr.type.pointee != member_type:
elem_ptr = Gen.builder.bitcast(elem_ptr, ir.PointerType(member_type), name=f"match_{bind_name}_cast")
Gen.variables[bind_name] = elem_ptr
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
for bind_name, _, _, _ in bindings:
if bind_name in Gen.variables:
del Gen.variables[bind_name]
@@ -525,7 +523,7 @@ class MatchHandle(BaseHandle):
elif isinstance(pattern, ast.MatchValue):
if CaseIdx < len(CaseBBs) and CaseBBs[CaseIdx] != DefaultBB:
Gen.builder.position_at_start(CaseBBs[CaseIdx])
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -535,7 +533,7 @@ class MatchHandle(BaseHandle):
elif isinstance(pattern, ast.MatchAs):
if pattern.pattern is None:
Gen.builder.position_at_start(DefaultBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
@@ -544,7 +542,7 @@ class MatchHandle(BaseHandle):
CaseIdx += 1
elif isinstance(pattern, ast.MatchSequence):
Gen.builder.position_at_start(DefaultBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB)
self._HandleMatchCaseBody(Gen, case.body, AfterBB, Node.is_switch)
if not Gen.builder.block.is_terminated:
if not HasNoBreak[i]:
Gen.builder.branch(AfterBB)
+4
View File
@@ -350,6 +350,10 @@ class BaseGenMixin:
self.class_member_annotations: dict[str, dict[str, Any]] = {}
self.loop_break_targets: list[ir.Block] = []
self.loop_continue_targets: list[ir.Block] = []
# 当前是否正处于 switch 的 case 语句体内(True=是)。供 `fallthrough`
# 标记语句(vast 脱糖为 c.NoBreak())判定合法性:只在 switch 的 case 体内
# 合法;写进 match(完全禁止 fallthrough)或 switch 之外一律 fail-fast。
self.in_switch_case: bool = False
self.eh_jmp_buf_stack: list[ir.Value] = []
self.eh_except_block_stack: list[ir.Block] = []
self.eh_finally_stack: list[Any] = []