fix(FuncGen): base/字段偏移一律按"重名条目不占槽位"算,修 MatchCase 偏移 6(应为 7)

类别:a(编译器缺陷)

现象(早前现场,本轮定位):
  TypeError: [fast-fail] len(mcc_pre.children) 分派 'list[….AST*].__len__' 失败:
  类='MatchCase' 偏移=6 接收者类型=i32

判据(磁盘实证):
  · MatchCase 的 body 在 temp 与**全部** output stub 中都是 **10 槽**
    (vtable + AST 的 7 字段 + pattern/guard)⇒ `children` 的正确偏移 = base(1) + 表内下标(6) = **7**。
  · 观测到 6 ⇒ base 被算成 0 ⇒ `_member_base` 的 `_Delta = 槽数 - 表长` 为 0,
    即**表长 = 10**(9 个不同字段 + 1 条重名条目),而不是 9。
  · 重名条目是**产物**:源码层面的字段名遮蔽已由 HandlesClassDef 的
    "字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 表里的重名只可能来自"展平残留
    (半截祖先块,已由 2e7bac18 在插入点剥除)/ 同一字段被两条收集路径各登记一次"。

修复(与 `_get_member_offset` 配对路径已有的"重名条目不占槽位"同口径,全线统一):
  · `_member_base` 的长度校准改用**去重名数**(`_Delta` 不再被重名条目冲掉 ⇒ base 正确);
  · 新增 `_MemberSlotIndex(Members, FieldName)`:取"首个同名的**去重**下标"
    (重名条目不计槽位),并把 `_get_member_offset` 中**全部 12 处** "base + 表内下标"
    站点改为调用它(含继承链回退、REnum 变体搜索、stub 重载后、_PreRegisterClassMembers
    之后等分支);"最后回退"里的 `_num_members_final` 同步改为去重计数。
  · 对无重名的表,取值与改前**逐字相同**(去重下标 == 原始下标)。

