fix(LlvmGenerator/Phase2Translator): 本模块声明的类钉死本模块 SHA1 前缀,消除同名类前缀漂移
类别:a(编译器缺陷)
现象(上一版 ab906f2c 之后的下一步):
includes/ast/parser.vp:1774 `kw: rawptr[AST] = KwNode(ps.pool, None, val)` 报
TypeError: Type of #1 arg mismatch:
%"fef783da4d24330d.Keyword"* != %"8312ce86a2fd82c0.Keyword"*
(llvmlite 该报文是「形参 != 实参」且下标 1-based ⇒ 实际就是**第 0 个形参 self**)
根因(磁盘实证,非推断):TransPyV/output/8312ce86a2fd82c0.stub.ll 内自相矛盾
- `%"fef783da4d24330d.Keyword" = type {…}` 与 `%"8312ce86a2fd82c0.Keyword" = type {…}`
两份同名类的 struct 同时存在;
- `declare %"fef783da4d24330d.Keyword"* @"8312ce86a2fd82c0.Keyword.__new__"(%"8312ce86a2fd82c0.Keyword"* %self, …)`
- `declare void @"8312ce86a2fd82c0.Keyword.__before_init__(%"fef783da4d24330d.Keyword"* %".1")`
其中 8312ce86a2fd82c0 = includes/ast/astaux.vp、fef783da4d24330d = includes/ast/tokens.vp
(TransPyV/temp/_sha1_map.txt),两者各自定义了一个 `class Keyword`。
机制:`_build_struct_sha1_map` 建的是「**类名** → 定义模块 SHA1」,但去重判据写成
`if sha1_key in struct_sha1_map` —— 用模块 SHA1 去查一张按类名建的表,判据恒假 ⇒
实际是「后扫到的覆盖先扫到的」,同名类归属随 os.listdir 顺序漂移。定义端
`StructGen._generate_structs` 在 `class_sha1_map` 为空时用此表取前缀,于是 astaux
**自己**的 Keyword 被命名成 `fef783da4d24330d.Keyword`(另一个模块的 sha1);而按本
模块前缀另建的 `8312ce86a2fd82c0.Keyword` 被 `__new__`/`__init__` 当作 self,
`HandlesClassDef._EmitNewFunctionLlvm` 生成的 `__before_init__` 却取
`Gen.structs['Keyword']`(错名前缀那份)⇒ 声明与调用点分叉。
修复(两点,均为正向事实判据):
1) LlvmGenerator(Gen 构造处,**先于** setup_from_symbol_table 内部的 _generate_structs):
把本文件顶层 ClassDef 名单钉死为本模块 SHA1,写进 Gen.class_sha1_map(其优先级高于
_struct_sha1_map)。"本文件声明的类"就在本次要翻译的 AST 里;class_sha1_map 是 Gen
实例级、每模块重建 ⇒ 非本模块声明的类不受影响。
2) Phase2Translator._build_struct_sha1_map:去重判据改为**类名**(先扫到的为准),并另按
模块 SHA1 去重"同一模块文件被两处目录重复解析"——归属不再随 listdir 顺序漂移。
未放宽任何 fail-fast;未改动 __new__/__before_init__ 的调用点。
This commit is contained in:
1 parent
ab906f2cde
commit
52ea21f082
2 files changed
+35
-2
No files matched your search
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user