Files
ViperC/App/lib/core/Gen/ImportsGenMixin.vp
T
TermiNexus 15b416b527 fix(type): 统一注入期类归属与导入上下文记账,修占位表爆与属性标记误解析
- StubMergerStore 新增共享导入上下文记账(AppendImportedModule / AppendFromImport /
  ImportCtx / AllocImportCtx / NotePlainImport),ImportsGenMixin 与 VpiReader 共用同一份
  口径;顺带移除 add_from_import 的 256 缓冲静默截断,VpiReader 删除两份私有副本

- 注入窗口按「名字归属」判据解析(is_module_class + CurDefModuleSha1):
  _forward_declare_ptr_from_node 的 Name 分支、resolve_annotation_type 的 Name/Attribute
  分支均补齐,使同一 stub 内先声明/先引用的类产出**具名占位**而不是 Ptr(i8),并新增
  _is_tattr_marker 让 t.attr.* 属性标记不参与类型联合竞争

- VpiReader._inject_class_struct_declare:命中前向声明占位时不再早退,改为「占位 Ty
  原地写入」(对齐 ClassGenDef.fill_fwd),修 StructTable 爆表(table full, cannot
  register Translator)

- 清除全部一次性探针:[DBG-GEN](VpiReader / StructGenMixin / ClassGenDef)、
  [DBG-ATTR](ExprGenAttr)、[MODSHA1](CallGenMixin)

根因记录:vpi 注入期漏记 `import X as Y -> from_imports "Y:X"`,使 GenTypes.VarEntry.Spec
(TSpec.TypeSpec | t.CPtr)取不到模块 SHA1、退化为 i8* 并被永久冻结
(CallGenCtor.vp:897 .PtrLevel 属性访问失败)。
2026-10-02 17:29:38 +08:00

