Files
ViperC/App/lib/core/Gen/MatchGenMixin.vp
T
TermiNexus 7a9bc5ae75 chore(messages): TransPyV 运行时中文字符串英文化
把 TransPyV/App 下 57 个文件的运行时中文字符串(编译器报错/告警/日志、CLI 帮助与横幅、REPL 提示、Phase1/Phase2 流程日志、Codegen 各 Mixin 的类型与调用报错、Stub/Vpi 诊断等)共 1091 行译为英文。

口径:只改字符串字面量内部文字,行数零变化(+1092/-1092);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0);译文不引入撇号以免干扰单引号字面量解析。验证:三重重编 SUCCESS(136.20s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0;新产物 help/日志输出已为英文。
2026-10-07 00:03:58 +08:00

1498 lines
91 KiB
Plaintext

import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
# MatchGenMixin - match-statement translation mixin (P6 methodization).
# Max number of match cases (was HandlesMatch.MAX_MATCH_CASES).
define MAX_MATCH_CASES = 32
# Scope-kind constant (distinct from HandlesVar's Scope.Kind; was HandlesMatch.SCOPE_MATCH).
define SCOPE_MATCH = 6
# Max recursion depth for match translation (match -> case body -> translate_value -> nested match).
# Incremented on entry and decremented on exit; exceeding it fail-fasts because a self-referential AST can recurse infinitely.
define MATCH_TRANSLATE_MAX_DEPTH = 64
novirtual class MatchGenMixin:
"""Mixin for match-statement translation."""
# _case_tail_unreachable - detect a structurally unreachable case-tail.
# If the current block is unterminated and its last stmt is control flow (If/While/For/Match) whose merge is structurally unreachable (all branches return), emit unreachable (same as HandlesIf/ClassGenVTable). Returns 1 if emitted, 0 for normal end (caller should br end_bb). Excludes Try/With.
Gen: Generator.Generator*
def _case_tail_unreachable(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, body_list: list[ast.AST*]*) -> int:
if trans is None or builder is None: return 0
if mlirlite.builder_cur_block_is_terminated(builder) != 0: return 0
if body_list is None or len(body_list) == 0: return 0
lst: ast.AST* = body_list.get(len(body_list) - 1)
if lst is None: return 0
lk: int = lst.kind()
if lk != ast.ASTKind.If and lk != ast.ASTKind.While and lk != ast.ASTKind.For and lk != ast.ASTKind.Match: return 0
if trans._ctrl_merge_reachable != 0: return 0
mlirlite.build_unreachable(builder)
return 1
# translate_match - match statement main entry, returns 0 (migrated from HandlesMatch.MatchHandle.Handle).
def translate_match(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate one match statement; wraps _translate_match_impl with a depth guard."""
if trans is not None:
if trans.MatchTranslateDepth >= MATCH_TRANSLATE_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(node, "match translation nested too deeply (suspected AST self-reference cycle or translation bug)", "RecursionError")
trans.MatchTranslateDepth += 1
mret: int = self._translate_match_impl(trans, node)
if trans is not None: trans.MatchTranslateDepth -= 1
return mret
def _translate_match_impl(self, trans: HT.Translator*, node: ast.AST*) -> int:
if node is None: return 0
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
if builder is None or func is None: return 0
# Reset match merge reachability to 0 before dispatch. Each handler declares and overwrites this flag locally; declaring it here would degrade to an implicit global and leak across matches.
trans._ctrl_merge_reachable = 0
m_node: ast.Match* = ast.Match*(node)
# Duplicate-case detection (fail-fast): match rejects duplicate value labels (an arm would be unreachable, usually a missed edit); switch allows duplicates.
# For wildcard arms, only one guardless wildcard is allowed per construct; guarded wildcards may repeat. Checked before any IR is produced; the message prefix depends on is_switch.
kind_label: str = "match"
default_label: str = "`case _`"
if m_node.is_switch != 0:
kind_label = "switch"
default_label = "`default`"
cases_chk: list[ast.AST*]* = m_node.cases
if cases_chk is not None:
ccn: sizet = len(cases_chk)
default_cnt: int = 0
dci: int = 0
while dci < ccn:
dcase: ast.AST* = cases_chk.get(dci)
if dcase is not None and dcase.kind() == ast.ASTKind.MatchCase:
dpat: ast.AST* = ast.MatchCase*(dcase).pattern
if dpat is not None:
if dpat.kind() == ast.ASTKind.MatchAs and ast.MatchAs*(dpat).pattern is None:
# Count only guardless wildcard arms; guarded `case _ if ...` arms may repeat and are not counted as default.
if ast.MatchCase*(dcase).guard is None: default_cnt = default_cnt + 1
elif m_node.is_switch == 0:
dcj: int = dci + 1
while dcj < ccn:
dcase2: ast.AST* = cases_chk.get(dcj)
if dcase2 is not None and dcase2.kind() == ast.ASTKind.MatchCase:
dpat2: ast.AST* = ast.MatchCase*(dcase2).pattern
if self._case_label_same(dpat, dpat2) != 0:
self.Gen.TypeGenErr.fatal_error(dcase2, f"match has duplicate case names: case {dci} and case {dcj} carry the same label (match does not allow duplicate case labels)", "SyntaxError")
dcj = dcj + 1
dci = dci + 1
if default_cnt > 1:
self.Gen.TypeGenErr.fatal_error(node, f"{kind_label} has {default_cnt} {default_label} arms (only one guardless wildcard/default is allowed; guarded `case _ if ...` / `default if ...` may appear multiple times)", "SyntaxError")
# 1. Evaluate the subject.
subject_node: ast.AST* = m_node.subject
if subject_node is None: return 0
# Sequence match (match (a, b): case (x, y): ...) takes a separate path: a Tuple/List subject cannot be evaluated by translate_value, so dispatch early to _handle_sequence_match to avoid a false fail-fast.
sk_seq: int = subject_node.kind()
if sk_seq == ast.ASTKind.Tuple or sk_seq == ast.ASTKind.List: return self._handle_sequence_match(trans, m_node, subject_node)
subject_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, subject_node, trans._funcs, trans._func_count, trans)
if subject_val is None:
# fail-fast: match subject translation failed.
self.Gen.TypeGenErr.fatal_error(subject_node, "match subject translation failed", "TypeError")
# 2. Detect whether this is a REnum match.
is_renum: int = 0
renum_name: str = None
subject_ptr: mlirlite.Value* = None
# 2a. If the subject is a Name, look up its annotated class name.
if subject_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(subject_node)
if nm.id is not None:
entry: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
entry_annot: str = self.Gen.VarGen.var_annot_class_name(entry)
if entry_annot is not None and (self.Gen.ClassGenEnum._is_renum_class_name(entry_annot) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(entry_annot) != 0):
is_renum = 1
renum_name = entry_annot
subject_ptr = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
# 2b. If undetermined, scan cases for a MatchClass variant.
if is_renum == 0:
cases_list: list[ast.AST*]* = m_node.cases
if cases_list is not None:
cn: sizet = len(cases_list)
ci: int = 0
while ci < cn and is_renum == 0:
case_node: ast.AST* = cases_list.get(ci)
if case_node is not None and case_node.kind() == ast.ASTKind.MatchCase:
mc: ast.MatchCase* = (ast.MatchCase*)(case_node)
pat: ast.AST* = mc.pattern
if pat is not None and pat.kind() == ast.ASTKind.MatchClass:
rnm: str = self._extract_renum_name_from_cls(pat)
if rnm is not None and (self.Gen.ClassGenEnum._is_renum_class_name(rnm) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rnm) != 0):
is_renum = 1
renum_name = rnm
if subject_ptr is None: subject_ptr = subject_val
ci = ci + 1
# 3. Dispatch.
if is_renum != 0 and subject_ptr is not None: return self._handle_renum_match(trans, m_node, renum_name, subject_ptr)
return self._handle_simple_match(trans, m_node, subject_val)
# _extract_renum_name_from_cls - extract the REnum class name from MatchClass.cls.
# LLVMType.Ptr -> 'LLVMType'; mlirlite.LLVMType.Ptr -> 'LLVMType'; a bare Name('Ptr') is resolved by scanning all REnum classes.
def _extract_renum_name_from_cls(self, pattern: ast.AST*) -> str:
if pattern is None: return None
mc: ast.MatchClass* = ast.MatchClass*(pattern)
cls_node: ast.AST* = mc.cls
if cls_node is None: return None
# Attribute form.
if cls_node.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cls_node)
if at.value is not None:
if at.value.kind() == ast.ASTKind.Name:
inner_nm: ast.Name* = ast.Name*(at.value)
return inner_nm.id
if at.value.kind() == ast.ASTKind.Attribute:
inner_at: ast.Attribute* = (ast.Attribute*)(at.value)
return inner_at.attr
# Name form: scan all REnum classes to resolve.
if cls_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cls_node)
return self._find_renum_class_for_variant(nm.id)
return None
# _extract_variant_name_from_cls - extract the variant name from MatchClass.cls.
def _extract_variant_name_from_cls(self, pattern: ast.AST*) -> str:
if pattern is None: return None
mc: ast.MatchClass* = ast.MatchClass*(pattern)
cls_node: ast.AST* = mc.cls
if cls_node is None: return None
if cls_node.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cls_node)
return at.attr
if cls_node.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cls_node)
return nm.id
return None
# _find_renum_class_for_variant - resolve the REnum class name from a variant name.
# After P5-2 the variant table lives in gen fields (RenumClassNames/RenumClassCount), so delegate to gen._lookup_renum_variant.
def _find_renum_class_for_variant(self, variant_name: str) -> str:
if variant_name is None: return None
return self.Gen.ClassGenEnum._lookup_renum_variant(variant_name)
# _value_label_same / _case_label_same - duplicate-case detection (switch-label comparison).
# Compares only switch labels: MatchValue (constant/name/attr chain), MatchSingleton(None), MatchClass (variant name); MatchOr compares sub-patterns pairwise; MatchAs/MatchSequence have no label and are never equal.
def _value_label_same(self, a: ast.AST*, b: ast.AST*) -> int:
"""Return 1 when two case value nodes are equivalent."""
if a is None or b is None: return 0
ka: int = a.kind()
if ka != b.kind(): return 0
if ka == ast.ASTKind.Constant:
ca: ast.Constant* = ast.Constant*(a)
cb: ast.Constant* = ast.Constant*(b)
if ca.const_kind != cb.const_kind: return 0
if ca.const_kind == ast.CONST_STR:
if ca.str_val is not None and cb.str_val is not None and string.strcmp(ca.str_val, cb.str_val) == 0: return 1
return 0
if ca.const_kind == ast.CONST_FLOAT:
if ca.float_val == cb.float_val: return 1
return 0
# Integer / bool / char code.
if ca.int_val == cb.int_val: return 1
return 0
if ka == ast.ASTKind.Name:
na: ast.Name* = ast.Name*(a)
nb: ast.Name* = ast.Name*(b)
if na.id is not None and nb.id is not None and string.strcmp(na.id, nb.id) == 0: return 1
return 0
if ka == ast.ASTKind.Attribute:
aa: ast.Attribute* = ast.Attribute*(a)
ab: ast.Attribute* = ast.Attribute*(b)
if aa.attr is None or ab.attr is None or string.strcmp(aa.attr, ab.attr) != 0: return 0
return self._value_label_same(aa.value, ab.value)
return 0
def _case_label_same(self, a: ast.AST*, b: ast.AST*) -> int:
"""Return 1 when two case patterns resolve to the same switch label."""
if a is None or b is None: return 0
ka: int = a.kind()
if ka != b.kind(): return 0
if ka == ast.ASTKind.MatchValue:
av: ast.AST* = ast.MatchValue*(a).value
bv: ast.AST* = ast.MatchValue*(b).value
return self._value_label_same(av, bv)
if ka == ast.ASTKind.MatchSingleton:
return 1 # MatchSingleton can only be None
if ka == ast.ASTKind.MatchClass:
an: str = self._extract_variant_name_from_cls(a)
bn: str = self._extract_variant_name_from_cls(b)
if an is None or bn is None: return 0
if string.strcmp(an, bn) == 0: return 1
return 0
if ka == ast.ASTKind.MatchOr:
ap: list[ast.AST*]* = ast.MatchOr*(a).patterns
bp: list[ast.AST*]* = ast.MatchOr*(b).patterns
if ap is None or bp is None or len(ap) != len(bp): return 0
oi: int = 0
while oi < len(ap):
if self._case_label_same(ap.get(oi), bp.get(oi)) == 0: return 0
oi = oi + 1
return 1
return 0
# _has_no_break - detect whether a case body requests fallthrough to the next case.
# Only the `fallthrough` keyword stmt (ASTKind.FallThrough) counts; it can only appear inside a switch case body (translate_fallthrough already fail-fasts outside match/switch), so no context check is needed here.
def _has_no_break(self, body_list: list[ast.AST*]*) -> int:
if body_list is None: return 0
n: sizet = len(body_list)
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
i = i + 1
return 0
# _handle_renum_match - REnum path.
# 1) bitcast subject and read __tag; 2) collect case values + create BBs + build the SwitchCase list; 3) emit build_switch; 4) translate each case body (scope isolation + variant field binding).
def _handle_renum_match(self, trans: HT.Translator*, node: ast.Match*, renum_name: str, subject_ptr: mlirlite.Value*) -> int:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
# Locally track whether any br enters end_bb and overwrite _ctrl_merge_reachable at the end; the declaration must live in this function, since delegating it to the Handle dispatcher would degrade to an implicit global and leak across matches.
rm_merge_reach: int = 0
# Guards: this path dispatches once via switch(tag) and does not evaluate guards, so a guarded arm would be silently ignored (possibly selecting the wrong branch). No silent fallback: any guarded arm fail-fasts before IR is produced.
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 ast.MatchCase*(rn_case).guard is not None:
self.Gen.TypeGenErr.fatal_error(rn_case, "REnum match does not yet support a guard (that arm would be silently ignored by switch(tag)); use a plain match or remove the guard", "NotImplementedError")
rn_i = rn_i + 1
# 0. Normalize subject_ptr to a pointer to the REnum struct (aligns TPC HandlesMatch.py:238-259 _HandleRenumMatchLlvm).
# Cases: T** -> load once; a non-pointer value (i64 from a payload slot) -> inttoptr; a non-struct pointee -> bitcast to the REnum struct pointer.
subj_ty: mlirlite.LLVMType* = subject_ptr.Ty
if subj_ty is not None and self.Gen.ExprGenBase.is_ptr_type(subj_ty) != 0 and subj_ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(subj_ty.Pointee) != 0:
# Case 1: T** -> load once to get T*.
loaded_sub: mlirlite.Value* = mlirlite.build_load(builder, subj_ty.Pointee, subject_ptr)
if loaded_sub is not None: subject_ptr = loaded_sub
# Case 2: a value-typed subject (non-pointer) comes from an REnum payload slot load (i64 holding a pointer), so inttoptr to renum_sty* directly; no alloca+store needed.
if subject_ptr.Ty is None or self.Gen.ExprGenBase.is_ptr_type(subject_ptr.Ty) == 0:
renum_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(renum_name)
renum_sty: mlirlite.LLVMType* = None
if renum_se is not None: renum_sty = renum_se.Ty
if renum_sty is None:
# fail-fast: REnum struct not registered, cannot construct.
self.Gen.TypeGenErr.fatal_error(node, f"REnum struct '{renum_name}' not registered", "NameError")
i64_ty0: mlirlite.LLVMType* = mlirlite.Int64(pool)
int_val: mlirlite.Value* = subject_ptr
# Widen/truncate non-i64 integers to i64 (inttoptr requires equal width); only when subj_ty is a known integer type.
if subj_ty is not None and self.Gen.ExprGenBase.get_llvm_type_bits(subj_ty) != 64 and self.Gen.ExprGenBase.is_ptr_type(subj_ty) == 0:
if self.Gen.ExprGenBase.get_llvm_type_bits(subj_ty) > 64: int_val = mlirlite.build_trunc(builder, subject_ptr, i64_ty0)
else: int_val = mlirlite.build_zext(builder, subject_ptr, i64_ty0)
renum_ptr_ty0: mlirlite.LLVMType* = mlirlite.Ptr(pool, renum_sty)
subj_ptr0: mlirlite.Value* = mlirlite.build_inttoptr(builder, int_val, renum_ptr_ty0)
if subj_ptr0 is not None: subject_ptr = subj_ptr0
# 1. bitcast subject to __tag* and read __tag. The tag type comes from the __tag field declaration (FieldEntry.Ty), shared with kind() and variant construction.
# The case constants (ConstInt) reuse tag_ty, so read/write/compare follow automatically if __tag changes width. (Default i32 only when the field entry is unavailable.)
tag_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
tag_se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(renum_name)
if tag_se is not None and tag_se.Ty is not None:
tag_fe: GenTypes.FieldEntry* = self.Gen.StructGen.lookup_field(tag_se.Ty, "__tag")
if tag_fe is not None and tag_fe.Ty is not None: tag_ty = tag_fe.Ty
tag_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, tag_ty)
tag_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, subject_ptr, tag_ptr_ty)
if tag_ptr is None:
# fail-fast: REnum tag pointer cast failed.
self.Gen.TypeGenErr.fatal_error(node, "REnum tag pointer cast failed", "TypeError")
tag_val: mlirlite.Value* = mlirlite.build_load(builder, tag_ty, tag_ptr)
if tag_val is None:
# fail-fast: REnum tag load failed.
self.Gen.TypeGenErr.fatal_error(node, "REnum tag read failed", "TypeError")
# 2. Create the default/end BBs.
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
if name_buf is None:
# fail-fast: label buffer allocation failed.
self.Gen.TypeGenErr.fatal_error(node, "label buffer allocation failed", "TypeError")
viperlib.snprintf(name_buf, 32, "match.default.%d", cnt)
default_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.end.%d", cnt)
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
# 3. Prepare collection arrays (CSizeT arrays holding pointers).
cases_list: list[ast.AST*]* = node.cases
if cases_list is None:
# fail-fast: match cases missing.
self.Gen.TypeGenErr.fatal_error(node, "match cases missing", "TypeError")
case_count: sizet = len(cases_list)
# BB pointer array, case-value pointer array, default-flag array, nobreak-flag array.
bb_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
cv_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
def_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
nb_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
# mc pointers cached in pass1 (reused in pass2 to avoid re-indexing).
mc_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
if bb_arr is None or cv_arr is None or def_arr is None or nb_arr is None or mc_arr is None:
# fail-fast: match dispatch array allocation failed.
self.Gen.TypeGenErr.fatal_error(node, "match dispatch array allocation failed", "TypeError")
collected: int = 0
has_default: int = 0
# Exhaustive arm (`case _`) node: recorded explicitly in pass1 and translated directly in the default block.
default_case_node: ast.AST* = None
# SwitchCase linked list.
sw_head: mlirlite.SwitchCase* = None
sw_tail: mlirlite.SwitchCase* = None
sw_count: int = 0
# Count of arms that must submit a case value (non-default MatchClass always has a variant tag, MatchValue a constant); a mismatch with sw_count means a case was silently dropped, so fail-fast before build_switch.
need_case: int = 0
# Diagnostics for arms that produced no case value (prints arm index/variant name/tag); capacity 8 x 128, sized generously because strcat is unchecked.
miss_buf: str = pool.alloc(1088)
diag_tmp: str = pool.alloc(128)
if miss_buf is None or diag_tmp is None:
# fail-fast: diagnostics buffer allocation failed (same handling as name_buf above).
self.Gen.TypeGenErr.fatal_error(node, "match diagnostics buffer allocation failed", "TypeError")
miss_buf[0] = '\0'
miss_cnt: int = 0
# The variant table registers bare class names (ClassDef.name) while renum_name comes from the annotation form (Spec.SourceName; an Attribute may carry a module prefix like "mod.LLVMType"), so an exact strcmp would always miss and silently drop the arm. Strip the module prefix first.
tag_cls_bare: str = renum_name
rn_dot: str = self.Gen.StructGen._last_dot_outside_brackets(renum_name)
if rn_dot is not None and rn_dot[1] != '\0': tag_cls_bare = rn_dot + 1
# 4. Pass 1: collect case info + create BBs + build the list.
i: int = 0
while i < case_count:
case_node: ast.AST* = cases_list.get(i)
if case_node is None:
i = i + 1
continue
if case_node.kind() != ast.ASTKind.MatchCase:
i = i + 1
continue
mc: ast.MatchCase* = ast.MatchCase*(case_node)
pat: ast.AST* = mc.pattern
is_def: int = 0
cv: mlirlite.Value* = None
# Hoisted to arm scope (was inside the MatchClass branch) so the "no case value" diagnostic can print the variant name/tag.
vnm: str = None
tag: int = -1
if pat is not None:
pk: int = pat.kind()
if pk == ast.ASTKind.MatchClass:
vnm = self._extract_variant_name_from_cls(pat)
if vnm is not None:
# Look up by bare class name first (the variant table uses bare names, same as the pattern-side 2b path); on failure retry with the annotation form (mangled generic names may need the dot). Both failing -> tag=-1 -> fail-fast before step 5.
tag = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(tag_cls_bare, vnm)
if tag < 0 and string.strcmp(tag_cls_bare, renum_name) != 0: tag = self.Gen.ClassGenEnum._lookup_renum_variant_in_class(renum_name, vnm)
if tag >= 0:
tag_buf: str = pool.alloc(32)
if tag_buf is not None: viperlib.snprintf(tag_buf, 32, "%d", tag)
cv = mlirlite.ConstInt(pool, tag_ty, tag, tag_buf if tag_buf is not None else "0")
elif pk == ast.ASTKind.MatchAs:
ma: ast.MatchAs* = ast.MatchAs*(pat)
if ma.pattern is None:
is_def = 1
has_default = 1
default_case_node = case_node
elif pk == ast.ASTKind.MatchValue:
mv: ast.MatchValue* = 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.MatchOr:
# MatchOr: multiple values share one BB (use the first value as the case).
mo: ast.MatchOr* = ast.MatchOr*(pat)
pats: list[ast.AST*]* = mo.patterns
if pats is not None and len(pats) > 0:
first_pat: ast.AST* = pats.get(0)
if first_pat is not None and first_pat.kind() == ast.ASTKind.MatchValue:
mv2: ast.MatchValue* = (ast.MatchValue*)(first_pat)
cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv2.value, trans._funcs, trans._func_count, trans)
# Create the case BB.
if is_def == 0:
if collected >= MAX_MATCH_CASES: break
viperlib.snprintf(name_buf, 32, "match.case.%d.%d", cnt, i)
bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
else: bb = default_bb
# Detect NoBreak.
body_list: list[ast.AST*]* = mc.children
nb: int = self._has_no_break(body_list)
# Store into arrays.
if collected < MAX_MATCH_CASES:
bb_arr[collected] = sizet(bb)
cv_arr[collected] = sizet(cv)
def_arr[collected] = is_def
nb_arr[collected] = nb
# Cache the mc pointer (reused in pass2 to avoid re-indexing).
mc_arr[collected] = sizet(mc)
collected = collected + 1
# Count arms that must submit a case (MatchClass always has a variant tag, MatchValue a constant); counted only after the MAX_MATCH_CASES check since over-limit arms already broke.
if is_def == 0 and pat is not None:
pkm: int = pat.kind()
if pkm == ast.ASTKind.MatchClass or pkm == ast.ASTKind.MatchValue: need_case = need_case + 1
# Build the SwitchCase list (non-default cases that have a value).
if is_def == 0 and cv is not None:
sc: mlirlite.SwitchCase* = mlirlite.new_switch_case(pool, cv, bb)
if sc is not None:
if sw_head is None:
sw_head = sc
sw_tail = sc
else:
sw_tail.Next = sc
sw_tail = sc
sw_count = sw_count + 1
# Record arms that should produce a case value but did not (variant tag lookup failure / constant translation failure); only diagnostics here, fatality is decided uniformly by the step-5 hard check.
if is_def == 0 and cv is None and miss_cnt < 8 and pat is not None:
pkm_d: int = pat.kind()
if pkm_d == ast.ASTKind.MatchClass or pkm_d == ast.ASTKind.MatchValue:
viperlib.snprintf(diag_tmp, 128, " #%d(%s,tag=%d)", i, vnm if vnm is not None else "?", tag)
string.strcat(miss_buf, diag_tmp)
miss_cnt = miss_cnt + 1
i = i + 1
# 5. Emit the switch. No silent case dropping: the number of must-submit arms (non-default MatchClass/MatchValue) must equal sw_count; a mismatch fail-fasts with class/lookup name/expected/actual and each missing arm's index(variant,tag).
if need_case != sw_count:
self.Gen.TypeGenErr.fatal_error(node, f"REnum match dispatch dropped cases: class='{renum_name}' (lookup name='{tag_cls_bare}') should submit {need_case} cases, actually submitted {sw_count}; arms with no value: {miss_buf}", "InternalCompilerError")
mlirlite.build_switch(builder, tag_val, default_bb, sw_head, sw_count)
# 6. Find the REnum struct type (for variant-field GEP). Fall back to the bare class name because renum_name may carry a module prefix, and scan for an entry with a valid Ty since find_struct may hit a neutralized old entry (Ty=None).
renum_entry: GenTypes.StructEntry* = None
renum_ty: mlirlite.LLVMType* = None
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(renum_name)
if se is not None and se.Ty is not None:
renum_entry = se
renum_ty = se.Ty
else:
se_v: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_entry_with_ty(renum_name, tag_cls_bare)
if se_v is not None:
renum_entry = se_v
renum_ty = se_v.Ty
# 6b. Cross-module REnum: variant binding GEPs renum_ty, but LLVM requires a sized pointee (an opaque Ptr(Struct) makes llc reject "base element of getelementptr must be sized"). Replace the opaque type in place with the full definition from entry.Ty; local non-cross-module types (no '.') are skipped.
if renum_ty is not None: self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, renum_ty)
# 7. Pass 2: translate each case body.
i = 0
while i < collected:
# pass2 uses the mc cached by pass1 (avoids re-index mismatch).
mc: ast.MatchCase* = ast.MatchCase*(mc_arr[i])
if mc is None:
i = i + 1
continue
pat: ast.AST* = mc.pattern
# Fetch the BB.
bb_addr: sizet = bb_arr[i]
case_bb: mlirlite.BasicBlock* = (mlirlite.BasicBlock*)(bb_addr)
is_def: int = def_arr[i]
nb: int = nb_arr[i]
# The default case body is translated separately in step 8; skip here to avoid duplication.
if is_def == 1:
i = i + 1
continue
# Position at the case BB.
mlirlite.position_at_end(builder, case_bb)
# break inside a case exits the match (C switch semantics): save the outer loop's break target, override with end_bb, restore on exit.
rmb_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
rmb_old_inmc: int = trans._in_switch_case
trans._in_switch_case = node.is_switch
# Enter the case scope (variant field binding isolation).
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# Variant field binding (MatchClass).
if pat is not None and pat.kind() == ast.ASTKind.MatchClass:
if renum_entry is not None and renum_ty is not None: self._bind_variant_fields(pat, renum_name, renum_entry, renum_ty, subject_ptr, trans, pool, builder)
elif (self.Gen.ClassGenEnum._is_renum_class_name(tag_cls_bare) != 0 or self.Gen.ClassGenEnum._is_renum_class_name(renum_name) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(tag_cls_bare) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(renum_name) != 0):
# Reached here -> dispatch judged this a REnum match but MatchClass got no usable struct entry/type: binding was previously silently skipped, hiding the NameError root cause (__types.vp `bits`). Fail-fast with the site context.
ent_nm: str = "None"
ent_fc: int = -1
if renum_entry is not None:
ent_nm = renum_entry.Name
ent_fc = renum_entry.FieldCount
ty_present: int = 0
if renum_ty is not None: ty_present = 1
self.Gen.TypeGenErr.fatal_error(pat, f"REnum match variant field binding failed: class='{tag_cls_bare}' (lookup name='{renum_name}')" f" entry='{ent_nm}' FieldCount={ent_fc} ty_present={ty_present}; " f"the variant table has registered this class, so the struct entry or its Ty is missing (binding is no longer silently skipped, " f"otherwise an undefined binding name would only surface as a NameError downstream)", "InternalCompilerError")
# Translate the case body.
body_list: list[ast.AST*]* = mc.children
if body_list is not None:
bc: sizet = len(body_list)
bi: int = 0
while bi < bc:
# Use list.get() to read elements (verified to compile to an 8-byte load, same as pass1's cases_list.get(i)). Never use a raw `bl_data + bi*8` address: it would pass a slot address as an AST node into translate_stmt and the virtual call jumps into the vtable -> DEP crash.
stmt: ast.AST* = body_list.get(bi)
if stmt is not None: self.Gen.StmtGen.translate_stmt(trans, stmt)
bi = bi + 1
# Exit the case scope and restore the break target / match-case flag.
self.Gen.VarGen.exit_scope(trans.SymTab)
trans._break_bb = rmb_old_break
trans._in_switch_case = rmb_old_inmc
# If not terminated: br to end by default; with fallthrough (the `fallthrough` marker or compatible c.NoBreak()) fall into the next case body, skipping its pattern check.
# If the tail is structurally unreachable, emit unreachable instead of br end_bb.
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, body_list) == 0:
if nb == 0: mlirlite.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: mlirlite.BasicBlock* = mlirlite.BasicBlock*(bb_arr[rmb_ft_nj])
mlirlite.build_br(builder, rmb_ft_bb)
elif has_default != 0: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
rm_merge_reach = 1
i = i + 1
# 8. Handle the default block.
mlirlite.position_at_end(builder, default_bb)
if has_default == 0 or default_case_node is None:
# No explicit default: br default -> end (a non-exhaustive match means merge is reachable).
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
rm_merge_reach = 1
else:
# Explicit default: translate the `case _` arm recorded by pass1 directly (no def_arr reverse lookup into cases_list, whose indices misalign when arms are skipped).
dmc_d: ast.MatchCase* = ast.MatchCase*(default_case_node)
# The default arm also follows match-case semantics: break exits the match.
rmd_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
rmd_old_inmc: int = trans._in_switch_case
trans._in_switch_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# Pre-declaring and reassigning dbody misdispatches .get to list[int].get (i32->inttoptr truncation crashes translate_stmt reading a garbage node); use an inline dbody3 receiver (like Converter arg_defaults).
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 = rmd_old_break
trans._in_switch_case = rmd_old_inmc
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, dbody3) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
rm_merge_reach = 1
# 9. Position at the end block.
mlirlite.position_at_end(builder, end_bb)
# Overwrite with this match's own br record to remove pollution of _ctrl_merge_reachable by nested control flow (should be unreachable when all cases return).
trans._ctrl_merge_reachable = rm_merge_reach
return 0
# _bind_variant_fields - bind the variant fields of a MatchClass.
# Binds the pointee in `case LLVMType.Ptr(pointee)` to the subject's payload field.
def _bind_variant_fields(self, pat: ast.AST*, renum_name: str, renum_entry: GenTypes.StructEntry*, renum_ty: mlirlite.LLVMType*, subject_ptr: mlirlite.Value*, trans: HT.Translator*, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*):
if pat is None or renum_entry is None or renum_ty is None: return
mc: ast.MatchClass* = ast.MatchClass*(pat)
pats: list[ast.AST*]* = mc.patterns
if pats is None: return
# bitcast subject_ptr to renum_ptr_ty (ensures GEP type match).
renum_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, renum_ty)
casted_ptr: mlirlite.Value* = mlirlite.build_bitcast(builder, subject_ptr, renum_ptr_ty)
if casted_ptr is None: casted_ptr = subject_ptr
pn: sizet = len(pats)
for j in range(pn):
sub_pat: ast.AST* = pats.get(j)
if sub_pat is not None and sub_pat.kind() == ast.ASTKind.MatchAs:
ma: ast.MatchAs* = ast.MatchAs*(sub_pat)
bind_name: str = ma.name
if bind_name is not None:
# Bind by position: the j-th MatchClass argument -> payload index j+1 (TPC HandlesMatch.py:305-306; no case-sensitive field-name matching).
payload_idx: int = j + 1
# Field-name lookup as a fallback (hits directly if the field name equals the MatchAs name).
if payload_idx >= renum_entry.FieldCount:
# class_name is compared by CSizeT pointer value: must pass renum_entry.Name (the perm_pool pointer unified by _update_renum_alias_class_ptr at registration).
# renum_name (AnnotClassName etc.) is a different pointer and would never match, causing a false fail-fast.
alt_idx: int = self.Gen.ClassGenEnum._lookup_renum_field_alias(renum_entry.Name, bind_name)
if alt_idx >= 1: payload_idx = alt_idx
else:
# fail-fast: payload out of range and alias lookup failed.
# (node is not available here; locate via sub_pat. Under TPC Python an undefined name errors only at runtime, but TPV lazily compiling the method body raises NameError immediately.)
self.Gen.TypeGenErr.fatal_error(sub_pat, f"REnum variant field '{bind_name}' is out of range in struct '{renum_name}' and alias lookup failed", "TypeError")
# Get the field type.
fe: GenTypes.FieldEntry* = (self.Gen.StructGen._get_field_entry(renum_entry, payload_idx))
field_ty: mlirlite.LLVMType* = None
if fe is not None: field_ty = fe.Ty
if field_ty is None:
# fail-fast: variant field type missing; no i8 fallback allowed.
self.Gen.TypeGenErr.fatal_error(sub_pat, f"REnum variant field type missing: {bind_name}", "TypeError")
# GEP to the field address.
elem_ptr: mlirlite.Value* = mlirlite.build_gep_struct(builder, renum_ty, field_ty,casted_ptr, payload_idx)
if elem_ptr is None:
# fail-fast: variant field address computation failed.
self.Gen.TypeGenErr.fatal_error(sub_pat, f"REnum variant field '{bind_name}' address computation failed", "TypeError")
# Actual variant-member type (aligns TPC class_members): the alias table stores the annotation-resolved member type, used to bitcast the slot address to Ptr(member type) so the binding reads the payload as the member type (otherwise i64 slot reads break is-comparison/index/strlen).
# Must be located by (class, variant name, position): a MatchAs name is a local variable name, not a field name, so field-name lookup never matches, and the same payload position differs across variants. Falls back to the slot type when a self-referential type fails to resolve.
mem_ty_m: mlirlite.LLVMType* = None
vnt_m: str = self._extract_variant_name_from_cls(pat)
if vnt_m is not None and renum_entry is not None and renum_entry.Name is not None: mem_ty_m = self.Gen.ClassGenEnum._lookup_renum_member_ty(renum_entry.Name, vnt_m, payload_idx)
if mem_ty_m is not None:
mem_ptr_m: mlirlite.LLVMType* = mlirlite.Ptr(pool, mem_ty_m)
if mem_ptr_m is not None:
cast_m: mlirlite.Value* = mlirlite.build_bitcast(builder, elem_ptr, mem_ptr_m)
if cast_m is not None: elem_ptr = cast_m
self.Gen.VarGen.define_var(trans.SymTab, bind_name, elem_ptr)
# _seq_cmp_eq - equality comparison for sequence-match elements.
# Align lhs/rhs (pointers to ptrtoint i64, integers widened to the wider one) then emit icmp eq, returning an i1 condition.
def _seq_cmp_eq(self, trans: HT.Translator*, builder: mlirlite.IRBuilder*, lhs: mlirlite.Value*, rhs: mlirlite.Value*) -> mlirlite.Value*:
"""Generate icmp eq after aligning lhs/rhs (pointers unified via ptrtoint i64)."""
if lhs is None or rhs is None or builder is None: return None
pool: memhub.MemBuddy* = trans.Pool
if self.Gen.ExprGenBase.is_ptr_type(lhs.Ty) != 0 or self.Gen.ExprGenBase.is_ptr_type(rhs.Ty) != 0:
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
if self.Gen.ExprGenBase.is_ptr_type(lhs.Ty) != 0: lhs = mlirlite.build_ptrtoint(builder, lhs, i64_ty)
if self.Gen.ExprGenBase.is_ptr_type(rhs.Ty) != 0: rhs = mlirlite.build_ptrtoint(builder, rhs, i64_ty)
lb: int = self.Gen.ExprGenBase.get_llvm_type_bits(lhs.Ty)
rb: int = self.Gen.ExprGenBase.get_llvm_type_bits(rhs.Ty)
if lb != rb and lb != 0 and rb != 0:
if lb < rb:
if lb == 1: lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
else: lhs = mlirlite.build_sext(builder, lhs, rhs.Ty)
else:
if rb == 1: rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
else: rhs = mlirlite.build_sext(builder, rhs, lhs.Ty)
return mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, lhs, rhs)
# _handle_sequence_match - sequence match (match (a, b): case (x, y): ...).
# Used when the subject is a Tuple/List and the case is a MatchSequence; picks the first case whose elements all match and whose guard is true. Element patterns: MatchValue -> subject[i] == v, MatchAs(None)/MatchStar -> wildcard. IR is a check/body chain: entry -> check0; check_i -> body_i else check_{i+1}; body_i -> end (fallthrough to the next case body on c.NoBreak); no match -> default -> end.
def _handle_sequence_match(self, trans: HT.Translator*, node: ast.Match*, subject_node: ast.AST*) -> int:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
if builder is None or func is None: return 0
# Same as the main path: locally track this sequence match's br end_bb and overwrite _ctrl_merge_reachable at the end to remove nested control-flow pollution.
sq_merge_reach: int = 0
# 1. Evaluate each subject element.
elts: list[ast.AST*]* = None
if subject_node.kind() == ast.ASTKind.Tuple:
st: ast.Tuple* = ast.Tuple*(subject_node)
if st is not None: elts = st.elts
elif subject_node.kind() == ast.ASTKind.List:
sl2: ast.List* = ast.List*(subject_node)
if sl2 is not None: elts = sl2.elts
if elts is None: return 0
ecount: sizet = len(elts)
if ecount == 0: return 0
subj_vals: sizet* = pool.alloc(8 * ecount)
if subj_vals is None: return 0
for ei in range(ecount):
# Pre-declaring/reassigning elts misdispatches .get to list[int].get (i32 truncation); use an inline alias receiver (like Converter arg_defaults).
elts_inl: list[ast.AST*]* = elts
elt: ast.AST* = elts_inl.get(ei)
sv: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, elt, trans._funcs, trans._func_count, trans)
if sv is None: self.Gen.TypeGenErr.fatal_error(elt, f"match sequence element {ei} translation failed", "TypeError")
subj_vals[ei] = sizet(sv)
# 2. Create the default/end BBs.
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
if name_buf is None: return 0
viperlib.snprintf(name_buf, 32, "match.seq.default.%d", cnt)
default_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.seq.end.%d", cnt)
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
# 3. Pass 1: collect case info (check/body BBs, default flag, nobreak, guard).
cases_list: list[ast.AST*]* = node.cases
if cases_list is None: return 0
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* = pool.alloc(4 * MAX_MATCH_CASES)
nb_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
guard_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 guard_arr is None: return 0
collected: int = 0
has_default: int = 0
# Exhaustive arm (`case _`) node: recorded explicitly in pass1 and translated directly in the default block.
default_case_node: ast.AST* = None
first_check: int = -1
i: int = 0
while i < case_count:
# Capacity guard (aligns the `collected < MAX_MATCH_CASES` check in the match-list paths): the default (case _) branch must also validate collected before writing check_arr/body_arr/def_arr/nb_arr/guard_arr, else too many cases overflow into adjacent pool blocks.
# Over-limit fail-fasts, never silently dropping a case.
if collected >= MAX_MATCH_CASES:
self.Gen.TypeGenErr.fatal_error(node, f"match case count exceeds the limit 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: ast.MatchCase* = ast.MatchCase*(case_node)
pat: ast.AST* = mc.pattern
is_def: int = 0
if pat is not None:
pk: int = pat.kind()
if pk == ast.ASTKind.MatchAs:
ma0: ast.MatchAs* = ast.MatchAs*(pat)
if ma0.pattern is None:
# A top-level `case _ if <cond>`: this sequence path treats `_` as default (always true) and would silently ignore the guard; fail-fast before any IR is produced.
if mc.guard is not None:
self.Gen.TypeGenErr.fatal_error(pat, "the top-level `case _ if <cond>` of a sequence match does not yet support a guard", "NotImplementedError")
is_def = 1
elif pk != ast.ASTKind.MatchSequence:
# fail-fast: sequence matching accepts only MatchSequence / a top-level `_` wildcard.
self.Gen.TypeGenErr.fatal_error(pat, "a sequence-match case supports only an (element) or _ pattern", "TypeError")
if is_def == 1:
has_default = 1
default_case_node = case_node
check_arr[collected] = 0
body_arr[collected] = sizet(default_bb)
else:
if collected >= MAX_MATCH_CASES: break
viperlib.snprintf(name_buf, 32, "match.seq.check.%d.%d", cnt, i)
cbb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.seq.body.%d.%d", cnt, i)
bbb: mlirlite.BasicBlock* = mlirlite.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
guard_arr[collected] = sizet(mc.guard)
collected = collected + 1
i = i + 1
# 4. Entry -> first check (or default/end).
if first_check >= 0:
head_bb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(check_arr[first_check])
mlirlite.build_br(builder, head_bb)
elif has_default == 1: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
# 5. Emit the check chain.
ci: int = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
cbb2: mlirlite.BasicBlock* = mlirlite.BasicBlock*(check_arr[ci])
bbb2: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[ci])
mlirlite.position_at_end(builder, cbb2)
# Compute the match condition (element comparisons && guard).
cond: mlirlite.Value* = None
cnode: ast.AST* = cases_list.get(ci)
mc2: ast.MatchCase* = ast.MatchCase*(cnode)
ms: ast.MatchSequence* = (ast.MatchSequence*)(mc2.pattern)
pats: list[ast.AST*]* = None
if ms is not None: pats = ms.patterns
pcount: sizet = 0
if pats is not None: pcount = len(pats)
if pcount != ecount:
# Element count mismatch -> always false (Python semantics).
cond = mlirlite.const_int(pool, 1, 0)
else:
for pi in range(ecount):
# Pre-declaring/reassigning pats misdispatches .get to list[int].get (i32 truncation); use an inline alias receiver (like Converter arg_defaults).
pats_inl: list[ast.AST*]* = pats
pat1: ast.AST* = pats_inl.get(pi)
if pat1 is None: continue
pk1: int = pat1.kind()
if pk1 == ast.ASTKind.MatchValue:
mv: ast.MatchValue* = (ast.MatchValue*)(pat1)
pv: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, mv.value, trans._funcs, trans._func_count, trans)
if pv is None: self.Gen.TypeGenErr.fatal_error(pat1, f"sequence pattern element {pi} translation failed", "TypeError")
sv1: mlirlite.Value* = mlirlite.Value*(subj_vals[pi])
c1: mlirlite.Value* = self._seq_cmp_eq(trans, builder, sv1, pv)
if c1 is None: self.Gen.TypeGenErr.fatal_error(pat1, "sequence pattern element comparison failed", "TypeError")
if cond is None: cond = c1
else: cond = mlirlite.build_and(builder, cond, c1)
elif pk1 == ast.ASTKind.MatchAs:
ma2: ast.MatchAs* = ast.MatchAs*(pat1)
if ma2.pattern is None:
continue # `_` wildcard: always true
if ma2.name is not None:
# fail-fast: sequence-pattern variable binding not supported.
self.Gen.TypeGenErr.fatal_error(pat1, "sequence-pattern variable binding is not yet supported", "NotImplementedError")
elif pk1 == ast.ASTKind.MatchStar:
continue # remaining elements wildcard: always true
else: self.Gen.TypeGenErr.fatal_error(pat1, "unsupported pattern type in a sequence pattern", "TypeError")
# guard (if any): match holds and guard is non-zero.
g_addr: sizet = guard_arr[ci]
if g_addr != 0:
gnode: ast.AST* = (ast.AST*)(g_addr)
gv: mlirlite.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 translation failed", "TypeError")
gbits: int = self.Gen.ExprGenBase.get_llvm_type_bits(gv.Ty)
if gbits == 1: g1: mlirlite.Value* = gv
else:
gzero: mlirlite.Value* = mlirlite.const_int(pool, gbits if gbits > 0 else 32, 0)
g1 = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, gv, gzero)
if cond is None: cond = g1
else: cond = mlirlite.build_and(builder, cond, g1)
if cond is None: cond = mlirlite.const_int(pool, 1, 1)
# Next check (or default/end).
nxt: mlirlite.BasicBlock* = None
nj: int = ci + 1
while nj < collected and def_arr[nj] == 1: nj = nj + 1
if nj < collected: nxt = mlirlite.BasicBlock*(check_arr[nj])
elif has_default == 1: nxt = default_bb
else: nxt = end_bb
mlirlite.build_cond_br(builder, cond, bbb2, nxt)
ci = ci + 1
# 6. Translate case bodies.
ci = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
bbb3: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[ci])
mlirlite.position_at_end(builder, bbb3)
cnode2: ast.AST* = cases_list.get(ci)
mc3: ast.MatchCase* = (ast.MatchCase*)(cnode2)
# break inside a case exits the match (C switch semantics): save/override _break_bb.
sqb_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
sqb_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 = sqb_old_break
trans._in_switch_case = sqb_old_inmc
# Tail control flow all returns -> emit unreachable (no br end, no fallthrough).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, body_list2) == 0:
if nb_arr[ci] == 0: mlirlite.build_br(builder, end_bb)
else:
# c.NoBreak: fallthrough to the next case body (skip pattern check).
nj2: int = ci + 1
while nj2 < collected and def_arr[nj2] == 1: nj2 = nj2 + 1
if nj2 < collected:
nbb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[nj2])
mlirlite.build_br(builder, nbb)
elif has_default == 1: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
sq_merge_reach = 1
ci = ci + 1
# 7. Default block.
mlirlite.position_at_end(builder, default_bb)
dbody: list[ast.AST*]* = None
if has_default == 1 and default_case_node is not None:
# Translate the `case _` arm recorded by pass1 directly (no def_arr reverse lookup into cases_list, whose collected indices misalign with the original ones when arms are skipped).
dmc_d: ast.MatchCase* = ast.MatchCase*(default_case_node)
# The default arm also follows match-case semantics: break exits the match.
sqd_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
sqd_old_inmc: int = trans._in_switch_case
trans._in_switch_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# Pre-declaring and reassigning dbody misdispatches .get to list[int].get (i32->inttoptr truncation crashes translate_stmt reading a garbage node); use an inline dbody3 receiver (like Converter arg_defaults).
dbody3: list[ast.AST*]* = dmc_d.children
dbody = dbody3
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 = sqd_old_break
trans._in_switch_case = sqd_old_inmc
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, dbody) == 0:
mlirlite.build_br(builder, end_bb)
sq_merge_reach = 1
# 8. Position at end.
mlirlite.position_at_end(builder, end_bb)
# Overwrite to remove nested control-flow pollution (same as the main path).
trans._ctrl_merge_reachable = sq_merge_reach
return 0
# _handle_simple_match - non-REnum path.
# ptrtoint the subject to i32 and dispatch via switch.
def _handle_simple_match(self, trans: HT.Translator*, node: ast.Match*, subject_val: mlirlite.Value*) -> int:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
# Same as the main path: locally track this simple match's br end_bb and overwrite _ctrl_merge_reachable at the end.
sm_merge_reach: int = 0
# 1. Convert the subject to i32 (reuse if already i32, else ptrtoint).
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
subj_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(subject_val.Ty)
subj_val: mlirlite.Value* = subject_val
if subj_bits != 32:
if self.Gen.ExprGenBase.is_ptr_type(subject_val.Ty) != 0: subj_val = mlirlite.build_ptrtoint(builder, subject_val, i32_ty)
elif subj_bits > 32:
# trunc to i32.
subj_val = mlirlite.build_trunc(builder, subject_val, i32_ty)
elif subj_bits == 0:
# Unknown type; try ptrtoint.
subj_val = mlirlite.build_ptrtoint(builder, subject_val, i32_ty)
if subj_val is None:
# fail-fast: subject-to-i32 conversion failed.
self.Gen.TypeGenErr.fatal_error(node, "match subject conversion to i32 failed", "TypeError")
# 1b. Guard pre-scan: any guarded arm switches to the in-order if-else chain (_handle_guarded_simple_match, Python match semantics).
# Without guards, the existing switch path below stays byte-identical (no behavior change).
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 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. Create the default/end BBs.
cnt: int = trans._label_counter
trans._label_counter = cnt + 1
name_buf: str = pool.alloc(32)
if name_buf is None:
# fail-fast: label buffer allocation failed.
self.Gen.TypeGenErr.fatal_error(node, "label buffer allocation failed", "TypeError")
viperlib.snprintf(name_buf, 32, "match.default.%d", cnt)
default_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.end.%d", cnt)
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
# 3. Collect case info.
cases_list: list[ast.AST*]* = node.cases
if cases_list is None:
# fail-fast: match cases missing.
self.Gen.TypeGenErr.fatal_error(node, "match cases missing", "TypeError")
case_count: sizet = len(cases_list)
bb_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
cv_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
def_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
nb_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
# MatchOr node pointer per collected arm (0 = not MatchOr): `case 1 | 2:` must emit a case value per alternative into the same body BB, so record the node and expand alternatives when building the sw list; non-MatchOr stays 0 on the single-cv path.
mo_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
if bb_arr is None or cv_arr is None or def_arr is None or nb_arr is None or mo_arr is None:
# fail-fast: match dispatch array allocation failed.
self.Gen.TypeGenErr.fatal_error(node, "match dispatch array allocation failed", "TypeError")
collected: int = 0
has_default: int = 0
# MatchCase node of the exhaustive `case _` arm: recorded in pass1 and used directly in step 7, instead of a def_arr reverse lookup (whose collected -> cases_list indices misalign when arms are skipped, idling default_bb and misjudging merge reachability).
default_case_node: ast.AST* = None
sw_head: mlirlite.SwitchCase* = None
sw_tail: mlirlite.SwitchCase* = None
sw_count: int = 0
# 4. Pass 1: collect + create BBs + build the list.
i: int = 0
while i < case_count:
case_node: ast.AST* = cases_list.get(i)
if case_node is None:
i = i + 1
continue
if case_node.kind() != ast.ASTKind.MatchCase:
i = i + 1
continue
mc: ast.MatchCase* = ast.MatchCase*(case_node)
pat: ast.AST* = mc.pattern
is_def: int = 0
cv: mlirlite.Value* = None
# MatchOr arm node (None otherwise): pass1 only records it; the per-alternative case values are produced while building the sw list so `case 1 | 2:` hits the same body.
or_node: ast.AST* = None
if pat is not None:
pk: int = pat.kind()
if pk == ast.ASTKind.MatchValue:
mv: ast.MatchValue* = 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:
ms: ast.MatchSingleton* = (ast.MatchSingleton*)(pat)
# None -> 0
cv = mlirlite.const_int32(pool, 0)
elif pk == ast.ASTKind.MatchAs:
ma: ast.MatchAs* = ast.MatchAs*(pat)
if ma.pattern is None:
is_def = 1
has_default = 1
default_case_node = case_node
elif pk == ast.ASTKind.MatchOr:
# Record the whole node: alternatives are expanded when building the sw list, not just the 0th.
or_node = pat
elif pk == ast.ASTKind.MatchClass:
# This path (non-REnum dispatch) does not produce a case value for a class-pattern arm, so it would be silently dropped and its body unreachable.
# If the class-pattern actually points to a registered REnum, REnum dispatch mismatched; fail-fast instead of silently dropping the case.
rnm_nr: str = self._extract_renum_name_from_cls(pat)
if rnm_nr is not None and (self.Gen.ClassGenEnum._is_renum_class_name(rnm_nr) != 0 or self.Gen.ClassGenEnum._is_renum_by_entry(rnm_nr) != 0):
self.Gen.TypeGenErr.fatal_error(pat, f"the non-REnum path received a REnum variant pattern 'case {rnm_nr}.*': no case value can be produced, so the arm would be silently dropped (REnum dispatch mismatch)", "InternalCompilerError")
if is_def == 0:
if collected >= MAX_MATCH_CASES: break
viperlib.snprintf(name_buf, 32, "match.case.%d.%d", cnt, i)
bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
else: bb = default_bb
body_list: list[ast.AST*]* = mc.children
nb: int = self._has_no_break(body_list)
# Collection slot used by this arm (collected increments after; bounds are already guaranteed when is_def==0).
cur_slot: int = collected
if collected < MAX_MATCH_CASES:
bb_arr[cur_slot] = sizet(bb)
cv_arr[cur_slot] = sizet(cv)
def_arr[cur_slot] = is_def
nb_arr[cur_slot] = nb
mo_arr[cur_slot] = sizet(or_node)
collected = collected + 1
if is_def == 0:
mo_addr: sizet = mo_arr[cur_slot]
if mo_addr != 0:
# MatchOr arm: emit one case value per alternative, all pointing to the same bb (equivalent to C `case 1: case 2:` sharing a body); non-MatchValue/MatchSingleton alternatives fail-fast.
mo2: ast.MatchOr* = (ast.MatchOr*)(mo_addr)
pats2: list[ast.AST*]* = mo2.patterns
if pats2 is None or len(pats2) == 0: self.Gen.TypeGenErr.fatal_error(pat, "switch OR pattern has no alternative sub-patterns", "TypeError")
pats2_n: sizet = len(pats2)
pats2_i: int = 0
while pats2_i < pats2_n:
alt_pat: ast.AST* = pats2.get(pats2_i)
if alt_pat is not None:
alt_cv: mlirlite.Value* = None
if alt_pat.kind() == ast.ASTKind.MatchValue:
alt_mv: ast.MatchValue* = (ast.MatchValue*)(alt_pat)
alt_cv = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, alt_mv.value, trans._funcs, trans._func_count, trans)
elif alt_pat.kind() == ast.ASTKind.MatchSingleton:
# None -> 0 (same as the single-value/guard paths).
alt_cv = mlirlite.const_int32(pool, 0)
else: self.Gen.TypeGenErr.fatal_error(alt_pat, "a switch OR pattern alternative is not a value pattern", "TypeError")
if alt_cv is not None:
sc2: mlirlite.SwitchCase* = mlirlite.new_switch_case(pool, alt_cv, bb)
if sc2 is not None:
if sw_head is None:
sw_head = sc2
sw_tail = sc2
else:
sw_tail.Next = sc2
sw_tail = sc2
sw_count = sw_count + 1
pats2_i = pats2_i + 1
elif cv is not None:
sc: mlirlite.SwitchCase* = mlirlite.new_switch_case(pool, cv, bb)
if sc is not None:
if sw_head is None:
sw_head = sc
sw_tail = sc
else:
sw_tail.Next = sc
sw_tail = sc
sw_count = sw_count + 1
i = i + 1
# 5. Emit the switch.
mlirlite.build_switch(builder, subj_val, default_bb, sw_head, sw_count)
# 6. Pass 2: translate case bodies.
i = 0
while i < collected:
case_node: ast.AST* = cases_list.get(i)
if case_node is None:
i = i + 1
continue
mc: ast.MatchCase* = ast.MatchCase*(case_node)
bb_addr: sizet = bb_arr[i]
case_bb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(bb_addr)
is_def: int = def_arr[i]
nb: int = nb_arr[i]
# The default case body is translated in step 7; skip here to avoid duplication.
if is_def == 1:
i = i + 1
continue
mlirlite.position_at_end(builder, case_bb)
# break inside a case exits the match (C switch semantics): save/override _break_bb.
smb_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
smb_old_inmc: int = trans._in_switch_case
trans._in_switch_case = 1
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
body_list: list[ast.AST*]* = mc.children
if body_list is not None:
bc: sizet = len(body_list)
bi: int = 0
while bi < bc:
stmt: ast.AST* = body_list.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 = smb_old_break
trans._in_switch_case = smb_old_inmc
# If not terminated: br to end by default; with fallthrough (the `fallthrough` marker or compatible c.NoBreak()) fall into the next case body, skipping its pattern check.
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, body_list) == 0:
if nb == 0: mlirlite.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: mlirlite.BasicBlock* = mlirlite.BasicBlock*(bb_arr[sm_ft_nj])
mlirlite.build_br(builder, sm_ft_bb)
elif has_default != 0: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
sm_merge_reach = 1
i = i + 1
# 7. Default block (including the `case _` exhaustive arm).
mlirlite.position_at_end(builder, default_bb)
if has_default == 0 or default_case_node is None:
# No explicit default: br default -> end (a non-exhaustive match means merge is reachable).
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
sm_merge_reach = 1
else:
# Translate the `case _` arm recorded by pass1 directly (no def_arr reverse lookup into cases_list, whose collected indices misalign with the original ones when arms are skipped).
dmc_d: ast.MatchCase* = ast.MatchCase*(default_case_node)
# The default arm also follows match-case semantics: break exits the match.
smd_old_break: mlirlite.BasicBlock* = trans._break_bb
trans._break_bb = end_bb
smd_old_inmc: int = trans._in_switch_case
trans._in_switch_case = node.is_switch
self.Gen.VarGen.enter_scope(trans.SymTab, SCOPE_MATCH)
# Pre-declaring and reassigning dbody misdispatches .get to list[int].get (i32->inttoptr truncation crashes translate_stmt reading a garbage node); use an inline dbody3 receiver (like Converter arg_defaults).
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 = smd_old_break
trans._in_switch_case = smd_old_inmc
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, dbody3) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
sm_merge_reach = 1
# 8. Position at end.
mlirlite.position_at_end(builder, end_bb)
# Overwrite to remove nested control-flow pollution (same as the main path).
trans._ctrl_merge_reachable = sm_merge_reach
return 0
# _handle_guarded_simple_match - guard branch of _handle_simple_match.
# When any arm has a guard, dispatch via an in-order if-else chain (Python match semantics: first arm whose pattern matches and guard is true). cond_i = value arm (subj_val == case value) / wildcard (always true), ANDed with the guard normalized to i1 (same as _handle_sequence_match). Unguarded match/switch never enters here; the existing switch path stays byte-identical.
def _handle_guarded_simple_match(self, trans: HT.Translator*, node: ast.Match*, subj_val: mlirlite.Value*) -> int:
pool: memhub.MemBuddy* = trans.Pool
builder: mlirlite.IRBuilder* = trans._cur_builder
func: mlirlite.Function* = trans._cur_func
# Same as the main path: locally track this guarded match's br end_bb and overwrite _ctrl_merge_reachable at the end.
gm_merge_reach: int = 0
# 1. Create the default/end BBs.
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, "label buffer allocation failed", "TypeError")
viperlib.snprintf(name_buf, 32, "match.guard.default.%d", cnt)
default_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.guard.end.%d", cnt)
end_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
# 2. Collect case info (check/body BBs, default flag, nobreak, mc, cv).
cases_list: list[ast.AST*]* = node.cases
if cases_list is None: self.Gen.TypeGenErr.fatal_error(node, "match cases missing", "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* = pool.alloc(4 * MAX_MATCH_CASES)
nb_arr: int* = pool.alloc(4 * MAX_MATCH_CASES)
mc_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
cv_arr: sizet* = pool.alloc(8 * MAX_MATCH_CASES)
# MatchOr node pointer per collected arm (0 = not MatchOr): the check chain turns `case 1 | 2` into an OR of subj_val == cv_i per alternative, so record the node and expand alternatives when emitting checks; non-MatchOr stays 0 on the single-cv path.
or_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 or or_arr is None:
self.Gen.TypeGenErr.fatal_error(node, "match dispatch array allocation failed", "TypeError")
collected: int = 0
has_default: int = 0
# The single guardless wildcard arm (default): its body is default_bb, the fall-through when all checks miss.
# Guarded wildcards are not defaults (each has its own check/body BB) so their guard is not silently ignored.
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 count exceeds the limit 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: ast.MatchCase* = ast.MatchCase*(case_node)
pat: ast.AST* = mc.pattern
gnode0: ast.AST* = mc.guard
is_def: int = 0
cv: mlirlite.Value* = None
# MatchOr arm node (None otherwise): pass1 only records it; check emission expands each alternative into an OR of subj_val == cv_i.
or_node: ast.AST* = None
if pat is not None:
pk: int = pat.kind()
if pk == ast.ASTKind.MatchValue:
mv: ast.MatchValue* = 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 = mlirlite.const_int32(pool, 0)
elif pk == ast.ASTKind.MatchAs:
ma: ast.MatchAs* = ast.MatchAs*(pat)
if ma.pattern is None and gnode0 is None:
# The single guardless wildcard arm -> default.
is_def = 1
# ma.pattern is None with a guard -> guarded wildcard: condition always true, ANDed with the guard at emission; not a default.
elif pk == ast.ASTKind.MatchOr:
# Record the whole node: expanded per alternative into an OR at check emission (step 4).
or_node = pat
else:
# No silent fallback: the guard branch only supports value/None/wildcard/OR patterns.
self.Gen.TypeGenErr.fatal_error(pat, "unsupported case pattern in the guard branch", "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: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
viperlib.snprintf(name_buf, 32, "match.guard.body.%d.%d", cnt, i)
bbb: mlirlite.BasicBlock* = mlirlite.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)
or_arr[collected] = sizet(or_node)
collected = collected + 1
i = i + 1
# 3. Entry -> first check (or default/end).
if first_check >= 0:
head_bb: mlirlite.BasicBlock* = mlirlite.BasicBlock*(check_arr[first_check])
mlirlite.build_br(builder, head_bb)
elif has_default == 1: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
# 4. Emit the check chain (in source order).
ci: int = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
cbb2: mlirlite.BasicBlock* = mlirlite.BasicBlock*(check_arr[ci])
bbb2: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[ci])
mlirlite.position_at_end(builder, cbb2)
mc2: ast.MatchCase* = ast.MatchCase*(mc_arr[ci])
# Value condition: subj_val == case value (a wildcard arm has no case value, so cond stays None = always true).
cond: mlirlite.Value* = None
or_addr: sizet = or_arr[ci]
cv_addr: sizet = cv_arr[ci]
if or_addr != 0:
# MatchOr arm: cond = (subj_val == cv_0) OR (subj_val == cv_1) OR ...; each alternative is compared then ORed (non-MatchValue/MatchSingleton alternatives cannot produce a value -> fail-fast).
mo4: ast.MatchOr* = (ast.MatchOr*)(or_addr)
pats4: list[ast.AST*]* = mo4.patterns
if pats4 is None or len(pats4) == 0: self.Gen.TypeGenErr.fatal_error(node, "guard OR pattern has no alternative sub-patterns", "TypeError")
pats4_n: sizet = len(pats4)
pats4_i: int = 0
while pats4_i < pats4_n:
alt_pat2: ast.AST* = pats4.get(pats4_i)
if alt_pat2 is not None:
alt_cv2: mlirlite.Value* = None
if alt_pat2.kind() == ast.ASTKind.MatchValue:
alt_mv2: ast.MatchValue* = (ast.MatchValue*)(alt_pat2)
alt_cv2 = self.Gen.ExprGen.translate_value(builder, pool, trans.Module, alt_mv2.value, trans._funcs, trans._func_count, trans)
elif alt_pat2.kind() == ast.ASTKind.MatchSingleton:
# None -> 0 (same as the single-value path).
alt_cv2 = mlirlite.const_int32(pool, 0)
else: self.Gen.TypeGenErr.fatal_error(alt_pat2, "a guard OR pattern alternative is not a value pattern", "TypeError")
if alt_cv2 is not None:
cc: mlirlite.Value* = self._seq_cmp_eq(trans, builder, subj_val, alt_cv2)
if cc is None: self.Gen.TypeGenErr.fatal_error(node, "match OR alternative value comparison failed", "TypeError")
if cond is None: cond = cc
else: cond = mlirlite.build_or(builder, cond, cc)
pats4_i = pats4_i + 1
if cond is None: self.Gen.TypeGenErr.fatal_error(node, "the guard OR pattern produced no comparison condition", "TypeError")
elif cv_addr != 0:
cv2: mlirlite.Value* = (mlirlite.Value*)(cv_addr)
c1: mlirlite.Value* = self._seq_cmp_eq(trans, builder, subj_val, cv2)
if c1 is None: self.Gen.TypeGenErr.fatal_error(node, "match case value comparison failed", "TypeError")
cond = c1
# guard (if any): match holds and guard is non-zero (normalized to i1, same as the sequence path).
g_addr: sizet = sizet(mc2.guard)
if g_addr != 0:
gnode: ast.AST* = (ast.AST*)(g_addr)
gv: mlirlite.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 translation failed", "TypeError")
gbits: int = self.Gen.ExprGenBase.get_llvm_type_bits(gv.Ty)
if gbits == 1: g1: mlirlite.Value* = gv
else:
gzero: mlirlite.Value* = mlirlite.const_int(pool, gbits if gbits > 0 else 32, 0)
g1 = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, gv, gzero)
if cond is None: cond = g1
else: cond = mlirlite.build_and(builder, cond, g1)
if cond is None: cond = mlirlite.const_int(pool, 1, 1)
# Next check (skipping default) -> else default/end.
nxt: mlirlite.BasicBlock* = None
nj: int = ci + 1
while nj < collected and def_arr[nj] == 1: nj = nj + 1
if nj < collected: nxt = mlirlite.BasicBlock*(check_arr[nj])
elif has_default == 1: nxt = default_bb
else: nxt = end_bb
mlirlite.build_cond_br(builder, cond, bbb2, nxt)
ci = ci + 1
# 5. Translate case bodies.
ci = 0
while ci < collected:
if def_arr[ci] == 1:
ci = ci + 1
continue
bbb3: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[ci])
mlirlite.position_at_end(builder, bbb3)
mc3: ast.MatchCase* = ast.MatchCase*(mc_arr[ci])
# break inside a case exits the match (C switch semantics): save/override _break_bb.
gmb_old_break: mlirlite.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
# If not terminated: br to end by default; with fallthrough fall into the next non-wildcard body.
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, body_list2) == 0:
if nb_arr[ci] == 0: mlirlite.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: mlirlite.BasicBlock* = mlirlite.BasicBlock*(body_arr[nj2])
mlirlite.build_br(builder, nbb)
elif has_default == 1: mlirlite.build_br(builder, default_bb)
else: mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
ci = ci + 1
# 6. Default block (the single guardless wildcard arm; reached when all checks miss).
mlirlite.position_at_end(builder, default_bb)
if has_default == 0 or default_case_node is None:
# No guardless wildcard arm: non-exhaustive match => merge reachable, br default -> end.
if mlirlite.builder_cur_block_is_terminated(builder) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
else:
dmc_d: ast.MatchCase* = ast.MatchCase*(default_case_node)
# The default arm also follows match-case semantics: break exits the match.
gmd_old_break: mlirlite.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)
# Inline dbody3 receiver traversal (avoids .get misdispatching to list[int].get with truncation).
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
# Tail control flow all returns -> emit unreachable (no br end_bb).
if mlirlite.builder_cur_block_is_terminated(builder) == 0 and self._case_tail_unreachable(trans, builder, dbody3) == 0:
mlirlite.build_br(builder, end_bb)
trans._ctrl_merge_reachable = 1
gm_merge_reach = 1
# 7. Position at end.
mlirlite.position_at_end(builder, end_bb)
# Overwrite to remove nested control-flow pollution (same as the main path).
trans._ctrl_merge_reachable = gm_merge_reach
return 0