核验:py_compile + compileall 全 lib 通过;离线冒烟(真实方法、未绑定实例调用):
含 1 条重复 'children' 的表上 `children -> 6`、`guard -> 8`、缺名 -> -1;
去重名数 9 ⇒ `_Delta = 10 - 9 = 1` ⇒ base=1 ⇒ `children` 偏移 7(与 body 10 槽一致)。
未放宽任何 fail-fast。
This commit is contained in:
TermiNexus committed 2026-09-30 18:44:48 +08:00
1 parent 2e7bac1816
commit ecd336771f
1 file changed
+94 -48
+94 -48
View File
@@ -432,7 +432,22 @@ class FuncGenMixin:
if (isinstance(_StructBase, ir.IdentifiedStructType)
and _StructBase.elements is not None and len(_StructBase.elements) > 0):
_NumElems: int = len(_StructBase.elements)
_NumMembers: int = len(self.class_members.get(_Cls, []))
_MembersRaw: list = self.class_members.get(_Cls, [])
_NumMembers: int = len(_MembersRaw)
# 【重名条目不是布局槽位 ⇒ 长度校准按**去重名数**算(2026-09-30)】
# 与同文件 `_get_member_offset` 的配对段(重名条目跳过、不占槽位)以及
# StructGen 的"重名条目"分支同口径。依据:源码层面的字段名遮蔽已由
# HandlesClassDef 的"字段名遮蔽 fail-fast"(:1083)拦下 ⇒ 成员表里的重名
# 只可能是**产物**(展平残留 / 同一字段被两条收集路径各登记一次),
# 不是合法槽位。
# 不这样算的后果(现场):`ast.MatchCase` 表 10 项(9 个字段 + 1 条重复)
# 而 body 恰 10 槽 ⇒ `_Delta == 0` ⇒ base 静默取 0 ⇒ 该类所有字段偏移整体
# 前移 1:`MatchCase.children` 表内下标 6 被当作槽 6(`end_col_offset`,i32)
# ⇒ `len(mc_c.children)` 分派 `list[AST*].__len__` 时收到 i32:
# "Type of #1 arg mismatch: %"…list[…]"* != i32"。
_UniqMembers: set = {n for n, _ in _MembersRaw}
if len(_UniqMembers) < _NumMembers:
_NumMembers = len(_UniqMembers)
_Delta: int = _NumElems - _NumMembers
_FirstElem: ir.Type = _StructBase.elements[0]
_IsVtablePtr: bool = (isinstance(_FirstElem, ir.PointerType)
@@ -535,6 +550,30 @@ class FuncGenMixin:
_Owner = _Key
return _Owner
def _MemberSlotIndex(self, Members: list, FieldName: str) -> int:
"""字段在成员表里的**槽位下标**(重名条目**不占槽位**;找不到返回 -1)。
与 `_get_member_offset` 的配对路径同口径:重名条目是产物,不是布局槽位
—— 源码层面的字段名遮蔽已由 HandlesClassDef 的"字段名遮蔽 fail-fast"拦下,
其余重名只可能来自"展平残留 / 同一字段被两条收集路径各登记一次"。
所有"按表内下标 + base 求偏移"的站点都必须走本方法,否则含重名条目的表会让
偏移整体偏斜:
· `ast.ExceptHandler` 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10(越界);
· `ast.MatchCase` 表 10 项 / 去重 9(body 10 槽)⇒ `_Delta == 0` ⇒ base 变 0 ⇒
`children` 偏移 6 落到 `end_col_offset`(i32)。
重名条目在**首个同名(正本)之后**才出现,故按"首个同名的去重下标"取值。
"""
_Slot: int = 0
_Seen: set = set()
for _n, _ in (Members or []):
if _n in _Seen:
continue
_Seen.add(_n)
if _n == FieldName:
return _Slot
_Slot += 1
return -1
def _get_member_offset(self, field_name: str, ClassName: str | None = None,
StructHint: Any = None) -> int | None:
# base 判定用未归一的原始类名:_member_base 内部自行做同一套归一
@@ -814,9 +853,18 @@ class FuncGenMixin:
f"返回值可能失准。重名来源通常是「同一字段被两条收集路径各登记一次"
f"(类内 AnnAssign 声明 + 方法体 self.x 赋值)」或继承前缀重复前置。",
"MemberBase")
for i, (name, _) in enumerate(_MemList):
# 回退取值也按"重名条目**不占槽位**"算(与上方配对路径同口径):直接取
# `base + 原始下标` 会把重名条目也算进槽位,整体后移(现场:ExceptHandler
# 表 10 项、'children' 出现两次 ⇒ 'name' 原始下标 9 ⇒ 偏移 10 越界)。
_SlotIdx: int = 0
_Counted: set = set()
for name, _ in _MemList:
if name in _Counted:
continue
_Counted.add(name)
if name == field_name:
return _BaseNow() + i
return _BaseNow() + _SlotIdx
_SlotIdx += 1
# 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入
# 父类未加载,字段被填充为 __inherited_N 占位名),沿 class_parent 链向上查找。
# 子类 struct 布局为 [vtable?] + [基类字段...] + [子类字段...],
@@ -846,10 +894,7 @@ class FuncGenMixin:
_members: list = self.class_members[_k]
_has_placeholder: bool = any(n.startswith('__inherited_') for n, _ in _members)
if not _has_placeholder:
for i, (name, _) in enumerate(_members):
if name == field_name:
_hit_idx = i
break
_hit_idx = self._MemberSlotIndex(_members, field_name)
if _hit_idx >= 0:
break
if _hit_idx >= 0:
@@ -863,18 +908,18 @@ class FuncGenMixin:
stub_name: str = f"{mod_sha1}.stub.ll"
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(_k, stub_name, self)
if _k in self.class_members and not any(n.startswith('__inherited_') for n, _ in self.class_members[_k]):
for i, (name, _) in enumerate(self.class_members[_k]):
if name == field_name:
return _BaseNow() + i
_pIdx: int = self._MemberSlotIndex(self.class_members[_k], field_name)
if _pIdx >= 0:
return _BaseNow() + _pIdx
break
if _k in self.class_members and any(n == field_name for n, _ in self.class_members.get(_k, [])):
break
_parent = self.class_parent.get(_parent)
short_name: str | None = ClassName.split('.')[-1] if ClassName and '.' in ClassName else None
if short_name and short_name in self.class_members:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
_sIdx: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
if _sIdx >= 0:
return _BaseNow() + _sIdx
if ClassName and field_name:
if ClassName not in self.class_members or field_name not in [n for n, _ in self.class_members.get(ClassName, [])]:
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
@@ -883,25 +928,25 @@ class FuncGenMixin:
stub_name: str = f"{mod_sha1}.stub.ll"
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(ClassName, stub_name, self)
if ClassName in self.class_members and len(self.class_members[ClassName]) > 0:
for i, (name, _) in enumerate(self.class_members[ClassName]):
if name == field_name:
return _BaseNow() + i
_rIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name)
if _rIdx >= 0:
return _BaseNow() + _rIdx
break
if short_name:
for mod_name, mod_sha1 in sha1_map.items():
stub_name: str = f"{mod_sha1}.stub.ll"
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_name, stub_name, self)
if short_name in self.class_members and len(self.class_members[short_name]) > 0:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
_rIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
if _rIdx2 >= 0:
return _BaseNow() + _rIdx2
break
if ClassName and field_name:
for cn_key in self.class_members:
if cn_key == ClassName or cn_key.endswith(f'.{ClassName}') or (short_name and (cn_key == short_name or cn_key.endswith(f'.{short_name}'))):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return _BaseNow() + i
_cIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
if _cIdx >= 0:
return _BaseNow() + _cIdx
# REnum 变体搜索: ClassName 是 REnum(如 MetaType),成员注册在变体结构
# (MetaType_Var, MetaType_Concrete, MetaType_App)中。变体与 REnum 主结构
# 共享 max_variant_struct 布局,偏移一致(__tag 在偏移 0,字段从偏移 1 开始)
@@ -909,33 +954,33 @@ class FuncGenMixin:
variant_prefix: str = f"{ClassName}_"
for cn_key in self.class_members:
if cn_key.startswith(variant_prefix):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return i
_vIdx: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
if _vIdx >= 0:
return _vIdx
if short_name:
variant_prefix = f"{short_name}_"
for cn_key in self.class_members:
if cn_key.startswith(variant_prefix):
for i, (name, _) in enumerate(self.class_members[cn_key]):
if name == field_name:
return i
_vIdx2: int = self._MemberSlotIndex(self.class_members[cn_key], field_name)
if _vIdx2 >= 0:
return _vIdx2
if ClassName and ClassName in self.structs:
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None and len(struct_type.elements) > 0:
for cn_key, members in self.class_members.items():
if cn_key == short_name or (short_name and cn_key.endswith(f'.{short_name}')):
for i, (name, _) in enumerate(members):
if name == field_name and i < len(struct_type.elements):
return i
_kIdx: int = self._MemberSlotIndex(members, field_name)
if 0 <= _kIdx < len(struct_type.elements):
return _kIdx
break
else:
if short_name:
for cn_key, members in self.class_members.items():
sn: str = cn_key.split('.')[-1] if '.' in cn_key else cn_key
if sn == short_name:
for i, (name, _) in enumerate(members):
if name == field_name and i < len(struct_type.elements):
return i
_kIdx2: int = self._MemberSlotIndex(members, field_name)
if 0 <= _kIdx2 < len(struct_type.elements):
return _kIdx2
break
if ClassName and field_name and ClassName not in self.class_members:
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
@@ -947,9 +992,9 @@ class FuncGenMixin:
short_cn: str = short_name if short_name else ClassName
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(short_cn, stub_name, self)
if short_cn in self.class_members and len(self.class_members[short_cn]) > 0:
for i, (name, _) in enumerate(self.class_members[short_cn]):
if name == field_name:
return _BaseNow() + i
_scIdx: int = self._MemberSlotIndex(self.class_members[short_cn], field_name)
if _scIdx >= 0:
return _BaseNow() + _scIdx
break
if ClassName and ClassName in self.structs and (ClassName not in self.class_members or len(self.class_members.get(ClassName, [])) == 0):
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
@@ -964,13 +1009,13 @@ class FuncGenMixin:
if len(self.class_members[node.name]) == 0:
class_handler._PreRegisterClassMembers(node, self)
if ClassName in self.class_members:
for i, (name, _) in enumerate(self.class_members[ClassName]):
if name == field_name:
return _BaseNow() + i
_prIdx: int = self._MemberSlotIndex(self.class_members[ClassName], field_name)
if _prIdx >= 0:
return _BaseNow() + _prIdx
if short_name and short_name in self.class_members:
for i, (name, _) in enumerate(self.class_members[short_name]):
if name == field_name:
return _BaseNow() + i
_prIdx2: int = self._MemberSlotIndex(self.class_members[short_name], field_name)
if _prIdx2 >= 0:
return _BaseNow() + _prIdx2
break
# 最后回退:当继承链回退失败但 struct body 已加载时,
# 从 struct elements 数和 class_members 数推断继承字段偏移。
@@ -985,7 +1030,8 @@ class FuncGenMixin:
if isinstance(_st_final, ir.IdentifiedStructType) and _st_final.elements is not None:
_num_elements_final: int = len(_st_final.elements)
_members_final: list = self.class_members.get(ClassName, [])
_num_members_final: int = len(_members_final)
# 与 _member_base 的长度校准同口径:重名条目**不是布局槽位** ⇒ 按去重名数算
_num_members_final: int = len({_Rn for _Rn, _ in _members_final})
if _num_elements_final > _num_members_final:
# base 是 vtable 占位(struct[0] = i8*),不是继承字段,必须扣除:
# 不扣 base 时,凡"struct 元素数恰好多 1"的类,差值其实只是 vtable
@@ -1018,10 +1064,10 @@ class FuncGenMixin:
self.class_members[ClassName] = _OldMembers
_ReloadedMembers = _OldMembers
_members_final = _ReloadedMembers
_num_members_final = len(_members_final)
for _Ri, (_Rn, _Rt) in enumerate(_members_final):
if _Rn == field_name:
return _BaseNow() + _Ri
_num_members_final = len({_Rn for _Rn, _ in _members_final})
_rfIdx: int = self._MemberSlotIndex(_members_final, field_name)
if _rfIdx >= 0:
return _BaseNow() + _rfIdx
_num_inherited_final: int = _num_elements_final - _num_members_final - _BaseNow()
if _num_inherited_final == 1:
# 推断(缺失槽位 = 位于 base+0 的未展平继承字段)仅在成员表