diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 4944b13..69fe99c 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -2155,23 +2155,69 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, elif sk == ast.ASTKind.ImportFrom: sifr: ast.ImportFrom* = ast.ImportFrom*(schild) sifr_mod: str = sifr.module - # 相对导入补包前缀:vpi 里 `from .base import X` 只存 module="base"(前导点记在 level),若按字面 "base" 精确查找则查不到 "ast.base" ⇒ 裸类名(AST 等)解析失败 ⇒ rawptr[AST] 等注解 fail-fast。 - # 故按 stub 自身相对路径还原包名(ast/parser.vp → "ast"),level==1 时拼成完整模块名 "ast.base" 再登记(level>1 保持原样,避免未确认路径形态时做过深上溯)。 + # 相对导入补包前缀 + 包 __init__ 判定(两者**都不再依赖 rel_path 表**)。 + # + # vpi 里 `from .base import X` 只存 module="base"(前导点记在 level),按字面 + # "base" 精确查找查不到 "ast.base" ⇒ 裸类名(AST 等)解析失败。包前缀的 + # 权威来源是**模块名表**:兄弟模块 S 的全名 = "<包前缀>.S"(如 "ast.stmts") + # ⇒ 包前缀 = S 的模块名去掉最后一段;而 Utils.PathToModuleName 把 + # `pkg/__init__.vp` 记成 "pkg"(**不带** .__init__,见其 :150 文档) + # ⇒ "本 stub 的模块名 == 该包前缀"即证明**本 stub 就是该包的 __init__**, + # 这正是重导出别名 entry 的登记条件。 + # 为什么要脱离 rel_path:rel_path 反查在部分运行形态下取不到条目 ⇒ + # 原 `strstr(rel_path,"__init__.vp")` 判定恒 false ⇒ 别名静默不登记 ⇒ + # `ast.ClassDef` 这类**限定名在泛型实参里保留包前缀**,与构造函数侧(真 + # 定义模块前缀)分裂成两个类型(现场 ClassGenGeneric.vp:92 的 CastError; + # 幽灵名 `list[1abcd5277fa442de.ClassDef*]` 在全部产物里恒为 opaque)。 + # 取不到模块名证据时回退旧的 rel_path 路线,行为不变。 + pkg_prefix_rt: str = None + if sifr.level > 0 and sifr_mod is not None and sifr_mod[0] != '\0': + _sib_sha_rt: str = StubMergerStore.LookupSha1ByModNameSuffix(sifr_mod) + if _sib_sha_rt is not None: + _sub_mod_rt: str = StubMergerStore.LookupModNameBySha1(_sib_sha_rt) + if _sub_mod_rt is not None: + _dot_rt: str = string.strrchr(_sub_mod_rt, '.') + if _dot_rt is not None: + _pfx_len_rt: sizet = sizet(uint64(_dot_rt) - uint64(_sub_mod_rt)) + if _pfx_len_rt > 0: + _my_mod_rt: str = StubMergerStore.LookupModNameBySha1(src_sha1) + # 确认:本 stub 的模块名必须**恰好等于**该前缀(= 本 stub + # 就是包 __init__)。后缀命中可能落到同后缀的别的模块, + # 故必须用本 stub 自己的模块名确认,不确认就让位给 rel_path。 + _conf_rt: int = 0 + if _my_mod_rt is not None and string.strlen(_my_mod_rt) == _pfx_len_rt: + if string.strncmp(_my_mod_rt, _sub_mod_rt, _pfx_len_rt) == 0: + _conf_rt = 1 + if _conf_rt != 0: + pkg_prefix_rt = pool.alloc(_pfx_len_rt + 1) + if pkg_prefix_rt is not None: + string.strncpy(pkg_prefix_rt, _sub_mod_rt, _pfx_len_rt) + pkg_prefix_rt[_pfx_len_rt] = '\0' sifr_mod_q: str = sifr_mod if sifr_mod is not None and sifr_mod[0] != '\0' and sifr.level == 1: - rel_p_r: str = StubMergerStore.LookupSha1RelPath(src_sha1) - if rel_p_r is not None: - slash_r: str = string.strrchr(rel_p_r, '/') - if slash_r is not None: - pkg_len_r: sizet = string.strlen(rel_p_r) - string.strlen(slash_r) - if pkg_len_r > 0: - qtotal_r: sizet = pkg_len_r + 1 + string.strlen(sifr_mod) + 1 - qbuf_r: str = pool.alloc(qtotal_r) - if qbuf_r is not None: - string.strncpy(qbuf_r, rel_p_r, pkg_len_r) - qbuf_r[pkg_len_r] = '.' - string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod) - sifr_mod_q = qbuf_r + if pkg_prefix_rt is not None: + qtotal_p: sizet = string.strlen(pkg_prefix_rt) + 1 + string.strlen(sifr_mod) + 1 + qbuf_p: str = pool.alloc(qtotal_p) + if qbuf_p is not None: + qbuf_p[0] = '\0' + string.strcat(qbuf_p, pkg_prefix_rt) + string.strcat(qbuf_p, ".") + string.strcat(qbuf_p, sifr_mod) + sifr_mod_q = qbuf_p + else: + rel_p_r: str = StubMergerStore.LookupSha1RelPath(src_sha1) + if rel_p_r is not None: + slash_r: str = string.strrchr(rel_p_r, '/') + if slash_r is not None: + pkg_len_r: sizet = string.strlen(rel_p_r) - string.strlen(slash_r) + if pkg_len_r > 0: + qtotal_r: sizet = pkg_len_r + 1 + string.strlen(sifr_mod) + 1 + qbuf_r: str = pool.alloc(qtotal_r) + if qbuf_r is not None: + string.strncpy(qbuf_r, rel_p_r, pkg_len_r) + qbuf_r[pkg_len_r] = '.' + string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod) + sifr_mod_q = qbuf_r if sifr_mod is not None: stub_imported_modules = _stub_add_imported(pool, stub_imported_modules, sifr_mod_q) # 方案A:包 __init__.vpi 的重导出类以包 sha1 注册别名 entry @@ -2179,16 +2225,26 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, # sha1 匹配,注册端按定义文件 sha1 → name+sha1 错位 fail-fast。 # 仅 __init__ 触发,普通模块 vpi 的跨模块 from-import 不注册, # 避免把类错误归属到当前模块 sha1) + # 包 __init__ 判定(**首选不依赖 rel_path 表**):pkg_prefix_rt 非空 + # 即上面的模块名证据成立——"本 stub 的模块名 == 兄弟模块名去掉末段" + # ⇒ 本 stub 就是该包的 __init__(同一事实,不重复计算)。 + # rel_path 反查取不到条目时原判定恒 false ⇒ 重导出别名静默不登记 ⇒ + # `ast.ClassDef` 这类限定名在泛型实参里保留**包前缀**,与定义模块 + # 前缀分裂(现场 ClassGenGeneric.vp:92 的 CastError)。 + # 仅当模块名证据不足时才回退旧 rel_path 判定(行为不变)。 is_pkg_init_rt: int = 0 - rel_path_rt: str = StubMergerStore.LookupSha1RelPath(src_sha1) - if rel_path_rt is not None: - # [FIX] 原判定 strcmp(rel_path + strlen(rel_path) - 9, "__init__.vp") - # 假设 "__init__.vp" 从 len-9 开始;实际包路径 "ast/__init__.vp" - # 长 15,该后缀从索引 4 起(11 字符),len-9=6 指向 "init__.vp" - # → 判定恒 false → 包重导出永不触发(ast.Attribute 未注册根因之一)。 - # 改用 strstr 判定 rel_path 包含 "__init__.vp"(仅包 __init__ 文件命中)。 - if string.strstr(rel_path_rt, "__init__.vp") is not None: - is_pkg_init_rt = 1 + if pkg_prefix_rt is not None: + is_pkg_init_rt = 1 + if is_pkg_init_rt == 0: + rel_path_rt: str = StubMergerStore.LookupSha1RelPath(src_sha1) + if rel_path_rt is not None: + # [FIX] 原判定 strcmp(rel_path + strlen(rel_path) - 9, "__init__.vp") + # 假设 "__init__.vp" 从 len-9 开始;实际包路径 "ast/__init__.vp" + # 长 15,该后缀从索引 4 起(11 字符),len-9=6 指向 "init__.vp" + # → 判定恒 false → 包重导出永不触发(ast.Attribute 未注册根因之一)。 + # 改用 strstr 判定 rel_path 包含 "__init__.vp"(仅包 __init__ 文件命中)。 + if string.strstr(rel_path_rt, "__init__.vp") is not None: + is_pkg_init_rt = 1 # 解析被 from-import 的模块 sha1:相对导入按 parser 表示判定—— # 前导点存于 level(from .exprs import → level=1, module="exprs", # 见 includes/ast/stmts.vp ImportFrom.level / parser.vp _parse_from_import), diff --git a/App/lib/Stub/StubMerger/StubMergerStore.vp b/App/lib/Stub/StubMerger/StubMergerStore.vp index 0a591a9..56254f5 100644 --- a/App/lib/Stub/StubMerger/StubMergerStore.vp +++ b/App/lib/Stub/StubMerger/StubMergerStore.vp @@ -620,3 +620,34 @@ def LookupSha1RelPath(sha1: str) -> str: ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN return StubMerger._sha1_store_rel + ridx return None + + +# ============================================================ +# LookupModNameBySha1 - 通过 SHA1 反查模块名 +# +# 用途:判定"本 stub 是否某包的 __init__"(模块名以 ".__init__" 结尾)——该事实 +# **不依赖 sha1→rel_path 字段**:rel_path 反查在部分运行形态下拿不到条目 ⇒ +# VpiReader 原来的 `strstr(rel_path, "__init__.vp")` 判定恒 false ⇒ 包重导出类的 +# 别名 entry 静默不登记 ⇒ `ast.ClassDef` 这类**限定名在泛型实参里保留包前缀**, +# 与构造函数侧(真定义模块前缀)分裂成两个类型(现场 ClassGenGeneric.vp:92 的 +# CastError;幽灵名 `list[1abcd5277fa442de.ClassDef*]` 全产物 opaque)。 +# 模块名与 rel_path 是同一次登记写入的(PopulateSha1MapStore / +# AppendToSha1MapStore),但模块名可用即足以作出判定与取包前缀。 +# +# 同一 SHA1 可能有多条(App 源文件按全路径模块名与短模块名各登记一次):返回 +# **第一条命中**;各条的包前缀一致,调用方只取"去 .__init__ 后的前缀"。 +# Args: sha1 SHA1 字符串(16 字符) +# Returns: 模块名字符串指针(只读,不要 free),None=未找到 +# ============================================================ +def LookupModNameBySha1(sha1: str) -> str: + """通过 SHA1 查找模块名,返回只读指针,None=未找到""" + if sha1 is None or sha1[0] == '\0': + return None + if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None: + return None + for i in range(StubMerger._sha1_store_count): + sidx_mb: sizet = sizet(i) * 17 + if string.strcmp(StubMerger._sha1_store_arr + sidx_mb, sha1) == 0: + midx_mb: sizet = sizet(i) * 64 + return StubMerger._sha1_store_mod + midx_mb + return None