Review fixes for #58: inline clause correctness and robustness #68
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/inline-clauses-review-findings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
}}>,}>inside comments/strings, and empty or unterminated wrappers no longer lose the annotation (they becomeinvalid_inline_clause_contents, and the linkedINV/SPECsurvives).SPEC_INLINEwithoutSPECare reported.unavailable/not-found, finds the real top-level:=, keeps one terminator, and is CRLF-safe.examples/sumarraycorruption and a README update.Verified with
npm run check, 139 unit tests and 115/115 grammar corpus cases; the extension-host tests run in CI.The inline contents came straight from the tree-sitter token and were written into the proof document unfiltered, so anything the grammar happened to accept ended up in a Rocq definition. Move the wrapper/body rules into one pure, unit-tested helper and use it when the annotation is checked: the body must be wrapped in <{ ... }>, must not be empty, and must not contain the C annotation terminator or a nested wrapper. Annotations now carry the unwrapped body, which is what gets written, instead of re-deriving it at write time.Forwarding cleared the whole diagnostic collection and then repopulated only the proof document that triggered the event, so with two C files inlining into different proof documents a diagnostics update in one wiped the forwarded diagnostics of the other until its next update. Recompute every inline target on each pass instead, keyed by the target's own proof document, and debounce the burst of onDidChangeDiagnostics events (each pass costs a definition lookup per annotation). Forwarded diagnostics now land on the body between `<{` and `}>` rather than on the wrapper tokens too.Three ways the replacement could produce invalid Rocq: - `indexOf(':=')` found the first assignment anywhere in the definition, so a type such as `(let y := 0 in y = 0)` was treated as the body start and the definition was cut in half. Find the first `:=` at bracket depth 0 instead, skipping Coq comments and strings (new pure scanner, unit tested). - the replacement always appended a period, so a body pasted with one ended up as `..`, and a definition range that excludes the final period also got two. Keep exactly one, whichever the outline range includes. - the "already up to date" check compared raw text, so CRLF documents were rewritten on every save. Normalise line endings for the comparison and write the replacement with the document's own EOL. `inlineClauseEdit` now takes an options object and the validated body (the wrapper rules live in one place, inlineClauseBody.ts).- Add an extension-host regression test for the example from examples/sumarray that produced corrupted proof documents: a malformed inline clause must report "malformed crocq annotation" and leave the proof document (memory and disk) untouched. - The end-to-end test wrote its fixtures into src/test/integration/test-assets, so a timeout left untracked files in the repository and parallel runs shared one directory; use the existing createTempDir helper instead. - Pin the exact generated body instead of `includes('\nFalse.')`, which cannot tell `False.` from `False..`, and check the file on disk (the update is saved now, not just applied to a buffer).A regex cannot delimit `<{ ... }>`: the body may contain `}`, and the token must not run past the annotation's own terminator. The regex workaround from the earlier commit was correct for ordinary bodies but had to reject `}}>`, could be fooled by `}>` in a Coq comment, and still lost the whole annotation when the wrapper was malformed. Replace it with an external scanner (src/scanner.c) that has the lookahead the regex engine lacks: - a valid wrapper ends at the first `}>` outside a Coq comment or string, so a body may contain `}` (including the `}}>` ending), - the scanner never consumes the annotation's terminator; when the wrapper is not closed before it, it hands back `invalid_inline_clause_contents`, so the annotation still parses and the extension reports a precise diagnostic on the clause while keeping its linked INV/SPEC, - the contents are optional, so an empty `INV_INLINE` is reported instead of losing the annotation, - only text that starts with `<{` is claimed: during error recovery every external symbol is valid, and a token that matched anything would swallow the rest of the file. The extension-side validator now allows `}>` inside the body (it can appear in a Coq comment or string) and keeps rejecting the annotation terminator as defence in depth. Corpus tests cover `}}>`, `}>` in a comment, an empty clause, an unterminated wrapper and a wrapper on the next line; the unit and end-to-end tests are updated to the new contract. The packaged extension no longer ships src/scanner.c (it is compile-only).@ -0,0 +1,64 @@import type { SyntaxNode as Node } from 'tree-sitter';can this be in a subfolder of src/?
assuming you mean the whole file, yes
@ -136,0 +135,4 @@void (async () => {try {await documentSync.onDocumentSaved(document);} catch (error: unknown) {i think this is a bit ugly inside extension.ts, is it necessary?
yes, but moved into its own function
@ -29,3 +31,1 @@),);}for (const error of collectAnnotationParseErrors(node, insideAnnotation)) {just inline collectAnnotationParseErrors from src/annotationParseErrors.ts here (or put it in a function in this file)
@ -0,0 +1,13 @@import type { Range } from 'vscode';can this be in a subfolder of src/?
@ -0,0 +1,35 @@import { setTimeout as delay } from 'node:timers/promises';can this be in a subfolder of src/?
@ -0,0 +1,80 @@/**can this be in a subfolder of src/?
@ -0,0 +4,4 @@// body must never run past the annotation's own terminator. The scanner has// the lookahead the regex engine lacks, so it can://// - end a well-formed wrapper at the first `}>` outside a Coq comment, so theCoq?
oops, should be Rocq
@ -0,0 +74,4 @@return true;}// Skips a Coq comment; the opening `(*` has already been consumed. CoqCoq?