fix(ClassGenGeneric): 泛型特化不再平移"类定义窗口",裸类名实参不再冻结成裸名

类别:a 编译器缺陷(TPV 侧)

现象(反复出现 4 轮):编译 includes/ast/base.vp 时 :359 `_append_child(self.children, node,
self.pool)` 报
「指针隐式转指针已禁止(被指类型是非基本类型…):源类型 %"…list[rawptr[AST]]"* →
目标类型 %"…list[rawptr<定义模块SHA1>.AST]]"*」——同一泛型特化被写成两种拼名(实参一侧
裸类名 `AST`、另一侧带定义模块 SHA1 前缀)。

判据(结构缺陷,代码级可证):
· `_norm_type_arg_name` 规则 5.5 判定"裸类名归属"依赖两个**全局窗口变量**
  `StructGen.CurDefModuleSha1` / `CurDefClassName`(证据 ② `is_module_class`、
  ③ `CurDefClassName` 逐字相同),入口闸门是 `_def_mod is not None`。
· `ClassGenGeneric._specialize_generic_class` 在 :715-720 把 `trans.ModuleSha1` 临时切到
  **泛型模板定义模块**,再于 :729 调 `ClassGenDef.translate_class_def`;后者在
  `ClassGenDef.vp:50` 会 `mark_module_class(pool, trans.ModuleSha1, class_name)`
  ⇒ **窗口被覆盖成"模板模块 + 特化类名"**。原实现返回时只恢复 `trans.ModuleSha1` 与
  `_imported_modules`(:733-735),**没有恢复这两个窗口变量**。
· 后果:调用方文件在此之后解析任何裸类名时,证据 ②③ 都在对着"模板模块 / 特化类"判 ⇒
  全落空 ⇒ 该实参以**裸名**形式被首次注册并被后续复用(首写者胜:`StructGenMixin.vp:455`
  按 Ty 指针去重、`ClassGenDef.vp:100-150` existing 早退不重算)⇒ 与同一特化在别处解析出的
  带 SHA1 形式分裂成两个名字 ⇒ 被"非基本类型指针不可隐式转换"拦下。

修法:
1. `ClassGenGeneric.vp`:窗口与 `ModuleSha1` **同进同出** —— 在切 ModuleSha1 之前保存
   `CurDefModuleSha1`/`CurDefClassName`,:744 之后一并恢复(嵌套特化不再污染调用方窗口)。
2. `TypeGenResolve.vp` 规则 5.5 的取证日志扩展:除原有"命中 struct 条目但 entry.ModuleSha1
   为空"外,**新增触发条件"窗口存在却属于另一个类"**(`_def_mod` 有值且
   `CurDefClassName` 与裸名不同)——正是"窗口被特化平移/污染"或"前向引用未登记类"的签名;
   报文补出 `entry_present` 与 `find_struct_by_module` 两项事实,一轮即可定位来源是否缺失。

验证:TPV 侧改动需重新自举 TransPyV.exe 后生效;本次以静态证据链(上述行号)与取证日志
自证。注:TransPyV 仓另有 7 个他人未提交文件,本次只提交本文件与 TypeGenResolve.vp。
This commit is contained in:
TermiNexus committed 2026-09-30 00:20:30 +08:00
1 parent 31f341cf6f
commit e1bb42fade
2 files changed
+43 -12

No files matched your search

