From e6451a077e29e86b28382e0d1ef662dfd1f96be0 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 28 Sep 2026 21:46:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E7=BB=A7=E6=89=BF=E5=B1=95?= =?UTF-8?q?=E5=B9=B3=E6=B2=BF=E7=BB=A7=E6=89=BF=E9=93=BE=E8=A1=A5=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E=E5=AD=97=E6=AE=B5=E5=90=8D=EF=BC=88=E6=B6=88=E9=99=A4?= =?UTF-8?q?=20=5F=5Finherited=5FN=20=E5=8D=A0=E4=BD=8D=E5=90=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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", ...)]`,属性访问是普通表命中, 不再依赖任何查找期回退。 --- lib/core/Handles/HandlesImports.py | 76 +++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index 1b2ff07..2d7af94 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -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 中找到该类并完成处理。