fix(lsp): show success hints on the introducing keyword of commands and tactics#1444
fix(lsp): show success hints on the introducing keyword of commands and tactics#1444ciaran-matthew-dunne wants to merge 4 commits into
Conversation
348fe8c to
e3ae767
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.
add4a14 to
e8a35a8
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.
e8a35a8 to
f25e739
Compare
| | None -> None | ||
| | Some p -> | ||
| Pos.(Some { p with start_line = p.end_line; start_col = p.end_col + 1 | ||
| ; end_line = p.end_line; end_col = p.end_col + 2 }) |
There was a problem hiding this comment.
There are several problems here:
-
start_offset and end_offset should be set properly as well
-
Why not taking end_col = p.end_col + 1 (";" is one character long only)?
-
But this position is wrong if the semicolon is at the beginning of the line as in:
command ...
;
In this case, we should use {p with start_line = p.end_line; start_col = 0; end_col = 0} instead.
Problem:
command ...
; // semicolon with one space before
gives the same position as the previous case because of the current definition of extend_pos which extends a position until the previous column of the next token if it is >0.
So we should instead do:
let at_semicolon (pos:Pos.popt): Pos.popt =
match pos with
| None -> None
| Some p ->
if p.end_col = 0 then {p with start_offset=p.end_offset; start_line=p.end_line; start_col=0;
end_offset=p.end_offset+1; end_col=1}
(* in this case we don't know whether ";" is at position 0 or 1, so we include both columns *)
else {p with start_offset=p.end_offset+1; start_line=p.end_line; start_col=p.end_col+1;
end_offset=P.end_offset+1; end_col=p.end_col+1}
To be fully precise and not include a useless space, we need to include ";" in the position of a command.
There was a problem hiding this comment.
Wait. The problem with the position of the trailing ";" comes from line 1719 of lpParser.ml. Instead of
| SEMICOLON -> c
one should do
| SEMICOLON -> extend_pos lb ... c
I think that that should fix the issue(*)
(*) In truth, the fact that the "command" parser is not allowed to consume the terminator is just plain weird and a symptom of bad coding elsewhere. The point is that in pure.ml (if I remember correctly) every time a new command is to be parsed, the lexbuf at the char Stream.t level is passed again. Instead the lexbuf at the lookahead level (the one I implemented) should be passed around. Right now the main parser functions exported by parser take in input the lexbuf at char level and build on-the-fly a new lexbuf at the backtracking level. Instead one should export a function to turn a lexbuf into an higher level lexbuf and then pass that again and again. Or, a less impacting alternative on the code, the parse_lexbuf function in parsing should take the low level lexbuf and return a closure that goes from unit to command, remembering the high level lexbuf inside the closure. Then pure.ml could just iterate this closure.
There was a problem hiding this comment.
Oh well, I decided that the diagnostic should sit on the keyword of the command anyway.
e.g., the diagnostic for the command constant symbol foo : TYPE; is placed on symbol,
and similarly the diagnostic for [a : Set] inductive T : TYPE ≔ ... is placed on inductive.
Frédéric talked to me this morning about the possibility of retiring the use of the semicolon in lambdapi. So hopefully these changes in this PR are compatible with any future changes to the parser.
There was a problem hiding this comment.
The fix at line 1719 I proposed should be implemented anyway since it is nevertheless a bug IMHO. About the idea of retiring the ; terminator: in that case it will be MANDATORY to also apply the long explanation improvement I described, because otherwise with no semicolon to stop at one character will be definitely lost at the next call.
| | None -> None | ||
| | Some p -> | ||
| Pos.(Some { p with end_line = p.start_line | ||
| ; end_col = p.start_col + String.length kw }) |
There was a problem hiding this comment.
end_offset should be set properly as well, I believe to the same value as for end_col
35fcff5 to
03b56f2
Compare
A bare open (not preceded by require) was parsed as require open since the LL(1) parser rework (Deducteam#1441), silently loading not yet required modules instead of failing, and pretty-printing back as require open.
The severity-4 "OK" diagnostic of a command or tactic previously spanned the whole command or tactic, so the success underline covered symbol bodies, rule right-hand sides and whole proofs. Instead: - A command's hint is shown on its terminating ";". The command's recorded position stops one character before it (the parser's extend_pos), so lp_doc derives the ";" position from it (at_semicolon). - Symbol declarations always create proof data, so their "OK" went through the proof path and landed on the symbol name. The initial goals stay anchored at the symbol (for the goals panel), but the visible hint now goes on the ";". - A tactic's hint is shown on its leading keyword (Syntax.tactic_keyword, exposed as Pure.Tactic.keyword), since a tactic's end is ill-defined once it has subproofs. These diagnostics also anchor the goals panel, whose lookup (closest_before) only reads the start of each position, which does not move. Also rename the lp_doc node field "ast" to "cmd".
Set the character offsets of hint positions consistently with their columns (at_keyword left end_offset at the end of the whole tactic, at_semicolon did not update offsets at all). Handle a ";" at the beginning of a line: the parser's extend_pos does not back up at column 0, so a command position with end_col = 0 cannot distinguish a ";" at column 0 from one at column 1 (the hint used to underline column 1, one character past a line-initial ";"). Cover both columns in that case.
Show the "OK" hint of a command on its keyword ("symbol", "rule",
"require", ...) rather than on the terminating ";".
The keyword of a symbol, inductive or open command is not always at
the command's start (modifiers or parameters may precede it), so the
parser records the keyword position in the AST for these three
constructors. For the other commands, Syntax.command_keyword_pos
computes it as a prefix of the command's position. Tactics use the
same mechanism (tactic_keyword_pos); at_keyword and at_semicolon
disappear from lp_doc, replaced by Pos.prefix.
03b56f2 to
1e2e768
Compare
Stacked on #1450 (bare
openparsing fix): the first commit in the diff belongs to that PR; review the last three here.The LSP server emits severity-4 "OK" diagnostics for every successfully checked command and tactic. These previously spanned the whole command or tactic, so the success underline covered symbol bodies, entire proofs and rule right-hand sides. With this PR the hint is shown on the introducing keyword only (
symbol,rule,require, …;assume,rewrite, …).Notes:
Pos.prefix). This is not possible forsymbol(modifiers may precede the keyword),inductive(modifiers and parameters may) andopen(aprivatemodifier may), so for these three constructors the parser records the keyword position in the AST.Syntax.command_keyword_poshandles all cases, and falls back to the whole command's position when no keyword position was recorded (Dedukti input).requireneeds no recorded position: with fix parsing of bare open: do not load modules as require open does #1450 it is always the first keyword of its command.closest_before) only reads position starts. Only the end of the range shrinks, soproof/goalsanswers are unchanged. The keyword is used rather than the tactic's end because the end is ill-defined once a tactic has subproofs.symbolkeyword.tactic_keyword,query_keyword) have no wildcard case, so adding a constructor is a compile error until they are updated.lp_docnode fieldasttocmd.