fix(tpc): 继承展平沿继承链补真实字段名(消除 __inherited_N 占位名)

a 编译器缺陷:泛型基类的继承字段在导入侧展平时名字丢失。父类是泛型特化
`GSListNode[ParamNode]`,其特化体不在任何 .vpi 中 ⇒ 按特化名加载必落空 ⇒ 占位符
路径按 struct 槽位数补 `__inherited_N`:偏移对、**名字丢**。于是继承字段的属性访问
找不到字段(现场 `[fast-fail] 属性 Next 在类 ParamNode 中找不到
(class_members=[__inherited_0, Ty])`)。

语义修正:新增 _ResolveInheritedFieldNames —— 按**声明顺序**沿继承链取父类声明里的
字段名;父类是泛型特化 `X[Arg]` 时回退用**模板名** X 加载其成员(模板声明了字段名,
如 linkedlist.vp:265 `GSListNode[T]: Next: T*` 的 Next),这正是编译期唯一可信的
名字来源;与 DeclarationGenerator._get_inherited_members(stub 端取模板成员)、
BaseGen._get_or_create_struct 的 `_struct_sha1_map` 拼名规则同一条"类名 → 模板/模块"
规则源。名字取不到才退回占位名(仅保证偏移)。

同时修正占位符配对的槽位起点:有 vtable 指针时首元素是 vtable,上面已把它从
_num_elements 扣掉,配对结构体元素必须同步右移一位(原实现从 elements[0] 起取,
会把 vtable 指针的类型当成第一个继承字段的类型)。

结果:展平表直接是 `[("Next", <槽位类型>), ("Ty", ...)]`,属性访问是普通表命中,
不再依赖任何查找期回退。
This commit is contained in:
TermiNexus committed 2026-09-28 21:46:30 +08:00
1 parent 3ca46c01cf
commit e6451a077e
1 file changed
+75 -1
+75 -1
View File
@@ -2447,10 +2447,25 @@ class ImportHandle(BaseHandle):
_has_vtable_ptr = IsCVTable or (has_methods and not IsNoVTable)
if _has_vtable_ptr and _num_elements > 0:
_num_elements -= 1
# struct 槽位起点:有 vtable 指针时首元素是 vtable,继承字段从元素 1 起
# (上面已把 vtable 从 _num_elements 扣掉,配对槽位必须同步右移一位,
# 否则会把 vtable 指针的类型当成第一个继承字段的类型)。
_slot0_ph: int = 1 if _has_vtable_ptr else 0
_num_members = len(Gen.class_members[ClassName])
if _num_elements > _num_members:
_num_inherited = _num_elements - _num_members
_inherited_ph: list[tuple] = [(f'__inherited_{i}', _st.elements[i]) for i in range(_num_inherited)]
# 继承字段的**真实名字**:按声明顺序沿继承链取父类声明中的字段名
# (父类可能是泛型特化 `GSListNode[ParamNode]` —— 特化体不在任何
# .vpi 中,回退用模板名 `GSListNode` 加载其成员),与 struct 槽位
# 按序配对。这样展平结果直接是 `('Next', <槽位类型>)`,属性访问
# 命中 class_members 真实条目,**无需任何查找期回退**。
# 名字取不到时(父类声明也不可得)才退回 __inherited_N 占位名,
# 仅保证偏移正确。
_inh_names: list[str] = self._ResolveInheritedFieldNames(Gen, _ParentClasses, _num_inherited)
_inherited_ph: list[tuple] = []
for _i_ph in range(_num_inherited):
_nm_ph: str = _inh_names[_i_ph] if _i_ph < len(_inh_names) else f'__inherited_{_i_ph}'
_inherited_ph.append((_nm_ph, _st.elements[_slot0_ph + _i_ph]))
# 占位符路径同样整体前置(继承字段在 struct 头部),与主路径保持一致
Gen.class_members[ClassName] = _inherited_ph + Gen.class_members[ClassName]
_placeholder_applied = True
@@ -2826,6 +2841,65 @@ class ImportHandle(BaseHandle):
finally:
_inprog.discard(class_name)
def _ResolveInheritedFieldNames(self, Gen: LlvmGeneratorMixin,
ParentClasses: list[str], Limit: int) -> list[str]:
"""沿继承链取父类声明中的**真实字段名**(按声明顺序,跨父类去重)。
用途:`_EmitExternalClassDeclLlvm` 的继承展平在"父类 class_members 未就绪"
时只能按 struct 槽位数补 `__inherited_N` 占位名 —— 偏移对、**名字丢**,
于是继承字段的属性访问找不到字段(`[fast-fail] 属性 'Next' 在类 'ParamNode'
中找不到`)。本方法把名字补回来:按声明顺序遍历父类,逐个取其声明里的字段名
(AnnAssign 目标名,见 `_EmitExternalClassDeclLlvm` 的成员收集),与 struct
头部槽位按序配对。名字来自**声明**,槽位类型仍取 struct 自身元素,二者一致。
泛型基类回退:父类名可能是特化体 `X[Arg]`(特化体不在任何 .vpi 中 ⇒ 直接
加载必然落空),此时回退用**模板名** `X` 加载其成员 —— 模板里声明了字段名
(如 `GSListNode[T]: Next: T*` 的 `Next`),这正是编译期唯一可信的名字来源。
与 [`basegen._get_or_create_struct` 的 `_struct_sha1_map` 拼名规则]、
[DeclarationGenerator._get_inherited_members 取模板成员] 同一条"类名 → 模板/模块"
规则源。
返回的列表长度可能小于 Limit(父类声明不可得 / 类内字段名重复)——调用方对
缺失位置继续用 `__inherited_N` 占位,保证偏移语义不变。
"""
Names: list[str] = []
Seen: set[str] = set()
for _Pc in ParentClasses:
if not _Pc:
continue
_cands: list[str] = [_Pc]
_tpl: str = _Pc.split('[', 1)[0]
if _tpl and _tpl != _Pc:
_cands.append(_tpl)
_members: list = []
for _key in _cands:
_m_cur: list = Gen.class_members.get(_key) or []
if _m_cur and not any(n.startswith('__inherited_') for n, _ in _m_cur):
_members = _m_cur
break
_sha: str | None = getattr(Gen, 'class_sha1_map', {}).get(_key)
_loaded: bool = False
if _sha:
_loaded = self._TryLoadClassMembersFromPyi(_key, f"{_sha}.stub.ll", Gen)
else:
for _mod_name, _mod_sha1 in (getattr(Gen, 'ModuleSha1Map', {}) or {}).items():
if self._TryLoadClassMembersFromPyi(_key, f"{_mod_sha1}.stub.ll", Gen):
_loaded = True
break
_m_new: list = Gen.class_members.get(_key) or []
if _loaded and _m_new and not any(n.startswith('__inherited_') for n, _ in _m_new):
_members = _m_new
break
for _nm, _ in _members:
if _nm.startswith('__inherited_') or _nm == '__vtable__':
continue
if _nm not in Seen:
Seen.add(_nm)
Names.append(_nm)
if len(Names) >= Limit:
break
return Names
def _TryLoadClassMembersFromPyi(self, class_name: str, stub_filename: str, Gen: LlvmGeneratorMixin,
force: bool = False) -> bool:
"""从 .vpi/.pyi 声明文件加载类成员。返回 True 表示在 pyi 中找到该类并完成处理。