feat(match): 支持 match/switch 守卫(case X if cond)

通配计数只统计无守卫的 case _,允许多个带守卫的 case _ if ...;_handle_simple_match 预扫描 has_guard,有守卫时改走 _handle_guarded_simple_match(按书写顺序的 if-else 比较链,guard 归一为 i1 后与值比较取与,链尾落 default/end),无守卫时保持原 switch 分派路径逐字不变(零回归);_handle_renum_match 对带守卫臂 fail-fast,避免 switch(tag) 静默忽略 guard。
This commit is contained in:
TermiNexus committed 2026-10-03 10:58:47 +08:00
1 parent 67667b10db
commit 19d7a90c28
1 file changed
+325 -3
+325 -3
View File
@@ -93,7 +93,9 @@ novirtual class MatchGenMixin:
# 重名 case 检测(fail-fast):**match 不允许重复 case 标签**(重复标签让
# 某个臂永远不可达,且常意味着漏改的分支);**switch 允许重复标签**(按书写
# 顺序,先出现者生效),故仅对 match 做值标签去重。
# 通配臂(match 的 `case _` / switch 的 `default`)两者都只允许一个。
# 通配臂(match 的 `case _` / switch 的 `default`):**无 guard** 的通配臂
# 两者都只允许一个(真正的 default);**带 guard** 的通配臂(`case _ if ...`)
# 允许多个(唯一性由各自的 guard 区分,见 _handle_guarded_simple_match)。
# 在任何 IR 产出之前检查,保证报错即终止。报文前缀按 is_switch 区分。
kind_label: str = "match"
default_label: str = "`case _`"
@@ -112,7 +114,10 @@ novirtual class MatchGenMixin:
if dpat is not None:
if dpat.kind() == ast.ASTKind.MatchAs \
and rawptr[ast.MatchAs](dpat).pattern is None:
default_cnt = default_cnt + 1
# 只统计**无 guard** 的通配臂:带 guard 的 `case _ if ...`
# 允许多个(各自 guard 区分),不当 default 计
if rawptr[ast.MatchCase](dcase).guard is None:
default_cnt = default_cnt + 1
elif m_node.is_switch == 0:
dcj: int = dci + 1
while dcj < ccn:
@@ -127,7 +132,7 @@ novirtual class MatchGenMixin:
dci = dci + 1
if default_cnt > 1:
self.Gen.TypeGenErr.fatal_error(node,
f"{kind_label} 出现 {default_cnt} 个 {default_label}(通配/default 只允许一个)", "SyntaxError")
f"{kind_label} 出现 {default_cnt} 个{default_label}(无 guard 的通配/default 只允许一个;带 guard 的 `case _ if ...` / `default if ...` 可有多个)", "SyntaxError")
# 1. 求值 subject
subject_node: ast.AST* = m_node.subject
@@ -375,6 +380,22 @@ novirtual class MatchGenMixin:
# 函数里会使本函数引用退化为隐式全局,跨 match 污染)。
rm_merge_reach: int = 0
# guard 守卫(match/switch 分支条件守卫):本路径用 switch(tag) 一次性分发,
# **不评估 guard**——带 guard 的臂会被静默忽略(可能选错分支)。禁止静默兜底:
# 一旦存在带 guard 的臂,在任何 IR 产出之前 fail-fast 报错。
rn_cases: list[ast.AST*]* = node.cases
if rn_cases is not None:
rn_n: sizet = len(rn_cases)
rn_i: int = 0
while rn_i < rn_n:
rn_case: ast.AST* = rn_cases.get(rn_i)
if rn_case is not None and rn_case.kind() == ast.ASTKind.MatchCase:
if rawptr[ast.MatchCase](rn_case).guard is not None:
self.Gen.TypeGenErr.fatal_error(rn_case,
"REnum match 暂不支持 guard 守卫(该臂会被 switch(tag) 静默忽略);请改为普通 match 或去掉 guard",
"NotImplementedError")
rn_i = rn_i + 1
# 0. subject 归一化:确保 subject_ptr 是指向 REnum 结构体的指针
# (对齐 TPC HandlesMatch.py:238-259 的 _HandleRenumMatchLlvm)
# 情况1: subject_ptr 是 T**(变量 alloca)→ load 一次得 T*
@@ -1021,6 +1042,13 @@ novirtual class MatchGenMixin:
if pk == ast.ASTKind.MatchAs:
ma0: rawptr[ast.MatchAs] = rawptr[ast.MatchAs](pat)
if ma0.pattern is None:
# 顶层 _ 通配臂带 guard(`case _ if <cond>`):本序列路径把
# `_` 一律当 default(恒真)处理,会**静默忽略 guard**。禁止
# 静默兜底 → 在任何 IR 产出之前 fail-fast。
if mc.guard is not None:
self.Gen.TypeGenErr.fatal_error(pat,
"序列匹配的顶层 `case _ if <cond>` 暂不支持 guard 守卫",
"NotImplementedError")
is_def = 1
elif pk != ast.ASTKind.MatchSequence:
# fail-fast:序列匹配只接受 MatchSequence / 顶层 _ 通配
@@ -1272,6 +1300,24 @@ novirtual class MatchGenMixin:
# fail-fast: subject 转 i32 失败
self.Gen.TypeGenErr.fatal_error(node, "match subject 转换为 i32 失败", "TypeError")
# 1b. guard 预扫描:任一臂带 guard → 改走按书写顺序的 if-else 比较链
# (Python match 语义:按序取首个"模式匹配且 guard 为真"的臂,见
# _handle_guarded_simple_match)。**无 guard 时**保持下方既有 switch 分发
# 路径逐字不变(不得有任何行为变化)。
cases_g: list[ast.AST*]* = node.cases
has_guard: int = 0
if cases_g is not None:
cg_n: sizet = len(cases_g)
cg_i: int = 0
while cg_i < cg_n:
cg_case: ast.AST* = cases_g.get(cg_i)
if cg_case is not None and cg_case.kind() == ast.ASTKind.MatchCase:
if rawptr[ast.MatchCase](cg_case).guard is not None:
has_guard = 1
cg_i = cg_i + 1
if has_guard != 0:
return self._handle_guarded_simple_match(trans, node, subj_val)
# 2. 创建 default/end BB
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
@@ -1507,3 +1553,279 @@ novirtual class MatchGenMixin:
# 覆盖嵌套控制流污染(同主路径)
trans._ctrl_merge_reachable = sm_merge_reach
return 0
# ============================================================
# _handle_guarded_simple_match - _handle_simple_match 的 guard 分支
#
# 任一臂带 guard 时改走**按书写顺序**的 if-else 比较链分发(对齐 Python
# match 语义:按序取首个"模式匹配且 guard 为真"的臂):
# entry → check0
# check_i: cond_i → body_i(不满足 → check_{i+1})
# body_i : <body> → end(fallthrough 时落到下一个非通配 body)
# 全部 check 不中 → default_bb(唯一的**无 guard** 通配臂 body,若有)→ end
# cond_i = 值臂(subj_val == case值) / 通配臂(恒真),再与 guard(归一为 i1)取与。
# guard 归一化、i1 判定、const_int 参数顺序(pool, bits, val)均与
# _handle_sequence_match 同款(L1118-1135)。
# 注意:**无 guard** 的 match/switch 不会进入本函数(见 _handle_simple_match
# 的 has_guard 预扫描),既有 switch 分派路径逐字不变。
# ============================================================
def _handle_guarded_simple_match(self, trans: HT.Translator*,
node: ast.Match*,
subj_val: llvmlite.Value*) -> int:
pool: memhub.MemBuddy* = trans.Pool
builder: llvmlite.IRBuilder* = trans._cur_builder
func: llvmlite.Function* = trans._cur_func
# 同主路径:局部记录本 guarded match 的 br end_bb,收尾覆盖
# _ctrl_merge_reachable,消除 case 体内嵌套控制流污染
gm_merge_reach: int = 0
# 1. 创建 default/end BB
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
if name_buf is None:
self.Gen.TypeGenErr.fatal_error(node, "标签缓冲区分配失败", "TypeError")
viperlib.snprintf(name_buf, 32, "match.guard.default.%d", cnt)
default_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.guard.end.%d", cnt)
end_bb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
# 2. 收集 case 信息(check/body BB、default 标记、nobreak、mc、cv)
cases_list: list[ast.AST*]* = node.cases
if cases_list is None:
self.Gen.TypeGenErr.fatal_error(node, "match cases 缺失", "TypeError")
case_count: sizet = len(cases_list)
check_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
body_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
def_arr: int | t.CPtr = pool.alloc(4 * MAX_MATCH_CASES)
nb_arr: int | t.CPtr = pool.alloc(4 * MAX_MATCH_CASES)
mc_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
cv_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
if check_arr is None or body_arr is None or def_arr is None \
or nb_arr is None or mc_arr is None or cv_arr is None:
self.Gen.TypeGenErr.fatal_error(node, "match 分派数组分配失败", "TypeError")
collected: int = 0
has_default: int = 0
# 唯一的**无 guard** 通配臂(default):其 body = default_bb,且 default_bb
# 是所有 check 不中时的落点。带 guard 的通配臂不算 default(各有独立
# check/body BB),避免其 guard 被静默忽略。
default_case_node: ast.AST* = None
first_check: int = -1
i: int = 0
while i < case_count:
if collected >= MAX_MATCH_CASES:
self.Gen.TypeGenErr.fatal_error(node, f"match case 数量超过上限 MAX_MATCH_CASES={MAX_MATCH_CASES}", "OverflowError")
break
case_node: ast.AST* = cases_list.get(i)
if case_node is None or case_node.kind() != ast.ASTKind.MatchCase:
i = i + 1
continue
mc: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](case_node)
pat: ast.AST* = mc.pattern
gnode0: ast.AST* = mc.guard
is_def: int = 0
cv: llvmlite.Value* = None
if pat is not None:
pk: int = pat.kind()
if pk == ast.ASTKind.MatchValue:
mv: rawptr[ast.MatchValue] = rawptr[ast.MatchValue](pat)
cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv.value,
trans._funcs, trans._func_count, trans)
elif pk == ast.ASTKind.MatchSingleton:
# None -> 0
cv = llvmlite.const_int32(pool, 0)
elif pk == ast.ASTKind.MatchAs:
ma: rawptr[ast.MatchAs] = rawptr[ast.MatchAs](pat)
if ma.pattern is None and gnode0 is None:
# 唯一的**无 guard** 通配臂 → default
is_def = 1
# ma.pattern is None 且 guard 非空 → 带 guard 的通配臂:条件恒真
# 再与 guard 取与(见发射阶段),不当作 default
elif pk == ast.ASTKind.MatchOr:
# MatchOr:取第一个 MatchValue 作比较(与 _handle_simple_match 的
# switch 路径同口径,其余子模式在 guard 链中不展开)
mo: rawptr[ast.MatchOr] = rawptr[ast.MatchOr](pat)
pats_o: list[ast.AST*]* = mo.patterns
if pats_o is not None and len(pats_o) > 0:
fp: ast.AST* = pats_o.get(0)
if fp is not None and fp.kind() == ast.ASTKind.MatchValue:
mv2: rawptr[ast.MatchValue] = rawptr[ast.MatchValue](fp)
cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv2.value,
trans._funcs, trans._func_count, trans)
else:
# 禁止静默兜底:guard 分支只支持 值/None/通配/或 模式
self.Gen.TypeGenErr.fatal_error(pat, "guard 分支不支持的 case 模式", "TypeError")
if is_def == 1:
has_default = 1
default_case_node = case_node
check_arr[collected] = 0
body_arr[collected] = sizet(default_bb)
else:
viperlib.snprintf(name_buf, 32, "match.guard.check.%d.%d", cnt, i)
cbb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.guard.body.%d.%d", cnt, i)
bbb: rawptr[llvmlite.BasicBlock] = llvmlite.create_block(pool, func, name_buf)
check_arr[collected] = sizet(cbb)
body_arr[collected] = sizet(bbb)
if first_check < 0:
first_check = collected
body_list: list[ast.AST*]* = mc.children
nb: int = self._has_no_break(body_list)
def_arr[collected] = is_def
nb_arr[collected] = nb
mc_arr[collected] = sizet(mc)
cv_arr[collected] = sizet(cv)
collected = collected + 1
i = i + 1
# 3. 入口 → 第一个 check(或 default/end)
if first_check >= 0:
head_bb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[first_check])
llvmlite.build_br(builder, head_bb)
elif has_default == 1:
llvmlite.build_br(builder, default_bb)
else:
llvmlite.build_br(builder, end_bb)
# 4. 发射 check 链(按书写顺序)
ci: int = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
cbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](check_arr[ci])
bbb2: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci])
llvmlite.position_at_end(builder, cbb2)
mc2: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](mc_arr[ci])
# 值条件:subj_val == case值(通配臂无 case 值 → cond 保持 None=恒真)
cond: llvmlite.Value* = None
cv_addr: sizet = cv_arr[ci]
if cv_addr != 0:
cv2: llvmlite.Value* = (llvmlite.Value*)(cv_addr)
c1: llvmlite.Value* = self._seq_cmp_eq(trans, builder, subj_val, cv2)
if c1 is None:
self.Gen.TypeGenErr.fatal_error(node, "match case 值比较失败", "TypeError")
cond = c1
# guard(若有):匹配成立且 guard 非零(归一为 i1,写法同序列路径)
g_addr: sizet = sizet(mc2.guard)
if g_addr != 0:
gnode: ast.AST* = (ast.AST*)(g_addr)
gv: llvmlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, gnode,
trans._funcs, trans._func_count, trans)
if gv is None:
self.Gen.TypeGenErr.fatal_error(gnode, "match guard 翻译失败", "TypeError")
gbits: int = self.Gen.ExprGenBase.get_llvm_type_bits(gv.Ty)
if gbits == 1:
g1: llvmlite.Value* = gv
else:
gzero: llvmlite.Value* = llvmlite.const_int(pool, gbits if gbits > 0 else 32, 0)
g1 = llvmlite.build_icmp(builder, llvmlite.ICMP_NE, gv, gzero)
if cond is None:
cond = g1
else:
cond = llvmlite.build_and(builder, cond, g1)
if cond is None:
cond = llvmlite.const_int(pool, 1, 1)
# 下一个 check(跳过 default)→ 否则 default/end
nxt: rawptr[llvmlite.BasicBlock] = None
nj: int = ci + 1
while nj < collected and def_arr[nj] == 1:
nj = nj + 1
if nj < collected:
nxt = rawptr[llvmlite.BasicBlock](check_arr[nj])
elif has_default == 1:
nxt = default_bb
else:
nxt = end_bb
llvmlite.build_cond_br(builder, cond, bbb2, nxt)
ci = ci + 1
# 5. 翻译 case body
ci = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
bbb3: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[ci])
llvmlite.position_at_end(builder, bbb3)
mc3: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](mc_arr[ci])
# case 体内 break = 跳出 match(C switch 语义):暂存/覆盖 _break_bb
gmb_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
gmb_old_inmc: int = trans._in_switch_case
trans._in_switch_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
body_list2: list[ast.AST*]* = mc3.children
if body_list2 is not None:
bc: sizet = len(body_list2)
bi: int = 0
while bi < bc:
stmt: ast.AST* = body_list2.get(bi)
if stmt is not None:
self.Gen.StmtGen.translate_stmt(trans, stmt)
bi = bi + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = gmb_old_break
trans._in_switch_case = gmb_old_inmc
# 若未终止:默认跳 end;含 fallthrough 则落到下一个非通配 body。
# 末句控制流全 return → 补 unreachable(不 br end_bb)
if llvmlite.builder_cur_block_is_terminated(builder) == 0 \
and self._case_tail_unreachable(trans, builder, body_list2) == 0:
if nb_arr[ci] == 0:
llvmlite.build_br(builder, end_bb)
else:
nj2: int = ci + 1
while nj2 < collected and def_arr[nj2] == 1:
nj2 = nj2 + 1
if nj2 < collected:
nbb: rawptr[llvmlite.BasicBlock] = rawptr[llvmlite.BasicBlock](body_arr[nj2])
llvmlite.build_br(builder, nbb)
elif has_default == 1:
llvmlite.build_br(builder, default_bb)
else:
llvmlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
ci = ci + 1
# 6. default 块(唯一的**无 guard** 通配臂;全部 check 不中时落此)
llvmlite.position_at_end(builder, default_bb)
if has_default == 0 or default_case_node is None:
# 无无 guard 通配臂:match 非穷尽 ⇒ merge 可达,default 直接跳 end
if llvmlite.builder_cur_block_is_terminated(builder) == 0:
llvmlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
else:
dmc_d: rawptr[ast.MatchCase] = rawptr[ast.MatchCase](default_case_node)
# default 臂同样按 match-case 语义:break = 跳出 match
gmd_old_break: rawptr[llvmlite.BasicBlock] = trans._break_bb
trans._break_bb = end_bb
gmd_old_inmc: int = trans._in_switch_case
trans._in_switch_case = node.is_switch
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# dbody3 内联声明接收者遍历(规避 .get 误分派成 list[int].get 的截断)
dbody3: list[ast.AST*]* = dmc_d.children
if dbody3 is not None:
dn3: sizet = len(dbody3)
dj3: int = 0
while dj3 < dn3:
stmt3: ast.AST* = dbody3.get(dj3)
if stmt3 is not None:
self.Gen.StmtGen.translate_stmt(trans, stmt3)
dj3 = dj3 + 1
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = gmd_old_break
trans._in_switch_case = gmd_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:
llvmlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
# 7. 定位到 end
llvmlite.position_at_end(builder, end_bb)
# 覆盖嵌套控制流污染(同主路径)
trans._ctrl_merge_reachable = gm_merge_reach
return 0