fix(ExprAttr): 嵌套属性读的每个 silent return 都留痕(含内层失败原因链),Repl.vp:802 类错误不再只能看到"基座形态"

类别:a 编译器缺陷(可观测性补齐:报错点与真因相隔数层)

现象:TPC 编译 TransPyV/App/lib/core/Repl.vp:802
  `ct_names: list[str]* = GenApi._g_gen.TypeGenCType.CTypedefNames`
报「带注解赋值表达式 …(Attribute 形态)编译期求值为空(表达式未解析出目标、无值可存)…
赋值将被跳过」,报文里只有「基座形态=Attribute(.TypeGenCType←Attribute(._g_gen←Name(GenApi)))」,
看不出是三级链的哪一级失败、为什么失败。

判据:`_handle_attr_nested`(多级属性读)里有 7 处 `return None` 是**静默**的,而
`Gen._LastAttrNone`(`_NoteAttrNone` 记账,供 `_DescribeAttrBase` 回显"读取侧原因")只在
`_handle_attr_name`/`_gep_and_Load_member` 一侧登记 ⇒ 本函数的失败在报文里完全没有原因。
且嵌套读取时**内层**的记账会被外层覆盖,即使内层登记了也回显不出来。

修法(纯记账,不改任何判定/IR):
1. `_handle_attr_nested` 的每处静默 return 都调 `_NoteAttrNone` 记下**具体事实**:
   · 基座求值为空/非指针 → 附 type + **内层链的失败原因**(把 `_LastAttrNone` 里"字段名不同"
     的那条记录串进来,形成原因链);
   · 类名提示未命中 Gen.structs 键(报 structs 键数);
   · struct 实例类型异常;
   · body 仍 opaque 且无泛型特化成员;
   · **字段在类中取不到偏移**(报成员表项数 / body 槽数 —— 跨模块成员表缺失或漂移的关键判据);
   · 槽是 void 占位;
   · 结构体分支 pointee 未匹配任何类(报 structs 键数)。
2. `_DescribeAttrBase` 的回显键是 (字段名, 类名),本次登记与报错节点一致 ⇒ 下次同类报错将
   直接给出"读取侧原因=…",一轮定位到级与因。

