diff --git a/App/lib/lsp/LspIndex.vp b/App/lib/lsp/LspIndex.vp index edca1e7..235654d 100644 --- a/App/lib/lsp/LspIndex.vp +++ b/App/lib/lsp/LspIndex.vp @@ -874,19 +874,22 @@ def _ChainFromModule(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*, hops: int) -> LspTarget*: segs: str = rest while segs is not None: - if cur_is_file != 0: - base: str = LspFiles.BaseName(pool, cur) - if base is not None and string.strcmp(base, "__init__.vp") == 0: - # A package's __init__.vp is not a symbol table for its submodules; descend from - # the package directory for the remaining dotted segments. - cur = LspFiles.DirName(pool, cur) - cur_is_file = 0 - continue h: str = None r: str = None LspSymbols._SplitHead(pool, segs, &h, &r) if h is None: return None + if cur_is_file != 0: + base: str = LspFiles.BaseName(pool, cur) + if base is not None and string.strcmp(base, "__init__.vp") == 0: + # A package's __init__.vp can re-export members (`from .leaf import X`). + # Try those first; only descend into the package directory when the name is + # not a member (then it may be a submodule/package). + pkg_member: LspSymbols.LspSymbol* = ResolveInFile(pool, table_pool, cur, None, h) + if pkg_member is None: + cur = LspFiles.DirName(pool, cur) + cur_is_file = 0 + continue if cur_is_file != 0: member: LspSymbols.LspSymbol* = ResolveInFile(pool, table_pool, cur, None, h) if member is None: diff --git a/Tools/lsp/fixtures/gate10_pkg/__init__.vp b/Tools/lsp/fixtures/gate10_pkg/__init__.vp index 2d30372..fb12257 100644 --- a/Tools/lsp/fixtures/gate10_pkg/__init__.vp +++ b/Tools/lsp/fixtures/gate10_pkg/__init__.vp @@ -1 +1,2 @@ """Gate 10 dotted-import package.""" +from .leaf import reexport_fn diff --git a/Tools/lsp/fixtures/gate10_pkg/leaf.vp b/Tools/lsp/fixtures/gate10_pkg/leaf.vp new file mode 100644 index 0000000..6adf57d --- /dev/null +++ b/Tools/lsp/fixtures/gate10_pkg/leaf.vp @@ -0,0 +1,5 @@ +"""Gate 10 package re-export leaf module.""" + + +def reexport_fn(x: int) -> int: + return x diff --git a/Tools/lsp/fixtures/gate10_use.vp b/Tools/lsp/fixtures/gate10_use.vp index f3efb24..5071d0a 100644 --- a/Tools/lsp/fixtures/gate10_use.vp +++ b/Tools/lsp/fixtures/gate10_use.vp @@ -4,6 +4,7 @@ from .gate10_types import Widget, DOUBLE from .gate10_stub import StubThing from .gate10_extra import * import gate10_types +import gate10_pkg import gate10_pkg.sub.mod import gate10_pkg.sub as pkgsub import gate10_pkg.sub as sub @@ -39,6 +40,10 @@ def stub_chain() -> int: return StubThing.count +def reexport_use() -> int: + return gate10_pkg.reexport_fn(1) + + def else_block(flag: int) -> int: if flag != 0: return 1 diff --git a/Tools/lsp/probe.py b/Tools/lsp/probe.py index dce8b2a..7fccdd0 100644 --- a/Tools/lsp/probe.py +++ b/Tools/lsp/probe.py @@ -1522,6 +1522,19 @@ def gate10(exe: str) -> int: raise AssertionError(f"missing definition in {label}: {else_def}") if not else_def[0]["uri"].replace("\\", "/").endswith("gate10_extra.vp"): raise AssertionError(f"wrong definition in {label}: {else_def}") + re_pos = _position(text, "gate10_pkg.reexport_fn") + re_pos["character"] += len("gate10_pkg.") + if not _find_token(sem_tokens, re_pos["line"], re_pos["character"], sem_types.index("function")): + raise AssertionError(f"package re-export member is not highlighted: {re_pos}") + re_def = client.request( + "textDocument/definition", + {"textDocument": {"uri": uri}, "position": re_pos}, + timeout=120.0, + ).get("result") + if not isinstance(re_def, list) or not re_def: + raise AssertionError(f"package re-export definition failed: {re_def}") + if not re_def[0]["uri"].replace("\\", "/").endswith("gate10_pkg/leaf.vp"): + raise AssertionError(f"package re-export definition is wrong: {re_def}") const_pos = _position(text, "EXTRA_CONST") if not _find_token_mods(sem_tokens, const_pos["line"], const_pos["character"], sem_types.index("variable"),