From c87f1b6f3ca951f46337e2ee6844ddaa87111586 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 28 Oct 2026 12:39:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(lsp):=20=E9=80=9A=E9=85=8D=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E9=93=BE=E5=BC=8F=E9=87=8D=E5=AF=BC=E5=87=BA=E5=8F=AF?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit from A import * 的符号没有本地声明,C.b 查 C 的成员失败后原来直接返回;现在沿目标文件的 wildcard 链继续解析(带 hop 上限),模块属性与裸名字两条路径都覆盖。gate10 增加三级通配重导出 token/跳转断言(使用大小写不冲突的夹具名)。 --- App/lib/lsp/LspIndex.vp | 35 +++++++++++++++++++--- Tools/lsp/fixtures/gate10_use.vp | 5 ++++ Tools/lsp/fixtures/gate10_wild_b.vp | 1 + Tools/lsp/fixtures/gate10_wild_c.vp | 1 + Tools/lsp/fixtures/gate10_wild_leaf.vp | 2 ++ Tools/lsp/fixtures/gate10_wild_reexport.vp | 1 + Tools/lsp/probe.py | 17 ++++++++++- 7 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 Tools/lsp/fixtures/gate10_wild_b.vp create mode 100644 Tools/lsp/fixtures/gate10_wild_c.vp create mode 100644 Tools/lsp/fixtures/gate10_wild_leaf.vp create mode 100644 Tools/lsp/fixtures/gate10_wild_reexport.vp diff --git a/App/lib/lsp/LspIndex.vp b/App/lib/lsp/LspIndex.vp index 235654d..b22b923 100644 --- a/App/lib/lsp/LspIndex.vp +++ b/App/lib/lsp/LspIndex.vp @@ -893,7 +893,8 @@ def _ChainFromModule(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, if cur_is_file != 0: member: LspSymbols.LspSymbol* = ResolveInFile(pool, table_pool, cur, None, h) if member is None: - return None + # A module file may re-export the name through `from X import *`. + return _ResolveWildcardsInFile(ws, pool, table_pool, cur, segs, hops + 1) if r is None: if member.import_kind != LspSymbols.LSP_IMPORT_NONE: if member.import_kind == LspSymbols.LSP_IMPORT_FROM: @@ -980,12 +981,37 @@ def _ModuleFile(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, return _ModulePath(ws, pool, s.import_mod) +def _ResolveWildcardsInFile(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, + table_pool: memhub.MemManager*, file: str, + name: str, hops: int) -> LspTarget*: + """Follow the `from X import *` wildcards declared in file to resolve name (bounded).""" + if file is None or name is None or hops > LSP_IMPORT_HOPS: + return None + text: str = LspFiles.ReadSource(pool, file) + if text is None: + return None + doc: LspDocuments.LspDocument* = LspSource.NewTempDoc(pool, text) + if doc is None: + return None + a: LspSymbols.LspAnalysis* = LspSymbols.Analyze(pool, table_pool, doc, file) + if a is None or a.wildcards is None: + return None + wi: int = 0 + while wi < int(len(a.wildcards)): + w: LspSymbols.LspSymbol* = a.wildcards.get(sizet(wi)) + wi += 1 + t: LspTarget* = ResolveWildcard(ws, pool, table_pool, file, w, name, hops + 1) + if t is not None: + return t + return None + + def ResolveWildcard(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, table_pool: memhub.MemManager*, from_file: str, wildcard: LspSymbols.LspSymbol*, - name: str) -> LspTarget*: + name: str, hops: int = 0) -> LspTarget*: """Resolve a name (optionally `Name.member...`) supplied by `from module import *`.""" - if wildcard is None or name is None: + if wildcard is None or name is None or hops > LSP_IMPORT_HOPS: return None file: str = _ResolveFromFile(ws, pool, from_file, wildcard) if file is None or not os.isfile(file): @@ -997,7 +1023,8 @@ def ResolveWildcard(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, return None member: LspSymbols.LspSymbol* = ResolveInFile(pool, table_pool, file, None, h) if member is None: - return None + # The target file may itself re-export the name through `from X import *`. + return _ResolveWildcardsInFile(ws, pool, table_pool, file, name, hops + 1) base: LspTarget* = None if member.import_kind != LspSymbols.LSP_IMPORT_NONE: base = _ResolveFromTarget(ws, pool, table_pool, member.file, member, 0) diff --git a/Tools/lsp/fixtures/gate10_use.vp b/Tools/lsp/fixtures/gate10_use.vp index 7aef0fe..e016e98 100644 --- a/Tools/lsp/fixtures/gate10_use.vp +++ b/Tools/lsp/fixtures/gate10_use.vp @@ -5,6 +5,7 @@ from .gate10_stub import StubThing from .gate10_extra import * import gate10_types import gate10_pkg +import gate10_wild_c import gate10_pkg.sub.mod import gate10_pkg.sub as pkgsub import gate10_pkg.sub as sub @@ -48,6 +49,10 @@ def ternary_use(flag: int) -> int: return extra_fn(EXTRA_CONST) if flag != 0 else extra_fn(EXTRA_CONST + 1) +def wild_use() -> int: + return gate10_wild_c.wild_b() + + def else_block(flag: int) -> int: if flag != 0: return 1 diff --git a/Tools/lsp/fixtures/gate10_wild_b.vp b/Tools/lsp/fixtures/gate10_wild_b.vp new file mode 100644 index 0000000..8b3aef4 --- /dev/null +++ b/Tools/lsp/fixtures/gate10_wild_b.vp @@ -0,0 +1 @@ +from gate10_wild_reexport import * diff --git a/Tools/lsp/fixtures/gate10_wild_c.vp b/Tools/lsp/fixtures/gate10_wild_c.vp new file mode 100644 index 0000000..8de8060 --- /dev/null +++ b/Tools/lsp/fixtures/gate10_wild_c.vp @@ -0,0 +1 @@ +from gate10_wild_b import * diff --git a/Tools/lsp/fixtures/gate10_wild_leaf.vp b/Tools/lsp/fixtures/gate10_wild_leaf.vp new file mode 100644 index 0000000..a247b8f --- /dev/null +++ b/Tools/lsp/fixtures/gate10_wild_leaf.vp @@ -0,0 +1,2 @@ +def wild_b() -> int: + return 1 diff --git a/Tools/lsp/fixtures/gate10_wild_reexport.vp b/Tools/lsp/fixtures/gate10_wild_reexport.vp new file mode 100644 index 0000000..35f67ba --- /dev/null +++ b/Tools/lsp/fixtures/gate10_wild_reexport.vp @@ -0,0 +1 @@ +from gate10_wild_leaf import wild_b diff --git a/Tools/lsp/probe.py b/Tools/lsp/probe.py index ab8443f..d9a5f6f 100644 --- a/Tools/lsp/probe.py +++ b/Tools/lsp/probe.py @@ -1455,7 +1455,9 @@ def gate10(exe: str) -> int: # The fixtures live outside the project's source roots; copy them into an isolated workspace # so the cross-file index contains the module, the stub and the consumer. workspace = Path(tempfile.mkdtemp(prefix="transpyc_lsp_gate10_")) - names = ("gate10_types.vp", "gate10_stub.vpi", "gate10_extra.vp", "gate10_use.vp") + names = ("gate10_types.vp", "gate10_stub.vpi", "gate10_extra.vp", "gate10_use.vp", + "gate10_wild_leaf.vp", "gate10_wild_reexport.vp", + "gate10_wild_b.vp", "gate10_wild_c.vp") for name in names: shutil.copy(fixtures / name, workspace / name) shutil.copytree(fixtures / "gate10_pkg", workspace / "gate10_pkg") @@ -1552,6 +1554,19 @@ def gate10(exe: str) -> int: raise AssertionError(f"missing definition in {label}: {t_def}") if not t_def[0]["uri"].replace("\\", "/").endswith("gate10_extra.vp"): raise AssertionError(f"wrong definition in {label}: {t_def}") + wild_pos = _position(text, "gate10_wild_c.wild_b") + wild_pos["character"] += len("gate10_wild_c.") + if not _find_token(sem_tokens, wild_pos["line"], wild_pos["character"], sem_types.index("function")): + raise AssertionError(f"wildcard re-export chain member is not highlighted: {wild_pos}") + wild_def = client.request( + "textDocument/definition", + {"textDocument": {"uri": uri}, "position": wild_pos}, + timeout=120.0, + ).get("result") + if not isinstance(wild_def, list) or not wild_def: + raise AssertionError(f"wildcard re-export chain definition failed: {wild_def}") + if not wild_def[0]["uri"].replace("\\", "/").endswith("gate10_wild_leaf.vp"): + raise AssertionError(f"wildcard re-export chain definition is wrong: {wild_def}") const_pos = _position(text, "EXTRA_CONST") if not _find_token_mods(sem_tokens, const_pos["line"], const_pos["character"], sem_types.index("variable"),