fix(TPV/TypeGenResolve): rawptr[T]/t.CPtr[T] 恒追加 1 层,删除"裸类引用吸收"旧补偿
现场:Test/SlotsBugTest 编译 includes/_list.vp:54 报
TypeError: 下标赋值的左值元素是对象 'd1ed020479454541.AST'(48 字节),右值却是指向
'd1ed020479454541.AST' 的指针
源码: elem_ptr[0] = item
用户判定正确:`elem_ptr: T*` 与 `item: T` 同为 T,赋值本身合法;错在编译器把 `T*`
少算了一层——T 实例化为 rawptr[AST](= AST*)时,`T*` 应为 AST**。
根因:rawptr[T] / t.CPtr[T] 分支里有"切片是裸类引用(解析结果 Ptr(已注册 struct))
就吸收这 1 层"的旧补偿(_ptr_subscript_absorbs_class)。那是"裸类名自动升指针"时代的
产物——当时裸类名解析成 Ptr(struct),于是 rawptr[X] 会多包一层,需要吸收;
自 875a9be 起裸类名恒为**原型(struct 值)**,Ptr(struct) 天然就是 X*,补偿已无对象;
而**泛型形参实例化为指针类型**时(T = rawptr[AST]),切片的解析结果同样是
Ptr(已注册 struct) ⇒ 被误吸收:`elem_ptr: T*` 变成 AST*(应为 AST**)⇒ 左值元素被判成
48 字节的 AST 对象、右值是指针 ⇒ fail-fast。
改动:
- 删除 _ptr_subscript_absorbs_class,以及 rawptr[T] / t.CPtr[T] 两处调用,改为**恒追加
1 层**:`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T*` ≡ `T | t.CPtr` 四种写法同型(用户口径),
不再有"裸类引用吸收"例外。
- 同步修正四处引用该函数的口径注释(:407 / :433 / :567 / :1000)。
- **保留** BinOp `| t.CPtr` 分支内的同名吸收:它只对**非裸名**侧可达(Constant/Attribute;
裸名侧在更早的"Name 必须已注册 struct"守卫处就改走其它分支),故
includes/json/__init__.vp 的引号注解 `'JsonValue' | t.CPtr` 行为不变。
仓库内也不存在 `rawptr['X']` / `CPtr['X']` 切片写法(仅注释提及),删除吸收无副作用。
已知遗留(本次未改,待你定夺):`T | t.CPtr` 这一写法在 T 实例化为指针类型或类类型时
仍会退化——Name 侧解析出的 Ptr(struct) 被丢弃,随后按名字 'T' 查 struct 失败 ⇒
返回 Ptr(i8)(位置:本文件 BinOp 分支的 keep_other_ty 判定,约 :3166-3194)。
当前仓库没有使用该写法的现场,故未一并改动。
待验证:重编译 TransPyV(自举两遍)→ SlotsBugTest / JsonTest / NoVTableCrossMixinTest。
This commit is contained in:
1 parent
f748cae562
commit
0a81e67e89
1 file changed
+16
-63
@@ -404,7 +404,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# 用途:泛型类型实参**文本**归一(_norm_type_arg_name)的唯一判据 ——
|
||||
# 用户定口径(2026-09-30):`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T | t.CPtr`
|
||||
# ⇒ 统一为 **`T*`**。裸标记只贡献一层指针、自身不是类型名。
|
||||
# (形式无关:等价性本身由 _ptr_subscript_absorbs_class 的文档口径给出。)
|
||||
# (形式无关:四种写法在类型域恒等价——`rawptr[T]` / `t.CPtr[T]` 一律追加 1 层。)
|
||||
#
|
||||
# 刻意**只认 CPtr 一族**:类型域(resolve_annotation_type 的 BinOp 分支)
|
||||
# 判 CPtr 标记用的是 _is_cptr_marker,同样只认 `t.CPtr` / 裸 `CPtr`
|
||||
@@ -430,7 +430,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# (resolve_annotation_type 的 BinOp 分支)判标记用 _is_cptr_marker,
|
||||
# 同样不认裸 `rawptr` ⇒ 名字域若多认会出现"名说 T*、类型说 T"。
|
||||
# · **下标写法** `rawptr[T]` / `t.rawptr[T]` 则必须认(本判据):类型域对
|
||||
# 下标形走 _ptr_subscript_absorbs_class,`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T*`
|
||||
# 下标形恒追加 1 层,`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T*`
|
||||
# 逐字等价。漏认的后果(现场):同名注解两种写法产出两个泛型特化体 ——
|
||||
# `_list.vp:83 return self` 报
|
||||
# 「源 %"….list[<sha>.AST*]"* → 目标 %"….list[rawptr[<sha>.AST]]"*」,
|
||||
@@ -564,7 +564,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# 【指针联合归一(2026-09-30,用户定口径)】`X | t.CPtr` ≡ `X | rawptr`
|
||||
# ≡ `rawptr[X]` ≡ `t.CPtr[X]` ⇒ 统一为 **`X*`**(T* 形)。裸指针标记
|
||||
# 只贡献一层指针;这不是新类型规则,只是**书写形式的唯一规范形**
|
||||
# (本仓自证等价:_ptr_subscript_absorbs_class 的文档口径)。
|
||||
# (四种写法在类型域恒等价:下标形与联合形都只追加 1 层指针)。
|
||||
# 为什么必须归一:同一元素类型 `AST*` 若一半写 `rawptr[AST]`、一半写
|
||||
# `AST|t.CPtr`,泛型特化名会分裂成两个结构体(现场
|
||||
# Test/AstTest/App/main.vp:63 `classify_list(ifn.orelse, ...)`:
|
||||
@@ -997,7 +997,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# t.CPtr[T] ≡ T|t.CPtr ≡ T*),而本函数负责把**类型名文本**还原成注解 AST
|
||||
# (典型调用方:ClassGenGeneric._clone_annotation 把泛型参数 T 替换为
|
||||
# 已归一的实参文本)⇒ 必须能解析尾随 '*'。语义与 `rawptr[T]` 逐字等价
|
||||
# (_ptr_subscript_absorbs_class 的文档口径),故按层数包成 rawptr[...]:
|
||||
# (下标形与联合形恒等价,都只追加 1 层指针),故按层数包成 rawptr[...]:
|
||||
# "{sha}.AST*" → rawptr[{sha}.AST];"i8**" → rawptr[rawptr[i8]]。
|
||||
_st_cnt: int = 0
|
||||
while seg_len > sizet(_st_cnt) and type_str[seg_len - sizet(_st_cnt) - 1] == '*':
|
||||
@@ -2072,57 +2072,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
if nm.id is None or string.strcmp(nm.id, "t") != 0:
|
||||
return 0
|
||||
return 1
|
||||
def _ptr_subscript_absorbs_class(self, slice_node: ast.AST*,
|
||||
resolved: llvmlite.LLVMType*) -> int:
|
||||
"""`rawptr[T]` / `t.CPtr[T]` 的切片 T 是**裸类引用**时是否吸收该层指针(1=吸收)。
|
||||
|
||||
语义真源:`rawptr[T]` ≡ `t.CPtr[T]` ≡ 源码联合写法 `T | t.CPtr`
|
||||
(TPC 侧 SymbolUtils.IsPtrSubscriptNode 文档),三者必须得到**同一类型**。
|
||||
联合写法在本文件 BinOp 分支得到的是「裸类名先被丢弃、再按类名取 struct 包一层」
|
||||
⇒ `X*`(见 :2682-2684 的口径注释与 :2703-2720 的回退);而 rawptr/CPtr
|
||||
下标分支此前无脑 `Ptr(resolve(T))`,对「有 __new__ 的堆类」(本文件 Name 分支
|
||||
:2269-2277 已把裸类名解析为 Ptr(struct))就多包一层 ⇒ `X**`
|
||||
(现场:includes/ast/tokens.vp:217 `ptr: rawptr[Token] = pool.alloc(...)`
|
||||
目标 %"…Token"** vs 源 i8* → CastError)。
|
||||
|
||||
判据(两条件同时成立才吸收,与 BinOp 分支的"Ptr 的 pointee 是否为已注册
|
||||
struct,find_struct_by_type 命中即吸收"逐字同源):
|
||||
① 切片不是**用户写明的指针包装**——联合写法(BinOp)与嵌套指针下标
|
||||
(`rawptr[...]` / `t.CPtr[...]` / 裸名 `CPtr[...]`)的层数由用户指定,
|
||||
必须逐层外包(`rawptr[rawptr[X]] → X**`、`t.CPtr[t.CPtr] → i8**`);
|
||||
② 解析结果已是 `Ptr(已注册 struct)`——即切片本身就是类引用;
|
||||
`str`/`bytes`(Ptr(i8))与标量(非指针)不满足 ⇒ 保持
|
||||
`rawptr[int] → i32*`、`rawptr[str] → i8**`(与 TPC 同口径)。
|
||||
|
||||
纯判据:不发射类型、不改动任何已解析结果。
|
||||
"""
|
||||
if slice_node is None or resolved is None:
|
||||
return 0
|
||||
k_sl: int = slice_node.kind()
|
||||
if k_sl == ast.ASTKind.BinOp:
|
||||
return 0
|
||||
if k_sl == ast.ASTKind.Subscript:
|
||||
sub_sl: ast.Subscript* = ast.Subscript*(slice_node)
|
||||
if sub_sl.value is not None:
|
||||
vk_sl: int = sub_sl.value.kind()
|
||||
if vk_sl == ast.ASTKind.Name:
|
||||
vn_sl: ast.Name* = ast.Name*(sub_sl.value)
|
||||
if vn_sl.id is not None:
|
||||
if string.strcmp(vn_sl.id, "rawptr") == 0 \
|
||||
or string.strcmp(vn_sl.id, "CPtr") == 0:
|
||||
return 0
|
||||
elif vk_sl == ast.ASTKind.Attribute:
|
||||
va_sl: ast.Attribute* = ast.Attribute*(sub_sl.value)
|
||||
if va_sl.attr is not None and string.strcmp(va_sl.attr, "CPtr") == 0:
|
||||
return 0
|
||||
match resolved:
|
||||
case llvmlite.LLVMType.Ptr(pointee_ab):
|
||||
if pointee_ab is not None:
|
||||
if self.Gen.StructGen.find_struct_by_type(pointee_ab) is not None:
|
||||
return 1
|
||||
case _:
|
||||
pass
|
||||
return 0
|
||||
def _is_cvoid_marker(self, node: ast.AST*,
|
||||
imported_modules: str) -> int:
|
||||
"""检测节点是否为 CVoid 指针标记,返回 1=是 / 0=否"""
|
||||
@@ -3416,15 +3365,19 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# rawptr[T] 内建裸指针:等价 t.CPtr[T] → Ptr(T)(T 自身可含指针层级)。
|
||||
# 与 t.CPtr[T] 语义一致;不处理则泛型特化/builtins 兜底失败 →
|
||||
# "函数返回类型解析失败:注解不可解析"。
|
||||
# T 是**裸类引用**时(已是 Ptr(struct))吸收这一层:`rawptr[X]`
|
||||
# ≡ `X | t.CPtr` ≡ X*(不是 X**),见 _ptr_subscript_absorbs_class。
|
||||
# **恒追加 1 层,无例外**:`rawptr[T]` ≡ `t.CPtr[T]` ≡ `T*` ≡ `T | t.CPtr`。
|
||||
# 旧实现在此处做"切片是裸类引用(解析结果为 Ptr(已注册 struct))就吸收这层"
|
||||
# ——那是"裸类名自动升指针"时代的补偿;自 2026-10-01 起裸类名恒为**原型**
|
||||
# (struct 值),`Ptr(struct)` 天然就是 X*,补偿已无对象,反而在**泛型形参
|
||||
# 实例化为指针类型**时吃掉真层数:`list[rawptr[AST]]` 的 `elem_ptr: T*`
|
||||
# (T=rawptr[AST])被吸收成 `AST*`(应为 `AST**`),现场
|
||||
# includes/_list.vp:54 `elem_ptr[0] = item` 被误判为"左值元素是对象
|
||||
# AST(48 字节)、右值是指针"而 fail-fast。
|
||||
if string.strcmp(gen_nm.id, "rawptr") == 0:
|
||||
if sub.slice.kind() != ast.ASTKind.Tuple:
|
||||
rp_elem: llvmlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1)
|
||||
if rp_elem is None:
|
||||
return None
|
||||
if self._ptr_subscript_absorbs_class(sub.slice, rp_elem) != 0:
|
||||
return rp_elem
|
||||
return llvmlite.Ptr(pool, rp_elem)
|
||||
return None
|
||||
gen_ty_nm: llvmlite.LLVMType* = self._resolve_generic_subscript_ty(pool, sub, gen_nm.id, trans,
|
||||
@@ -3483,8 +3436,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
|
||||
# 指针类型: t.CPtr[T] → Ptr(T)(T 自身可含指针层级):t.CPtr[t.CPtr] → i8**,
|
||||
# t.CPtr[int] → i32*(DeclarationGenerator.py L1014-1016 文档化语法)。
|
||||
# T 是**裸类引用**时(已是 Ptr(struct))吸收这一层:t.CPtr[X] ≡ X | t.CPtr
|
||||
# ≡ X*(不是 X**),见 _ptr_subscript_absorbs_class。
|
||||
# **恒追加 1 层,无例外**(与 rawptr[T] 分支同口径):`t.CPtr[T]` ≡ `T*`
|
||||
# ≡ `T | t.CPtr`。此处原先也做"切片是裸类引用就吸收一层",同 rawptr[T]
|
||||
# 分支:裸类名恒为原型后该补偿无对象,且会在 T 实例化为指针类型时吃掉
|
||||
# 真层数(`t.CPtr[T]` 应得 T**)。
|
||||
if string.strcmp(arr_at.attr, "CPtr") == 0:
|
||||
if string.strcmp(arr_val_name.id, "t") != 0 and string.strcmp(arr_val_name.id, "c") != 0 \
|
||||
and self.Gen.ImportsGen.is_module_imported(imported_modules, arr_val_name.id) == 0:
|
||||
@@ -3496,8 +3451,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
||||
# Phase 1a 无 vpi 注入时依赖 typedef 的类会被跳过)。与裸名 CArray 路径及
|
||||
# ClassGenDef 字段 pending 机制一致;真实类型错误由 Phase A 收敛检查暴露。
|
||||
return None
|
||||
if self._ptr_subscript_absorbs_class(sub.slice, inner_cptr_ty) != 0:
|
||||
return inner_cptr_ty
|
||||
return llvmlite.Ptr(pool, inner_cptr_ty)
|
||||
if string.strcmp(arr_at.attr, "CArray") != 0:
|
||||
self.Gen.TypeGenErr.fatal_type_error(node, "不支持的 Subscript 类型注解(仅支持 t.CArray / t.Callable)")
|
||||
|
||||
Reference in new issue
Block a user