fix(TypeGenResolve): X | t.CPtr 的 struct 引用语义吸收这层指针,修 json __getitem__ 报 JsonValue** vs JsonValue* 层数不同

现象(TransPyV 编译 includes/json/__init__.vp):
  line 90  return cur
  CastError: 指针隐式转指针已禁止:指针层数不同(源 1 级 → 目标 2 级)
    源 %"e966d554e91364bc.JsonValue"* → 目标 %"e966d554e91364bc.JsonValue"**

根因链(逐环取证):
1. includes/json/__init__.vp:79
     def __getitem__(self, key: str) -> 'JsonValue' | t.CPtr:
   注意左边是**引号字符串注解**(Constant),不是裸 Name。
2. JsonValue 是带 __new__ 的**堆类**(该文件 :34),按引用语义,裸类名注解在
   resolve_annotation_type 的 Name/Attribute 分支直接给 Ptr(struct)
   (:2806-2813 注释「有 __new__ 的类(堆对象)注解 → Ptr(struct)(引用语义,对齐 TPC)」,
    Attribute/sha1 前缀/from-import 各分支同口径)。
3. 于是 BinOp(`| t.CPtr`) 分支里 other_ty 已经是 Ptr(JsonValue);而 :3176 那段
   「Name 且未注册 struct 才丢弃」的守卫只认 Name —— Constant 绕过了它,
   带着 Ptr(struct) 走到 :3264 `return llvmlite.Ptr(pool, other_ty)` 再包一层
   ⇒ JsonValue**。
4. 裸名写法(不带引号)不会暴露:它会在 :3222-3239 被判 td_res_is_ptr=1 →
   other_ty 置 None → 走 :3254 回退取 struct 再包一层,恰好抵消。
⇒ 同一类型两种写法产出两个层数(`JsonValue*` vs `JsonValue**`),与用户
   2026-09-30 定的统一口径冲突:T* ≡ rawptr[T] ≡ t.CPtr[T] ≡ T | t.CPtr。

改动(TypeGenResolve.resolve_annotation_type 的 BinOp(|t.CPtr) 分支):
- 在 :3264 包装前落实本文件 :3219-3221 注释里早已写明、但此前**未实现**的那条
  规则:「| t.CPtr 恒为追加 1 层,唯一例外是 struct 引用语义时吸收这 1 层
  (判据 = Ptr 的 pointee 是已注册 struct,find_struct_by_type 命中即吸收)」。
- 即:other_ty 已是 Ptr(已注册 struct) 时直接返回 other_ty,不再加一层。
- 副作用(同步修正同族写法):引号字符串写法、以及 Attribute 分支在
  find_struct_by_module 命中 HasNew 时返回 Ptr(struct) 的那条路径
  (:3040-3044),都从「多一层」收敛为 T*。
- 不影响的既有例外:VOIDPTR/CVoid|CPtr 等「指针别名」侧解析出的是 Ptr(i8)
  (i8 非 struct,命中不到 find_struct_by_type)⇒ `VOIDPTR | t.CPtr` 仍是
  i8**(追加一层),与 VpiReader 侧口径一致。

仅 TPV 侧(TPC 侧走 SymbolUtils.AddPointerLayers 的另一套口径,本次不动)。

待验证(不自行执行编译命令):先重编译 TransPyV,再跑
  TransPyV.exe --project ./Test/JsonTest/project.json --clean --clear-cache --rebuild-includes --traceback
预期 :90 不再报层数不同;若还有同族报错请把栈贴回。
This commit is contained in:
TermiNexus committed 2026-10-01 00:51:51 +08:00
1 parent 3329c9beae
commit 660427f759
1 file changed
+22
+22
View File
@@ -3261,6 +3261,28 @@ 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*):
# 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
return llvmlite.Ptr(pool, other_ty)
# t.CPtr[...] 下标指针标记(普通 _is_cptr_marker 不识别 Subscript):