fix(gen): 收敛 static 跨模块拦截为独立方法,消除类型推断污染

新增五个独立方法承载 static 检查与登记:_CheckStaticCrossModule(mod.name 取值)、_CheckStaticCallCrossModule(mod.fn 调用)、_CheckStaticFromImport(from-import)、_RegisterStaticModuleVar(模块级变量登记)、_RegisterStaticFunc(函数登记)。调用点只保留一行调用——内联新增分支会改变超大函数的类型推断,曾导致 translate_attribute 后续 match obj_ptr.Ty 报指针隐式转整数已禁止。

删除 _resolve_module_attribute_global 内与入口检查重复的 static 拦截(其两个调用点均在 _CheckStaticCrossModule 之后)。
This commit is contained in:
TermiNexus committed 2026-09-27 13:12:22 +08:00
1 parent dd56580855
commit 12bd7b7a16
5 files changed
+211 -104

No files matched your search

+51 -26
View File
@@ -41,6 +41,53 @@ define LLVMIR_TEMPLATE_BUF = 512
novirtual class CallGenMixin(CallGenBase.CallGenBase):
"""调用分派 Mixin(拆分后顶层,直接继承 CallGenBase——TPV self 调用仅支持 1 级父类)"""
# ============================================================
# _CheckStaticCallCrossModule - static 符号跨模块**调用**拦截(`mod.fn(...)`)
#
# 命中别的模块登记的 static 函数即 fail-fast。放在 translate_call 入口按 AST
# 形态统一拦截:后续分派有 found_func 命中 / vpi 解析 / 裸名兜底多条分支,
# 只在某一分支检查会漏。基座不是已导入模块(self.method / obj.method /
# Class.static_method)时放行;同模块自引用(sha1 相同)放行。
# 独立成函数:translate_call 极大,内联该检查会干扰其类型推断。
# ============================================================
def _CheckStaticCallCrossModule(self, pool: memhub.MemBuddy*,
trans: HT.Translator*,
cl: ast.Call*) -> int:
if cl is None or cl.func is None or trans is None:
return 0
if cl.func.kind() != ast.ASTKind.Attribute:
return 0
if trans.ModuleSha1 is None or trans._imported_modules is None:
return 0
# 快速短路:项目内无 static 符号时零开销(不查 sha1、不扫表)
if self.Gen.CallGenBase.StaticCount <= 0:
return 0
sc_at: ast.Attribute* = ast.Attribute*(cl.func)
if sc_at.value is None or sc_at.attr is None:
return 0
sc_mod: str = None
if sc_at.value.kind() == ast.ASTKind.Name:
sc_nm: ast.Name* = ast.Name*(sc_at.value)
sc_mod = sc_nm.id
elif sc_at.value.kind() == ast.ASTKind.Attribute:
sc_mod = self.Gen.ExprGenAttr._attr_module_path_str(pool, sc_at.value)
if sc_mod is None:
return 0
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, sc_mod) == 0:
return 0
sc_sha1: str = self.Gen.StructGen._resolve_module_sha1(sc_mod, trans._from_imports)
if sc_sha1 is None:
return 0
if string.strncmp(sc_sha1, trans.ModuleSha1, 16) == 0:
return 0
if self.Gen.CallGenBase.IsStaticSymbol(sc_sha1, sc_at.attr) == 0:
return 0
self.Gen.TypeGenErr.fatal_error(cl,
f"'{sc_mod}.{sc_at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
return 0
# ============================================================
# 翻译函数调用 Call(func, args) — 模块级版本
# ============================================================
@@ -53,32 +100,10 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
trans: HT.Translator* = None) -> llvmlite.Value*:
"""翻译函数调用,返回 call 结果 Value"""
cl: ast.Call* = ast.Call*(node)
# static 符号(禁止跨模块使用):模块限定**调用** `mod.fn(...)` 命中别的
# 模块登记的 static 函数即 fail-fast。放在入口处统一拦截——后续分派有
# 多条分支(found_func 命中 / 走 vpi 解析 / 走裸名兜底),只在某一分支
# 检查会漏;此处按 AST 形态一次覆盖全部调用路径。基座不是已导入模块
# (self.method / obj.method / Class.static_method)时放行。
if cl is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute \
and trans is not None and trans.ModuleSha1 is not None \
and trans._imported_modules is not None \
and self.Gen.CallGenBase.StaticCount > 0:
st_at: ast.Attribute* = ast.Attribute*(cl.func)
if st_at.value is not None and st_at.attr is not None:
st_mod: str = None
if st_at.value.kind() == ast.ASTKind.Name:
st_nm: ast.Name* = ast.Name*(st_at.value)
st_mod = st_nm.id
elif st_at.value.kind() == ast.ASTKind.Attribute:
st_mod = self.Gen.ExprGenAttr._attr_module_path_str(pool, st_at.value)
if st_mod is not None \
and self.Gen.ImportsGen.is_module_imported(trans._imported_modules, st_mod) != 0:
st_sha1: str = self.Gen.StructGen._resolve_module_sha1(st_mod, trans._from_imports)
if st_sha1 is not None \
and string.strncmp(st_sha1, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(st_sha1, st_at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(cl,
f"'{st_mod}.{st_at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# static 符号(禁止跨模块使用):`mod.fn(...)` 命中别的模块登记的 static 函数
# 即 fail-fast。逻辑收敛在 _CheckStaticCallCrossModule 内——本函数极大,
# 内联该检查(新增分支 + 跨函数调用)会干扰其类型推断。
self._CheckStaticCallCrossModule(pool, trans, cl)
if cl is None: return None
func_name: str = self.Gen.ExprGenBase.get_func_name(cl.func)
+60 -41
View File
@@ -166,6 +166,59 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
viperlib.snprintf(buf_m, blen + alen + 2, "%s.%s", base_m, at_m.attr)
return buf_m
return None
# ============================================================
# _CheckStaticCrossModule - static 符号跨模块引用拦截(`mod.name` 形态)
#
# `static def f` / `static x: int` / @t.CStatic / x: t.CStatic|T 四种写法在
# 解析层已归一为同一标记,语义 = 禁止跨模块使用(C 的 internal linkage)。
# 本函数拦截 `mod.name` 取值形态(含 import 别名还原);调用形态 `mod.fn()`
# 与 from-import 形态分别在 CallGenMixin.translate_call 与
# ImportsGenMixin.translate_import_from_names 拦截。
# 同模块自引用(sha1 相同)与非模块基座(obj.field / Class.field)放行。
#
# 独立成函数而非内联到 translate_attribute:内联(新增分支 + 跨函数调用)
# 会改变该大函数的类型推断结果,曾使其后续 `match obj_ptr.Ty` 报
# "指针隐式转整数已禁止"(MemManager* → i32)。
# ============================================================
def _CheckStaticCrossModule(self, pool: memhub.MemBuddy*,
trans: HT.Translator*,
at: ast.Attribute*) -> int:
if at is None or at.value is None or trans is None:
return 0
if trans.ModuleSha1 is None or trans._imported_modules is None:
return 0
# 快速短路:项目内无 static 符号时零开销(不查 sha1、不扫表)
if self.Gen.CallGenBase.StaticCount <= 0:
return 0
cs_path: str = self._attr_module_path_str(pool, at.value)
if cs_path is None:
return 0
# import 别名还原(import X as Y → 查表得原始模块名,否则 sha1 查不到)
if trans._from_imports is not None:
cs_orig: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, cs_path, 0)
if cs_orig is not None:
cs_len: sizet = 0
while cs_orig[cs_len] != ' ' and cs_orig[cs_len] != '\0' and cs_orig[cs_len] != ':':
cs_len += 1
if cs_len > 0:
cs_buf: str = pool.alloc(cs_len + 1)
if cs_buf is not None:
string.strncpy(cs_buf, cs_orig, cs_len)
cs_buf[cs_len] = '\0'
cs_path = cs_buf
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, cs_path) == 0:
return 0
cs_sha1: str = self._lookup_module_sha1(cs_path)
if cs_sha1 is None:
return 0
if string.strncmp(cs_sha1, trans.ModuleSha1, 16) == 0:
return 0
if self.Gen.CallGenBase.IsStaticSymbol(cs_sha1, at.attr) == 0:
return 0
self.Gen.TypeGenErr.fatal_error(at,
f"'{cs_path}.{at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
return 0
def _resolve_module_attribute_global(self, pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
trans: HT.Translator*,
@@ -202,16 +255,8 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
# 检查是否是已导入模块
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, mod_path_w) == 0:
return None
# static 符号(`static x: int` / @t.CStatic / x: t.CStatic|T):语义为
# 禁止跨模块使用(C 的 internal linkage),模块限定属性访问命中别的模块
# 的 static 符号即 fail-fast。同模块自引用(sha1 相同)放行。
mod_sha1_st: str = self._lookup_module_sha1(mod_path_w)
if mod_sha1_st is not None and trans.ModuleSha1 is not None \
and string.strncmp(mod_sha1_st, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(mod_sha1_st, at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(at,
f"'{mod_path_w}.{at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# 注:static 符号的跨模块拦截统一在 translate_attribute 入口的
# _CheckStaticCrossModule 内完成(本函数的两个调用点都在其后),此处不再重复。
# 在当前模块查找全局变量:优先按"目标模块 SHA1 前缀 + 属性名"精确匹配,
# 避免多模块同名全局(@<A>._mbuddy / @<B>._mbuddy 同时注入)时裸名后缀
# 匹配返回错误模块的全局(串扰)。目标 sha1 未知时退化裸名(后缀匹配兜底)
@@ -353,37 +398,11 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
VLogger.error(diag_buf_an, "ATTR")
return None
# static 符号(禁止跨模块使用)——统一入口拦截:
# 属性基座是已导入模块名(含 import 别名还原)、且 attr 命中该模块登记的
# static 符号(`static def f` / `static x: int` / @t.CStatic / x: t.CStatic|T)
# 时 fail-fast。放在所有分支之前,一次覆盖 `mod.fn`(函数取址)、
# `mod.var`(全局变量)等全部模块限定属性形态。同模块自引用(sha1 相同)
# 与非模块基座(obj.field / Class.field)放行。
if trans is not None and trans.ModuleSha1 is not None \
and trans._imported_modules is not None \
and self.Gen.CallGenBase.StaticCount > 0:
st_path: str = self._attr_module_path_str(pool, at.value)
if st_path is not None and trans._from_imports is not None:
orig_st: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, st_path, 0)
if orig_st is not None:
ol_st: sizet = 0
while orig_st[ol_st] != ' ' and orig_st[ol_st] != '\0' and orig_st[ol_st] != ':':
ol_st += 1
if ol_st > 0:
ob_st: str = pool.alloc(ol_st + 1)
if ob_st is not None:
string.strncpy(ob_st, orig_st, ol_st)
ob_st[ol_st] = '\0'
st_path = ob_st
if st_path is not None \
and self.Gen.ImportsGen.is_module_imported(trans._imported_modules, st_path) != 0:
st_sha1: str = self._lookup_module_sha1(st_path)
if st_sha1 is not None \
and string.strncmp(st_sha1, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(st_sha1, at.attr) != 0:
self.Gen.TypeGenErr.fatal_error(at,
f"'{st_path}.{at.attr}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# static 符号(禁止跨模块使用):`mod.name` 命中别的模块登记的 static 符号
# 即 fail-fast。逻辑全部收敛在 _CheckStaticCrossModule 内——不在此内联展开
# (内联会让本函数的类型推断受干扰:新增分支/跨函数调用改变了后续变量
# 的推断结果,曾导致本函数 L765 的 `match obj_ptr.Ty` 报"指针隐式转整数")。
self._CheckStaticCrossModule(pool, trans, at)
# 魔法属性 __doc__:查询 docstring 登记表(预注册阶段从 AST 提取),
# 命中则当前模块内联生成字符串常量返回;未命中(无 docstring 的
+35 -17
View File
@@ -163,16 +163,12 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if has_extern != 0 or has_state != 0:
is_extern_decl = 1
# static(禁止跨模块使用):注解形式(`def f() -> t.CVoid | t.CStatic:`)
# 与装饰器形式(`@t.CStatic`)同源识别,命中即登记到 static 符号表。
# 裸 `static def f(...)` 已在解析层归一为 @t.CStatic 装饰器
# (parser.vp 的 _ParseStaticModified),此处无需额外判据。
has_static: int = 0
if fd.returns is not None:
has_static = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CStatic", 0)
if has_static == 0 and fd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CStatic") == 1:
has_static = 1
# static(禁止跨模块使用):注解形态(`def f() -> t.CVoid | t.CStatic:`)
# 与装饰器形态(`@t.CStatic`;裸 `static def f(...)` 已由解析层归一为
# 装饰器)同源识别并登记。逻辑收敛在 _RegisterStaticFunc 内(不内联——
# 本函数含多分支,内联新增分支会干扰其类型推断,与 ExprGenAttr /
# CallGenMixin / TranslatorGenMixin 同款处理)。
self._RegisterStaticFunc(trans, fd)
# 计算 mangled name
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0:
@@ -223,13 +219,6 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None:
self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
# 登记 static 符号(禁止跨模块使用):定义侧登记 (符号名, 模块 SHA1),
# 跨模块引用侧(mod.name 取值 / mod.fn() 调用 / from mod import name)
# 经 IsStaticSymbol 拦截并 fail-fast。函数名用裸名 fd.name 登记——
# 跨模块引用侧的解析入口拿到的也是源码里的裸名(不含 sha1 前缀)。
if has_static != 0 and trans.ModuleSha1 is not None:
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, fd.name)
# 添加参数
args_node: ast.Arguments* = fd.args
if args_node is not None:
@@ -1065,6 +1054,35 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
DebugInfoGen.PopFunction(func_builder)
return 0
# ============================================================
# _RegisterStaticFunc - static 函数登记(禁止跨模块使用)
#
# 两种书写形态同源识别(解析层已归一):
# 1) 注解形态:`def f() -> t.CVoid | t.CStatic:`(CStatic 标记在 returns 里)
# 2) 装饰器形态:`@t.CStatic` / 裸 `static def f(...)`(解析层合成装饰器)
# 命中即登记 (函数名, 定义模块 SHA1) 到 static 符号表;跨模块引用侧
# (_CheckStaticCallCrossModule / _CheckStaticFromImport / _CheckStaticCrossModule)
# 据此 fail-fast。独立成方法:translate_function_def 含多分支,内联新增分支
# 会干扰其类型推断(同 ExprGenAttr / CallGenMixin / TranslatorGenMixin 处理)。
# ============================================================
def _RegisterStaticFunc(self, trans: HT.Translator*,
fd: ast.FunctionDef*) -> int:
"""static 函数登记:returns 注解或 @t.CStatic 装饰器命中即登记;1=已登记 / 0=未命中"""
if trans is None or fd is None or fd.name is None:
return 0
if trans.ModuleSha1 is None:
return 0
has: int = 0
if fd.returns is not None:
has = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CStatic", 0)
if has == 0 and fd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "CStatic") == 1:
has = 1
if has == 0:
return 0
return self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, fd.name)
# ============================================================
# _gen_decor_wrapper - 生成装饰器 wrapper 函数(对齐 TPC DecoratorPass.py
# _generate_single_wrapper v4)
+38 -14
View File
@@ -920,6 +920,38 @@ novirtual class ImportsGenMixin:
# 同样使用解析后的完整模块名,确保 from-import 映射
# (如 LLVMType:llvmlite.__types)能正确查到 SHA1。
# ============================================================
# _CheckStaticFromImport - static 符号 from-import 拦截
#
# `from mod import name` 命中源模块登记的 static 符号 → fail-fast(与
# ExprGenAttr 的 `mod.name` 取值拦截、CallGenMixin 的 `mod.fn()` 调用拦截同源,
# 覆盖 from-import 形态)。用源码原名判表——定义侧登记用的也是裸名。
# 独立成函数:内联进 translate_import_from_names 会干扰其类型推断(同款处理
# 见 ExprGenAttr._CheckStaticCrossModule 与 CallGenMixin._CheckStaticCallCrossModule)。
# ============================================================
def _CheckStaticFromImport(self, trans: HT.Translator*,
fi_target: str,
orig_name: str,
node: ast.AST*) -> int:
if trans is None or fi_target is None or orig_name is None:
return 0
if trans.ModuleSha1 is None:
return 0
# 快速短路:项目内无 static 符号时零开销(不查 sha1、不扫表)
if self.Gen.CallGenBase.StaticCount <= 0:
return 0
sf_sha1: str = self.Gen.StructGen._resolve_module_sha1(fi_target, trans._from_imports)
if sf_sha1 is None:
return 0
if string.strncmp(sf_sha1, trans.ModuleSha1, 16) == 0:
return 0
if self.Gen.CallGenBase.IsStaticSymbol(sf_sha1, orig_name) == 0:
return 0
self.Gen.TypeGenErr.fatal_error(node,
f"'{fi_target}.{orig_name}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
return 0
def translate_import_from_names(self, trans: HT.Translator*,
node: ast.AST*) -> int:
"""处理 from X import Y 的名称部分,更新 trans._from_imports"""
@@ -957,20 +989,12 @@ novirtual class ImportsGenMixin:
fi_target = jb_t
trans._from_imports = self.add_from_import(trans.Pool, trans._from_imports,
local_name, fi_target, orig_name)
# static 符号(禁止跨模块使用):from mod import name 命中源
# 模块登记的 static 符号 → fail-fast(与 `mod.name` 取值 /
# `mod.fn()` 调用两处拦截同源,覆盖 from-import 形态)。
# 用 alias.name(源码原名)判表——登记侧也用裸名。
if trans.ModuleSha1 is not None and fi_target is not None \
and self.Gen.CallGenBase.StaticCount > 0:
fi_sha1_st: str = self.Gen.StructGen._resolve_module_sha1(fi_target, trans._from_imports)
if fi_sha1_st is not None \
and string.strncmp(fi_sha1_st, trans.ModuleSha1, 16) != 0 \
and self.Gen.CallGenBase.IsStaticSymbol(fi_sha1_st, alias.name) != 0:
self.Gen.TypeGenErr.fatal_error(node,
f"'{fi_target}.{alias.name}' 是 static 符号,禁止跨模块使用(static 仅限定义模块内可见)",
"NameError")
# 命名空间隔离:from-import 的名称标记为可见结构体
# static 符号(禁止跨模块使用):from mod import name 命中源
# 模块登记的 static 符号 → fail-fast。逻辑收敛在
# _CheckStaticFromImport 内(不内联——内联新增分支会干扰本函数
# 的类型推断,与 ExprGenAttr/CallGenMixin 同款处理)。
self._CheckStaticFromImport(trans, fi_target, alias.name, node)
# 命名空间隔离:from-import 的名称标记为可见结构体
self.Gen.StructGen.add_visible_struct(trans.Pool, local_name)
# 注册 re-export 映射:当前模块 re-export 了 local_name,
# 源模块为 fi_target(from . import Y 时是子模块名 vpsdk.Y,
+27 -6
View File
@@ -628,6 +628,30 @@ novirtual class TranslatorGenMixin:
# AnnAssign/Assign 创建全局变量 @var_name 并注册到 SymTab 模块作用域
# 支持整数、列表字面量、字符串字面量初始值
# ============================================================
# ============================================================
# _RegisterStaticModuleVar - 模块级 static 变量登记
#
# `static x: int = 0`(解析层已归一为 `x: t.CStatic | int`)与手写
# `x: t.CStatic | int` 均命中 → 登记 (变量名, 定义模块 SHA1) 到 static 符号表,
# 供引用侧(ExprGenAttr / CallGenMixin / ImportsGenMixin)拦截跨模块使用。
# 变量无装饰器通道(@t.CStatic 只作用于 def),故只看注解。
# 时机:调用点位于 handle_module_level_var 的 `_declare_only` 提前返回之后,
# 预声明遍不会走到;登记接口本身也做了幂等,重复登记无副作用。
# 独立成函数:不内联(内联新增分支会干扰调用方的类型推断)。
# ============================================================
def _RegisterStaticModuleVar(self, trans: HT.Translator*,
annot_node: ast.AST*,
var_name: str) -> int:
if trans is None or annot_node is None or var_name is None:
return 0
if trans.ModuleSha1 is None:
return 0
if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CStatic", 0) == 0:
return 0
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, var_name)
return 0
def handle_module_level_var(self, trans: HT.Translator*,
node: ast.AST*) -> int:
"""处理模块级变量声明,创建全局变量"""
@@ -690,12 +714,9 @@ novirtual class TranslatorGenMixin:
# static 符号(禁止跨模块使用):模块级变量带 t.CStatic 标记
# (`static x: int = 0` 经解析层归一为 `x: t.CStatic | int`;亦覆盖
# 手写 `x: t.CStatic | int`)→ 登记 (变量名, 定义模块 SHA1)。
# 变量无装饰器通道(@t.CStatic 只作用于 def),故只看注解。
# 时机:本处已在 `_declare_only` 提前返回(L662)之后,预声明遍不会
# 走到,避免同一符号重复登记。
if annot_node is not None and var_name is not None and trans.ModuleSha1 is not None:
if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CStatic", 0) != 0:
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, var_name)
# 逻辑收敛在 _RegisterStaticModuleVar 内(不内联——本函数含多分支,
# 内联新增分支会干扰其类型推断,与 ExprGenAttr/CallGenMixin 同款处理)。
self._RegisterStaticModuleVar(trans, annot_node, var_name)
# t.Callable 模块级函数指针:登记 (变量名 → 返回类型) 签名。此路径原先
# 只创建全局变量、不登记签名,模块级 t.Callable 变量被间接调用时在
# CallGenLeafKit._translate_func_ptr_call 报"返回类型未注册(t.Callable