fix(tpc): REnum 变体嵌套结构体按需补发 + 模式捕获绑不上即 fail-fast

a 编译器缺陷:导入模块侧 stub 只声明主 REnum(`%"…LLVMType" = type {…}`),**不含**
变体嵌套结构体(`LLVMType_Ptr` 等)——它们由 `_EmitExternalClassDeclLlvm` 的 REnum
分支按主结构体布局就地生成。若本模块从未触发该 REnum 的外部声明发射:

  1) match 模式捕获(`case llvmlite.LLVMType.Ptr(pointee_cs):`)绑定静默为空 ⇒ case
     body 里引用的绑定名随后报"未定义名称"(现场 VpiReader.vp:1521 `pointee_cs`);
  2) REnum 变体构造的载荷实参静默丢弃(只写 tag)—— 静默错码。

修法:
  - 新增 BaseHandle._EnsureRenumVariantStruct:成员缺失时按需补发(先试 stub,再按
    类 sha1 找到定义模块 .vpi 触发 `_TryLoadClassMembersFromPyi` → 内部即调
    `_EmitExternalClassDeclLlvm`)。
  - HandlesMatch._HandleRenumMatchLlvm:改用该助手;**模式捕获登记不全即 fail-fast**
    (报出变体名、class_members 项数、structs 命中与同前缀键),绝不让绑定名留到
    取名时才炸。
  - HandlesMatch 非 REnum 兜底分支:MatchClass 含捕获时改为 fail-fast,并列出判据
    (IsEnumMember/EnumName/IsRenum/IsStruct/IsEnum),直接暴露"外层 REnum 判定为何
    未成立"。
  - HandlesExprCall._HandleREnumConstructLlvm:载荷实参存在而嵌套结构体缺失时补齐,
    补不上则 fail-fast(拒绝产出"只有 tag 没有载荷"的值)。
This commit is contained in:
TermiNexus committed 2026-09-29 11:21:19 +08:00
1 parent 02d89b0900
commit 53bde9157e
3 files changed
+130 -7

No files matched your search

