fix(tpc): 属性取值失败的报文补"基址诊断" + 补掉声明加载去重签名的真实缺口

类别:a(编译器缺陷)—— 两者都是"缺事实/漏输入",不是放宽判定。

【一】`_RaiseUnresolvedExpr` 增打基址事实(HandlesBase)
现场:TransPyV/App/lib/core/Gen/MatchGenMixin.vp:231
  `val: ast.AST* = expr_node.value`(base 是 `expr_node: rawptr[ast.Expr]`)
  → Attribute 取值返回 None → 本报文 + sys.exit(1)。
属性读回 None 的根因只有两类:① 类名登记退化(var_struct_class 存 'rawptr' /
'rawptr[X]' / 折叠态,或压根没登记);② 登记名与 Gen.structs / class_members 的键
不同源(短名 vs sha1 全名)。原报文只有"目标变量 + 注解 + 源码",不含任何一项事实
⇒ 只能从"求值为空"反推。现追加一行(仅 Attribute + Name 基址时,其余形态逐字不变):
  基址诊断: 'expr_node' 的 var_struct_class=<...>;alloca 类型=<...>;
            该名在 Gen.structs=<是/否>;class_members=<N 个字段/无该键>
与 `_DescribeMemberSlot` 同口径:列数据、不猜。

【二】`_ClassMembersReloadSignature` 的"类数"改为"逐类 (键, 表长) 元组"(HandlesImports)
上一轮 3399b44c 的签名里全局进度只记 `len(class_members)`(键数)。缺口:**某已有类的
成员表继续增长时键数不变**(典型=父类成员后到),而父类成员正是继承前缀的唯一来源
⇒ 极端时序下会漏掉一次必要重跑。改为逐类 (键, 长度) 元组,并补记
`len(class_parents)` / `len(class_parent)`(父链登记本身也是输入)。
代价 O(类数)(约 300 项/次调用),仍远低于一次重跑的成本。

验证:GetDiagnostics 干净;py 层 import + 既有 16 例类型判定 smoke test 全过。
未跑编译器(无 includes 改动,无需重编)。
This commit is contained in:
TermiNexus committed 2026-09-29 16:50:51 +08:00
1 parent aa1b536517
commit 716cd61a75
2 files changed
+36 -2

No files matched your search

+28
View File
@@ -658,6 +658,34 @@ class BaseHandle:
f"{(',目标变量 ' + repr(VarName)) if VarName else ''}"
f"{(',注解 ' + ann_text) if ann_text else ''}。位置 {loc}"
f"{(',源码: ' + code_line) if code_line else ''}")
# 【基址诊断】属性读返回 None 的根因只有两类(先说事实,不再从"求值为空"反推):
# ① 类名登记退化 —— var_struct_class 存的是占位/折叠态('rawptr'、'rawptr[X]'、
# 'CVoid'…)或压根没登记;
# ② 登记名与 Gen.structs / class_members 的键不同源(短名 vs sha1 全名)。
# 故把"基址变量登记成什么 + alloca 实际 LLVM 类型 + 该登记名在两表的命中情况"
# 一并打进报文(与 _DescribeMemberSlot 同口径:列数据而非猜)。仅 Attribute + Name
# 基址时追加,其余形态报文逐字不变。
if isinstance(Node, ast.Attribute) and isinstance(Node.value, ast.Name):
_bn: str = Node.value.id
_vc: str | None = None
_alloc: Any = None
try:
_vc = (getattr(Gen, 'var_struct_class', None) or {}).get(_bn)
if not _vc:
_vc = (getattr(Gen, 'global_struct_class', None) or {}).get(_bn)
_alloc = (getattr(Gen, 'variables', None) or {}).get(_bn)
except Exception: # noqa: BLE001
pass
_slot_ty: object = getattr(_alloc, 'type', None)
if _vc:
_hit_st: str = '是' if _vc in Gen.structs else '否'
_hit_cm: str = ('%d 个字段' % len(Gen.class_members.get(_vc) or [])) \
if _vc in Gen.class_members else '无该键'
else:
_hit_st, _hit_cm = '—', '—'
msg += (f"\n│ 基址诊断: '{_bn}' 的 var_struct_class={_vc!r};"
f"alloca 类型={_slot_ty};该名在 Gen.structs={_hit_st};"
f"class_members={_hit_cm}")
_vlog().compile_error(msg, src_file, lineno, code_line)
sys.exit(1)
+8 -2
View File
@@ -3206,7 +3206,10 @@ class ImportHandle(BaseHandle):
"""`_TryLoadClassMembersFromPyi` 重跑的依赖签名(纯读,不产生副作用)。
收录"重跑能否产生新结果"的全部输入:
- 全局进度:`len(class_members)` / `len(_members_flattened)`
- 全局进度:**逐类**(`class_members` 键, 该表长度) 的完整元组(不是"类数"——
只看键数会在"某已有类的成员表继续增长"(父类成员后到)时看不出变化,
而父类成员正是继承前缀的来源);`_members_flattened` 长度;
`class_parents` / `class_parent` 的条目数(父链登记本身也是输入)
- 本类自身:成员表长度、是否已登记已展平、struct body 元素数(None ⇒ -1)
- 各父类:成员表长度 + 是否已展平(父类从 `class_parents` / `class_parent` 取;
两者都缺时用空元组 —— 此时本类依赖尚未建立,签名仍稳定,不影响收敛)
@@ -3225,9 +3228,12 @@ class ImportHandle(BaseHandle):
(_p, len(Gen.class_members.get(_p, []) or []), _p in _flat)
for _p in _parents
)
_cm_sig: tuple = tuple((_k, len(_v)) for _k, _v in Gen.class_members.items())
return (
len(Gen.class_members),
_cm_sig,
len(_flat),
len(getattr(Gen, 'class_parents', {}) or {}),
len(getattr(Gen, 'class_parent', {}) or {}),
len(Gen.class_members.get(class_name, []) or []),
class_name in _flat,
_st_len,