+15 -1
View File
@@ -714,6 +714,18 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
# 避免模板方法体内的跨模块调用被误报"模块未导入"。 # 避免模板方法体内的跨模块调用被误报"模块未导入"。
saved_module_sha1: str = trans.ModuleSha1 saved_module_sha1: str = trans.ModuleSha1
saved_imported: str = trans._imported_modules saved_imported: str = trans._imported_modules
# 【窗口同进同出】类定义窗口(CurDefModuleSha1/CurDefClassName)必须与
# ModuleSha1 一起保存/恢复:下方 translate_class_def(:729)内部会按
# **模板模块 sha1 + 特化类名** 调 mark_module_class(ClassGenDef.vp:50),
# 覆盖这两个窗口变量。原实现只恢复 trans.ModuleSha1 ⇒ 调用方文件在此之后
# 解析任何裸类名时,规则 5.5 的归属判据(证据 ② is_module_class /
# ③ CurDefClassName 逐字相同)全都在对着"模板模块 / 特化类"判 ⇒ 判不到
# ⇒ 该实参以**裸名**形式被首次注册并永久复用,与同一特化在别处解析出的
# 带 SHA1 形式分裂成两个名字,最终以"被指类型是非基本类型……不可隐式转换"
# 报出(现场:includes/ast/base.vp:359 `_append_child(self.children, ...)`
# 源 list[rawptr[AST]] vs 目标 list[rawptr<定义模块SHA1>.AST])。
saved_def_mod: str = self.Gen.StructGen.CurDefModuleSha1
saved_def_cls: str = self.Gen.StructGen.CurDefClassName
template_sha1: str = self._find_generic_template_sha1(class_name) template_sha1: str = self._find_generic_template_sha1(class_name)
if template_sha1 is not None: if template_sha1 is not None:
trans.ModuleSha1 = template_sha1 trans.ModuleSha1 = template_sha1
@@ -730,9 +742,11 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if tpl_file_full is not None: if tpl_file_full is not None:
self.Gen.TypeGenBase.pop_trace() self.Gen.TypeGenBase.pop_trace()
self.Gen.TypeGenResolve.set_current_file(saved_cur_full) self.Gen.TypeGenResolve.set_current_file(saved_cur_full)
# 恢复原始 ModuleSha1 与 imports # 恢复原始 ModuleSha1 / imports / **类定义窗口**(窗口不平移,见上方说明)
trans.ModuleSha1 = saved_module_sha1 trans.ModuleSha1 = saved_module_sha1
trans._imported_modules = saved_imported trans._imported_modules = saved_imported
self.Gen.StructGen.CurDefModuleSha1 = saved_def_mod
self.Gen.StructGen.CurDefClassName = saved_def_cls
# 8. 清除 type_map 上下文 # 8. 清除 type_map 上下文
trans.GenericTypeParamNames = None trans.GenericTypeParamNames = None
+28 -11
View File
@@ -564,35 +564,52 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
res_d: str = self._concat_sha1_class(pool, _def_sha1, type_name) res_d: str = self._concat_sha1_class(pool, _def_sha1, type_name)
if res_d is not None: if res_d is not None:
return res_d return res_d
# 取证(只加日志,不改返回值):命中了 struct 条目却一条 SHA1 证据都取不到 # 取证(只加日志,不改返回值):一条 SHA1 证据都取不到 ⇒ 本次会保留裸名,
# ⇒ 本次会保留裸名,可能与本类在别处解析出的"带前缀"形式分裂。把五种证据 # 可能与本类在别处解析出的"带前缀"形式分裂。把各处证据一次打出,便于一轮
# 的取值一次打出,便于一轮定位是哪个来源缺失(可疑子集:entry 的 ModuleSha1 # 定位是哪个来源缺失。两个触发条件:
# 为空——正是 .vpi 注入的半条目形态)。 # ① 命中 struct 条目但 entry.ModuleSha1 为空(.vpi 注入的半条目形态);
# ② **类定义窗口存在却属于另一个类**(_def_mod 有值、CurDefClassName 与
# 裸名不同):这是"窗口被泛型特化平移/污染"(某次
# _specialize_generic_class 内部 translate_class_def 覆盖了窗口且未恢复)
# 或"前向引用尚未登记的类"的签名 —— 证据 ②③ 都在对着别的类判,
# 裸名一旦被首次注册就会被永久复用。
_w_cls_cur: str = self.Gen.StructGen.CurDefClassName
_w_need_log: int = 0
if _bare_entry is not None and _bare_entry.ModuleSha1 is None: if _bare_entry is not None and _bare_entry.ModuleSha1 is None:
_w_need_log = 1
if _def_mod is not None and _w_cls_cur is not None:
if string.strcmp(_w_cls_cur, type_name) != 0:
_w_need_log = 1
if _w_need_log != 0:
_w_mod: str = "None" _w_mod: str = "None"
if _def_mod is not None: _w_mod = _def_mod if _def_mod is not None: _w_mod = _def_mod
_w_cls: str = "None" _w_cls: str = "None"
if self.Gen.StructGen.CurDefClassName is not None: if _w_cls_cur is not None:
_w_cls = self.Gen.StructGen.CurDefClassName _w_cls = _w_cls_cur
_w_fi: str = "None" _w_fi: str = "None"
if from_imports is not None: _w_fi = from_imports if from_imports is not None: _w_fi = from_imports
_w_file: str = "None" _w_file: str = "None"
if self.CurrentFile is not None: _w_file = self.CurrentFile if self.CurrentFile is not None: _w_file = self.CurrentFile
_w_imc: int = 0 _w_imc: int = 0
_w_fbm: int = 0
if _def_mod is not None: if _def_mod is not None:
_w_imc = self.Gen.StructGen.is_module_class(pool, _def_mod, type_name) _w_imc = self.Gen.StructGen.is_module_class(pool, _def_mod, type_name)
if self.Gen.StructGen.find_struct_by_module(type_name, _def_mod) is not None:
_w_fbm = 1
_w_ep: int = 0
if _bare_entry is not None: _w_ep = 1
_w_ty: str = "None" _w_ty: str = "None"
if _bare_entry.Ty is not None: if _bare_entry is not None and _bare_entry.Ty is not None:
_w_buf: str = pool.alloc(288) _w_buf: str = pool.alloc(288)
if _w_buf is not None: if _w_buf is not None:
_w_buf[0] = '\0' _w_buf[0] = '\0'
llvmlite.TypePrint(_w_buf, 288, _bare_entry.Ty, pool) llvmlite.TypePrint(_w_buf, 288, _bare_entry.Ty, pool)
if _w_buf[0] != '\0': _w_ty = _w_buf if _w_buf[0] != '\0': _w_ty = _w_buf
VLogger.error( VLogger.error(
f"[类型实参归属] 裸类名 '{type_name}' 命中 struct 条目但取不到定义模块 SHA1" f"[类型实参归属] 裸类名 '{type_name}' 本次保留裸名(可能与带前缀形式分裂):"
f"(本次保留裸名,可能与带前缀形式分裂):" f"entry_present={_w_ep} entry.Ty={_w_ty} cur_def_mod={_w_mod} cur_def_cls={_w_cls} "
f"entry.Ty={_w_ty} cur_def_mod={_w_mod} cur_def_cls={_w_cls} " f"is_module_class={_w_imc} find_struct_by_module={_w_fbm} "
f"is_module_class={_w_imc} from_imports={_w_fi} file={_w_file}", f"from_imports={_w_fi} file={_w_file}",
"TypeGen") "TypeGen")
# ---- 6. 查不到:保持原样(内置类型名等)---- # ---- 6. 查不到:保持原样(内置类型名等)----
return type_name return type_name