diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index 7127580..53dc1c7 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -1152,6 +1152,7 @@ class Phase2Translator: pyi_dirs: list[str] = [self.temp_dir] if os.path.isdir(_GLOBAL_CACHE_DIR): pyi_dirs.append(_GLOBAL_CACHE_DIR) + _scanned_sha1: set[str] = set() for scan_dir in pyi_dirs: if not os.path.isdir(scan_dir): continue @@ -1161,15 +1162,24 @@ class Phase2Translator: sha1_key = fname.replace('.vpi', '').replace('.pyi', '') if sha1_key not in valid_sha1_keys: continue - if sha1_key in struct_sha1_map: + # 同一模块在两处目录都有文件时只解析一次(与下面的类名归属判据无关) + if sha1_key in _scanned_sha1: continue + _scanned_sha1.add(sha1_key) pyi_path = os.path.join(scan_dir, fname) # 复用 parse_python_file 的 AST 缓存(.pyi 文件可能被多处解析) _, tree = parse_python_file(pyi_path) if tree is None: continue for node in ast.walk(tree): - if isinstance(node, ast.ClassDef): + # 每类只登记一次,**先扫到的为准**:判据必须是**类名**。 + # 原实现写成 `if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张 + # 按**类名**建的表,判据恒假 ⇒ 实际是"后扫到的覆盖先扫到的", + # 同名类的归属随 os.listdir 顺序漂移(现场:includes/ast/astaux.vp 与 + # includes/ast/tokens.vp 各有一个 `class Keyword`,定义端拿到的前缀 + # 可能是**另一个模块**的 sha1 ⇒ 同一类出现两个 struct 实例、 + # `__before_init__` 声明与调用点分叉)。 + if isinstance(node, ast.ClassDef) and node.name not in struct_sha1_map: struct_sha1_map[node.name] = sha1_key return struct_sha1_map diff --git a/lib/core/Translator/LlvmGenerator.py b/lib/core/Translator/LlvmGenerator.py index f96dbad..16a162a 100644 --- a/lib/core/Translator/LlvmGenerator.py +++ b/lib/core/Translator/LlvmGenerator.py @@ -694,6 +694,29 @@ class LlvmGeneratorMixin: self.LlvmGen.hotspot_sites_all = getattr(self, 'HotspotSitesAll', False) self.LlvmGen.hotspot_callees = getattr(self, 'HotspotCallees', None) self.LlvmGen.hotspot_sites_module_local = getattr(self, 'HotspotSitesModuleLocal', False) + # 【本模块声明的类:SHA1 前缀钉死为本模块(2026-09-30)】 + # 必须**先于** setup_from_symbol_table —— 其内部 _generate_structs 会为符号表里的 + # 每个类建 struct,IR 名前缀取自 `class_sha1_map`(空)→ `_struct_sha1_map` + # (Phase2 扫描各 .vpi 得到的"裸类名 → 定义模块 SHA1")。两张表都按**裸类名** + # 索引,两个模块各定义同名类时只能留一个:现场 includes/ast/astaux.vp 的 + # `class Keyword(AST)` 与 includes/ast/tokens.vp 的 `class Keyword(t.CEnum)` + # 同名,`_build_struct_sha1_map` 的"每类只登记一次"判据误用了 sha1 键(恒假、 + # 实为后扫到的覆盖先扫到的)⇒ astaux 自己那份被命名成 + # `%"fef783da4d24330d.Keyword"`(= tokens 的 sha1)。于是同一类在本模块里出现 + # 两个 struct 实例:`__new__`/`__init__` 用按本模块前缀另建的那份 + # `%"8312ce86a2fd82c0.Keyword"*`,而 `HandlesClassDef._EmitNewFunctionLlvm` + # 生成的 `__before_init__` 用 `Gen.structs['Keyword']`(错名前缀那份)⇒ 声明与 + # 调用点分叉,llvmlite 报 + # "Type of #1 arg mismatch: %"fef783da4d24330d.Keyword"* != %"8312ce86a2fd82c0.Keyword"*" + # (报文是 `形参 != 实参`,下标 1-based ⇒ 指的其实是第 0 个形参 self)。 + # "本文件顶层 ClassDef 名单"是**正向事实**(就在本次要翻译的 AST 里,且 + # class_sha1_map 是 Gen 实例级、每模块重建)⇒ 命中即钉死为本模块 SHA1; + # 非本模块声明的类不受影响,仍走原来的全局映射。 + _DefSha1: str | None = getattr(self, '_module_sha1', None) + if _DefSha1: + for _Nd in ast.iter_child_nodes(Tree): + if isinstance(_Nd, ast.ClassDef) and getattr(_Nd, 'name', None): + self.LlvmGen.class_sha1_map[_Nd.name] = _DefSha1 self.LlvmGen.setup_from_symbol_table(self.SymbolTable) if hasattr(self, 'CurrentFile') and self.CurrentFile: self.LlvmGen._set_source_info(self.CurrentFile)