fix(tpc): 裸赋值 RHS 是"联合注解返回"的调用时,从返回注解恢复被指类名

类别:a(编译器缺陷)—— 类型信息被静默丢成通用指针 i8*,后果在数行之后以看不出真因
的报文暴露。

现场(Test/FakeDuckTest/App/main.vp:111/115):
  `parts = s1.split(',')` → `p0: str = parts[0]`
  报"整数隐式转指针已禁止…出错表达式: 下标表达式 Subscript"。

证据链(三段各有代码/探针依据):
1. `_fakeduck.vp:273` 的 `def split(...) -> list[str]*`:只读探针确认 `list[str]*`
   与 `list[str] | t.CPtr` 的 AST **逐字同形**
   (`BinOp(BitOr, Subscript(Name(list)), Attribute(t.CPtr))`)⇒ 注解写法没问题。
2. 该联合形态在 LLVM 层映射成**通用指针 i8***(`list[T] | t.CPtr` 的既有约定,
   见 HandlesAnnAssign:372-383)。
3. `HandlesAssign` 原有的类名推断整块以 `Value.type.pointee` 是结构体为前提
   (:186 的 `isinstance(..., (LiteralStructType, IdentifiedStructType))`)⇒ i8* 直接
   整块跳过 ⇒ `var_struct_class['parts']` 从不登记 ⇒ `parts[0]` 不走
   `list[str].__getitem__`,退化成通用指针/字节索引 ⇒ 结果类型是整数 i8 ⇒ 赋给
   `str` 时触发 int→ptr 拦截。真因位置比报文位置早 4 行。

修复(编译器侧,不要求源码补注解):
- `HandlesExprCall._CallReturnClass(Node, Gen)`:从被调者的 **returns 注解**取回被指
  类名。被调者候选键覆盖三种既有形态(`Name` 函数名、`<接收者类>.<方法>`、
  FakeDuck 影子类 `_str`/`_bytes`、裸 `<方法>`),取第一个能解析出类名的。
- `HandlesExprCall._ClassFromAnnotation(Ann, Gen)`:剔除 `| t.CPtr` 侧取核类型,
  泛型 Subscript 走 `_resolve_generic_slice_type` 构特化名(`list[str]`),并要求该名
  能在 `Gen.structs` / `Gen.class_members` 命中(含 sha1 前缀全名的短名归一);
  取不到一律返回 None —— **不硬塞名字**。
- `HandlesAssign`:仅当"RHS 是调用 + 值类型是通用指针(pointee 非结构体)+
  var_struct_class 尚无该变量"这三个条件同时成立时,才据上恢复登记
  `var_struct_class` / `global_struct_class` / `_var_to_heap_ptr`。其余路径逐字不变。

验证:只读探针覆盖 7 种注解形态(`list[str]*` / `list[str] | t.CPtr` / `Foo*` /
`Foo | t.CPtr` / `ast.AST*` …),提取结果全部符合预期(未命中类名时返回 None);
GetDiagnostics 干净。未跑编译器。

遗留(同族,下一轮):上游"联合注解→i8*"这一步丢类名才是真根因;本次是在调用点
恢复。另外 `parts.__len__()` 在类名缺失时可能被 FakeDuck `_str.__len__` 错接,
修完建议跑一次该测试确认 `split len OK`。
This commit is contained in:
TermiNexus committed 2026-09-29 17:09:42 +08:00
1 parent 783aba1e49
commit b652634356
2 files changed
+121

No files matched your search