+39
View File
@@ -997,6 +997,45 @@ class BaseHandle:
return True
return False
def _EnsureRenumVariantStruct(self, Gen: Any, RenumName: str, VariantName: str) -> str:
"""确保 REnum 变体嵌套结构体 `<REnumName>_<VariantName>` 及其 class_members 就绪。
为什么需要:导入模块侧的 stub **只声明主 REnum**(如
`%"…LLVMType" = type { i32, i64, i64, i64 }`),**不含**变体嵌套结构体声明
(`LLVMType_Ptr` 等)——它们由 `_EmitExternalClassDeclLlvm` 的 REnum 分支按主
结构体布局就地生成。若本模块从未触发该 REnum 的外部声明发射,`Gen.structs` /
`Gen.class_members` 里就没有变体——于是:
- match 模式捕获(`case LLVMType.Ptr(pointee):`)绑定静默为空 ⇒ case body
里引用的绑定名随后变成"未定义名称"(现场 VpiReader.vp:1521 `pointee_cs`);
- REnum 变体构造的载荷实参静默丢弃(只写了 tag)。
故按需补发:先试 stub,再按类 sha1 找到定义模块的 `.vpi` 触发完整外部声明发射
(`_TryLoadClassMembersFromPyi` 内部即调 `_EmitExternalClassDeclLlvm`)。
返回嵌套结构体名(调用方据此查 `Gen.class_members` 判可用性)。
"""
NestedStructName: str = f"{RenumName}_{VariantName}"
if Gen.class_members.get(NestedStructName):
return NestedStructName
_sha1: str | None = None
for _map in (getattr(Gen, 'class_sha1_map', {}), getattr(Gen, '_struct_sha1_map', {})):
_sha1 = _map.get(RenumName)
if _sha1:
break
_IH: Any = getattr(self.Trans, 'ImportHandler', None)
if _IH is None:
return NestedStructName
try:
_IH._TryLoadStructFromStub(NestedStructName, Gen)
except Exception: # 与既有导入路径同口径:失败由下方判据统一暴露
pass
if not Gen.class_members.get(NestedStructName) and _sha1:
try:
_IH._TryLoadClassMembersFromPyi(RenumName, f"{_sha1}.stub.ll", Gen)
except Exception:
pass
return NestedStructName
def CollectAnnAssignMember(
self, item: ast.AST, Gen: Any
) -> tuple[str, _ir.Type, CTypeInfo | None] | None:
+16
View File
@@ -2231,6 +2231,22 @@ class ExprCallHandle(BaseHandle):
tag_ptr = Gen.builder.gep(result, [ir.Constant(ir.IntType(32), 0), ir.Constant(ir.IntType(32), 0)], name="renum_tag_ptr")
Gen._store(ir.Constant(ir.IntType(32), TagValue), tag_ptr)
NestedStructName = f"{RenumName}_{VariantName}"
if Node.args:
# 变体嵌套结构体按需补发:导入模块侧 stub 只声明主 REnum,不含
# `<REnum>_<Variant>` 声明(见 BaseHandle._EnsureRenumVariantStruct)。
# 缺它时历史行为是**静默丢弃全部载荷实参**(只写 tag)—— 属静默错码,
# 故此处补齐;补不上则响亮失败,绝不产出"只有 tag 没有载荷"的值。
self._EnsureRenumVariantStruct(Gen, RenumName, VariantName)
if NestedStructName not in Gen.structs or not Gen.class_members.get(NestedStructName):
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"REnum 变体 '{RenumName}.{VariantName}' 的嵌套结构体/载荷成员缺失,"
f"无法写入 {len(Node.args)} 个载荷实参(拒绝静默丢弃):"
f"structs 命中={'有' if NestedStructName in Gen.structs else '无'} "
f"class_members={len(Gen.class_members.get(NestedStructName, []))} 项;"
f"同前缀 structs 键="
f"{[k for k in Gen.structs if k.startswith(RenumName + '_')][:8]}"
)
if NestedStructName in Gen.structs and Node.args:
NestedStructType = Gen.structs[NestedStructName]
NestedStructPtrType = ir.PointerType(NestedStructType)
+75 -7
View File
@@ -8,6 +8,32 @@ import llvmlite.ir as ir
class MatchHandle(BaseHandle):
@staticmethod
def _PatternCaptureNames(Pattern: ast.AST) -> list[str]:
"""模式中所有**捕获名**(递归 MatchOr/MatchClass/MatchSequence/MatchMapping)。
用于判定"该 case 是否必须登记绑定":有捕获却登记不上,case body 里的名字
就会变成"未定义名称",必须在**生成 body 之前**响亮失败,而不是留到取名时。
"""
Names: list[str] = []
if isinstance(Pattern, ast.MatchAs):
if Pattern.name:
Names.append(Pattern.name)
if Pattern.pattern is not None:
Names.extend(MatchHandle._PatternCaptureNames(Pattern.pattern))
elif isinstance(Pattern, ast.MatchStar):
if Pattern.name:
Names.append(Pattern.name)
elif isinstance(Pattern, (ast.MatchOr, ast.MatchSequence, ast.MatchClass, ast.MatchMapping)):
for p in (getattr(Pattern, 'patterns', None) or []):
Names.extend(MatchHandle._PatternCaptureNames(p))
for p in (getattr(Pattern, 'kwd_patterns', None) or []):
Names.extend(MatchHandle._PatternCaptureNames(p))
_rest: str | None = getattr(Pattern, 'rest', None)
if _rest:
Names.append(_rest)
return Names
def _HandleMatchLlvm(self, Node: ast.Match) -> None:
Gen: "Translator.LlvmGen" = self.Trans.LlvmGen
SubjectVal: ir.Value | None = self.HandleExprLlvm(Node.subject)
@@ -152,6 +178,32 @@ class MatchHandle(BaseHandle):
if SymInfo_mc and SymInfo_mc.IsEnumMember:
TagValue_mc = SymInfo_mc.value
if TagValue_mc is not None:
# 兜底分支**不登记捕获绑定**(它按整数 tag 处理,无变体载荷可 GEP)。
# 因此只要该 case 有捕获,就必须在此 fail-fast —— 否则 case body 里的
# 绑定名随后变成"未定义名称"(现场 VpiReader.vp:1521 pointee_cs),
# 真因(外层 REnum 判定未成立)被掩盖。报文直接列出判据供定位。
_caps_mc: list[str] = self._PatternCaptureNames(pattern)
if _caps_mc:
from lib.core.LLVMCG.FuncGen import FailFastError
_en_mc: str | None = getattr(SymInfo_mc, 'EnumName', None)
_sym_en: "SymbolTable.SymbolInfo" = (
self.Trans.SymbolTable.lookup(_en_mc) if _en_mc else None)
_sym_v: "SymbolTable.SymbolInfo" = (
self.Trans.SymbolTable.lookup(VariantName_mc) if VariantName_mc else None)
raise FailFastError(
f"REnum 变体模式 '{QualifiedName_mc or VariantName_mc}' 含捕获 {_caps_mc},"
f"但外层 REnum 匹配判定未成立(IsRenumMatch=False)⇒ 兜底分支不登记捕获绑定,"
f"case body 里这些名字必然成为'未定义名称'。判据:"
f"符号表['{QualifiedName_mc}'] => "
f"IsEnumMember={getattr(SymInfo_mc, 'IsEnumMember', None)} "
f"value={getattr(SymInfo_mc, 'value', None)} EnumName={_en_mc!r};"
f"符号表['{_en_mc}'] => IsRenum={getattr(_sym_en, 'IsRenum', None)} "
f"IsStruct={getattr(_sym_en, 'IsStruct', None)} "
f"IsEnum={getattr(_sym_en, 'IsEnum', None)};"
f"符号表['{VariantName_mc}'] => "
f"IsEnumMember={getattr(_sym_v, 'IsEnumMember', None)} "
f"EnumName={getattr(_sym_v, 'EnumName', None)!r}"
)
CaseValues.append(ir.Constant(SwitchIntType, TagValue_mc))
CaseBBs.append(Gen.func.append_basic_block(name=f"match.case_{VariantName_mc}"))
else:
@@ -297,13 +349,29 @@ class MatchHandle(BaseHandle):
CaseValues.append(ir.Constant(ir.IntType(32), TagValue))
CaseBB: ir.Block = Gen.func.append_basic_block(name=f"match.case_{VariantName}")
CaseBBs.append(CaseBB)
NestedStructName: str = f"{RenumName}_{VariantName}"
if NestedStructName in Gen.structs:
members: list[tuple[str, ir.Type]] = Gen.class_members.get(NestedStructName, [])
payLoad_members: list[tuple[str, ir.Type]] = [(n, t) for n, t in members if n != '__tag']
for j, sub_pat in enumerate(pattern.patterns):
if isinstance(sub_pat, ast.MatchAs) and sub_pat.name and j < len(payLoad_members):
bindings.append((sub_pat.name, payLoad_members[j][0], payLoad_members[j][1], j))
# 变体嵌套结构体按需补发:导入模块侧 stub 只声明主 REnum,
# 不含 `<REnum>_<Variant>` 声明(见 _EnsureRenumVariantStruct)。
NestedStructName: str = self._EnsureRenumVariantStruct(Gen, RenumName, VariantName)
members: list[tuple[str, ir.Type]] = Gen.class_members.get(NestedStructName, [])
payLoad_members: list[tuple[str, ir.Type]] = [(n, t) for n, t in members if n != '__tag']
# 模式捕获必须能全部绑定:绑不上就在此 fail-fast,绝不让 case body
# 里的绑定名留到后面变成"未定义名称"(现场 VpiReader.vp:1521 pointee_cs)。
_captures: list[str] = [p.name for p in pattern.patterns
if isinstance(p, ast.MatchAs) and p.name]
if _captures and len(payLoad_members) < len(_captures):
from lib.core.LLVMCG.FuncGen import FailFastError
raise FailFastError(
f"REnum 变体模式 '{RenumName}.{VariantName}' 的载荷成员不足,"
f"无法绑定模式捕获 {_captures}:"
f"class_members['{NestedStructName}']={len(members)} 项"
f"(去 __tag 后载荷 {len(payLoad_members)} 项);"
f"structs 命中={'有' if NestedStructName in Gen.structs else '无'};"
f"同前缀 structs 键="
f"{[k for k in Gen.structs if k.startswith(RenumName + '_')][:8]}"
)
for j, sub_pat in enumerate(pattern.patterns):
if isinstance(sub_pat, ast.MatchAs) and sub_pat.name and j < len(payLoad_members):
bindings.append((sub_pat.name, payLoad_members[j][0], payLoad_members[j][1], j))
CaseBindings.append(bindings)
else:
CaseValues.append(ir.Constant(ir.IntType(32), 0))