1005 lines
52 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import memhub
import hashtable
import string
import stdlib
import viperlib
import stdio
import sys
import w32.fileio as fileio
import lib.Stub.StubMerger as StubMerger
import lib.core.CompilerFactory as CompilerFactory
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.VLogger as VLogger
# ============================================================
# ImportsGenMixin - 导入处理 Mixin
#
# 职责:模块导入表(imported_modules/from_imports)维护、re-export 登记、
# 相对导入解析与模块 SHA1 校验。@t.NoVTable:静态绑定(MRO 直接调用)。
# ============================================================
# ============================================================
# 常量(与 HandlesImports 保持一致)
# ============================================================
MAX_REEXPORT: t.CDefine = 512
# ============================================================
# ImportsGenMixin - 导入处理
# ============================================================
novirtual class ImportsGenMixin:
"""导入处理 Mixin(P6 方法化)"""
# --- 全局 re-export 映射表(原 _g_reexport_sha1s/_g_reexport_funcs/
# _g_reexport_srcs/_g_reexport_count) ---
Gen: Generator.Generator*
ReexportSha1s: bytes
ReexportFuncs: bytes
ReexportSrcs: bytes
ReexportCount: int
# --- 导入查找索引(把 O(串长) 的逐字符扫描降为 O(名字长) 哈希查找) ---
# imported_modules/_from_imports 是空格拼接的扁平串("a b c" / "n:m[:orig]"),
# 长度可达数千字符,而每个 Name 节点都会多次查询。索引与「当前串指针」绑定:
# 调用方查询的总是 add_* 刚生成的当前串,故命中索引快路径;其余来源(stub 期
# stdlib malloc 缓冲等)指针不匹配 → 回退逐字符扫描,语义完全不变。
# 索引表用 PermPool 分配(跨文件/Phase 存活),由对应 add_* 增量维护;key/value
# 均为指向该扁平串内部的指针,串在文件级 reset 前始终有效。
CurImportedModulesPtr: str
CurImportedModulesNames: hashtable.HashTable*
CurFromImportsPtr: str
CurFromImportsMods: hashtable.HashTable*
CurFromImportsOrigs: hashtable.HashTable*
# 注意:不声明 CurrentFile 字段——TypeGenMixin 已声明(Generator 展平块
# 只有一份,跨父类不去重规则下重复声明会导致字段错位)。本 Mixin 的
# set_current_file 与 CurrentFile 读取统一经 HandlesType 委托(延迟 import
# 规避 HandlesType↔HandlesImports 循环依赖)。
# _index_imported_module - 增量维护 imported_modules 查找索引
# old_ptr 为追加前的串指针(None = 首个模块),new_ptr 为追加后的新串,
# name_ptr 指向新串内的新模块名(key 直接引用串内部,无需复制)。
# 仅当 old_ptr 恰为当前索引绑定的串时复用其索引;否则放弃索引绑定并回退
# 逐字符扫描(语义不变)。
def _index_imported_module(self, old_ptr: str, new_ptr: str,
name_ptr: str) -> None:
"""增量更新 imported_modules 索引(复用旧索引或新建,失败则解除绑定)"""
if new_ptr is None or name_ptr is None:
return
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm is None:
return
idx: hashtable.HashTable* = None
if old_ptr is None:
# 新文件/新链首:建全新索引(旧索引随旧串一起废弃)
idx = hashtable.HashTable(perm)
elif old_ptr == self.CurImportedModulesPtr and self.CurImportedModulesNames is not None:
# 正常路径:复用当前串的索引并追加(不重建,内存 O(导入数))
idx = self.CurImportedModulesNames
else:
# 旧串非当前索引串(异常来源):解除绑定,回退逐字符扫描
self.CurImportedModulesPtr = None
self.CurImportedModulesNames = None
return
if idx is None:
self.CurImportedModulesPtr = None
self.CurImportedModulesNames = None
return
# 同名已在(重复导入)则跳过;否则写入(key/value 均指向 new_ptr 内部)
if idx.__contains__(name_ptr) == 0:
idx.set_str(name_ptr, name_ptr)
self.CurImportedModulesPtr = new_ptr
self.CurImportedModulesNames = idx
# _index_from_import - 增量维护 from_imports 查找索引
# entry_off 为新增 entry("local:module[:orig]")在新串内相对首字节的偏移。
# 模块表 key=local_name → 指向模块名切片;orig 表 key=local_name → 指向原始名
# 切片(无别名时写入 None,以"名存在但无原始名"区别于未命中)。
# 语义对齐逐字符扫描:精确名首次命中为准(不覆盖),star 模块名以最后一条为准。
def _index_from_import(self, old_ptr: str, new_ptr: str, entry_off: sizet,
local_name: str, module_name: str,
original_name: str) -> None:
"""增量更新 from_imports 索引(复用旧索引或新建,失败则解除绑定)"""
if new_ptr is None or local_name is None or module_name is None:
return
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm is None:
return
idx_m: hashtable.HashTable* = None
idx_o: hashtable.HashTable* = None
if old_ptr is None:
idx_m = hashtable.HashTable(perm)
idx_o = hashtable.HashTable(perm)
elif old_ptr == self.CurFromImportsPtr \
and self.CurFromImportsMods is not None \
and self.CurFromImportsOrigs is not None:
idx_m = self.CurFromImportsMods
idx_o = self.CurFromImportsOrigs
else:
self.CurFromImportsPtr = None
self.CurFromImportsMods = None
self.CurFromImportsOrigs = None
return
if idx_m is None or idx_o is None:
self.CurFromImportsPtr = None
self.CurFromImportsMods = None
self.CurFromImportsOrigs = None
return
local_len: sizet = string.strlen(local_name)
mod_len: sizet = string.strlen(module_name)
# 哈希键必须是 NUL 结尾串:扁平串内 local 段以 ':' 结尾(非 NUL),直接当键会让
# FNV 哈希到 "local:module..." 整段,与查询名 "local" 永不相等 → 索引恒 miss。
# 故用 PermPool 分配 NUL 结尾的稳定副本作键(与索引表同寿命),值仍指向扁平串
# 内模块名切片。
key_ptr: str = perm.alloc(local_len + 1)
if key_ptr is None:
self.CurFromImportsPtr = None
self.CurFromImportsMods = None
self.CurFromImportsOrigs = None
return
string.strncpy(key_ptr, local_name, local_len)
key_ptr[local_len] = '\0'
mod_ptr: str = new_ptr + entry_off + local_len + 1
if local_len == 1 and local_name[0] == '*':
# star import:模块名以最后一条为准(覆盖旧值,对齐 star_mod 语义)
idx_m.set_str(key_ptr, mod_ptr)
else:
if idx_m.__contains__(key_ptr) == 0:
idx_m.set_str(key_ptr, mod_ptr)
if idx_o.__contains__(key_ptr) == 0:
if original_name is not None and string.strcmp(original_name, local_name) != 0 \
and string.strlen(original_name) > 0:
idx_o.set_str(key_ptr, mod_ptr + mod_len + 1)
else:
# 首次出现即无别名(或别名为空)→ 该名查询返回 None(与逐字符扫描一致)
idx_o.set_str(key_ptr, None)
self.CurFromImportsPtr = new_ptr
self.CurFromImportsMods = idx_m
self.CurFromImportsOrigs = idx_o
# ============================================================
# 添加已导入模块名
# ============================================================
def add_imported_module(self, pool: memhub.MemBuddy*,
imported_modules: str,
name: str) -> str:
"""记录已导入的模块名,返回新的 imported_modules 字符串指针
字符串记账走 StubMergerStore.AppendImportedModule(单一真源,与 vpi
注入期同源);本方法只额外维护查询索引。
"""
if name is None:
return imported_modules
old_len: sizet = 0
if imported_modules is not None:
old_len = string.strlen(imported_modules)
buf: str = StubMergerStore.AppendImportedModule(pool, imported_modules, name)
if buf is None:
return imported_modules
if imported_modules is None:
# 索引:首个模块名(key 指向 buf 内部)
self._index_imported_module(None, buf, buf)
else:
# 索引:复用旧串索引并追加新名字(key 指向 buf 内的 name 段)
self._index_imported_module(imported_modules, buf, buf + old_len + 1)
return buf
# ============================================================
# 检查模块是否已导入
# ============================================================
def is_module_imported(self, imported_modules: str, name: str) -> int:
"""检查模块是否已导入(词边界精确匹配,避免子串误匹配)"""
if name is None or imported_modules is None:
return 0
# [索引快路径] 传入串即当前索引绑定的串(trans 字段的常态)→ O(名字长) 哈希查;
# 指针不匹配(stub 期 malloc 缓冲等)→ 回退下方逐字符扫描,语义不变。
if imported_modules == self.CurImportedModulesPtr \
and self.CurImportedModulesNames is not None:
if self.CurImportedModulesNames.__contains__(name) != 0:
return 1
# 索引未命中:绝不据此返回 0(索引不完整会误判"未导入"),
# 落到下方原逐字符扫描按真实串内容判定(正确性优先 | 性能次之)。
name_len: sizet = string.strlen(name)
cur: str = imported_modules
total_len: sizet = string.strlen(imported_modules)
ci: sizet = 0
while ci < total_len:
while ci < total_len and cur[ci] == ' ':
ci += 1
if ci >= total_len:
break
word_start: sizet = ci
while ci < total_len and cur[ci] != ' ':
ci += 1
word_len: sizet = ci - word_start
if word_len == name_len:
match: int = 1
for ei in range(name_len):
if cur[word_start + ei] != name[ei]:
match = 0
break
if match == 1:
return 1
return 0
# ============================================================
# 检查模块是否已导入或全局已登记(包子模块限定解析用)
# ============================================================
def is_module_imported_or_known(self, imported_modules: str, name: str) -> int:
"""已导入或可访问的模块(包子模块限定解析用)。
包子模块场景:`import intr` 只注册包名 "intr",但 intr.gdt / intr.idt
等子模块由包 __init__.vp 的 from . import X 在全局注册——模块限定
调用/属性访问(intr.gdt.init())的 is_module_imported 校验必须放行,
否则解析退化到函数名后缀匹配劫持其他模块同名函数
(serial.init 劫持 intr.gdt.init → GDT/IDT 未初始化根因)。
放行条件(保持单层名 fast-fail 语义不变):
1. 名字已在 _imported_modules(直接 import);
2. 名字在 Sha1Store 全局已登记,且其任一祖先包名已导入
(import intr → intr.gdt 可访问;`this.this()` 但 import this
被注释 → this 未导入 → 仍 fast-fail 报"模块未导入")。
"""
if self.is_module_imported(imported_modules, name) != 0:
return 1
if name is None:
return 0
if StubMergerStore.LookupSha1ByModName(name) is None:
return 0
nl: sizet = string.strlen(name)
dot_pos: sizet = 0
while dot_pos < nl:
if name[dot_pos] == '.':
pfx: str = stdlib.malloc(dot_pos + 1)
if pfx is not None:
string.strncpy(pfx, name, dot_pos)
pfx[dot_pos] = '\0'
hit: int = self.is_module_imported(imported_modules, pfx)
stdlib.free(pfx)
if hit != 0:
return 1
dot_pos += 1
return 0
# ============================================================
# 添加 from-import 名称映射
# ============================================================
def add_from_import(self, pool: memhub.MemBuddy*,
from_imports: str,
local_name: str,
module_name: str,
original_name: str = None) -> str:
"""添加 from-import 映射,返回新的 from_imports 字符串
格式:
无别名: "local_name:module_name"
有别名: "local_name:module_name:original_name"
(original_name 为源模块中的真实函数名,用于跨模块 SHA1 修饰)
字符串记账走 StubMergerStore.AppendFromImport(单一真源,与 vpi 注入期
同源;按精确长度分配,无旧的 256 缓冲静默截断);本方法只额外维护索引。
"""
if local_name is None or module_name is None:
return from_imports
old_len: sizet = 0
if from_imports is not None:
old_len = string.strlen(from_imports)
buf: str = StubMergerStore.AppendFromImport(
pool, from_imports, local_name, module_name, original_name)
if buf is None:
return from_imports
# 索引:首条 entry_off=0;追加时 entry 从 buf[old_len+1] 开始
entry_off: sizet = 0
if from_imports is not None:
entry_off = old_len + 1
self._index_from_import(from_imports, buf, entry_off,
local_name, module_name, original_name)
return buf
# ============================================================
# 查找 from-import 名称 → 返回原始函数名(别名场景)
#
# 对于 from X import Y as Z,返回 Y(源模块中的真实函数名)。
# 无别名时返回 None(local_name 即为原始名)。
# ============================================================
def lookup_from_import_original(self, from_imports: str,
local_name: str) -> str:
"""查找 from-import 别名对应的原始函数名,返回 None=无别名或未找到"""
if local_name is None or from_imports is None:
return None
# [索引快路径] 传入串即当前索引绑定的串 → O(名字长) 哈希查;否则回退扫描。
# orig 表:名存在但存 None = 首次出现即无别名 → 返回 None(与扫描一致)。
if from_imports == self.CurFromImportsPtr and self.CurFromImportsOrigs is not None:
if self.CurFromImportsOrigs.__contains__(local_name) != 0:
v_o: t.CPtr = self.CurFromImportsOrigs.__getitem__(local_name)
if v_o is not None:
return rawptr(v_o)
# 索引未命中 | 值为 None(首个即无别名):绝不据此直接返回 None——
# 真实串可能仍含该名,落到下方原逐字符扫描按串内容判定(正确性优先)。
name_len: sizet = string.strlen(local_name)
cur: str = from_imports
ci: sizet = 0
total_len: sizet = string.strlen(from_imports)
while ci < total_len:
# 跳过前导空格
while ci < total_len and cur[ci] == ' ':
ci += 1
if ci >= total_len:
break
# 找到第一个 ':' 的位置
colon1: sizet = ci
while colon1 < total_len and cur[colon1] != ':' and cur[colon1] != ' ':
colon1 += 1
if colon1 >= total_len or cur[colon1] != ':':
break
entry_name_len: sizet = colon1 - ci
# 跳过 star import
if entry_name_len == 1 and cur[ci] == '*':
ci = colon1
while ci < total_len and cur[ci] != ' ':
ci += 1
continue
# 比较名称
if entry_name_len == name_len:
match: int = 1
ei: sizet = 0
while ei < name_len:
if cur[ci + ei] != local_name[ei]:
match = 0
break
ei += 1
if match == 1:
# 找到匹配,检查是否有第三个字段(原始名)
pos: sizet = colon1 + 1
# 跳过模块名
while pos < total_len and cur[pos] != ':' and cur[pos] != ' ' and cur[pos] != '\0':
pos += 1
if pos < total_len and cur[pos] == ':':
# 有第三个字段: original_name
orig_start: sizet = pos + 1
orig_end: sizet = orig_start
while orig_end < total_len and cur[orig_end] != ' ' and cur[orig_end] != '\0':
orig_end += 1
orig_len: sizet = orig_end - orig_start
if orig_len > 0:
orig_buf: str = cur + orig_start
# 返回指向内部的指针(调用方需在使用期内保持 from_imports 有效)
return orig_buf
return None
return None
# 跳到下一个条目
ci = colon1
while ci < total_len and cur[ci] != ' ':
ci += 1
return None
# ============================================================
# 查找 from-import 名称 → 返回模块名或 None
#
# allow_star_fallback: 是否允许 star import 回退(默认 1=允许)。
# 当查询明确模块名(如 "BuildPipeline")时,应传 0 禁用回退,
# 避免被 star import 模块名误导(如 from stdint import * 后
# 查询 "BuildPipeline" 错误回退到 "stdint")。
# ============================================================
def lookup_from_import(self, from_imports: str, name: str,
allow_star_fallback: int = 1) -> str:
"""查找 from-import 名称,返回模块名或 None
支持 star import: 如果 from_imports 中有 "*:module" 条目,
且未找到精确名称匹配,且 allow_star_fallback != 0,则返回 star import 的模块名。
"""
if name is None or from_imports is None:
return None
# [索引快路径] 传入串即当前索引绑定的串 → O(名字长) 哈希查;否则回退扫描。
# name 恰为 "*" 时与扫描一致:不做精确匹配,直接走 star 回退。
if from_imports == self.CurFromImportsPtr and self.CurFromImportsMods is not None:
name_is_star: int = 0
if string.strlen(name) == 1 and name[0] == '*':
name_is_star = 1
if name_is_star == 0:
v_f: t.CPtr = self.CurFromImportsMods.__getitem__(name)
if v_f is not None:
return rawptr(v_f)
if allow_star_fallback != 0:
sv_f: t.CPtr = self.CurFromImportsMods.__getitem__("*")
if sv_f is not None:
return rawptr(sv_f)
# 索引精确名 | star 均未命中:绝不据此直接返回 None(索引不完整会漏报
# "名已 import",_op_head NameError 即此类),落到下方原逐字符扫描按串内容判定。
name_len: sizet = string.strlen(name)
cur: str = from_imports
ci: sizet = 0
total_len: sizet = string.strlen(from_imports)
star_mod: str = None
while ci < total_len:
# 跳过前导空格
while ci < total_len and cur[ci] == ' ':
ci += 1
if ci >= total_len:
break
# 找到 ':' 的位置
colon_pos: sizet = ci
while colon_pos < total_len and cur[colon_pos] != ':' and cur[colon_pos] != ' ':
colon_pos += 1
if colon_pos >= total_len or cur[colon_pos] != ':':
break
entry_name_len: sizet = colon_pos - ci
# 检测 star import ("*:module")
if entry_name_len == 1 and cur[ci] == '*':
mod_start: sizet = colon_pos + 1
mod_end: sizet = mod_start
while mod_end < total_len and cur[mod_end] != ' ' and cur[mod_end] != '\0' and cur[mod_end] != ':':
mod_end += 1
star_mod = cur + mod_start
# 比较名称
elif entry_name_len == name_len:
match: int = 1
ei: sizet = 0
while ei < name_len:
if cur[ci + ei] != name[ei]:
match = 0
break
ei += 1
if match == 1:
mod_start2: sizet = colon_pos + 1
mod_end2: sizet = mod_start2
# 模块名结束于: 空格、null、或第二个':'(别名格式的分隔符)
while mod_end2 < total_len and cur[mod_end2] != ' ' and cur[mod_end2] != '\0' and cur[mod_end2] != ':':
mod_end2 += 1
return cur + mod_start2
# 跳到下一个条目
ci = colon_pos
while ci < total_len and cur[ci] != ' ':
ci += 1
# 未找到精确匹配,仅在允许时回退到 star import
if allow_star_fallback == 0:
return None
return star_mod
# ============================================================
# register_reexport - 注册 re-export 映射
#
# 在 HandleImportFromNames 中调用:当处理 from .X import Y 时,
# 记录当前模块(通过 ModuleSha1)re-export 了函数 Y,源模块为 X。
#
# 幂等:重复注册相同映射不会增加条目。
# 内存用 stdlib.malloc 分配(全局存储器,跨 Phase 持久化)。
# ============================================================
def register_reexport(self, mod_sha1: str, func_name: str, source_module: str) -> int:
"""注册 re-export 映射(幂等),返回 0 成功,-1 失败"""
if mod_sha1 is None or func_name is None or source_module is None:
return -1
# 懒初始化
if self.ReexportSha1s is None:
self.ReexportSha1s = CompilerFactory.factory.PermPool.alloc(MAX_REEXPORT * 17)
self.ReexportFuncs = CompilerFactory.factory.PermPool.alloc(MAX_REEXPORT * 64)
self.ReexportSrcs = CompilerFactory.factory.PermPool.alloc(MAX_REEXPORT * 64)
if self.ReexportSha1s is None or self.ReexportFuncs is None or self.ReexportSrcs is None:
return -1
string.memset(self.ReexportSha1s, 0, MAX_REEXPORT * 17)
string.memset(self.ReexportFuncs, 0, MAX_REEXPORT * 64)
string.memset(self.ReexportSrcs, 0, MAX_REEXPORT * 64)
if self.ReexportCount >= MAX_REEXPORT:
return -1
# 幂等检查:查找是否已存在相同映射
for i in range(self.ReexportCount):
sidx: sizet = sizet(i) * 17
fidx: sizet = sizet(i) * 64
if string.strcmp(self.ReexportSha1s + sidx, mod_sha1) == 0:
if string.strcmp(self.ReexportFuncs + fidx, func_name) == 0:
# 已存在,更新 source_module(以防变化;64 字节槽,strncpy 钳制)
string.strncpy(self.ReexportSrcs + fidx, source_module, 63)
self.ReexportSrcs[fidx + 63] = '\0'
return 0
# 添加新条目(槽位定长 17/64 字节,strcpy 无界会越界写相邻槽,全部 strncpy 钳制)
idx2: sizet = sizet(self.ReexportCount) * 17
fidx2: sizet = sizet(self.ReexportCount) * 64
string.strncpy(self.ReexportSha1s + idx2, mod_sha1, 16)
self.ReexportSha1s[idx2 + 16] = '\0'
string.strncpy(self.ReexportFuncs + fidx2, func_name, 63)
self.ReexportFuncs[fidx2 + 63] = '\0'
string.strncpy(self.ReexportSrcs + fidx2, source_module, 63)
self.ReexportSrcs[fidx2 + 63] = '\0'
self.ReexportCount += 1
return 0
# ============================================================
# lookup_reexport - 查找 re-export 映射
#
# 给定模块 SHA1 和函数名,返回源模块名(如 "ast.lexer")或 None。
# 调用方通过 _lookup_module_sha1(source_module) 获取源模块的 SHA1。
# ============================================================
def lookup_reexport(self, mod_sha1: str, func_name: str) -> str:
"""查找 re-export 映射,返回源模块名或 None"""
if mod_sha1 is None or func_name is None:
return None
if self.ReexportSha1s is None or self.ReexportCount <= 0:
return None
for i in range(self.ReexportCount):
sidx: sizet = sizet(i) * 17
fidx: sizet = sizet(i) * 64
if string.strcmp(self.ReexportSha1s + sidx, mod_sha1) == 0:
if string.strcmp(self.ReexportFuncs + fidx, func_name) == 0:
return self.ReexportSrcs + fidx
return None
# ============================================================
# _resolve_relative_module - 解析相对导入为完整模块名
#
# 对于 from .X import Y(level=1, module="X")在包 pkg 中:
# 解析为 "pkg.X"
# 对于 from . import Y(level=1, module=None)在包 pkg 中:
# 解析为 "pkg"
# 对于 from ..X import Y(level=2, module="X")在包 pkg.sub 中:
# 解析为 "pkg.X"(先从 pkg.sub 上溯一级到 pkg,再追加 .X)
#
# Args:
# pool: 内存池
# current_package: 当前文件所属包名(如 "llvmlite"),None 表示无包
# level: 相对导入级别(0=绝对,1=., 2=..)
# module: ImportFrom 的 module 字段(可能为 None)
#
# Returns:
# 解析后的完整模块名;绝对导入(level<=0)直接返回 module;
# 无法解析时返回 module(回退到原始值)
# ============================================================
def _resolve_relative_module(self, pool: memhub.MemBuddy*,
current_package: str,
level: int,
module: str) -> str:
"""解析相对导入为完整模块名"""
if level <= 0:
return module
if current_package is None:
# 无包上下文(includes 顶级文件 / App 顶层文件):
# `from .X import Y` 的语义 = "同目录/根目录的 X 模块"
# (如 includes/viperstring.vp 的 `from .string import *`)。
# 直接返回 module 是正确解析,不是降级——禁止 fail-fast。
return module
# 从 current_package 开始,上溯 (level-1) 级
pkg: str = current_package
up: int = level - 1
while up > 0:
pkg_len: sizet = string.strlen(pkg)
last_dot: sizet = 0
found: int = 0
i: sizet = 0
while i < pkg_len:
if pkg[i] == '.':
last_dot = i
found = 1
i += 1
if found == 0:
# 无更多父级(相对导入级别超过包深度,上溯到根目录):
# 回退到 module 原始值(根目录模块语义),后续查找失败
# 由 import SHA1 检查明确报"模块未找到",此处不误杀。
pkg = None
break
# 截断到最后一个 '.' 处
new_pkg: str = pool.alloc(last_dot + 1)
if new_pkg is None:
self.Gen.TypeGenErr.fatal_error(None, f"相对导入解析失败:level={level} module='{module}'", "ImportError")
return module
string.strncpy(new_pkg, pkg, last_dot)
new_pkg[last_dot] = '\0'
pkg = new_pkg
up -= 1
if module is None:
# from . import Y → 模块就是包本身
return pkg
if pkg is None:
# 包已上溯到空(相对导入越界):回退到 module 原始值
return module
# 拼接 pkg + "." + module
pkg_len2: sizet = string.strlen(pkg)
mod_len: sizet = string.strlen(module)
buf: str = pool.alloc(pkg_len2 + 1 + mod_len + 1)
if buf is None:
self.Gen.TypeGenErr.fatal_error(None, f"相对导入解析失败:level={level} module='{module}'", "ImportError")
return module
string.strcpy(buf, pkg)
buf[pkg_len2] = '.'
string.strcpy(buf + pkg_len2 + 1, module)
return buf
# ============================================================
# compute_package_from_relpath - 从相对路径计算包名
#
# 包名 = 文件所在目录路径,将 / 和 \ 替换为 .
# 对于顶级文件(无目录分隔符),返回 None
#
# 示例:
# "llvmlite/__init__.vp" → "llvmlite"
# "llvmlite/__types.vp" → "llvmlite"
# "ast/parser.vp" → "ast"
# "ast.vp" → None(顶级文件)
# ============================================================
def compute_package_from_relpath(self, pool: memhub.MemBuddy*,
rel_path: str) -> str:
"""从相对路径计算包名(目录部分,分隔符替换为 .)"""
if rel_path is None:
return None
rlen: sizet = string.strlen(rel_path)
# 找最后一个 / 或 \
last_sep: sizet = 0
found: int = 0
i: sizet = 0
while i < rlen:
ch: char = rel_path[i]
if ch == '/' or ch == '\\':
last_sep = i
found = 1
i += 1
if found == 0:
# 无目录分隔符 → 顶级文件,无包
return None
# 复制目录部分,将 / 和 \ 替换为 .
dir_len: sizet = last_sep
buf: str = pool.alloc(dir_len + 1)
if buf is None:
return None
j: sizet = 0
while j < dir_len:
ch2: char = rel_path[j]
if ch2 == '/' or ch2 == '\\':
buf[j] = '.'
else:
buf[j] = ch2
j += 1
buf[dir_len] = '\0'
return buf
# ============================================================
# set_current_file - 设置当前编译的文件名
#
# 与 TypeGenMixin.set_current_file 行为一致(写 CurrentFile 字段),
# BuildPipeline/Phase1 通过 HandlesImports.set_current_file 委托调用。
# 经 HandlesType 委托(延迟 import 规避循环依赖),字段由 TypeGenMixin 持有。
# ============================================================
def set_current_file(self, file_path: str) -> None:
"""设置当前正在编译的文件名(与 HandlesType.set_current_file 同步调用)"""
# 文件级失效:索引绑定的是上一文件 arena 内的串指针,本文件 reset 后该
# 地址可能被新分配复用(如 _merge_template_imports 的合并串)→ 必须解绑,
# 否则可能命中陈旧索引返回错误结果。本文件的索引由后续 add_* 重新建立。
self.CurImportedModulesPtr = None
self.CurImportedModulesNames = None
self.CurFromImportsPtr = None
self.CurFromImportsMods = None
self.CurFromImportsOrigs = None
self.Gen.TypeGenResolve.set_current_file(file_path)
# ============================================================
# _report_import_error - 本地实现 Python 风格报错(避免循环依赖)
#
# 无法访问 HandlesType.fatal_error(HandlesType import 了本模块,
# 若本模块再 import HandlesType 会形成循环依赖),这里用简化格式:
# Traceback (most recent call last):
# File "<当前文件>", line <N>
# ImportError: <msg>
# ============================================================
def _report_import_error(self, node: ast.AST*, msg: str) -> None:
"""以 Python 风格报错并退出(本地实现,避免循环依赖)"""
stdio.printf("Traceback (most recent call last):\n")
lineno: int = 0
if node is not None:
lineno = node.lineno
file_name: str = self.Gen.TypeGenResolve.get_current_file()
if file_name is None:
file_name = "<unknown>"
stdio.printf(' File "%s", line %d\n', file_name, lineno)
# 显示行内内容(读源文件第 lineno 行的实际代码,便于快速定位)
if lineno > 0 and file_name is not None:
f_err: rawptr[fileio.File] = fileio.File(file_name, fileio.MODE.R)
if not f_err.closed:
line_buf_err: bytes = stdlib.malloc(512)
if line_buf_err is not None:
cur_ln: int = 0
while cur_ln < lineno:
r_err: LONG = f_err.readline(line_buf_err, 511)
if r_err <= 0:
break
cur_ln += 1
if cur_ln == lineno:
ll_err: sizet = string.strlen(line_buf_err)
while ll_err > 0 and (line_buf_err[ll_err - 1] == '\n' or line_buf_err[ll_err - 1] == '\r'):
ll_err -= 1
line_buf_err[ll_err] = '\0'
stdio.printf(" %s\n", line_buf_err)
break
stdlib.free(line_buf_err)
f_err.close()
stdio.printf("ImportError: %s\n", msg)
# 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印),批处理下终止
VLogger.repl_abort()
# ============================================================
# _is_local_module_file - 当前文件同目录存在 {mod_name}.vp/.py
#
# 多文件项目(如 Test/G4/App 的 caller1.vp import g4node)的兄弟模块
# 不在 includes 扫描存储器中(SHA1 在项目源文件列表),检查应豁免。
# ============================================================
def _is_local_module_file(self, mod_name: str) -> int:
"""当前文件同目录存在 {mod_name}.vp/.py → 项目内兄弟模块"""
cur: str = self.Gen.TypeGenResolve.get_current_file()
if cur is None or mod_name is None:
return 0
cur_len: sizet = string.strlen(cur)
if cur_len == 0:
return 0
sep_pos: sizet = cur_len
while sep_pos > 0:
sep_pos -= 1
ch: char = cur[sep_pos]
if ch == '/' or ch == '\\':
sep_pos += 1
break
mod_len: sizet = string.strlen(mod_name)
path_len: sizet = sep_pos + mod_len + 5
path_buf: bytes = stdlib.malloc(path_len + 1)
if path_buf is None:
return 0
# .vp 文件(Viper 源仅 .vp,不再回退 .py)
string.strncpy(path_buf, cur, sep_pos)
path_buf[sep_pos] = '\0'
string.strcat(path_buf, mod_name)
string.strcat(path_buf, ".vp")
fex: rawptr[fileio.File] = fileio.File(path_buf, fileio.MODE.R)
if not fex.closed:
fex.close()
stdlib.free(path_buf)
return 1
stdlib.free(path_buf)
return 0
# _check_import_sha1 - 导入时校验模块 SHA1 是否已注册(fast-fail)
# includes 路径配置错误时扫描结果为 0、模块不注册 SHA1,若延迟到实际跨模块调用才报错
# 会误导("跨模块调用 'bd.alloc' 找不到模块 SHA1"),故在 import 语句处立即检测。
# t/c 是内部库,不参与 includes 扫描,豁免检查。
def _check_import_sha1(self, node: ast.AST*, mod_name: str) -> int:
"""导入时校验模块 SHA1 是否已注册,未注册则立即报错退出
Args:
node: Import/ImportFrom AST 节点(提供行号)
mod_name: 导入的模块名(如 "memhub"、"w32.fileio")
Returns:
1=检查通过;模块未注册时直接报错退出(不会返回 0)
"""
if mod_name is None:
return 1
# t/c 是内部库,不参与 includes 扫描,豁免检查
# ctraits 是纯编译期宏库(.vpi 后缀,IncludesScanner 不扫描 .vpi),豁免检查
if string.strcmp(mod_name, "t") == 0 or string.strcmp(mod_name, "c") == 0 or string.strcmp(mod_name, "ctraits") == 0:
return 1
mod_sha1: str = StubMergerStore.LookupSha1ByModName(mod_name)
if mod_sha1 is None:
# App 兄弟模块豁免:当前文件同目录存在 {mod_name}.vp/.py
# (如 Test/G4/App/caller1.vp import g4node——同项目源文件,
# SHA1 在项目文件列表而非 includes 扫描存储器,不应报错)
if self._is_local_module_file(mod_name) != 0:
return 1
err_buf: str = stdlib.malloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256,
"模块 '%s' 未注册 SHA1(includes 扫描未找到该模块,请检查 project.json 的 includes 路径配置)",
mod_name)
self._report_import_error(node, err_buf)
else:
self._report_import_error(node, "导入模块 SHA1 未注册(includes 扫描未找到该模块)")
return 1
# ============================================================
# translate_import - 处理 import 语句,更新 trans._imported_modules
# (迁移自 HandlesImports.ImportsHandle.HandleImport)
# ============================================================
def translate_import(self, trans: HT.Translator*,
node: ast.AST*) -> int:
"""处理 import 语句,返回 0
记账走 StubMergerStore.NotePlainImport(**该约定的唯一实现**):与 vpi
注入期(VpiReader 顶层遍历)同口径,包含
· `import X as Y` → from_imports 追加 "Y:X"(别名反查模块真名的唯一
入口,见 StubMergerStore.LookupModNameByAlias);
· `import a.b.c` → imported_modules 追加顶层包名 "a"。
两侧各自只保留特有副作用(此处:导入 SHA1 校验)。
注:NotePlainImport 为纯记账,不再续接 imported/from 两个查询索引 ⇒ 这两
个串上的查找走 is_module_imported / lookup_from_import 的逐字符扫描回退
(与 vpi 注入期上下文同款,正确性不受影响)。
"""
imp: ast.Import* = ast.Import*(node)
if imp is None:
return 0
names: list[ast.AST*]* = imp.names
if names is None:
return 0
nn: sizet = len(names)
pool_val: memhub.MemBuddy* = trans.Pool
ctx: StubMergerStore.ImportCtx* = StubMergerStore.AllocImportCtx(
pool_val, trans._imported_modules, trans._from_imports)
if ctx is None:
return 0
for ni in range(nn):
alias: ast.Alias* = ast.Alias*(names.get(ni))
if alias is not None and alias.name is not None:
# 导入时立即校验模块 SHA1 是否注册(fast-fail,仅全量翻译时检查)
# declare_only=1/2 是 struct 预注册/import 扫描阶段:此时 SHA1 表
# 尚未包含后续才追加的模块(如 App 源文件),检查会造成误报。
if trans._declare_only == 0:
self._check_import_sha1(node, alias.name)
StubMergerStore.NotePlainImport(pool_val, ctx, alias.name, alias.asname)
trans._imported_modules = ctx.ImportedModules
trans._from_imports = ctx.FromImports
return 0
# ============================================================
# translate_import_from_module - 处理 from X import Y 的模块部分
# (迁移自 HandlesImports.ImportsHandle.HandleImportFromModule)
#
# 对相对导入(level > 0),使用 trans.CurrentPackage 解析为
# 完整模块名(如 __types → llvmlite.__types),确保 .deps.txt
# 记录的模块名与 SHA1 映射表一致。
# ============================================================
def translate_import_from_module(self, trans: HT.Translator*,
node: ast.AST*) -> int:
"""处理 from X import Y 的模块部分,更新 trans._imported_modules"""
impf: ast.ImportFrom* = ast.ImportFrom*(node)
if impf is None:
return 0
# 解析模块名:相对导入需补充包前缀
resolved: str = self._resolve_relative_module(trans.Pool, trans.CurrentPackage,
impf.level, impf.module)
if resolved is not None:
# 导入时立即校验模块 SHA1 是否注册(fast-fail,仅全量翻译时检查)
# from . import Y(impf.module is None):resolved 是包目录名
# (lib.core.Gen.Call 等无 __init__.vp 的目录包也允许这样导入,
# 见 CallGenCall.vp L12 from . import CallGenBase),目录本身不是
# 模块文件、无 SHA1 → 跳过包级校验;真实目标子模块(pkg.Y)由
# 下方 L759-762 逐个校验(LookupSha1ByModName / 本地兄弟文件)。
if trans._declare_only == 0 and impf.module is not None:
self._check_import_sha1(node, resolved)
trans._imported_modules = self.add_imported_module(trans.Pool, trans._imported_modules, resolved)
# from . import Y(impf.module is None):resolved 是包名(vpsdk),
# 各 name 是包的子模块 vpsdk.Y。须把完整子模块名也加入 _imported_modules,
# 否则别名 "syscall" 精确匹配不到包名 "vpsdk" → is_module_imported 返回 0
# → syscall._syscall1 / syscall.LOAD_SO 跨模块解析失败。
if impf.module is None:
names_m: list[ast.AST*]* = impf.names
if names_m is not None:
nn_m: sizet = len(names_m)
for ni_m in range(nn_m):
alias_m: ast.Alias* = ast.Alias*(names_m.get(ni_m))
if alias_m is not None and alias_m.name is not None:
rl_m: sizet = string.strlen(resolved)
rn_m: sizet = string.strlen(alias_m.name)
jb_m: str = trans.Pool.alloc(rl_m + rn_m + 2)
if jb_m is not None:
string.strcpy(jb_m, resolved)
jb_m[rl_m] = '.'
string.strcpy(jb_m + rl_m + 1, alias_m.name)
# from . import Y 的 Y 不一定是包子模块文件:可能是包
# __init__.vp 中定义的符号(类/常量,如 JsonValue),无独立
# 文件、不注册 SHA1。仅当完整名已注册 SHA1(真子模块)或是
# 本地文件时才校验+登记;否则跳过,符号映射由
# translate_import_from_names 的 from-import / re-export 处理。
if (StubMergerStore.LookupSha1ByModName(jb_m) is not None
or self._is_local_module_file(alias_m.name) != 0):
if trans._declare_only == 0:
self._check_import_sha1(node, jb_m)
trans._imported_modules = self.add_imported_module(trans.Pool, trans._imported_modules, jb_m)
return 0
# ============================================================
# translate_import_from_names - 处理 from X import Y 的名称部分
# (迁移自 HandlesImports.ImportsHandle.HandleImportFromNames)
#
# 同样使用解析后的完整模块名,确保 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"""
impf: ast.ImportFrom* = ast.ImportFrom*(node)
if impf is None:
return 0
# 解析模块名:相对导入需补充包前缀
resolved: str = self._resolve_relative_module(trans.Pool, trans.CurrentPackage,
impf.level, impf.module)
if resolved is not None:
names: list[ast.AST*]* = impf.names
if names is not None:
nn: sizet = len(names)
for ni in range(nn):
alias: ast.Alias* = ast.Alias*(names.get(ni))
if alias is not None and alias.name is not None:
local_name: str = alias.name
orig_name: str = None
if alias.asname is not None:
local_name = alias.asname
orig_name = alias.name
# from . import Y(impf.module is None):_resolve_relative_module
# 返回的是包名(如 vpsdk),但 Y 是包的子模块 vpsdk.Y。须映射
# local_name → 完整子模块名,否则 syscall.LOAD_SO 跨模块 CDefine /
# syscall._syscall1 跨模块调用在包里找不到。
fi_target: str = resolved
if impf.module is None and resolved is not None:
fl_t: sizet = string.strlen(resolved)
fn_t: sizet = string.strlen(alias.name)
jb_t: str = trans.Pool.alloc(fl_t + fn_t + 2)
if jb_t is not None:
string.strcpy(jb_t, resolved)
jb_t[fl_t] = '.'
string.strcpy(jb_t + fl_t + 1, alias.name)
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。逻辑收敛在
# _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,
# 非包名)。使跨模块调用 module.func() 能解析到正确 SHA1
if trans.ModuleSha1 is not None:
self.register_reexport(trans.ModuleSha1, local_name, fi_target)
return 0
def InitImportsGen(self) -> int:
"""初始化 ImportsGen 字段(构造时由 Generator 调用)"""
if self is None:
return 1
self.ReexportSha1s = None
self.ReexportFuncs = None
self.ReexportSrcs = None
self.ReexportCount = 0
# 导入查找索引字段(索引表由 add_* 增量创建,PermPool 分配)
self.CurImportedModulesPtr = None
self.CurImportedModulesNames = None
self.CurFromImportsPtr = None
self.CurFromImportsMods = None
self.CurFromImportsOrigs = None
return 0