fix(lsp): 通配导入链式重导出可解析

from A import * 的符号没有本地声明,C.b 查 C 的成员失败后原来直接返回;现在沿目标文件的 wildcard 链继续解析(带 hop 上限),模块属性与裸名字两条路径都覆盖。gate10 增加三级通配重导出 token/跳转断言(使用大小写不冲突的夹具名)。
This commit is contained in:
TermiNexus committed 2026-10-28 12:39:28 +08:00
1 parent 8e86eea159
commit c87f1b6f3c
7 files changed
+57 -5

No files matched your search

+31 -4
View File
@@ -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)
+5
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
from gate10_wild_reexport import *
+1
View File
@@ -0,0 +1 @@
from gate10_wild_b import *
+2
View File
@@ -0,0 +1,2 @@
def wild_b() -> int:
return 1
@@ -0,0 +1 @@
from gate10_wild_leaf import wild_b
+16 -1
View File
@@ -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"),