diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index e6c28bd..1cc5d78 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/Expr/ExprGenAttr.vp b/App/lib/core/Gen/Expr/ExprGenAttr.vp index e33148e..13844a2 100644 --- a/App/lib/core/Gen/Expr/ExprGenAttr.vp +++ b/App/lib/core/Gen/Expr/ExprGenAttr.vp @@ -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 前缀 + 属性名"精确匹配, # 避免多模块同名全局(@._mbuddy / @._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 的 diff --git a/App/lib/core/Gen/Func/FuncGenDef.vp b/App/lib/core/Gen/Func/FuncGenDef.vp index e3950bf..ad01f24 100644 --- a/App/lib/core/Gen/Func/FuncGenDef.vp +++ b/App/lib/core/Gen/Func/FuncGenDef.vp @@ -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) diff --git a/App/lib/core/Gen/ImportsGenMixin.vp b/App/lib/core/Gen/ImportsGenMixin.vp index 13e3b45..a207129 100644 --- a/App/lib/core/Gen/ImportsGenMixin.vp +++ b/App/lib/core/Gen/ImportsGenMixin.vp @@ -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, diff --git a/App/lib/core/Gen/TranslatorGenMixin.vp b/App/lib/core/Gen/TranslatorGenMixin.vp index ae5cb44..7e25f80 100644 --- a/App/lib/core/Gen/TranslatorGenMixin.vp +++ b/App/lib/core/Gen/TranslatorGenMixin.vp @@ -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