fix(TPV/CallGenBase): text.ll 返回类型的命名指针不再坍缩成通用 i8*,修 FakeDuck parts[0] 报整数隐式转指针

现象(TransPyV 编译 Test/FakeDuckTest):
  main.vp:115  p0: str = parts[0]
  CastError: 整数隐式转指针已禁止…出错表达式: 下标表达式 Subscript
    (parts = s1.split(','),s1: str,split 由 includes/_fakeduck.vp 的 _str 提供)

根因链(逐环取证,均落在产物文件上):
1. _fakeduck.vp:273  `def split(self, delimiter: str) -> list[str]*`。
2. 定义侧产物正确:temp/88/884eaabef9bd161c.text.ll:1775
     define %"b0798adca340002b.list[str]"* @"884eaabef9bd161c._str.split"(...)
   (884eaabef9bd161c = includes/_fakeduck.vp,b0798adca340002b = includes/_list.vp)
3. 调用侧 declare 走了 vpi 注解:temp/88/884eaabef9bd161c.vpi:27
     def split(self, delimiter: str) -> list[str] | t.CPtr: pass
   该联合在 stub 侧解析不出 `list[str]` 这个泛型特化名 ⇒ 坍缩成 i8*(既有设计已知的
   「跨模块 declare 的 list[T] 返回类型被简化」),故 _translate_fakeduck_call 里那条
   「从定义模块 text.ll 读精确返回类型」的兜底(CallGenFd.vp:134-142)必须生效。
4. 但该兜底读到的 `%"b0798adca340002b.list[str]"*` 又被本函数坍缩回 i8*:
   CallGenBase._ParseLLVMRetTyStr 的「含 * 者一律返回通用 Ptr(Int8)」分支
   (原注释:"包括 i8*, i32*, %"name"*, { i32, i8 }* 等")。
5. 于是 parts 的 alloca 退化为 i8* ⇒ parts[0] 走字节下标取到整数 ⇒ 赋给 str 报
   「整数隐式转指针已禁止」(报文指不到真因,正是本问题最难查的地方)。

改动(CallGenBase._ParseLLVMRetTyStr):
- 新增「命名类型 `%"name"*` / `%name*`」分支:剥引号/空格与尾随 `*`(支持多级),
  先 GenApi.get_struct_type(name)、未命中再 GenApi.get_enum_base_type(name),命中即
  按星号层数包 Ptr 返回真实结构体/枚举指针;查不到登记项时才保持原来的通用 Ptr(Int8)
  行为(名字可能来自尚未登记的模块,不引入新的静默降级面)。
- 同步更新该函数顶部说明(原写「含 * 者返回通用 Ptr(Int8)」,现区分字面量结构体 /
  命名类型 / 其余)。
- 收益面不限 FakeDuck:所有经 text.ll 取返回类型的方法调用(CallGenCall / CallGenDispatch
  / CallGenProp / CallGenFd 等)都从「指针一律 i8*」变为「命名类指针精确」,
  调用方 alloca 与下标/字段语义不再退化。

待验证(不自行执行编译命令):用 TransPyV.exe 重跑
  --project ./Test/FakeDuckTest/project.json --clean --clear-cache --rebuild-includes --traceback
预期 115 行不再报错;若仍在 parts2.get(0)(131 行)等处冒新错,请把栈贴回来。
This commit is contained in:
TermiNexus committed 2026-09-30 23:53:18 +08:00
1 parent f2f3e5b7bf
commit 3329c9beae
1 file changed
+43 -1
+43 -1
View File
@@ -927,8 +927,50 @@ novirtual class CallGenBase:
return llvmlite.Ptr(pool, st_ty_st)
return st_ty_st
# 命名类型(%"name" / %name)带指针:先解析到真实结构体/枚举类型再包 Ptr。
# 为什么不许坍缩成通用 i8*:返回类型是「指向类对象」时,坍缩后调用方变量
# 的 alloca 变成 i8*,下标语义随之从「类的 __getitem__」掉到「字节下标」。
# 现场(Test/FakeDuckTest/App/main.vp:115 `p0: str = parts[0]`):
# includes/_fakeduck.vp `def split(self, delimiter: str) -> list[str]*`,
# 定义侧 text.ll 为
# define %"<list_sha1>.list[str]"* @"<sha1>._str.split"(...)
# 本函数若把 `%"<list_sha1>.list[str]"*` 当通用指针,parts 的 alloca 即
# 退化为 i8* ⇒ parts[0] 取到整数 ⇒ 赋给 str 报
# 「整数隐式转指针已禁止」(报文指不到真因)。
# 解析不到登记项时保持原「通用指针」行为(名字可能来自尚未登记的模块)。
if s < e and buf[s] == '%':
nm_s: sizet = s + 1
nm_e: sizet = e
nm_star: int = 0
while nm_e > nm_s and buf[nm_e - 1] == '*':
nm_star += 1
nm_e -= 1
while nm_e > nm_s and (buf[nm_e - 1] == ' ' or buf[nm_e - 1] == '\t'):
nm_e -= 1
if nm_star > 0 and nm_e > nm_s:
if buf[nm_s] == '"' and buf[nm_e - 1] == '"':
nm_s += 1
nm_e -= 1
if nm_e > nm_s:
nm_len: sizet = nm_e - nm_s
nm_buf: bytes = stdlib.malloc(nm_len + 1)
if nm_buf is not None:
string.strncpy(nm_buf, buf + nm_s, nm_len)
nm_buf[nm_len] = '\0'
nm_ty: llvmlite.LLVMType* = GenApi.get_struct_type(nm_buf)
if nm_ty is None:
nm_ty = GenApi.get_enum_base_type(nm_buf)
stdlib.free(nm_buf)
if nm_ty is not None:
nm_wrap: llvmlite.LLVMType* = nm_ty
nm_i: int = 0
while nm_i < nm_star:
nm_wrap = llvmlite.Ptr(pool, nm_wrap)
nm_i += 1
return nm_wrap
# 检查是否含 * (指针类型) → 返回 i8* (通用指针)
# 包括 i8*, i32*, %"name"*, { i32, i8 }* 等
# 包括 i8*, i32*, { i32, i8 }*,以及上面未登记命名的 %name*
ci: sizet = s
while ci < e:
if buf[ci] == '*':