验证:py_compile EXIT=0。真因证据(同一现场、另一侧):`TypeGenCType` 类体一个字段都没声明,
5 个字段全靠方法体裸 `self.x = ...`;其 .vpi(temp/05d515186a7c3668.vpi)里确实没有
CTypedefNames —— 跨模块成员表缺该字段即本次链断之处,已在 TransPyV 侧按"跨模块可见字段
必须在类体声明"补上(见另一仓 commit)。
This commit is contained in:
TermiNexus committed 2026-09-30 09:59:27 +08:00
1 parent c86ecb0c89
commit a18ca4da3b
1 file changed
+39
+39
View File
@@ -834,6 +834,18 @@ class ExprAttrHandle(BaseHandle):
return cdefine_result
ObjVal: Any = self.HandleExprLlvm(Node.value)
if not ObjVal or not isinstance(ObjVal.type, ir.PointerType):
# 取证(纯记账):基座求值为空/非指针时,把**内层链的失败原因**一并挂上——
# 嵌套读取的内层记录随后会被本节点的记录覆盖,报错点(本节点)与真因(内层某级)
# 相隔数层的可读性问题由此解决(现场:`GenApi._g_gen.TypeGenCType.CTypedefNames`)。
_prev_an: Any = getattr(Gen, '_LastAttrNone', None)
_chain_an: str = ''
if (isinstance(_prev_an, tuple) and len(_prev_an) >= 3 and _prev_an[2]
and _prev_an[1] and _prev_an[1] != Node.attr):
_chain_an = f";内层字段 '{_prev_an[1]}' 的失败原因:{_prev_an[2]}"
self._NoteAttrNone(
Gen, None, Node.attr,
f"基座 {self._ExprShape(Node.value)} 求值为空或非指针"
f"(type={getattr(ObjVal, 'type', None)}){_chain_an}")
return None
pointee: Any = ObjVal.type.pointee
@@ -887,25 +899,41 @@ class ExprAttrHandle(BaseHandle):
or getattr(Gen, 'global_struct_class', {}).get(_mva.attr))
ResolvedKey: str | None = self._find_struct_key_for_class_hint(Gen, AttrClassName)
if ResolvedKey is None or ResolvedKey not in Gen.structs:
self._NoteAttrNone(
Gen, AttrClassName, Node.attr,
f"基座字段的所属类未能在 Gen.structs 命中键(类名提示={AttrClassName!r},"
f"structs 键数={len(Gen.structs)})")
return None
st: Any
ok: bool
st, ok = self._try_Load_struct_from_stub(Gen, ResolvedKey)
if not isinstance(st, ir.IdentifiedStructType):
self._NoteAttrNone(Gen, ResolvedKey, Node.attr,
f"类 '{ResolvedKey}' 的 struct 实例类型异常({type(st).__name__})")
return None
if st.elements is None:
# stub 仍 opaque(如跨模块泛型仅声明):走泛型特化候选访问
spec_val: Any = self._try_access_opaque_generic_member(Gen, ObjVal, st, Node, ResolvedKey, AttrClassName)
if spec_val is not None:
return spec_val
self._NoteAttrNone(
Gen, ResolvedKey, Node.attr,
f"类 '{ResolvedKey}' 的 body 仍为 opaque(elements=None),且无泛型特化成员可访问")
return None
CastedObj: Any = Gen.builder.bitcast(ObjVal, ir.PointerType(st), name=f"cast_{ResolvedKey}")
if Node.attr in Gen.class_member_bitoffsets.get(ResolvedKey, {}):
return self._load_bitfield_member(CastedObj, ResolvedKey, Node.attr)
offset: int | None = Gen._get_member_offset(Node.attr, ResolvedKey)
if offset is None:
self._NoteAttrNone(
Gen, ResolvedKey, Node.attr,
f"字段 '{Node.attr}' 在类 '{ResolvedKey}' 中取不到偏移"
f"(成员表 {len(Gen.class_members.get(ResolvedKey) or [])} 项 / body {len(st.elements)} 槽)")
return None
if offset < len(st.elements) and isinstance(st.elements[offset], ir.VoidType):
self._NoteAttrNone(
Gen, ResolvedKey, Node.attr,
f"字段 '{Node.attr}' 的槽 {offset} 是 void(占位槽:类型信息缺失)")
return None
return self._gep_and_Load_member(Gen, CastedObj, offset, Node.attr, ResolvedKey, st)
@@ -934,6 +962,11 @@ class ExprAttrHandle(BaseHandle):
return self._load_bitfield_member(ObjVal, CN, Node.attr)
offset = Gen._get_member_offset(Node.attr, CN)
if offset is None:
self._NoteAttrNone(
Gen, CN, Node.attr,
f"字段 '{Node.attr}' 在类 '{CN}' 中取不到偏移"
f"(成员表 {len(Gen.class_members.get(CN) or [])} 项 / "
f"body {len(st.elements) if isinstance(st, ir.IdentifiedStructType) and st.elements is not None else 'opaque'} 槽)")
break
# 泛型基础类型字段特化(根本修复):
# 当字段声明为特化泛型(如 GSList[BasicBlock])时,基础类型(GSList)的
@@ -965,6 +998,12 @@ class ExprAttrHandle(BaseHandle):
return self._gep_and_Load_member(Gen, _casted, _off2, Node.attr, CN2, ST2)
break
return self._gep_and_Load_member(Gen, ObjVal, offset, Node.attr, CN, st)
# 结构体分支未命中任何类:pointee 与 Gen.structs 全部键都不等(同名类双实例 /
# 该类未注册)——这是"基座类型已知却读不到字段"的最后一种来源,必须留痕。
self._NoteAttrNone(
Gen, None, Node.attr,
f"基座 pointee {pointee} 未匹配 Gen.structs 中任何 struct(键数={len(Gen.structs)})"
f";基座形态={self._ExprShape(Node.value)}")
return None
def _handle_attr_call_result(self, Node: ast.Attribute) -> ir.Value | None: