fix(HandlesImports): 继承前缀前置改为"按表头判幂等",不再按名字过滤整块(重名继承项不再被删)

类别:a 编译器缺陷(静默删掉布局条目,使继承段下标整体前移、写到类型不同的槽)。

问题(旧判据):`_EmitExternalClassDeclLlvm` 的前置段用
`nm2 not in {class_members[ClassName] 整表所有名字}` 过滤祖先块。该判据把
**与子类自有字段重名的继承项整条删掉**,于是继承段与 body 的继承前缀失去一一对应:
被删项**之后**的继承字段表内下标整体前移 k(k = 被删项数),而查询侧回退路径用
`base + 表内下标`(FuncGen._get_member_offset)⇒ 落到类型完全不同的槽。
现场(本轮):TypeGenCType 重复声明 5 个父类已有字段后,继承字段
LastBuiltinsTypedefResult 的下标 32→27,`= "t.CPtr"` 写进 i32 槽 ⇒
"指针隐式转整数已禁止:不能把指针类型 i8* 赋给整数类型 i32"。

新判据(抽成模块级 PrependInheritedBlock,可独立验证):
  ① 不删块内条目——块内每项都对应 body 的一个继承槽,删任一项都会整体前移;
  ② 幂等——只问"表头前 len(块) 项的名字序列是否等于祖先块的名字序列",相等即整块
     已前置 → 不再前置(保住 2026-08-10 修的重复字段问题:BasicBlock [Next, Next, …]);
  ③ 同名并存——子类自有字段与继承项同名时两者都留(定义端 body 同样是"继承前缀 +
     自有段",两侧槽数一致);源码层面的这种重名已由 HandlesClassDef 的"字段名遮蔽
     fail-fast"拦下(1903ed14)。

冒烟(纯逻辑,跑完删除):首次前置=块+自有;重复调用不再前置;**同名项仍全保留**
(对照旧实现会删掉块首项 ⇒ 下标前移);空块 no-op。py_compile 通过。

已知残留(写进函数文档):若此前只前置过部分祖先块(父类表当时较短),本次会整块再前置
一次 ⇒ 该前缀出现重复条目;调用点上方刚对父类 force 重载(块内容稳定),该情形仅在
"父类表被中途改写"时出现,且重复条目会在成员槽失败报文的"名序"里直接可见。
This commit is contained in:
TermiNexus committed 2026-09-30 11:18:07 +08:00
1 parent a43513c9a7
commit 55cbf1d261
1 file changed
+53 -8
+53 -8
View File
@@ -144,6 +144,46 @@ def _ReportDeclarationParseFailure(FilePath: str, Exc: BaseException, Consequenc
_vlog().warning(Message, "Exception")
def PrependInheritedBlock(Gen: object, ClassName: str, Block: list) -> bool:
"""把祖先字段块 `Block` 前置到 `Gen.class_members[ClassName]`(幂等,且**不删块内条目**)。
为什么单独成函数:这段判据是"继承段与 body 继承前缀一一对应"的唯一保障,必须可
独立验证(见本仓"按'表头是否已等于块'判幂等"的三条性质)。
判据与性质:
① **不删块内条目**。块里每一项都对应 body 的一个继承槽;只要块内某项被删,
被删项之后的继承字段的表内下标就会整体前移 k,而查询侧回退路径用
`base + 表内下标`(FuncGen._get_member_offset)⇒ 落到类型不同的槽。
旧实现按"名字已在整表内"过滤整块,正是这样删掉了**与子类自有字段重名**的
继承项(现场:TypeGenCType 重复声明 5 个父类已有字段后,继承字段
LastBuiltinsTypedefResult 下标 32→27,写进 i32 槽 → "指针隐式转整数已禁止")。
② **幂等**。本函数会被多个加载路径重复调用(_LoadModuleDeclarationsFromFile /
_LoadDeclarationsFromStubLlvm / _TryLoadClassMembersFromPyi 懒加载);判据是
"表头前 len(Block) 项的名字序列是否等于 Block 的名字序列"——相等即整块已前置,
直接返回 False(不再前置),避免 2026-08-10 那类重复字段
(BasicBlock: [Next, Next, Name, ...] → 与 struct elements 数量不匹配 → 偏移错乱)。
③ **同名并存**。子类自有字段与继承项同名时,两者都保留(定义端
HandlesClassDef 的 body 同样是"继承前缀 + 自有段",两侧槽数一致);
源码层面的这种重名已由 HandlesClassDef 的"字段名遮蔽 fail-fast"拦下,
此处只是不让它把布局判据带偏。
已知残留:若此前只前置过**部分**祖先块(父类表当时较短),本次会整块再前置一次,
该前缀出现重复条目。调用点上方刚对父类做过 force 重载(块内容稳定),故该情形仅在
"父类表被中途改写"时出现,且重复条目会在成员槽失败报文的"名序"里直接可见。
返回 True 表示本次真的前置了。
"""
_Members: list = getattr(Gen, 'class_members', {}).get(ClassName) or []
_BlockNames: list = [nm for nm, _ in Block]
if not _BlockNames:
return False
_HeadNames: list = [nm for nm, _ in _Members[:len(_BlockNames)]]
if _HeadNames == _BlockNames:
return False
getattr(Gen, 'class_members')[ClassName] = list(Block) + list(_Members)
return True
class ImportHandle(BaseHandle):
_struct_Load_cache = {}
_pyi_cache = {}
@@ -2511,14 +2551,19 @@ class ImportHandle(BaseHandle):
# 加载路径重复调用(_LoadModuleDeclarationsFromFile / _LoadDeclarationsFromStubLlvm
# / _TryLoadClassMembersFromPyi 懒加载),此前调用已前置的父类字段会被再次
# 前置 → class_members 出现重复字段(如 BasicBlock: [Next, Next, Name, ...])
# → 与 struct elements 数量不匹配 → 属性偏移错乱。按字段名过滤已存在的
# 父类字段;跨父类重名字段(不同父类各自声明)仍全部保留。
_cur_member_names_pre: set = {n for n, _ in Gen.class_members.get(ClassName, [])}
_filtered_inherited: list[tuple] = [
(nm2, tp2) for nm2, tp2 in _all_inherited if nm2 not in _cur_member_names_pre
]
if _filtered_inherited:
Gen.class_members[ClassName] = _filtered_inherited + Gen.class_members[ClassName]
# → 与 struct elements 数量不匹配 → 属性偏移错乱。
# 【判据改为"前置段是否已等于祖先块"(2026-09-30)】旧判据是
# `nm2 not in {整表所有名字}`——它会把**与子类自有字段重名的继承项**删掉,
# 于是继承段与 body 的继承前缀失去一一对应:被删项**之后**的继承字段表内
# 下标整体前移 k(k = 被删项数),而查询侧回退路径用 `base + 表内下标`
# (FuncGen._get_member_offset)⇒ 落到**类型完全不同的槽**。现场:
# TypeGenCType 重复声明 5 个父类已有字段后,继承字段 LastBuiltinsTypedefResult
# 下标 32→27,写到 i32 槽 → "指针隐式转整数已禁止"。
# 新判据(幂等且不删块内条目):只问"表头前 len(块) 项的名字序列是否等于
# 祖先块的名字序列"——相等 ⇒ 整块已前置(本次是重复调用)⇒ 不再前置;
# 否则整块前置。**任何情况下都不删块内条目**,因此"同名自有字段 + 同名继承项"
# 会并存(body 里本来就是两个槽,定义端 HandlesClassDef 同样两者都在)。
PrependInheritedBlock(Gen, ClassName, _all_inherited)
# 父类字段已处理(可能此前已前置),跳过占位符路径避免叠加
_any_parent_loaded = True
if not _any_parent_loaded and _ParentClasses: