fix(TPV): 删除残留的"| t.CPtr struct 引用语义吸收"(T* 被降一层的真正根因)

现场(0a81e67 之后仍复现):Test/SlotsBugTest 编译 includes/_list.vp:54
  TypeError: 下标赋值的左值元素是对象 'd1ed020479454541.AST'(48 字节),右值却是指向
  'd1ed020479454541.AST' 的指针
  elem_ptr[0] = item

关键事实:`T*` **不是** `rawptr[T]` 下标形——parser 把尾随 '*' 解析成
BinOp(`T | t.CPtr`)(includes/ast/parser.vp:1528-1556 `_make_ptr_type`、:2700-2707)。
所以它走 resolve_annotation_type 的 **BinOp 联合分支**,0a81e67 只修了
rawptr[T] / t.CPtr[T] 两条**下标**分支,覆盖不到这里。

链路(逐段在源码上核对过):
1. 泛型实参 `rawptr[AST]` 经 _norm_type_arg_name 归一为文本 `{sha}.AST*`;
2. `T` 走 Name 分支的泛型替换 → _parse_type_string 的 `T*` 反解析(:995-1018,
   按尾随 '*' 层数包 rawptr[...])⇒ other_ty = Ptr(AST),本身正确;
3. BinOp 分支尾部的"struct 引用语义吸收":other_ty 已是 Ptr(已注册 struct)
   ⇒ 直接 `return other_ty`,**吞掉 CPtr 这一层** ⇒ `T*` 变成 AST*(应 AST**)。

改动:
- TypeGenResolve.vp BinOp 分支:删除该吸收,恒 `return Ptr(other_ty)`
  (与 rawptr[T] / t.CPtr[T] 下标形、_parse_type_string 的 `T*` 反解析三处同口径)。
