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:
1 parent
3ca46c01cf
commit
e6451a077e
1 file changed
+75
-1
@@ -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 中找到该类并完成处理。
|
||||
|
||||
Reference in new issue
Block a user