+19
View File
@@ -278,6 +278,25 @@ class AssignHandle(BaseHandle):
else: else:
if VarName in ('mctx', 's1ctx', 's2ctx', 's5ctx'): if VarName in ('mctx', 's1ctx', 's2ctx', 's5ctx'):
pass pass
# 【联合返回注解的被指类恢复】RHS 是调用、且值的 LLVM 类型是**通用指针**
# (i8*/i8**,pointee 不是结构体)时,上面那块类名推断整块失效 —— 现场
# `parts = s1.split(',')`:`split` 的返回注解 `list[str]*` ≡
# `list[str] | t.CPtr`(只读探针确认二者 AST 逐字同形)被映射成 i8*
# ⇒ 类名丢失 ⇒ 随后 `parts[0]` 不走 `list[str].__getitem__` 而按通用
# 指针/字节索引 ⇒ 得到整数 ⇒ 赋给 `str` 时报"整数隐式转指针已禁止"
# (真因在 4 行之前,报文完全指不到)。此处从被调者的**返回注解**
# 取回被指类名(判据与 HandlesAnnAssign 的同类恢复同源)。
if (not Gen.var_struct_class.get(VarName)
and isinstance(Node.value, ast.Call)
and isinstance(Value.type, ir.PointerType)
and not isinstance(Value.type.pointee,
(ir.IdentifiedStructType, ir.LiteralStructType))):
_rc: str | None = self.Trans.ExprCallHandle._CallReturnClass(Node.value, Gen)
if _rc:
Gen.var_struct_class[VarName] = _rc
if getattr(Gen, 'global_struct_class', None) is not None:
Gen.global_struct_class[VarName] = _rc
Gen._var_to_heap_ptr[VarName] = Value
if isinstance(Value.type, ir.PointerType) and isinstance(Value.type.pointee, (ir.LiteralStructType, ir.IdentifiedStructType)): if isinstance(Value.type, ir.PointerType) and isinstance(Value.type.pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
# BUG-8 修复:清除读取全局变量时缓存的 GlobalVariable(非显式 global 声明) # BUG-8 修复:清除读取全局变量时缓存的 GlobalVariable(非显式 global 声明)
if (VarName in Gen.variables and VarName not in Gen.global_vars if (VarName in Gen.variables and VarName not in Gen.global_vars
+102
View File
@@ -4266,6 +4266,108 @@ class ExprCallHandle(BaseHandle):
raw_name: str = self._get_raw_type_name(sn) raw_name: str = self._get_raw_type_name(sn)
return (type_str, raw_name) return (type_str, raw_name)
@staticmethod
def _IsCPtrMark(Node: ast.AST) -> bool:
"""`t.CPtr` / 裸名 `CPtr` 标记节点。"""
if isinstance(Node, ast.Attribute):
return Node.attr == 'CPtr'
if isinstance(Node, ast.Name):
return Node.id == 'CPtr'
return False
def _CallReturnClass(self, Node: ast.Call, Gen: LlvmGeneratorMixin) -> str | None:
"""调用结果的**被指类名**:从被调者的返回注解恢复。
背景:`list[str]*` ≡ `list[str] | t.CPtr`(只读探针已确认二者 AST 逐字同形,
都是 `BinOp(BitOr, Subscript(...), Attribute(t.CPtr))`)这类返回注解在 LLVM 层
被映射成通用指针 `i8*`,类名随之丢失 ⇒ 调用点只能按通用指针处理
(`parts = s1.split(',')` 之后 `parts[0]` 退化成按字节索引、得到整数,赋给
`str` 时报"整数隐式转指针已禁止",真因在 4 行之前)。
本方法把类名从**注解**取回;判据与 HandlesAnnAssign 的同类恢复
(:644-684 `rawptr[X]`、:1012-1034 联合注解构特化名)同源。
取不到返回 None —— 调用方据此不改动既有行为。
"""
_fn: ast.AST = Node.func
_cands: list[str] = []
if isinstance(_fn, ast.Name):
_cands.append(_fn.id)
elif isinstance(_fn, ast.Attribute):
_m: str = _fn.attr
_recv_cls: str | None = None
if isinstance(_fn.value, ast.Name):
_recv_cls = (Gen.var_struct_class.get(_fn.value.id)
or (getattr(Gen, 'global_struct_class', {}) or {}).get(_fn.value.id))
# FakeDuck 影子类(str→_str / bytes→_bytes)与接收者真实类名两条线都试;
# FuncDefCache 的方法键在仓库里既有 'cls.method' 也有裸 'method' 两种形态。
for _c in ([_recv_cls] if _recv_cls else []) + ['_str', '_bytes']:
_cands.append(f'{_c}.{_m}')
_cands.append(_m)
for _nm in _cands:
_fd: ast.FunctionDef | None = self.Trans.FunctionDefCache.get(_nm)
if _fd is not None and _fd.returns is not None:
_cls: str | None = self._ClassFromAnnotation(_fd.returns, Gen)
if _cls:
return _cls
return None
def _ClassFromAnnotation(self, Ann: ast.AST, Gen: LlvmGeneratorMixin) -> str | None:
"""从注解恢复"类名(含泛型特化名)":剔除 `| t.CPtr` 侧与指针层后取核类型。
- `list[str] | t.CPtr` / `list[str]*` → `list[str]`(泛型特化名,与
HandlesAnnAssign:1012-1034 的构名方式一致);
- `Foo | t.CPtr` / `Foo*` → `Foo`;
- 核类型名必须能在 `Gen.structs` / `Gen.class_members` 命中(含 sha1 前缀
全名的短名归一),否则返回 None(类型信息不完整时不硬塞名字)。
"""
_core: ast.AST = Ann
if isinstance(_core, ast.BinOp) and isinstance(_core.op, ast.BitOr):
_l: ast.AST = _core.left
_r: ast.AST = _core.right
if self._IsCPtrMark(_r):
_core = _l
elif self._IsCPtrMark(_l):
_core = _r
else:
return None
if isinstance(_core, ast.Subscript):
_base: ast.expr = _core.value
_bn: str | None = None
if isinstance(_base, ast.Name):
_bn = _base.id
elif isinstance(_base, ast.Attribute):
_bn = _base.attr
if not _bn:
return None
_sl: ast.AST = _core.slice
if hasattr(ast, 'Index') and isinstance(_sl, ast.Index):
_sl = _sl.value
_r2: tuple[str, str] | None = self._resolve_generic_slice_type(_sl)
if not _r2:
return None
_spec: str = f'{_bn}[{_r2[0]}]'
if _spec in Gen.structs or _spec in Gen.class_members:
return _spec
# 端键归一:特化键可能是 sha1 前缀全名
for _k in Gen.structs:
if _k.split('.')[-1] == _spec:
return _k
return _spec
if isinstance(_core, ast.Name):
if _core.id in Gen.structs or _core.id in Gen.class_members:
return _core.id
for _k in Gen.structs:
if _k.split('.')[-1] == _core.id:
return _k
return None
if isinstance(_core, ast.Attribute):
if _core.attr in Gen.structs or _core.attr in Gen.class_members:
return _core.attr
for _k in Gen.structs:
if _k.split('.')[-1] == _core.attr:
return _k
return None
return None
def _ResolveGenericSlice(self, Sub: ast.Subscript) -> str | None: def _ResolveGenericSlice(self, Sub: ast.Subscript) -> str | None:
"""从泛型 Subscript (如 Stack[int]) 解析特化名,确保特化类型已生成。""" """从泛型 Subscript (如 Stack[int]) 解析特化名,确保特化类型已生成。"""
if not isinstance(Sub.value, ast.Name): if not isinstance(Sub.value, ast.Name):