- VpiReader.vp:1614-1621 注入侧的同源吸收一并删除(维持"定义侧 / 注入侧 / 调用侧
  三处指针层数口径同步")。
- 两处口径注释同步改为"`| t.CPtr` 恒追加恰好 1 层,无例外"。

安全性(该吸收已是死补偿):它是"裸类名自动升指针"时代的产物——当时裸类名解析成
Ptr(struct),所以要吸收;自 2026-10-01 起裸类名恒为原型 struct,`X | t.CPtr` 由
Ptr(struct) 天然得到 X*。json/__init__.vp:90 的引号注解 `'JsonValue' | t.CPtr`
现在 other_ty 就是 struct ⇒ 吸收本不触发,删除后行为不变(JsonTest 已实测翻译通过并
进入 llc 阶段)。`list[int]`(i32*)、`list[ast.AST]`(struct*)、裸类名 `X | t.CPtr`
(struct*)均不受影响。

待验证:重编译 TransPyV(自举两遍)→ SlotsBugTest / JsonTest / WebTest。
This commit is contained in:
TermiNexus committed 2026-10-01 17:46:53 +08:00
1 parent 0a81e67e89
commit 93e983f74e
2 files changed
+25 -31

No files matched your search

+8 -8
View File
@@ -1605,19 +1605,19 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*,
if first_ty is not None:
# 复刻定义侧规则(N12/N13):限定名 / 白名单裸标量 / 已注册结构体 / 可解析的非指针裸 typedef 别名 → 保留 pointee;
# 其余(解析失败、裸枚举名、指针别名)→ 联合坍缩为 i8*,与定义侧 resolve_annotation_type 对称(两侧同型即收敛)。
# 指针别名(first_td 不置位,如 VOIDPTR = CVoid|CPtr → i8*):`| t.CPtr` **追加 1 层** → i8**(不吸收);
# 仅当 pointee 命中 find_struct_by_type(类引用语义)时吸收 1 层,其余 ⇒ Ptr(first_ty)(与定义侧指针层数口径同源)。
# 指针别名(first_td 不置位,如 VOIDPTR = CVoid|CPtr → i8*):`| t.CPtr`
# **恒追加恰好 1 层** → i8**(不吸收);指针型 T 同理(见下)。
if first_qual != 0 or first_wl != 0 or first_td != 0:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Struct:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Ptr:
match first_ty:
case llvmlite.LLVMType.Ptr(pointee_v):
if GenApi.find_struct_by_type(pointee_v) is not None:
return first_ty
case _:
pass
# `| t.CPtr` **恒追加恰好 1 层**(与定义侧 TypeGenResolve 的 BinOp 分支同口径):
# 这里原先还做"pointee 命中 find_struct_by_type(类引用语义)就吸收 1 层",
# 那是"裸类名自动升指针"时代的补偿,自裸类名恒为**原型 struct** 后已无对象;
# 而泛型形参实例化为指针类型时(T 的实参文本 `{sha}.AST*`)first_ty 正是
# Ptr(已注册 struct) ⇒ 误吸收把 `T*` 降成 AST*(应为 AST**),
# 现场 includes/_list.vp:54 `elem_ptr[0] = item`。
return llvmlite.Ptr(pool, first_ty)
return i8_ptr_ty
return i8_ptr_ty
+17 -23
View File
@@ -3161,8 +3161,9 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
# 判定该裸名是否可解析,命中即保留已解析类型参与「基类型 + 指针层数」合并
# (对齐 TPC HandlesTypeMerge._MergeAllComponentTypes)。
# 指针层数口径(本处 / VpiReader 注入侧 / 调用侧三处必须同步):`| t.CPtr`
# 恒为"追加 1 层指针",唯一例外是 struct 引用语义时吸收这 1 层
# (判据 = Ptr 的 pointee 是否为已注册 struct,find_struct_by_type 命中即吸收)。
# **恒为"追加恰好 1 层指针",无例外**——裸类名恒为原型 struct,
# `X | t.CPtr` 由 Ptr(struct) 天然得到 X*,不再需要"pointee 是已注册
# struct 就吸收这 1 层"的旧补偿(该补偿曾把指针型 T 的 `T*` 降一层)。
if keep_other_ty == 0 and other_node is not None and other_node.kind() == ast.ASTKind.Name:
other_nm_td: ast.Name* = ast.Name*(other_node)
if other_nm_td.id is not None:
@@ -3205,28 +3206,21 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
# 回退 Ptr(i8) 由 AnnotClassName 恢复 + 多遍注册收敛
# 处理,禁止 fail-fast(误杀合法前向引用)。
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# struct 引用语义吸收(本文件 :3219-3221 早已写明、此前未实现的
# 那条规则):other_ty 若已是「指向已注册 struct 的指针」,说明
# 该侧裸类名已按**引用语义**解析过(有 __new__ 的堆类,Name/
# Attribute 分支直接给 Ptr(struct),见 :2806-2813、:3040-3044)
# ⇒ `| t.CPtr` 不再追加一层,否则多一层指针。
# 三种写法必须同型(用户 2026-09-30 定:统一为 T*):
# `X | t.CPtr` **恒追加恰好 1 层**(用户口径,与 rawptr[T]/t.CPtr[T] 下标形
# 以及 `_parse_type_string` 的 `T*` 反解析三处同源):
# T* ≡ rawptr[T] ≡ t.CPtr[T] ≡ T | t.CPtr
# 现场:includes/json/__init__.vp:90 `return cur`
# `def __getitem__(...) -> 'JsonValue' | t.CPtr`(**引号字符串**
# 注解,左边是 Constant 不是 Name —— 绕过了上面 :3176 那段
# "Name 必须已注册 struct 否则丢弃"的守卫,于是带着 Ptr(struct)
# 走到这里再包一层 ⇒ JsonValue**),而 `cur: JsonValue*` 是
# JsonValue* ⇒ CastError「指针层数不同(源 1 级 → 目标 2 级)」。
# 之所以只在引号写法暴露:裸名写法会在 :3222-3239 被判定
# td_res_is_ptr=1 → other_ty 置 None → 走 :3254 回退取 struct 再包
# 一层,恰好抵消;引号写法绕过了那条路径。
match other_ty:
case llvmlite.LLVMType.Ptr(pointee_ab):
if self.Gen.StructGen.find_struct_by_type(pointee_ab) is not None:
return other_ty
case _:
pass
# 这里原先还有一段"struct 引用语义吸收"(other_ty 若已是 Ptr(已注册 struct)
# 就直接返回它、吞掉 CPtr 这层),那是"裸类名自动升指针"时代的补偿:
# 当时裸类名/引号注解解析成 Ptr(struct),所以要吸收;自 2026-10-01 起
# 裸类名恒为**原型 struct**(见本文件 Name/Attribute 各分支),
# `X | t.CPtr` 由 Ptr(struct) 天然得到 X*,吸收已无正当对象
# (json/__init__.vp:90 的引号注解 `'JsonValue' | t.CPtr` 现在 other_ty
# 就是 struct,吸收本就不触发,删除后行为不变)。
# 而**泛型形参实例化为指针类型**时(`list[rawptr[AST]]` 里 `T` 的实参文本是
# `{sha}.AST*`,parser 把源码 `T*` 解析成 BinOp `T | t.CPtr`)other_ty 正是
# Ptr(已注册 struct) ⇒ 误吸收把 `T*` 降成 AST*(应为 AST**),
# 现场 includes/_list.vp:54 `elem_ptr[0] = item` 被误判为"左值元素是对象
# AST(48 字节)、右值是指针"而 fail-fast。
return llvmlite.Ptr(pool, other_ty)
# t.CPtr[...] 下标指针标记(普通 _is_cptr_marker 不识别 Subscript):