From c268e5a9c05eeb508bbc686d7d3967911c0a36e6 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 27 Oct 2026 13:03:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(lsp):=20=E5=88=AB=E5=90=8D=E7=82=B9?= =?UTF-8?q?=E5=8F=B7=E5=AF=BC=E5=85=A5=E9=80=90=E6=AE=B5=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit import a.b.c as x 的路径段先于别名写入出现点,避免顺序倒置被丢弃;模块路径段即使解析不到模块也保持 namespace,便于识别缺失依赖。 --- App/lib/lsp/LspSemantic.vp | 12 ++++++++---- App/lib/lsp/LspSymbols.vp | 6 +++++- Tools/lsp/fixtures/gate10_use.vp | 2 ++ Tools/lsp/probe.py | 24 ++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/App/lib/lsp/LspSemantic.vp b/App/lib/lsp/LspSemantic.vp index 53596ec..10c53ca 100644 --- a/App/lib/lsp/LspSemantic.vp +++ b/App/lib/lsp/LspSemantic.vp @@ -287,10 +287,14 @@ def _TokenForOcc(ctx: LspSemCtx*, pool: memhub.MemManager*, typ = _TypeForTarget(tw) mods = _ModsForTarget(tw) if typ < 0: - # Builtin type names have no declaration; color them like class types. - if o.builtin_type == 0: - return None - typ = ST_CLASS + if o.module_path != 0: + # Dotted import segments stay namespaced even when the module cannot be found. + typ = ST_NAMESPACE + else: + # Builtin type names have no declaration; color them like class types. + if o.builtin_type == 0: + return None + typ = ST_CLASS tok: LspSemToken* = _NewTok(pool, o.line, o.sc, o.line, o.ec, typ) if tok is not None: tok.mods = mods diff --git a/App/lib/lsp/LspSymbols.vp b/App/lib/lsp/LspSymbols.vp index 79ad604..e483961 100644 --- a/App/lib/lsp/LspSymbols.vp +++ b/App/lib/lsp/LspSymbols.vp @@ -93,6 +93,8 @@ class LspOccurrence: force_module: int # 1 when the name is an unresolved builtin type (int/str/list/...), 0 otherwise. builtin_type: int + # 1 for a segment of a dotted module path (always a namespace, even when the module is absent). + module_path: int def __new__(self, pool: memhub.MemManager*) -> rawptr: p: LspOccurrence* = (LspOccurrence*)(pool.alloc(LspOccurrence.__sizeof__())) @@ -954,6 +956,7 @@ def _AddModulePathOccs(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScop o: LspOccurrence* = _AddOcc(pool, a, scope, name, lineno, col + start, int(string.strlen(name)), ast.ASTCtx.Load, 0) if o is not None: + o.module_path = 1 if start == 0: o.sym = head o.head = head @@ -1260,11 +1263,12 @@ def _WalkStmt(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScope*, if sym is not None: sym.import_kind = LSP_IMPORT_MODULE sym.import_mod = bound_mod - _DeclImportOcc(pool, a, scope, al, bound, sym) if al.asname is not None: _AddModulePathOccs(pool, a, scope, al.lineno, al.col_offset, al.name, 0, 0, None) + _DeclImportOcc(pool, a, scope, al, bound, sym) else: + _DeclImportOcc(pool, a, scope, al, bound, sym) _AddModulePathOccs(pool, a, scope, al.lineno, al.col_offset, al.name, 0, 1, sym) i += 1 diff --git a/Tools/lsp/fixtures/gate10_use.vp b/Tools/lsp/fixtures/gate10_use.vp index 19f70ae..afa05ac 100644 --- a/Tools/lsp/fixtures/gate10_use.vp +++ b/Tools/lsp/fixtures/gate10_use.vp @@ -5,6 +5,8 @@ from .gate10_stub import StubThing from .gate10_extra import * import gate10_types import gate10_pkg.sub.mod +import gate10_pkg.sub as pkgsub +import ghost_lib.path def star_use(e: Extra) -> int: diff --git a/Tools/lsp/probe.py b/Tools/lsp/probe.py index c9e3d21..1ccbc43 100644 --- a/Tools/lsp/probe.py +++ b/Tools/lsp/probe.py @@ -1545,6 +1545,30 @@ def gate10(exe: str) -> int: if not got.endswith(suffix): raise AssertionError(f"dotted import definition target is wrong: {pos} -> {got}") + alias_line = _position(text, "import gate10_pkg.sub as pkgsub") + for delta, segment in ((7, "gate10_pkg"), (18, "sub"), (25, "pkgsub")): + pos = {"line": alias_line["line"], "character": alias_line["character"] + delta} + if not _find_token(sem_tokens, pos["line"], pos["character"], + sem_types.index("namespace")): + raise AssertionError(f"aliased dotted import is not highlighted: {segment} {pos}") + alias_sub = {"line": alias_line["line"], "character": alias_line["character"] + 18} + alias_def = client.request( + "textDocument/definition", + {"textDocument": {"uri": uri}, "position": alias_sub}, + timeout=120.0, + ).get("result") + if not isinstance(alias_def, list) or not alias_def: + raise AssertionError(f"aliased dotted import definition failed: {alias_def}") + if not alias_def[0]["uri"].replace("\\", "/").endswith("gate10_pkg/sub/__init__.vp"): + raise AssertionError(f"aliased dotted import target is wrong: {alias_def}") + + ghost_line = _position(text, "import ghost_lib.path") + for delta, segment in ((7, "ghost_lib"), (17, "path")): + pos = {"line": ghost_line["line"], "character": ghost_line["character"] + delta} + if not _find_token(sem_tokens, pos["line"], pos["character"], + sem_types.index("namespace")): + raise AssertionError(f"unresolved module path is not highlighted: {segment} {pos}") + def at(needle: str, delta: int = 0, occurrence: int = 0) -> dict[str, int]: pos = _position(text, needle, occurrence) pos["character"] += delta