fix(lsp): go-to-definition, hover, and stale session state#1366
Conversation
|
Hi @ciaran-matthew-dunne . Thanks for your PR! Let me know when it is ready for review. |
47074a8 to
819f767
Compare
|
@Alidra ! |
| let doc_file = string_field "uri" document in | ||
| Hashtbl.remove doc_table doc_file | ||
| Hashtbl.remove doc_table doc_file; | ||
| Hashtbl.remove completed_table doc_file |
There was a problem hiding this comment.
questionable that this actually solves any 'memory leak' issue. needs deeper investigation/profiling. assigned to @ciaran-matthew-dunne
| let mk_event m p = | ||
| `Assoc [ "jsonrpc", `String "2.0"; "method", `String m; "params", `Assoc p ] | ||
|
|
||
| (* LSP positions are uinteger; clamp defensively to avoid rejection by |
There was a problem hiding this comment.
why do we need this? when do we pass Pos.pos with negative or zero line/col values? then why does file_start have a col values 0? is there a difference between client-side/server-side line/col position indexing? does this differ between vscode/zed/emacs/vim???
There was a problem hiding this comment.
It may be the case that symbols generated by the inductive commands internally have negative positions (to be checked). I don't know whether this can affect LSP which normally only sees user declared symbols (which have >=0 positions).
Emacs counts columns from 0.
There was a problem hiding this comment.
Thanks. The inductive N : TYPE ≔ ... command generates an illegal position for ind_N where the end col is before the start col. I won't touch this since a comment in term.ml says: "These virtual positions are however important for exporting lambdapi files to other formats. "
json_of_range now handles the sanitization.
There was a problem hiding this comment.
With this PR, do you get meaningful positions for these generated symbols?
There was a problem hiding this comment.
Yes, doing go-to-definition on ind_T now points to the end of the inductive command that defined T.
|
Thank you @ciaran-matthew-dunne for the PR. |
819f767 to
0f64aa6
Compare
0f64aa6 to
95d725c
Compare
56ff791 to
de42231
Compare
The test harness was written against the old bundled LSP branch. The fixes split out into Deducteam#1366 (file:// URIs in definition responses, qualified-name resolution) and Deducteam#1444 (keyword-anchored OK hints) are not on this branch, so the cases asserting them are marked expectedFailure, each tagged with the PR it waits for. When those PRs merge and this branch rebases, they flip to unexpected-success and the decorators come off.
Python harness driving the server over stdio (make test_lsp): lifecycle, diagnostics, definition, hover, goals, symbols, completion, debouncing. Five cases assert behavior from Deducteam#1366 and Deducteam#1444 and are marked expectedFailure until those PRs merge.
fblanqui
left a comment
There was a problem hiding this comment.
Thanks Ciaran for this PR! You removed quite some logging code. It is not useful anymore, or do you have other ways to get these information?
| let get_symbols : state -> Term.sym Extra.StrMap.t = | ||
| fun (_, ss) -> ss.in_scope | ||
|
|
||
| let find_sym : state -> Term.qident Pos.loc -> Term.sym option = |
There was a problem hiding this comment.
Pure.find_sym is always called with (Pos.none qid) and Sig_state.find_sym uses the position in Fatal only. Therefore, Pure.find_sym could take a Term.qident as argument (with no position).
|
@ciaran-matthew-dunne Please add your name in AUTHORS.md and update CHANGES.md. |
I removed it because it seemed somewhat excessive and could degrade performance (e.g., the logging commands within
|
c69d25f to
617df5f
Compare
Go-to-definition and hover: - External symbols landed at line 1 of the target file (URI vs raw path comparison always failed). - "M.foo" resolved wrong: a local "foo" shadowed it, and with "require M as A", "A.foo" did not resolve at all. Route the qualified identifier through Sig_state.find_sym instead of the flat name map. - Hover returned a range shifted one column left (stale "column - 1" from 1-based days). - Illegal ranges reached the wire: the error fallback fabricated line-0 positions that became -1 after 1-based to 0-based conversion, and generated symbols carry virtual positions encoding their declaration order for the Dedukti export (a recursor points one column past the end of its inductive command, an inverted range). Add the Pos.file_start helper for the former, and sanitize in mk_range for the latter: floor coordinates at 0 and collapse inverted ranges to a caret at their start. - Ghost symbols (internal: unification rules, string literals) have no user-facing definition site and are skipped. Stale session state: initial_state restores Time and re-applies the opened file's package config, wiping other open documents' library mappings. A subsequent request on an older document resolved paths against the newest document's mappings. Call Pure.restore_time at the start of every affected request handler. Also: mk_definfo takes a plain filesystem path (its only caller never passes a URI); concise per-request logging in do_definition and hover_symInfo, replacing the unbounded token-map/symbol-map debug dumps; fix the misleading "Root state is missing" message in hover_symInfo.
617df5f to
52bb2b2
Compare
Python harness driving the server over stdio (make test_lsp): lifecycle, diagnostics, definition, hover, goals, symbols, completion, debouncing. Five cases assert behavior from Deducteam#1366 and Deducteam#1444 and are marked expectedFailure until those PRs merge.
1. Fix go-to-definition and hover
Four bugs in
do_definition/hover_symInfo. Three share a root cause: the handlers carried hand-rolled conversions (URI vs path, short-name vs qualified lookup, 1-based vs 0-based columns) instead of reusing the helpers the rest of the codebase uses.1a. Go-to-definition on an external symbol lands at line 1 of the target file
do_definitioncompared the symbol'ssym_pos.fname(a raw filesystem path) againstdoc.uri(afile://URI). The comparison never succeeds for external symbols, so the code fell back to a synthesized line-1 position.sym_posdirectly when present;mk_definfotakes the filesystem path (the only thing its caller ever passes) and builds the URI from it.1b. Qualified identifiers resolve to the wrong symbol; aliases don't resolve at all
(i). go-to-definition/hover on
M.foowhile a local symbol is also namedfootargets the localfoo.(ii). go-to-definition/hover on a qualified symbol (i.e.,
A.foogivenrequire M as A;) gives no result.Pure.find_symwrappingSig_state.find_symas used by scoper, with correct shadowing precedence and alias support. Both handlers now pass the full qualified identifier through it.1c. Hover highlights a range shifted one column to the left
hover_symInfowas the only handler still assembling its range JSON by hand, and its column arithmetic had drifted: a stalecolumn - 1from when columns were 1-based.LSP.mk_range_of_interval; hover now uses the sameRange.t→ JSON conversion that diagnostics and go-to-definition go through.1d. Illegal positions reach the client
uintegerwithstart <= end.json_of_rangesanitizes every outgoing range: coordinates floored at 0, inverted ranges collapsed to a caret at their start.2. Requests on one document run against another document's state
e.g., with
Foo.lpopen, openingNat.lpfrom Stdlib made subsequent queries inFoo.lpproduce URIs relative to<lib_root>/Stdlib/...rather than that of the package.Pure.initial_statedoesTime.restore t0and re-applies the opened file's package config, which installs that file's library mappings and wipes the mappings of every other open document (they live in timed state).Pure.restore_timeand call it at the start ofdo_definition,hover_symInfoanddo_symbols, so each request restores the timed state saved with its own document before touching timed globals likeLibrary.lib_mappings.