fix(transpyv): 自引用字段的特化名不再留在裸名(新增"本模块声明类"归属表)

类别:a(编译器缺陷 —— 字段类型在类注册前被冻结成裸名版特化名)

现场(上一轮未修掉的那一处):includes/ast/base.vp:357
    if self.children is None: self.children = list[rawptr[AST]](self.pool, 8)
CastError:源 %"7e2b6dda6e423bfb.list[rawptr[d1ed020479454541.AST]]"*
       → 目标 %"7e2b6dda6e423bfb.list[rawptr[AST]]"*

根因:字段 children: list[rawptr[AST]] | t.CPtr 写在**类 AST 自己体内**(自引用),
其类型在 translate_class_def 的字段循环里解析,而本类的 register_struct 在该函数
**末尾**才执行 ⇒ 解析时 struct 表里没有 AST ⇒ 上一轮 5.5 的两条查法
(find_struct 按名 / find_struct_by_module)全部落空 ⇒ 实参仍留裸名 rawptr[AST],
字段类型被冻结成 list[rawptr[AST]](第一遍 A-pre 即发生);而 :357 的构造调用发生在
类注册之后 ⇒ 带 SHA1 ⇒ 两侧分裂。(同一分裂在 stmts.vp:60 表现为形参侧/实参侧,
本处表现为字段侧/构造侧。)

改动:
1. StructGenMixin 新增"本模块声明类"归属表 ModuleClasses(键 "{模块SHA1}.{类名}")
   与 CurDefModuleSha1(正在定义类所属模块 SHA1,登记时同步记录):
   - mark_module_class(pool, sha1, name) / is_module_class(pool, sha1, name)
   - 键含 SHA1 ⇒ 与翻译顺序/模块切换无关、无需每文件重置,也不会把别的模块的类
     错认成本模块。
2. ClassGenDef 类定义入口(add_visible_struct 之后、枚举/联合分派之前)登记
   mark_module_class(pool, trans.ModuleSha1, class_name),覆盖 class/enum/union/
   renum 全部类种类。
3. TypeGenResolve 规则 5.5 补齐证据链:① 按名取 StructEntry.ModuleSha1;
   ② struct 表暂无条目时用 CurDefModuleSha1 走 find_struct_by_module;
   ③ 仍无条目(正是自引用窗口)时查归属表 ⇒ 加 SHA1 前缀。
   CurDefModuleSha1 取代 CurrentModuleSha1 作为"当前定义模块"真源——后者在
   A-pre/StubMerger 路径未设置或留着上一文件旧值(只有 Translator.ModuleSha1 权威)。

影响面:ABI 可见(特化名);需 TPV 自举重建 + 回归验证。
This commit is contained in:
TermiNexus committed 2026-09-29 18:47:35 +08:00
1 parent dddaba3254
commit 868d48ae22
3 files changed
+98 -20

No files matched your search

