feat(gen,err): CastError 定位到出错表达式;vpi 生成 fail-fast;函数属性接受直接形态

- ExprGenBase:新增 _cast_fail_where,指针/整数隐式转换失败时打印出错表达式与列号(多实参调用可定位到具体实参,如 Name 'toggle_ptr')

- Converter:import / from-import 别名列表为空、Alias 指针为空、name 为空分别 fail-fast,不再写出 import ,  这类残缺 vpi 行(该残缺会让包重导出注册失效,报出与根因无关的 NameError)

- FuncGenBase.extract_func_attrs:接受直接 @t.attr.<name> 装饰器形态(与类侧 _has_decorator 同口径),使 inline def 在无 import c 的文件里也生效

- 附:CallGenDispatch 内保留一处临时 [MDISP] 探针(MpoolOopTest pool.alloc 定位用)
This commit is contained in:
TermiNexus committed 2026-09-27 11:41:11 +08:00
1 parent f084da7c01
commit 7d28c9649e
4 files changed
+178 -12

No files matched your search

+52 -10
View File
@@ -890,6 +890,12 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*,
if k == ast.ASTKind.Import:
imp2: ast.Import* = ast.Import*(stmt)
pos = _PyiEmit(buf, buf_size, pos, "import ")
# fail-fast:别名列表为空 ⇒ 写出 `import ` 这种残缺行,import 名字
# 全丢,包重导出注册随之失效(与下面逐个别名为空的检查互补)。
if imp2.names is None or len(imp2.names) == 0:
GenApi.fatal_error(stmt,
"vpi 生成:import 语句别名列表为空(应为 ≥1 个别名)——AST 构造/解析异常",
"NameError")
if imp2.names is not None:
imp2_names: list[ast.AST*]* = imp2.names
an2: sizet = len(imp2_names)
@@ -898,11 +904,29 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*,
if ai2 > 0:
pos = _PyiEmit(buf, buf_size, pos, ", ")
al2: ast.Alias* = ast.Alias*(imp2_names.get(ai2))
if al2 is not None and al2.name is not None:
pos = _PyiEmit(buf, buf_size, pos, al2.name)
if al2.asname is not None and string.strlen(al2.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al2.asname)
# fail-fast:别名节点或其名字为空 ⇒ 绝不静默写出 "import , "。
# vpi 里 import 行的名字是**包重导出注册的唯一输入**
# (VpiReader._inject_reexport_class_declare 靠它把子模块里的类以
# 包 __init__ 的 sha1 注册别名 entry),名字静默丢失会让 `包.类`
# 形式的模块限定注解解析落空,下游报出与根因无关的 NameError
# (实测:ast/__init__.vpi 的 `from .base import , , ,` 名字全空
# ⇒ ast.AST 报"方法 'Result.__init__' 形参 'elem' 类型注解解析失败")。
# 两种成因分开报,便于一次定位:节点指针为空 = Alias 构造失败;
# 节点在而 name 为空 = _copy_str/__init__ 未生效。
if al2 is None:
GenApi.fatal_error(stmt,
"vpi 生成:import 别名**节点指针为空**(Alias(...) 构造未产出对象:"
"pool.alloc 失败或 __init__ 未执行)——import 名字丢失会破坏包重导出注册",
"NameError")
if al2.name is None or al2.name[0] == '\0':
GenApi.fatal_error(stmt,
"vpi 生成:import 别名节点存在但 **name 为空**(_copy_str 返回 None / "
"__init__ 未赋值 name)——import 名字丢失会破坏包重导出注册",
"NameError")
pos = _PyiEmit(buf, buf_size, pos, al2.name)
if al2.asname is not None and string.strlen(al2.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al2.asname)
ai2 += 1
pos = _PyiEmit(buf, buf_size, pos, "\n")
@@ -917,6 +941,14 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*,
if ifr.module is not None:
pos = _PyiEmit(buf, buf_size, pos, ifr.module)
pos = _PyiEmit(buf, buf_size, pos, " import ")
# fail-fast:同 Import 分支——`from X import *` 走的是 Alias("*"),
# 列表为空或名字为空都说明 AST 坏;静默写出 `from X import ` 会让
# 包重导出注册失效(star 也用于 re-export 判断)。
if ifr.names is None or len(ifr.names) == 0:
GenApi.fatal_error(stmt,
"vpi 生成:from-import 语句别名列表为空(应为 ≥1 个别名,含 '*')"
"——AST 构造/解析异常",
"NameError")
if ifr.names is not None:
ifr_names: list[ast.AST*]* = ifr.names
an3: sizet = len(ifr_names)
@@ -925,11 +957,21 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*,
if ai3 > 0:
pos = _PyiEmit(buf, buf_size, pos, ", ")
al3: ast.Alias* = ast.Alias*(ifr_names.get(ai3))
if al3 is not None and al3.name is not None:
pos = _PyiEmit(buf, buf_size, pos, al3.name)
if al3.asname is not None and string.strlen(al3.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al3.asname)
# fail-fast:同 Import 分支(两种成因分开报,一次定位)。
if al3 is None:
GenApi.fatal_error(stmt,
"vpi 生成:from-import 别名**节点指针为空**(Alias(...) 构造未产出对象:"
"pool.alloc 失败或 __init__ 未执行)——包重导出注册会因此失效",
"NameError")
if al3.name is None or al3.name[0] == '\0':
GenApi.fatal_error(stmt,
"vpi 生成:from-import 别名节点存在但 **name 为空**(_copy_str 返回 None / "
"__init__ 未赋值 name)——包重导出注册会因此失效",
"NameError")
pos = _PyiEmit(buf, buf_size, pos, al3.name)
if al3.asname is not None and string.strlen(al3.asname) > 0:
pos = _PyiEmit(buf, buf_size, pos, " as ")
pos = _PyiEmit(buf, buf_size, pos, al3.asname)
ai3 += 1
pos = _PyiEmit(buf, buf_size, pos, "\n")
+75
View File
@@ -8,6 +8,7 @@ import ast
import llvmlite
import memhub
import string
import stdio
import stdlib
import sys
import viperlib
@@ -1241,6 +1242,80 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
if method_at.value.kind() == ast.ASTKind.Name and trans is not None:
method_obj_nm: ast.Name* = ast.Name*(method_at.value)
if method_obj_nm.id is not None:
# [DIAG] 并排取证「已知成功」与「刚落空」的形的方法调用,看 H6 各级
# 判据的命中差异(临时探针,定位后连同 import stdio 一起删除):
# A) recv=__with_ctx m=__enter__ :with 绑定 asname 之前执行,已知成功
# B) recv=pool m=alloc :与 A 同一注释类名(memhub.MemPool),
# 但接收者 LLVM 类型来自 __enter__ 的**返回值**(A 是构造结果)
# 只有这一对能解释 H6 为何对 B 全线落空 —— 落空且不置 hit_box 时,
# translate_call 会把接收者名当模块名,报「跨模块调用 'pool.alloc'
# 找不到模块 SHA1」(MpoolOopTest/App/mpooltest.vp:14)。
dg_hit: int = 0
if string.strcmp(func_name, "alloc") == 0 \
and string.strcmp(method_obj_nm.id, "pool") == 0:
dg_hit = 1
if string.strcmp(func_name, "__enter__") == 0 \
and string.strcmp(method_obj_nm.id, "__with_ctx") == 0:
dg_hit = 1
if dg_hit != 0:
dg_obj: llvmlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, method_obj_nm.id)
dg_found: int = 0
dg_ty: str = "-"
dg_c1: str = "-"
dg_c2: str = "-"
dg_an: str = "-"
dg_struct_ok: int = -1
dg_sha1_ok: int = -1
dg_spc_ok: int = -1
if dg_obj is not None:
dg_found = 1
if dg_obj.Ty is not None:
dg_ty = pool.alloc(256)
if dg_ty is not None:
llvmlite.TypePrint(dg_ty, 256, dg_obj.Ty, pool)
dg_s1: llvmlite.LLVMType* = None
if self.Gen.ExprGenBase.is_ptr_type(dg_obj.Ty) != 0:
dg_s1 = dg_obj.Ty.Pointee
if dg_s1 is not None:
dg_x1: str = self.Gen.StructGen.get_class_name_by_type(pool, dg_s1)
if dg_x1 is not None:
dg_c1 = dg_x1
if self.Gen.ExprGenBase.is_ptr_type(dg_s1) != 0:
dg_x2: str = self.Gen.StructGen.get_class_name_by_type(pool, dg_s1.Pointee)
if dg_x2 is not None:
dg_c2 = dg_x2
dg_ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, method_obj_nm.id)
dg_anr: str = self.Gen.VarGen.var_annot_class_name(dg_ve)
if dg_anr is not None:
dg_an = dg_anr
dg_bare: str = dg_anr
dg_dot: str = string.strrchr(dg_anr, '.')
if dg_dot is not None:
dg_bare = dg_dot + 1
if self.Gen.StructGen.get_struct_type(dg_bare) is not None:
dg_struct_ok = 1
else:
dg_struct_ok = 0
if dg_dot is not None:
dg_ml: sizet = string.strlen(dg_anr) - string.strlen(dg_bare) - 1
dg_mb: str = pool.alloc(dg_ml + 1)
if dg_mb is not None:
string.strncpy(dg_mb, dg_anr, dg_ml)
dg_mb[dg_ml] = '\0'
if self._lookup_module_sha1(dg_mb) is not None:
dg_sha1_ok = 1
else:
dg_sha1_ok = 0
if self._special_method_ret_ty(pool, dg_bare, func_name) is not None:
dg_spc_ok = 1
else:
dg_spc_ok = 0
dg_nl: int = GenApi.is_nonlocal_name(trans, method_obj_nm.id)
stdio.printf("[MDISP] mod=%s line=%d recv=%s m=%s found=%d nonlocal=%d recv_ty=%s cls1=%s cls2=%s annot=%s struct_ok=%d sha1_ok=%d spc_ok=%d\n",
trans.ModuleSha1, cl.lineno, method_obj_nm.id, func_name,
dg_found, dg_nl, dg_ty, dg_c1, dg_c2, dg_an,
dg_struct_ok, dg_sha1_ok, dg_spc_ok)
stdio.fflush(None)
# nonlocal 对象:提升函数内从 env 读对象指针(load_nonlocal_var),
# 不能 lookup_var 跨函数命中父函数 alloca(SSA 编号错乱 →
# llc "use of undefined value")。entry.get() 等方法调用的
+40 -2
View File
@@ -317,6 +317,42 @@ novirtual class ExprGenBase:
if loaded is None: return obj_ptr
return loaded
# ============================================================
# _cast_fail_where - 隐式转换失败时报出**出错的表达式**(→ 定位到具体实参)
#
# 调用点只传 AST 节点,报错原本只有"调用所在行",多实参时看不出是哪一个
# (如 `_td_set_token(td, TD_TOKEN_IN, dev_addr, endpoint, toggle_ptr, max_pkt)`
# 报指针→整数,无法判断是哪个实参)。此处按节点形态给出可读名:
# Name→'id'、Attribute→'.attr'、Call/Subscript→形态名,并附列号(源码行已
# 由 traceback 打出,列号即可指到具体实参)。
# ============================================================
def _cast_fail_where(self, node: ast.AST*) -> str:
"""取隐式转换失败的定位文本(出错表达式 + 列号)"""
if node is None:
return "节点未知(无 AST 信息)"
k: int = node.kind()
pos: int = -1
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
pos = nm.col_offset
return f"Name '{nm.id}'(列 {pos})"
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None:
pos = at.col_offset
return f"属性 '.{at.attr}'(列 {pos})"
elif k == ast.ASTKind.Call:
pos = (ast.Call*(node)).col_offset
return f"调用结果 Call(列 {pos})"
elif k == ast.ASTKind.Subscript:
pos = (ast.Subscript*(node)).col_offset
return f"下标表达式 Subscript(列 {pos})"
elif k == ast.ASTKind.Constant:
pos = (ast.Constant*(node)).col_offset
return f"常量 Constant(列 {pos})"
return f"表达式 kind={k}(列 {pos})"
# ============================================================
# 类型强制转换:整数 sext/trunc,浮点 fpext/fptrunc,int↔float si2fp/fp2si
# ============================================================
def coerce_to_type(self, builder: llvmlite.IRBuilder*,
@@ -361,14 +397,16 @@ novirtual class ExprGenBase:
# (inttoptr 要求同宽,对齐 MatchGenMixin 模式),再 inttoptr 成空指针。
if val.IsConst == 1 and val.IntVal == 0:
return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty)
self.Gen.TypeGenErr.fatal_error(node, "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型", "CastError")
m_i2p: str = "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型"
self.Gen.TypeGenErr.fatal_error(node, f"{m_i2p};出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
# Pointer -> integer: rejected (symmetric with the integer -> pointer rule above).
# No implicit pointer <-> integer conversion is allowed; an explicit cast is
# required. (The old code silently did ptrtoint, and for 8-bit targets fell
# through to the build_load branch below, copying the pointee's first byte.)
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0:
self.Gen.TypeGenErr.fatal_error(node, "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型", "CastError")
m_p2i: str = "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型"
self.Gen.TypeGenErr.fatal_error(node, f"{m_p2i};出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits:
+11
View File
@@ -43,6 +43,17 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
dk: int = deco.kind()
# 直接形式 `@t.attr.<name>`(与类侧 ClassGenBase._has_decorator 同口径,
# 类定义早就接受两种写法):`inline def f(...)` 合成的就是这一形态,
# 且**不要求** import c。此前只认 c.Attribute(...) ⇒ 没有 import c 的文件
# 会静默不内联。不支持的属性名由 _append_llvm_attr 返回 0 自然忽略
# (如 @t.CVTable / @t.NoVTable / packed / aligned 对函数无意义)。
if dk == ast.ASTKind.Attribute:
dan: str = self._get_attr_name_from_node(deco)
if dan is not None and string.strcmp(dan, "section") != 0:
if self._append_llvm_attr(attrs_buf, dan) != 0:
found_any = 1
continue
if dk != ast.ASTKind.Call: continue
call_node: ast.Call* = ast.Call*(deco)
if call_node.func is None: continue