8.2 KiB
Viper 多继承支持方案
背景:Viper 语法层已支持
class X(A, B)(ClassDef.bases是列表,parser 正常解析), 已支持class X(A, B)多继承:StructEntry含ParentName(首父兼容)+ParentNames+ParentCount。 目标:实现多继承(含字段合并、vtable 合并、方法查找、菱形去重)并同步参考实现——本文为实施方案记录,均已落地。 状态:已实现。
1. 现状(已实测定位)
| 位置 | 现状 | 多继承缺口 |
|---|---|---|
ast/stmts.vp:504 ClassDef.bases |
列表,parser 支持多 base | 无(语法已就绪) |
HandlesClassDef.vp:2868 entry.ParentName = parent_name |
单值 | 需父类数组 |
| HandlesClassDef.vp:2618-2655 字段展平 | 只遍历单个 parent_entry |
需 MRO 多父合并 |
HandlesClassDef.vp:3028+ _collect_vtable_methods |
单父 parent_vt_count |
需多父 vtable 合并 |
HandlesStruct.vp:56 ParentName |
单 str 字段 | 需数组 + MRO 辅助 |
HandlesExprCall.vp try_operator_overload / _translate_method_call |
get_parent_name 单链 |
需多父链 |
| HandlesExpr.vp 属性访问回退链 | get_parent_name 单链 |
需多父链 |
注:上表 Handles.vp 位置/行号均为实施方案(历史稿)引用,路径已失效;等价实现已迁移至 Gen:字段展平 ClassGenDef、vtable 合并 ClassGenVTable、MRO StructGenMixin、this 调整 CallGenCall、运算符重载 ExprGenOps(下文各节同)。*
2. MRO 规则(分阶段)
落地状态:已按方案落地:DFS + 声明序去重 MRO、字段/方法短名去重先声明优先、菱形静默最左;无 C3(Phase 2 未实施)、无虚继承。
Phase 1(本方案默认):深度优先 + 声明顺序 + 去重
- 查找顺序:当前类 → bases 声明顺序逐个递归
- 同名冲突:先声明者优先(最左优先)
- 菱形(
D(A, B),A/B 均继承 C):深度优先去重(已访问集合),C 的字段/方法只合并一次 - 理由:与 Python C3 相比实现简单,覆盖本仓库实际场景(Generator Mixin 链、无复杂菱形)
Phase 2(可选,后续):C3 线性化
- 严格 C3 保证单调性,处理复杂菱形歧义
- 仅在 Phase 1 遇到真实需求后实施
3. 数据结构改造(HandlesStruct.vp)
novirtual class StructEntry:
# 现有字段不变
ParentNames: list[str]* # 新增:父类名数组(声明顺序,跳过 marker)
ParentCount: int # 新增
# 辅助函数(新增)
def get_parent_count(entry) -> int
def get_parent_name_at(entry, i) -> str
def get_mro_chain(entry) -> list[str] # 深度优先去重线性化(含自身)
def get_mro_parent_entries(entry) -> 父类 StructEntry 数组
ParentName保留(兼容现有调用,指向第一个父类),新代码优先用ParentNames- 迁移点:
get_parent_name()调用方逐步替换为 MRO 链遍历
4. 字段布局(HandlesClassDef.vp 字段展平 L2618-2655)
改造前(单父):
[parent_fields..., child_fields...]
改造后(多父):
[vtable?, parent1_fields..., parent2_fields..., child_fields...]
实现:
parent_entries = get_mro_parent_entries(self_entry)(深度优先去重)- 遍历每个父类字段:
__vtable__跳过;字段名去重(已存在跳过,先声明者优先) - 布局顺序 = MRO 父类顺序 → 子类自身字段
影响面:所有继承类的 __sizeof__/GEP 偏移变化 → 需全量回归
5. vtable 合并(HandlesClassDef.vp _collect_vtable_methods L3028+)
改造前(单父):parent_vt_count 遍历,full_name 用实际定义类(find_func_global 探测,已支持多层单继承)
改造后:
- 遍历 MRO 所有父类的
VTableMethods(每个父类的 vtable 方法短名) - 方法短名去重:先声明父类优先;子类覆盖优先(
child_overrides检查不变) - full_name 定位:沿用
find_func_global("类名.方法")探测(多父链探测) - 槽位顺序:父类方法(MRO 序)→ 子类新增方法
6. 方法查找/运算符重载/属性访问(多父链替换单链)
| 位置 | 单链现状 | 改法 |
|---|---|---|
HandlesExprCall.vp try_operator_overload L123-135 |
while cur_parent is not None |
改为 get_mro_chain 遍历 |
HandlesExprCall.vp try_unary_operator_overload L211-221 |
同上 | 同上 |
HandlesExprCall.vp _translate_method_call 继承方法查找 |
get_parent_name 单链 |
MRO 链 |
| HandlesExpr.vp 属性访问 lookup_field 回退 | 单链 | MRO 链 |
HandlesClassDef.vp _find_method_defining_class(本轮已加) |
entry.ParentName 单链 |
MRO 链 |
HandlesType.vp / 其他 get_parent_name 调用 |
单链 | 逐步替换 |
7. 菱形继承处理
- 字段:MRO 去重(已访问父类集合),C 字段只合并一次
- 方法/vtable:方法短名去重,C 的 vtable 槽合并一次
- 歧义:先声明优先;Phase 1 不报歧义错误(静默最左优先),Phase 2 C3 时再考虑警告
8. TPC 同步清单
TPC(Python,lib/core/)与 TPV 结构一致,按顺序同步:
Handles/HandlesStruct.py:StructEntry 父类数组 + MRO 辅助Handles/HandlesClassDef.py:字段展平多父、vtable 多父合并、parent 收集Handles/HandlesExprCall.py:方法查找/运算符重载多父链Handles/HandlesExpr.py(若 TPC 属性访问在 HandlesExprAttr.py):多父回退
顺序:先 TPV(活跃编译器)实现 + 验证,再 TPC 同步(保持一致,防分叉)。
9. 实施计划
| 阶段 | 内容 | 验证 |
|---|---|---|
| M1 | HandlesStruct:父类数组 + MRO 辅助(新增字段/函数,不改现有逻辑) | bootstrap |
| M2 | HandlesClassDef:parent 收集多 base + 字段展平多父 | bootstrap + 单继承回归 |
| M3 | HandlesClassDef:vtable 多父合并 | bootstrap + 多继承 vtable 用例 |
| M4 | HandlesExprCall/HandlesExpr:方法/运算符/属性多父链 | bootstrap + 全用例 |
| M5 | TPC 同步(4 文件) | TPC 自测 |
| M6 | 新增 Test/MultiInheritTest(字段/vtable/菱形去重/运算符重载) | 编译 + 运行 |
10. 验证用例(Test/MultiInheritTest/App/main.vp)
注:回归用例位于仓库根 Test/MultiInheritTest 与 Test/NoVTableCrossMixinTest(TransPyV/Test 自身无双父用例)。
- 多父字段合并 + 属性访问(
d = D(); d.a + d.b) - 多父方法调用(非虚 + 虚 vtable 分派)
- 运算符重载继承链(父类 dunder)
- 菱形去重(
D(A, B),A/B ← C:C 字段/方法只出现一次) - 同名冲突最左优先
- Generator 多继承实例化(
class Generator(BaseGenMixin, TypeGenMixin, ...))——本轮对象化 P2+ 的落地验证
11. 风险与对策
| 风险 | 对策 |
|---|---|
| 字段布局变化破坏现有继承类 | 全量回归(所有 Test 项目) |
| vtable 合并槽位错误 | 多继承 vtable 用例 + obj 反汇编核对 |
| Viper 方法符号不转发(P1 已踩坑) | 多继承下子类仍只调自己文件定义的方法;父类方法用字段/模块函数 |
| 菱形去重遗漏 | MRO 已访问集合严格实现 |
| TPC/TPV 分叉 | TPV 先验,TPC 紧跟同步 |
12. 规模上限(实现常量)
| 常量 | 值 | 说明 |
|---|---|---|
PARENT_MAX |
64 | 单类最大父类数 |
MRO_MAX |
64 | MRO 链最大长度 |
FIELD_MAX |
256 | 结构体字段上限 |
13. 兄弟 Mixin 方法解析(已落地,判据化)
Viper 的 "Mixin" 用普通类 + 多继承表达:novirtual class Mixin 持有回指针,组合类经 class X(A.Mixin) 继承。novirtual 多继承下,BaseB.caller 内调用 self.helper()(helper 定义在兄弟 BaseA)由两条正向判据解析到定义类并做 this-adjust:
- 接收类必须是某个已注册类的直接父类(真的被组合使用);
- 候选类与接收类必须同处某个已注册组合类的 MRO(真·兄弟)。
两条判据缺一不可——不做「同名即绑」的猜测。命中后按字段区偏移前移 this,直调定义类实现。
另:类体内方法采用先声明后定义(翻译方法体前先为全部方法建前向声明),消除方法书写的源顺序依赖。回归用例:Test/NoVTableCrossMixinTest。