fix(gen): 装饰器标记支持 + len 分发 + 跨模块调用修复

- _is_enum_class/_is_union_class/_is_renum_class 支持 @t.CEnum/@t.CUnion/
  @t.REnum 装饰器形式(此前只认继承基类,装饰器形式 Color 未注册枚举,
  Color.GREEN 落到跨模块 CDefine FATAL)
- forward_declare_functions/translate_function_def 支持 @t.CExport/@t.CExtern/
  @t.State 函数存储装饰器(对齐 TPC _extract_decorator_storage_flags);
  用 HandlesClassDef._has_decorator 委托(Mixin @t.NoVTable 静态绑定,
  self. 跨类调生成不存在的 FuncGenMixin._has_decorator → 链接 undefined)
- len() 内建对结构体对象分发 __len__(此前恒 strlen 读对象首字段垃圾长度)
- 静态调用检测 elif:module.ClassName.static_method 形态;零参 @staticmethod
  去掉 head is None 守卫;_translate_property_call 按形参类型 coerce 实参
- translate_compare 指针判空(ptr != 0/== 0)跳过 __eq__/__ne__ 重载走
  icmp null;同模块类无此 dunder 干净返回 None 防幽灵 call;/ → __truediv__
This commit is contained in:
TermiNexus committed 2026-08-11 18:42:04 +08:00
1 parent 48d9fbab4c
commit 83d46dd646
4 files changed
+176 -18

No files matched your search

+86 -4
View File
@@ -3206,6 +3206,34 @@ class CallGenMixin:
else:
call_ret_ty = self._fail_method_ret_ty(method_name)
# 参数类型对齐:按目标函数声明的参数类型转换实参(与 _translate_method_call
# 一致)。例如 from_long(10) 的 i32 字面量 → i64 形参(v: t.CLong),
# 不转换则 call(i32) 与 declare(i64) 不匹配,llc 报参数类型错误。
# 目标函数优先用已找到的 found_func,否则按 mangled 调用名查模块 declare。
tgt_func_pc: llvmlite.Function | t.CPtr = found_func
if tgt_func_pc is None:
tgt_func_pc = self.find_func_in_module(mod, call_name)
if tgt_func_pc is not None:
prev_pc: llvmlite.Value | t.CPtr = None
cur_arg_pc: llvmlite.Value | t.CPtr = call_head
cur_param_pc: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(tgt_func_pc)
while cur_arg_pc is not None and cur_param_pc is not None:
param_ty_pc: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(cur_param_pc)
if param_ty_pc is not None and cur_arg_pc.Ty is not None:
coerced_pc: llvmlite.Value | t.CPtr = HandlesExpr.coerce_to_type(
builder, cur_arg_pc, param_ty_pc)
if coerced_pc is not None and coerced_pc != cur_arg_pc:
# 保持链表完整性:新 Value 继承原节点的 Next,前驱指向新节点
llvmlite.value_set_next(coerced_pc, cur_arg_pc.Next)
if prev_pc is None:
call_head = coerced_pc
else:
llvmlite.value_set_next(prev_pc, coerced_pc)
cur_arg_pc = coerced_pc
prev_pc = cur_arg_pc
cur_arg_pc = cur_arg_pc.Next
cur_param_pc = llvmlite.param_get_next(cur_param_pc)
# 确保 declare 存在,然后调用
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count)
return llvmlite.build_call(builder, call_name, call_head, call_count, call_ret_ty, 0)
@@ -3413,9 +3441,11 @@ class CallGenMixin:
tail = arg_val
total_count += 1
if head is None:
return None
# 零参 @staticmethod(如 PyListObj.new()):head 保持 None 是合法的
# 无参调用,照常走 _translate_property_call(SHA1 前缀名 + 精确返回类型)。
# 若此处 return None,调用方回退通用路径把方法名当裸函数解析,生成
# call i32 @new() → 链接 undefined reference(CPythonTest 根因)。
# @classmethod 恒有 cls_ptr(head 非 None),不受影响。
return self._translate_property_call(pool, builder, mod, class_name,
method_name, head, total_count, trans)
@@ -5137,7 +5167,7 @@ class CallGenMixin:
if count_ptr is not None:
return llvmlite.build_load(builder, i64_ty_len, count_ptr)
# 检测 len() 内置函数调用 — 转换为 strlen 调用
# 检测 len() 内置函数调用 — 结构体对象走 __len__ 方法,字符串走 strlen
# len(s) 对 str 类型调用 strlen,返回 i64
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
if func_name is not None and string.strcmp(func_name, "len") == 0:
@@ -5147,6 +5177,42 @@ class CallGenMixin:
len_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value(
builder, pool, mod, len_arg_node, None, 0, trans)
if len_val is not None:
# len(对象):结构体指针且类定义了 __len__ → 调用
# Class.__len__(obj)(返回类型取方法定义,如 i64)。
# 此前恒走 strlen:对结构体对象生成 strlen(struct*)
# → 把对象首字段当字符串读 → 垃圾长度(CPythonTest
# PyUnicodeObj len('Hello') / PyListObj len == 3 根因)。
len_struct_ty: llvmlite.LLVMType | t.CPtr = \
HandlesStruct.get_struct_type_from_value(len_val)
if len_struct_ty is not None:
len_cls_nm: str = HandlesStruct.get_class_name_by_type(
pool, len_struct_ty)
if len_cls_nm is not None:
len_lookup: t.CChar | t.CPtr = pool.alloc(128)
if len_lookup is not None:
viperlib.snprintf(len_lookup, 128, "%s.__len__", len_cls_nm)
len_fn2: llvmlite.Function | t.CPtr = self.find_func_in_module(
mod, len_lookup)
if len_fn2 is not None:
len_ret_ty2: llvmlite.LLVMType | t.CPtr = \
llvmlite.function_get_ret_ty(len_fn2)
len_fn_name2: t.CChar | t.CPtr = \
llvmlite.function_get_name(len_fn2)
if len_ret_ty2 is not None and len_fn_name2 is not None:
# self 参数类型对齐(按方法首个形参 bitcast)
len_self_arg2: llvmlite.Value | t.CPtr = len_val
len_first_param2: llvmlite.Param | t.CPtr = \
llvmlite.function_get_param_head(len_fn2)
if len_first_param2 is not None:
len_self_ty2: llvmlite.LLVMType | t.CPtr = \
llvmlite.param_get_ty(len_first_param2)
if len_self_ty2 is not None and len_val.Ty is not None:
len_self_arg2 = llvmlite.build_bitcast(
builder, len_val, len_self_ty2)
len_self_arg2.Next = None
return llvmlite.build_call(
builder, len_fn_name2, len_self_arg2,
1, len_ret_ty2, 0)
# 确保 strlen 在模块中有声明
strlen_fn: llvmlite.Function | t.CPtr = self.find_func_in_module(mod, "strlen")
if strlen_fn is None:
@@ -5194,6 +5260,22 @@ class CallGenMixin:
pool, builder, mod, st_nm.id, func_name, cl, trans)
if st_res is not None:
return st_res
elif st_at.value.kind() == ast.ASTKind.Attribute:
# 模块限定静态调用: module.ClassName.static_method(args)
# cl.func = Attribute(value=Attribute(value=Name(module),
# attr=ClassName), attr=method_name)
# 此前未检测 → 走通用路径把 method_name 当裸函数解析,
# 生成 call i32 @from_long(无类名/SHA1 前缀)→ 链接
# undefined reference(CPythonTest PyLongObj.from_long 根因)
st_at2: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(st_at.value)
if st_at2 is not None and st_at2.attr is not None and st_at2.value is not None:
if st_at2.value.kind() == ast.ASTKind.Name:
# ClassName 是已注册结构体类名时尝试静态/类方法
if HandlesStruct.get_struct_type(st_at2.attr) is not None:
st_res2: llvmlite.Value | t.CPtr = self._translate_static_class_call(
pool, builder, mod, st_at2.attr, func_name, cl, trans)
if st_res2 is not None:
return st_res2
# 检测方法调用: obj.method(args)
# 当 cl.func 是 Attribute 且 obj 是已注册的结构体变量时,翻译为方法调用
+24 -6
View File
@@ -130,8 +130,14 @@ class ClassGenMixin:
return 0
def _is_enum_class(self, cd: ast.ClassDef | t.CPtr) -> int:
"""检查 ClassDef 是否继承自 t.CEnum,返回 1=是 / 0=否"""
if cd is None or cd.bases is None:
"""检查 ClassDef 是否为 CEnum:继承自 t.CEnum 或 @t.CEnum 装饰器形式"""
if cd is None:
return 0
# 装饰器形式: @t.CEnum class Color:(无基类,等价 class Color(t.CEnum):)
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "CEnum") == 1:
return 1
if cd.bases is None:
return 0
bases: list[ast.AST | t.CPtr] | t.CPtr = cd.bases
bn: t.CSizeT = bases.__len__()
@@ -165,8 +171,14 @@ class ClassGenMixin:
return 0
def _is_union_class(self, cd: ast.ClassDef | t.CPtr) -> int:
"""检查 ClassDef 是否继承自 t.CUnion,返回 1=是 / 0=否"""
if cd is None or cd.bases is None:
"""检查 ClassDef 是否为 CUnion:继承自 t.CUnion 或 @t.CUnion 装饰器形式"""
if cd is None:
return 0
# 装饰器形式: @t.CUnion class Data:
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "CUnion") == 1:
return 1
if cd.bases is None:
return 0
bases: list[ast.AST | t.CPtr] | t.CPtr = cd.bases
bn: t.CSizeT = bases.__len__()
@@ -200,8 +212,14 @@ class ClassGenMixin:
return 0
def _is_renum_class(self, cd: ast.ClassDef | t.CPtr) -> int:
"""检查 ClassDef 是否继承自 t.REnum,返回 1=是 / 0=否"""
if cd is None or cd.bases is None:
"""检查 ClassDef 是否为 REnum:继承自 t.REnum 或 @t.REnum 装饰器形式"""
if cd is None:
return 0
# 装饰器形式: @t.REnum class X:
if cd.decorator_list is not None:
if self._has_decorator(cd.decorator_list, "REnum") == 1:
return 1
if cd.bases is None:
return 0
bases: list[ast.AST | t.CPtr] | t.CPtr = cd.bases
bn: t.CSizeT = bases.__len__()
+39 -8
View File
@@ -1200,6 +1200,19 @@ class ExprGenMixin:
if lhs_cn is not None and lhs_cn.const_kind == ast.CONST_NONE:
cmp_with_none = 1
# === 指针判空模式: ptr != 0 / ptr == 0 ===
# 跳过运算符重载走身份比较(icmp null)。跨模块类(如 cpython.PyObject)
# 未定义 __eq__/__ne__ 时,try_operator_overload 无法判定方法不存在
# (类在依赖模块,cls_sha1 != trans.ModuleSha1),会误发幽灵
# `call void @...__ne__` → 链接 undefined reference(CPythonTest main.vp
# `py_num != 0` 根因)。C 语义:指针与字面量 0 比较 = 判空。
cmp_zero: int = 0
if op == ast.OpKind.Eq or op == ast.OpKind.Ne:
if rhs_node is not None and self._is_zero_int_literal(rhs_node) != 0:
cmp_zero = 1
if cmp_zero == 0 and lhs_node is not None and self._is_zero_int_literal(lhs_node) != 0:
cmp_zero = 1
# === 比较运算符重载路径 1: lhs 是 Name 且对应结构体变量 ===
# 对于值类型变量(如 cnt: Counter),用 alloca 指针尝试重载
# None 比较跳过重载(走身份比较)
@@ -1209,10 +1222,11 @@ class ExprGenMixin:
lhs_alloca: llvmlite.Value | t.CPtr = HandlesVar.lookup_var(
trans.SymTab, nm.id)
if lhs_alloca is not None:
ovl_result: llvmlite.Value | t.CPtr = self.try_operator_overload(
pool, builder, mod, lhs_alloca, rhs, op, trans, 1)
if ovl_result is not None:
return ovl_result
if cmp_zero == 0 or self.get_ptr_level(lhs_alloca.Ty) == 0:
ovl_result: llvmlite.Value | t.CPtr = self.try_operator_overload(
pool, builder, mod, lhs_alloca, rhs, op, trans, 1)
if ovl_result is not None:
return ovl_result
# 正常翻译 lhs
lhs: llvmlite.Value | t.CPtr = self.translate_value(
@@ -1222,8 +1236,8 @@ class ExprGenMixin:
# === 比较运算符重载路径 2: lhs 是 Ptr(Struct) ===
# Is/IsNot 不在 _cmpop_to_dunder 映射中,会自动返回 None 回退原生比较
# None 比较跳过重载(走身份比较)
if cmp_with_none == 0:
# None 比较跳过重载(走身份比较);指针判空(cmp_zero + 指针)同样跳过
if cmp_with_none == 0 and (cmp_zero == 0 or self.get_ptr_level(lhs.Ty) == 0):
ovl_result2: llvmlite.Value | t.CPtr = self.try_operator_overload(
pool, builder, mod, lhs, rhs, op, trans, 1)
if ovl_result2 is not None:
@@ -4013,11 +4027,14 @@ class ExprGenMixin:
# BinOp 运算符 → dunder 方法名
# ============================================================
def _binop_to_dunder(self, op: int) -> str:
"""将 BinOp 运算符映射到 dunder 方法名,无映射时返回 None"""
"""将 BinOp 运算符映射到 dunder 方法名,无映射时返回 None
Python 3 语义:/ 映射 __truediv__(Python 2 的 __div__ 已废弃)。
"""
if op == ast.OpKind.Add: return "__add__"
if op == ast.OpKind.Sub: return "__sub__"
if op == ast.OpKind.Mult: return "__mul__"
if op == ast.OpKind.Div: return "__div__"
if op == ast.OpKind.Div: return "__truediv__"
if op == ast.OpKind.Mod: return "__mod__"
if op == ast.OpKind.BitAnd: return "__and__"
if op == ast.OpKind.BitOr: return "__or__"
@@ -4138,6 +4155,14 @@ class ExprGenMixin:
if found_func is None and cls_sha1 is None:
return None
# 方法在"定义它的模块"中确实不存在(同模块类但模块内无此 dunder):
# 返回 None 回退原生运算,绝不发出幽灵 call——否则 IR 残留
# `call void @...__div__`(副作用)且调用方回退原生 sdiv 双重发射,
# llc 报 invalid operand type for instruction(CPythonTest PyFloatObj 根因)
if found_func is None and trans is not None and trans.ModuleSha1 is not None:
if cls_sha1 is not None and string.strcmp(cls_sha1, trans.ModuleSha1) == 0:
return None
# 构建 extra_args 数组(仅含 rhs 一个参数)
extra_args: t.CSizeT | t.CPtr = pool.alloc(8)
if extra_args is None:
@@ -4229,6 +4254,12 @@ class ExprGenMixin:
if found_func is None and cls_sha1 is None:
return None
# 同模块类但模块内无此 dunder → 返回 None 回退原生运算,不发幽灵 call
# (与 try_operator_overload 一致,防 IR 残留 call void @...__neg__)
if found_func is None and trans is not None and trans.ModuleSha1 is not None:
if cls_sha1 is not None and string.strcmp(cls_sha1, trans.ModuleSha1) == 0:
return None
# 一元方法无额外参数(extra_args 为空)
return HandlesExprCall._call_method_on_ptr(
pool, builder, mod, search_class, dunder,
+27
View File
@@ -16,6 +16,7 @@ import lib.core.Handles.HandlesNonlocal as HandlesNonlocal
import lib.core.Handles.HandlesType as HandlesType
import lib.core.Handles.HandlesBody as HandlesBody
import lib.core.Handles.HandlesImports as HandlesImports
import lib.core.Handles.HandlesClassDef as HandlesClassDef
# ============================================================
@@ -362,6 +363,19 @@ class FuncGenMixin:
has_extern = HandlesType.has_decorator_marker(fd.returns, "CExtern")
has_state = HandlesType.has_decorator_marker(fd.returns, "State")
has_export = HandlesType.has_decorator_marker(fd.returns, "CExport")
# 装饰器形式存储类标记(@t.CExport/@t.CExtern/@t.State/@t.CStatic)
# 对齐 TPC HandlesFunctions._extract_decorator_storage_flags;
# CExport/CExtern/State 影响函数名(导出裸名),CStatic 仅标记。
# 注意: 用 HandlesClassDef._has_decorator 委托而非 self._has_decorator
# (Mixin 是 @t.NoVTable 静态绑定,跨 Mixin 方法经 MRO 调用会生成
# 不存在的 FuncGenMixin._has_decorator → 链接 undefined symbol)
if fd.decorator_list is not None:
if has_extern == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "CExtern") == 1:
has_extern = 1
if has_state == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "State") == 1:
has_state = 1
if has_export == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "CExport") == 1:
has_export = 1
is_extern_decl: int = 0
if has_extern != 0 or has_state != 0:
is_extern_decl = 1
@@ -509,6 +523,19 @@ class FuncGenMixin:
# t.CExtern/t.State 忽略 body 体,仅声明(无论 body 是否为 pass)
if has_extern != 0 or has_state != 0:
is_extern_decl = 1
# 装饰器形式存储类标记(@t.CExport/@t.CExtern/@t.State/@t.CStatic),
# 对齐 TPC HandlesFunctions._extract_decorator_storage_flags(与
# forward_declare_functions 一致,保证 declare 与 define 同名同语义)。
# 用 HandlesClassDef._has_decorator 委托(Mixin 静态绑定不能 self. 跨类调)
if fd.decorator_list is not None:
if has_extern == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "CExtern") == 1:
has_extern = 1
if has_state == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "State") == 1:
has_state = 1
if has_export == 0 and HandlesClassDef._has_decorator(fd.decorator_list, "CExport") == 1:
has_export = 1
if (has_extern != 0 or has_state != 0) and is_extern_decl == 0:
is_extern_decl = 1
# SHA1 命名空间:t.CExtern/t.State/t.CExport 不加前缀,直接用裸名
# (裸名映射到 C 标准库符号,带 sha1 前缀会导致 undefined reference)