+6
View File
@@ -42,6 +42,12 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
# 命名空间隔离:标记为当前文件可见(必须在 existing 检查之前,
# 因为 Phase B 时结构体已注册会提前 return,否则永远无法标记本地类)
self.Gen.StructGen.add_visible_struct(pool, class_name)
# 登记"本模块声明了这个类"(键 "{SHA1}.{类名}",与 struct 注册状态无关):
# 泛型类型实参命名规范化(TypeGenResolve._norm_type_arg_name 规则 5.5)据此
# 判定裸类名归属 —— 类体里的**自引用**(如 includes/ast/base.vp
# `AST.children: list[rawptr[AST]]`)解析时本类尚未 register_struct,
# 只查注册表会留在裸名 ⇒ 与后续带 SHA1 的形式分裂成两个特化名。
self.Gen.StructGen.mark_module_class(pool, trans.ModuleSha1, class_name)
# 枚举类:交给 HandlesEnum 处理
# 分派歧义检测(fail-fast):REnum 优先于 CEnum/CUnion。若某类同时被判为枚举,
+66
View File
@@ -95,6 +95,23 @@ novirtual class StructGenMixin:
VisibleStructs: hashtable.HashTable*
StrictVisibility: int
# --- 各模块"声明的类名"集合(键 "{模块SHA1}.{类名}")---
# 与 StructTable 的注册状态**无关**:类定义入口处即登记(ClassGenDef:43 之后),
# 因此"类正在定义中、struct 还没有条目"时也能答出该裸类名的归属模块。
# 用途:泛型类型实参命名规范化(TypeGenResolve._norm_type_arg_name 规则 5.5)
# 判定裸类名属于哪个模块 —— 自引用字段(如 includes/ast/base.vp
# `AST.children: list[rawptr[AST]]`)解析时本类尚未 register_struct,只按
# 注册表查会把实参留在裸名,与同一个类在构造调用/别的模块处的带 SHA1 形式
# 分裂成两个特化名(现场:base.vp:357 CastError,源/目标只差实参类名前缀)。
ModuleClasses: hashtable.HashTable*
# --- **正在定义类**所属模块的 SHA1(mark_module_class 同步记录)---
# 为什么不复用 TypeGenBase.CurrentModuleSha1:A-pre/StubMerger 等路径只设了
# Translator.ModuleSha1、没设 CurrentModuleSha1(或留着上一文件的旧值),
# 而类定义入口拿到的 trans.ModuleSha1 在任何路径上都是权威的 ⇒ 以它为"当前
# 定义模块"的唯一真源。
CurDefModuleSha1: str
# --- 名 | 类型 下标索引(查找快速路径;索引不可信时回退原线性扫描) ---
StructNameIdx: sizet* # 名 → 首注册下标(槽 u64:0=空槽,否则 index+1)
StructTypeIdx: sizet* # Ty 指针值 → 首注册下标(同布局)
@@ -109,6 +126,8 @@ novirtual class StructGenMixin:
self.StdlibAlloc = 0
self.VisibleStructs = None
self.StrictVisibility = 0
self.ModuleClasses = None
self.CurDefModuleSha1 = None
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
@@ -159,6 +178,53 @@ novirtual class StructGenMixin:
if name is None or self.VisibleStructs is None: return 0
return self.VisibleStructs.__contains__(name)
def _module_class_key(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> str:
"""拼 "{sha1}.{类名}" 查询/登记键(分配在给定 pool,调用方无需释放)。"""
if pool is None or sha1 is None or name is None:
return None
k_len: sizet = string.strlen(sha1) + 1 + string.strlen(name) + 1
k_buf: str = pool.alloc(k_len)
if k_buf is None:
return None
k_buf[0] = '\0'
string.strcat(k_buf, sha1)
string.strcat(k_buf, ".")
string.strcat(k_buf, name)
return k_buf
def mark_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> None:
"""登记"{sha1}.{类名}":**该模块声明了这个类**(类定义入口调用一次)。
键里带 SHA1 ⇒ 天然按"声明模块"区分,且与翻译顺序/模块切换无关(不需要
每文件重置):即使表里残留别的模块的登记,查询方只会命中
(它自己那一对 sha1+类名),不会把别的模块的类错认成自己的。
"""
if pool is None or sha1 is None or name is None:
return
# 同步记录"正在定义类所属模块"(任何翻译路径上唯一的权威来源)
self.CurDefModuleSha1 = sha1
if self.ModuleClasses is None:
self.ModuleClasses = hashtable.HashTable(pool)
if self.ModuleClasses is None:
return
k_buf: str = self._module_class_key(pool, sha1, name)
if k_buf is None:
return
if self.ModuleClasses.__contains__(k_buf) != 0:
return
self.ModuleClasses.set_int(k_buf, 1)
def is_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> int:
"""裸名 name 是否由模块 sha1 声明(1=是 / 0=否;与 struct 注册状态无关)。"""
if pool is None or sha1 is None or name is None:
return 0
if self.ModuleClasses is None:
return 0
k_buf: str = self._module_class_key(pool, sha1, name)
if k_buf is None:
return 0
return self.ModuleClasses.__contains__(k_buf)
def init_struct_table(self, pool: memhub.MemBuddy*) -> int:
"""初始化结构体注册表,返回 1 成功
+26 -20
View File
@@ -279,14 +279,16 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
完整模块名 → SHA1;再尝试 mod 直接查 SHA1(无别名 import 的完整路径)
5. 裸名 "Class":经 from_imports 查所属模块 SHA1
5.5 裸名 "Class" 是**本模块/已注册类**但不在 from_imports 里(典型:本模块
自己声明的类):先按名取结构体登记项的**定义模块 SHA1**(StructEntry.
ModuleSha1);类正在定义中(自引用字段)按名查不到时,回退用与 Name
分支同一处证据 find_struct_by_module(name, 当前模块 SHA1) 认定本模块类。
缺这一步会让同一类型"本模块内是裸名、被引用模块里带前缀"分裂成两个
特化名(现场:includes/ast/stmts.vp:60 `_set_parent_list(targets, self)`
源 %"…list[rawptr[d1ed020479454541.AST]]"* vs 目标 %"…list[rawptr[AST]]"*;
includes/ast/base.vp:357 `children = list[rawptr[AST]](self.pool, 8)`
同族反向:字段侧裸名 vs 构造侧带前缀)
自己声明的类):证据依次为 ① 按名取结构体登记项的**定义模块 SHA1**
(StructEntry.ModuleSha1);② struct 表暂无条目时,用"正在定义类所属模块"
(ClassGenDef 类定义入口登记,权威来源)查 find_struct_by_module;
③ 仍无条目(类定义尚未 register_struct —— 自引用字段的窗口)时,查
该类定义入口登记的"{SHA1}.{类名}"归属表。缺这一步会让同一类型"本模块内
是裸名、被引用模块里带前缀"分裂成两个特化名(现场:includes/ast/stmts.vp
:60 `_set_parent_list(targets, self)` 源 %"…list[rawptr[d1ed020479454541.AST]]"*
vs 目标 %"…list[rawptr[AST]]"*;includes/ast/base.vp:357
`children = list[rawptr[AST]](self.pool, 8)` 同族反向:字段侧裸名 vs
构造侧带前缀)
6. 仍查不到:返回原串(内置类型名等,保持原样)
返回 None 表示 type_name 为 None。
@@ -508,18 +510,22 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
_bare_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(type_name)
if _bare_entry is not None:
_def_sha1 = _bare_entry.ModuleSha1
if _def_sha1 is None:
# 类**正在定义中**(自引用字段,如 base.vp `AST.children: list[rawptr[AST]]`)
# 时按名查不到/登记项尚无 ModuleSha1 ⇒ 用与 resolve_annotation_type 的
# Name 分支**同一处证据**认定"本模块类":find_struct_by_module(name,
# 当前模块 SHA1)。缺这条回退时,字段类型留在裸名、而同类在
# 构造调用(类已注册)处已带 SHA1 ⇒ 同一 list[rawptr[AST]] 又分裂
# (现场:base.vp:357 `self.children = list[rawptr[AST]](self.pool, 8)`)。
_cur_sha1: str = self.Gen.TypeGenBase.CurrentModuleSha1
if _cur_sha1 is not None:
_own_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(type_name, _cur_sha1)
if _own_entry is not None:
_def_sha1 = _cur_sha1
# "正在定义类所属模块"的唯一真源:ClassGenDef 在类定义入口用
# trans.ModuleSha1 登记(不像 TypeGenBase.CurrentModuleSha1 那样存在
# "A-pre/StubMerger 未设置或留着上一文件旧值"的窗口)。
_def_mod: str = self.Gen.StructGen.CurDefModuleSha1
if _def_sha1 is None and _def_mod is not None:
_own_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(type_name, _def_mod)
if _own_entry is not None:
# ① struct 表里已有该类(上一遍已注册 / 本模块更早声明的类)
_def_sha1 = _def_mod
elif self.Gen.StructGen.is_module_class(pool, _def_mod, type_name) != 0:
# ② struct 表里**还没有**该类条目:类定义入口已登记"{SHA1}.{类名}"
# 但尚未走到 register_struct —— 正是**自引用字段**的窗口
# (如 base.vp `AST.children: list[rawptr[AST]]`)。这条与注册
# 状态无关,故第一遍(A-pre)就能给出带 SHA1 的实参名,字段
# 类型不会被冻结成裸名版本、与后续构造调用处分裂。
_def_sha1 = _def_mod
if _def_sha1 is not None:
res_d: str = self._concat_sha1_class(pool, _def_sha1, type_name)
if res_d is not None: