fix(call): 类名与模块同名时不再误报“模块未导入”

- _fail_on_unimported_module_call 先按文件级类名表(is_file_class)与 (name, sha1) 判定类静态调用,再走模块判定

- 修复 TypeSpec.vp 中 TypeSpec.__sizeof__() 被当模块调用而报 NameError

- 注:本文件同时包含此前未提交的既有改动,按整文件提交
This commit is contained in:
TermiNexus committed 2026-10-24 00:48:17 +08:00
1 parent e2192db72d
commit 30e0a71cc6
1 file changed
+202 -18
+202 -18
View File
@@ -1,4 +1,5 @@
import lib.core.Gen.GenTypes as GenTypes
import lib.core.CompilerFactory as CompilerFactory
from stdint import *
import ast
import mlirlite
@@ -9,6 +10,7 @@ import viperlib
import lib.core.Gen.Translator as HT
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.Stub.StubGen.VpiReader as VpiReader
import lib.core.VLogger as VLogger
from . import CallGenBase
import lib.core.Gen.GenApi as GenApi
@@ -145,9 +147,117 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
cur_param = mlirlite.param_get_next(cur_param)
return 0
# _defaults_entry_from_vpi - 全局表里没有该函数的缺省值时,直接按定义模块 .vpi 签名临时造一条
# 快照条目(kinds/ints/strs 由文本解析得到,字符串已深拷贝进 PermPool),仅本次调用使用。
def _defaults_entry_from_vpi(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> GenTypes.FuncDefaultsEntry*:
"""按 .vpi 签名临时构造缺省值条目;无缺省值或不可重建返回 None"""
cap: int = 128
# 快照数组放 PermPool:条目可能在后续 pool reset 之后才被消费,放 pool 会悬空。
kinds: int* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 4)
ints: int64* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 8)
strs: sizet* = CompilerFactory.factory.PermPool.alloc(sizet(cap) * 8)
if kinds is None or ints is None or strs is None: return None
enc: int = VpiReader.LookupSignDefaultsByName(pool, sha1, name, kinds, ints, strs)
if enc < 0: return None
dc: int = enc & 0xFF
pc: int = enc >> 8
if dc <= 0: return None
ent_mem: bytes = pool.alloc(sizet(GenTypes.FuncDefaultsEntry.__sizeof__()))
if ent_mem is None: return None
ent: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(uint64(ent_mem))
ent.Name = None
ent.ModuleSha1 = None
ent.Defaults = None
ent.DefaultCount = dc
ent.ParamCount = pc
ent.ConstKinds = kinds
ent.ConstInts = ints
ent.ConstStrs = strs
return ent
# _fill_defaults_late - call_name 定型后按 {sha1 前缀}.{名字} 再补一次缺省值。
# 早期补参用的是 dfx_callee_sha,链式接收者(`self.Gen.X.m(...)`)解析不出发起模块时它会
# 退化成调用方 SHA1,表查不到;此时 call_name 已带定义模块 SHA1 前缀,是权威键,再补一次。
# 返回补齐后的实参个数。
def _fill_defaults_late(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, builder: mlirlite.IRBuilder*, trans: HT.Translator*, cl: ast.Call*, call_name: str, head: mlirlite.Value*, can: sizet) -> sizet:
"""按 call_name 里的定义模块 SHA1 前缀补缺省参数;返回新的实参个数"""
if head is None or can == 0 or call_name is None: return can
if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) == 0: return can
sh_buf: str = pool.alloc(17)
if sh_buf is None: return can
string.strncpy(sh_buf, call_name, 16)
sh_buf[16] = '\0'
cname: str = call_name + 17
if cname[0] == '\0': return can
# 只做 SHA1 隔离的查找:表按 (名字, 定义模块 SHA1) 定位;表里没有就直接读该模块的 .vpi。
# 绝不按裸名跨模块搜(`run` 这类名字在多个模块里都有,会拿错签名去补参/校验)。
de: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(cname, sh_buf)
if de is None: de = self._defaults_entry_from_vpi(pool, sh_buf, cname)
if de is None: return can
n: int = int(can)
if de.DefaultCount <= 0 or n >= de.ParamCount: return can
df_off: int = de.ParamCount - de.DefaultCount
df_i: int
for df_i in range(int(de.DefaultCount)):
df_pidx: int = df_off + df_i
if df_pidx < n: continue
df_val: mlirlite.Value* = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, de, df_i, trans, builder)
if df_val is None:
self.Gen.TypeGenErr.fatal_error(cl, f"default argument {df_i + 1} of '{call_name}' cannot be rebuilt into a value; refusing to emit a call with fewer operands than the callee declares", "TypeError")
df_val.Next = None
df_tail: mlirlite.Value* = head
while df_tail.Next is not None: df_tail = df_tail.Next
df_tail.Next = df_val
n += 1
# 补完仍少于声明形参:说明签名里的缺省值不够用,直接给出可读的 VPC Diag,
# 不要放行到 mlir-opt 变成 "incorrect number of operands"。
if n < de.ParamCount:
self.Gen.TypeGenErr.fatal_error(cl, f"call to '{call_name}' passes {n} argument(s) but the callee declares {de.ParamCount} parameter(s); only {de.DefaultCount} usable default value(s) are available", "TypeError")
return sizet(n)
# _fail_on_unimported_module_call - 模块限定调用的模块必须已导入(否则报 NameError)
# `mod.func(...)` 里的 mod 若确实是模块表里的模块、但本文件没 import,后续会把它当成
# "外部 C 函数" 去猜返回类型,报出与真因无关的 TypeError
# (现场:vpui.vp 用了 viperlib.snprintf 却漏写 `import viperlib`,报的是
# "external function return type is unregistered ... snprintf")。
def _fail_on_unimported_module_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, cl: ast.Call*) -> int:
"""Fail with NameError when `mod.f(...)` names a known module this file never imported."""
if cl is None or cl.func is None or trans is None: return 0
if cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is None or at.value is None: return 0
# 只处理一级模块段;多级路径(os._win32.exists)与表达式接收者交给别的路径
if at.value.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(at.value)
if nm.id is None: return 0
# obj.method():接收者是局部/全局变量,不是模块
if trans.SymTab is not None:
if self.Gen.VarGen.lookup_var(trans.SymTab, nm.id) is not None: return 0
# 已导入的模块
if trans._imported_modules is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, nm.id) != 0: return 0
# import-as 别名 / from-import 名字
if trans._from_imports is not None:
if self.Gen.ImportsGen.lookup_from_import(trans._from_imports, nm.id, 0) is not None: return 0
# REPL 会话宽松:会话里允许后补 import
if trans.ReplCtx is not None: return 0
# 类名优先:`TypeSpec.__sizeof__()` 是类静态调用,不是模块调用。类名与模块名同名时
# (现场:TypeSpec.vp 里模块名 = 类名 = TypeSpec)不能因为"没 import 自己"而误报。
# 先用文件级类名表(不依赖 StructTable 是否已注册),再补 (name, sha1) 精确查找。
if pool is not None:
if self.Gen.StructGen.is_file_class(pool, nm.id) != 0: return 0
if trans.ModuleSha1 is not None:
if self.Gen.StructGen.find_struct_by_module(nm.id, trans.ModuleSha1) is not None: return 0
# 只有"确实是模块"才判;未定义名字交给其它路径
if self.Gen.StructGen._resolve_module_sha1(nm.id, trans._from_imports) is None: return 0
self.Gen.TypeGenErr.fatal_error(at, f"call '{nm.id}.{at.attr}(...)' targets module '{nm.id}', but this file does not import it; add `import {nm.id}`. Without the import the call degrades into an external C-function call and its return type cannot be resolved (pointer returns would be truncated to i32)", "NameError")
return 0
def translate_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate a Call node and return the result Value."""
cl: ast.Call* = ast.Call*(node)
# 模块限定调用先校验"模块已导入":不导入会被降级成外部 C 函数,报出与真因无关的 TypeError
self._fail_on_unimported_module_call(pool, trans, cl)
# Static symbols must not be used cross-module: fail fast on mod.fn(...) hitting another
# module's static; the check lives in _CheckStaticCallCrossModule to keep this huge function inferable.
self._CheckStaticCallCrossModule(pool, trans, cl)
@@ -310,8 +420,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
# VPC compiles generic functions as plain functions, so drop [T] if a same-name function exists.
if self.find_func_in_module(mod, gen_class_name) is not None: func_name = gen_class_name
else:
# Builtin pointer/type subscript cast: (rawptr[list[int]])(lst), (t.CPtr[T])(x),
# (t.CArray[i8, N])(x); rawptr/CPtr/CArray are type subscripts, not generic templates.
# Builtin pointer/type subscript cast: (rawptr[list[int]])(lst), (rawptr[T])(x),
# (t.ArrayT[i8, N])(x); rawptr/PtrT/ArrayT are type subscripts, not generic templates.
if cl.args is not None and len(cl.args) == 1:
sub_cast_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
if sub_cast_ty is not None:
@@ -685,7 +795,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
ff_nm2: str = mlirlite.function_get_name(found_func)
if ff_nm2 is not None:
if string.strncmp(ff_nm2, mod_sha1_chk, 16) != 0:
# CExport / t.State 型跨模块函数**定义与声明都用裸名**(stdlib.free、
# ExportT / t.State 型跨模块函数**定义与声明都用裸名**(stdlib.free、
# CPython 的 PyList_Size 等):裸名不是"绑错了模块",必须放行。
# 否则这里会被清成 None → 关键字实参路径整体跳过 → 静默生成
# 零实参调用(现场:stdlib.free(p = p) → llvm.call @free() → mlir-opt
@@ -755,6 +865,11 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
bi_ret = mlirlite.Double(pool)
bi_p1 = mlirlite.Double(pool)
bi_p2 = mlirlite.Double(pool)
elif string.strcmp(func_name, "abs") == 0:
# abs 也是语言内建(int -> int),由 builtins.vp 提供实现;不再落到 C 库的
# `abs` 声明上,否则无 libc 的目标(VKernel)会 undefined symbol: abs。
bi_ret = mlirlite.Int32(pool)
bi_p1 = mlirlite.Int32(pool)
if bi_ret is not None:
bi_sym: str = func_name
if self.Gen.CallGenBase.is_cexport_func(bi_sha1, func_name) == 0:
@@ -824,17 +939,42 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
if rr_sha1 is not None:
VpiReader._ensure_vpi_ret_annot_loaded(rr_sha1, self.Gen.CallGenBase.TempDir)
ret_annot_fc = VpiReader._lookup_vpi_ret_annot(rr_sha1, None, func_name)
if ret_annot_fc is not None: vpi_ret_ty = VpiReader._resolve_vpi_ret_annot_str(pool, ret_annot_fc, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
if ret_annot_fc is not None:
vpi_ret_ty = VpiReader._resolve_vpi_annot_with_typedefs(pool, mod_ret_sha1, ret_annot_fc, mlirlite.Int32(pool), mlirlite.Ptr(pool, mlirlite.Int8(pool)))
# Generic instantiation (`mlirlite.function_get_params -> GSList[Param]*`)
# needs on-demand specialization; the string resolver cannot build it.
if vpi_ret_ty is None: vpi_ret_ty = self.Gen.CallGenBase._resolve_generic_annot_str(trans, pool, ret_annot_fc)
if vpi_ret_ty is not None: call_ret_ty = vpi_ret_ty
elif mod_ret_sha1 is not None:
# Cross-module bare CExport function (e.g. mm.malloc -> @malloc): resolve the return type
# Cross-module bare ExportT function (e.g. mm.malloc -> @malloc): resolve the return type
# exactly from the dependency module, else the default i32 truncates the pointer (mm.malloc root cause).
cross_ret: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, mod_ret_sha1, None, func_name)
cross_ret: mlirlite.LLVMType* = self._lookup_method_ret_ty_via_vpi(pool, mod_ret_sha1, None, func_name, trans)
if cross_ret is None:
if trans is not None and trans._declare_only == 0: cross_ret = self._LookupMethodRetTyFromTextLL(pool, mod_ret_sha1, None, func_name, 1)
if cross_ret is not None: call_ret_ty = cross_ret
else:
# Dependency text.ll not generated yet (Phase A middle pass): fatal_if_unknown=0, only a temporary placeholder.
# 结果被丢弃(独立调用语句)时不 fail-fast:占位 i32 无害,没必要中断编译。
if trans is not None and trans._declare_only == 0 and trans._discard_result == 0:
vpi_rc_d: int = 0
vpi_annot_d: str = None
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
vpi_rc_d = VpiReader._ensure_vpi_ret_annot_loaded(mod_ret_sha1, self.Gen.CallGenBase.TempDir)
vpi_annot_d = VpiReader._lookup_vpi_ret_annot(mod_ret_sha1, None, func_name)
annot_s_d: str = "-"
if vpi_annot_d is not None: annot_s_d = vpi_annot_d
# sha1 来源拆解:exact / suffix 各自返回什么,用来区分"存表里就是错的"
# 与"后缀匹配撞到了别的模块"(两者修法完全不同)。
ex_s_d: str = "-"
if mod_path_ret is not None:
_ex_d: str = self.Gen.CallGenBase._lookup_module_sha1(mod_path_ret)
if _ex_d is not None: ex_s_d = _ex_d
sf_s_d: str = "-"
if mod_path_ret is not None:
_sf_d: str = self.Gen.CallGenBase._lookup_module_sha1_suffix(mod_path_ret)
if _sf_d is not None: sf_s_d = _sf_d
# 走 VPC Diag(源码行 + 调用栈),而不是单行 VLogger.error:
# 单行输出丢掉了位置与调用链,无法定位到底是谁在什么上下文里调用了它。
self.Gen.TypeGenErr.fatal_error(node, f"cross-module function return type unresolved: {mod_path_ret}.{func_name} (sha1={mod_ret_sha1}, exact={ex_s_d}, suffix={sf_s_d}, tempdir_set={self.Gen.CallGenBase.TempDirLen}, vpi_load_rc={vpi_rc_d}, vpi_annot={annot_s_d}); a placeholder i32 would truncate a pointer return", "TypeError")
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 0)
else:
# Bare call (cl.func is Name, mod_ret_sha1=None): from-import/closure/function-pointer targets are resolvable (placeholder, no fail-fast); pure external C functions fail-fast.
@@ -885,15 +1025,15 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans)
# Function-pointer variable (t.Callable annotation -> i8** alloca): build the signature from args and call indirectly.
if self.Gen.CallGenLeafKit._is_func_ptr_alloca(fp_alloca) != 0:
# Distinguish t.Callable (true function pointer) from t.CPtr (closure pointer): a t.CPtr
# Distinguish t.Callable (true function pointer) from rawptr (closure pointer): a rawptr
# holds {fn_ptr, env_ptr} and must pass env, else a direct call jumps into closure data and crashes.
ve_fp: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, func_name)
is_cptr_annot_fp: int = 0
fp_annot: str = self.Gen.VarGen.var_annot_class_name(ve_fp)
if fp_annot is not None:
# [S4] CPtr marker = annotation base name exactly "CPtr": t.Callable[[A],R] also resolves to
# [S4] PtrT marker = annotation base name exactly "PtrT": t.Callable[[A],R] also resolves to
# Ptr(i8) (same Spec), so only the annotation name can carry the marker; substring matching would misfire.
if TSpec.SpecTypeNameBaseIs(fp_annot, "CPtr") != 0: is_cptr_annot_fp = 1
if TSpec.SpecTypeNameBaseIs(fp_annot, "PtrT") != 0: is_cptr_annot_fp = 1
if is_cptr_annot_fp == 0:
return self.Gen.CallGenLeafKit._translate_func_ptr_call(pool, builder, mod, fp_alloca, func_name, cargs, can, trans)
return self.Gen.CallGenLeafKit.translate_closure_call(pool, builder, mod, fp_alloca, cargs, can, trans)
@@ -1010,7 +1150,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
else: arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, ext_param_ty, arg)
elif found_func is not None:
# found_func present: coerce to the param type (e.g. stdlib.malloc(size: sizet)); a
# CDefine/i32 literal passed to an i64 param would otherwise leave call(i32) vs define(i64) mismatched.
# DefineT/i32 literal passed to an i64 param would otherwise leave call(i32) vs define(i64) mismatched.
if fp is not None:
if fpty is not None:
# "str/bytes is not a generic pointer wildcard": a same-module bare function whose
@@ -1101,6 +1241,9 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
if rr_src_dx is not None:
rr_sha_dx: str = self._lookup_module_sha1(rr_src_dx)
if rr_sha_dx is not None: gd_entry = self.Gen.CallGenLeafKit.find_func_defaults(func_name, rr_sha_dx)
# 键失配兜底:链式接收者(`self.Gen.ExprGenMixin.translate_value(...)`)解析不出发起模块,
# dfx_callee_sha 退化成调用方自己的 SHA1,限定查表落空 —— 由后面的 _fill_defaults_late
# 用 call_name 里的定义模块 SHA1 前缀(权威键)再补一次,这里不做跨模块裸名猜测。
# Override when df_default_count<=0 too: the caller table may hold a same-name entry with empty
# Defaults (a VpiReader declare), so the real global entry must win or filling never triggers.
if gd_entry is not None and (df_defaults is None or df_default_count <= 0):
@@ -1176,7 +1319,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
fp_z = mlirlite.param_get_next(fp_z)
fpi_z += 1
# Use the actual function name from the module (may carry a SHA1 prefix); CExtern/State/CExport
# Use the actual function name from the module (may carry a SHA1 prefix); ExternT/State/ExportT
# functions use the bare name. Both declare and define must use found_func.Name (mismatched names cause undefined references).
call_name: str = func_name
# Original function name (recorded for aliased imports, e.g. from X import parse as json_parse -> "parse"),
@@ -1186,8 +1329,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
found_name: str = mlirlite.function_get_name(found_func)
if found_name is not None:
call_name = found_name
# CExport bare-name fallback: a cross-module stub declare may carry a SHA1 prefix while the
# definition uses @malloc, causing a link error; extract the sha1 and use the bare name if it is a CExport.
# ExportT bare-name fallback: a cross-module stub declare may carry a SHA1 prefix while the
# definition uses @malloc, causing a link error; extract the sha1 and use the bare name if it is a ExportT.
if found_func.IsDeclared != 0:
# Use the single source of truth GenApi.sha1_prefix_len (converged in C10).
if GenApi.sha1_prefix_len(found_name) != 0:
@@ -1258,7 +1401,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
rs_found = 1
call_name = func_name
else:
# Original module has a plain def (not CExport/State): a vpi hit keeps the original
# Original module has a plain def (not ExportT/State): a vpi hit keeps the original
# module sha (@{mod_sha1}.func) instead of falling back to a star candidate that would hijack resolution.
orig_has_def: int = 0
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
@@ -1268,7 +1411,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
# Original module does not define it: fall back to the first candidate (e.g. ast._lexer_init in ast.lexer).
reexport_sha1: str = self._lookup_module_sha1(reexport_src)
if reexport_sha1 is not None: mod_sha1 = reexport_sha1
# CExport functions define with the bare name (@strlen), so the cross-module call must use the bare name too.
# ExportT functions define with the bare name (@strlen), so the cross-module call must use the bare name too.
if rs_found != 0:
pass # Re-export defining module already verified and bare name set.
elif self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0: call_name = func_name
@@ -1319,7 +1462,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
from_sha1: str = None
if trans is not None and trans._from_imports is not None: from_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0)
if from_sha1 is not None:
# CExport functions define with the bare name, so the cross-module call must use the bare name.
# ExportT functions define with the bare name, so the cross-module call must use the bare name.
if self.Gen.CallGenBase.is_cexport_func(from_sha1, func_name) != 0: call_name = func_name
else:
fmangled_len: sizet = string.strlen(from_sha1) + string.strlen(func_name) + 2
@@ -1328,6 +1471,22 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
viperlib.snprintf(fmangled, fmangled_len, "%s.%s", from_sha1, func_name)
call_name = fmangled
else:
# The base name is neither a local/closure variable nor a class: the
# attribute call can only be a module-qualified call, so an unresolved
# module means the name itself is undefined (missing import). Reporting
# "module SHA1 not found" here hid the real cause (现场: VLogger.error
# used without `import lib.core.VLogger as VLogger`).
base_defined: int = 0
if trans is not None:
if self.Gen.VarGen.lookup_var(trans.SymTab, xmod_path) is not None: base_defined = 1
if base_defined == 0 and GenApi.is_nonlocal_name(trans, xmod_path) != 0: base_defined = 1
if base_defined == 0 and self.Gen.StructGen.find_struct(xmod_path) is not None: base_defined = 1
if base_defined == 0:
err_msg_ne: str = pool.alloc(256)
if err_msg_ne is not None:
viperlib.snprintf(err_msg_ne, 256, "name '%s' is not defined (used as a module in '%s.%s'; missing an import?)", xmod_path, xmod_path, func_name)
self.Gen.TypeGenErr.fatal_error(xmod_at, err_msg_ne, "NameError")
self.Gen.TypeGenErr.fatal_error(xmod_at, f"name '{xmod_path}' is not defined", "NameError")
err_msg2: str = pool.alloc(256)
if err_msg2 is not None:
viperlib.snprintf(err_msg2, 256, "Cross-module call '%s.%s': module SHA1 not found", xmod_path, func_name)
@@ -1423,11 +1582,36 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
cf_ret: mlirlite.LLVMType* = self._LookupMethodRetTyFromTextLL(pool, cf_sha1, None, cf_bare, 1)
if cf_ret is not None: call_ret_ty = cf_ret
else:
# 编译器内置(ord/chr/min/max):builtins.vp 只是注释桩,没有任何声明,
# 签名由上面 "builtins auto-link" 按约定合成(ord(i8)->i32 等),
# 因此不能按"跨模块缺 vpi/text.ll"判失败,否则任何 ord/chr 调用都会误报。
bi_synth: int = 0
if string.strcmp(cf_bare, "ord") == 0: bi_synth = 1
elif string.strcmp(cf_bare, "chr") == 0: bi_synth = 1
elif string.strcmp(cf_bare, "min") == 0: bi_synth = 1
elif string.strcmp(cf_bare, "max") == 0: bi_synth = 1
elif string.strcmp(cf_bare, "abs") == 0: bi_synth = 1
# Dependency text.ll does not exist (module not yet translated): Phase A translates files
# in order (main.vp before its imported mbuddytest.vp), so keep the default type without fail-fast.
if self._dep_text_ll_exists(pool, cf_sha1) != 0: self.Gen.TypeGenErr.fatal_error(node, f"Cannot obtain the cross-module function return type precisely (neither vpi nor text.ll hit); the default i32 will truncate pointers: {call_name}", "TypeError")
if bi_synth == 0 and self._dep_text_ll_exists(pool, cf_sha1) != 0: self.Gen.TypeGenErr.fatal_error(node, f"Cannot obtain the cross-module function return type precisely (neither vpi nor text.ll hit); the default i32 will truncate pointers: {call_name}", "TypeError")
stdlib.free(cf_sha1)
stdlib.free(cf_bare)
# 迟到的补参:call_name 此时已带定义模块 SHA1 前缀(权威键),补上早期因链式接收者
# 退化而成查不到的缺省值;必须在 _ensure_method_declare 之前完成,否则 declare 会按
# 少参的实参个数被建短(同一函数出现两种 arity 声明)。
can = self._fill_defaults_late(pool, mod, builder, trans, cl, call_name, head, can)
# 静态实参个数校验(以定义模块 .vpi 签名为准,self 不计):declare 此时可能还没创建,
# 而 _ensure_method_declare 又会按 can 建短 declare —— 旧逻辑会把这个错误一路藏到
# mlir-opt 的 "incorrect number of operands"。含关键字实参或可变参数时跳过。
if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) != 0 and (cl.keywords is None or len(cl.keywords) == 0):
ar_sha: str = pool.alloc(17)
if ar_sha is not None:
string.strncpy(ar_sha, call_name, 16)
ar_sha[16] = '\0'
ar_name: str = call_name + 17
ar_n: int = VpiReader.LookupParamCountByName(pool, ar_sha, ar_name)
if ar_n >= 0 and int(can) != ar_n:
self.Gen.TypeGenErr.fatal_error(cl, f"call to '{ar_name}' passes {int(can)} argument(s) but the definition declares {ar_n} parameter(s) (self excluded)", "TypeError")
# Bare C library function (string.memset -> @memset): promote args to the standard signature
# (third arg i32 literal -> i64) before creating the declare, so call/declare/define widths match.
head = self.Gen.CallGenLib._promote_clib_call_args(pool, builder, mod, call_name, head, can)