Skip to content

Commit f1c4333

Browse files
lemmyclaude
andcommitted
Run SANY before invoking TLAPS proof commands
TLAPS accepts "raw TLA" (rTLA) that SANY rejects: formulas that are not insensitive to stuttering, permitting unrestrictive assertions about behaviors that should be unassertable in TLA+. Without a SANY check, a user can obtain a passing TLAPS result for a module that is not valid TLA+. Parse the saved module with SANY first (reusing tlaplus.parse) and abort the proof check, surfacing diagnostics, when SANY reports errors. Closes Github issue #526 #526 [Feature][TLAPS] Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
1 parent acc095d commit f1c4333

2 files changed

Lines changed: 55 additions & 10 deletions

File tree

src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
262262
);
263263
tlapsClient = new TlapsClient(
264264
context,
265+
diagnostic,
265266
details => currentProofStepWebviewViewProvider.showProofStepDetails(details),
266267
configChanged => currentProofStepWebviewViewProvider.considerConfigChanged(configChanged)
267268
);

src/tlaps.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
InitResponseCapabilitiesExperimental,
2929
} from './model/paths';
3030
import { moduleSearchPaths, TLAPS } from './paths';
31+
import { parseSpec } from './commands/parseModule';
32+
import { applyDCollection } from './diagnostic';
3133

3234
export enum proofStateNames {
3335
proved = 'proved',
@@ -67,6 +69,7 @@ export class TlapsClient {
6769

6870
constructor(
6971
private context: vscode.ExtensionContext,
72+
private diagnostic: vscode.DiagnosticCollection,
7073
private currentProofStepDetailsListener: ((details: TlapsProofStepDetails) => void),
7174
private configChangedListener: ((configChanged: TlapsConfigChanged) => void)
7275
) {
@@ -118,16 +121,7 @@ export class TlapsClient {
118121
if (!this.client) {
119122
return;
120123
}
121-
vscode.commands.executeCommand('tlaplus.tlaps.check-step.lsp',
122-
{
123-
uri: te.document.uri.toString(),
124-
version: te.document.version
125-
} as VersionedTextDocumentIdentifier,
126-
{
127-
start: te.selection.start,
128-
end: te.selection.end
129-
} as Range,
130-
);
124+
this.checkStep(te);
131125
}
132126
));
133127
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(event => {
@@ -174,6 +168,56 @@ export class TlapsClient {
174168
this.tryStop();
175169
}
176170

171+
// Runs SANY on the module before forwarding the proof check to TLAPS.
172+
//
173+
// TLAPS and SANY do not accept exactly the same language: TLAPS accepts some
174+
// specifications that SANY (the standard TLA+ front end) correctly rejects.
175+
// TLAPS accepts "raw TLA" (rTLA), which permits unrestrictive assertions
176+
// about behaviors that should be unassertable in TLA+, i.e. formulas that
177+
// are not insensitive to stuttering. Eliminating rTLA is the primary reason
178+
// for running SANY before TLAPS.
179+
// To preserve the invariant the TLA+ Toolbox used to enforce, we parse the
180+
// module with SANY first (reusing the implementation behind `tlaplus.parse`)
181+
// and only invoke TLAPS if the module is a valid TLA+ module.
182+
private async checkStep(te: vscode.TextEditor) {
183+
const document = te.document;
184+
// Capture the selection before any `await`, as it may change meanwhile.
185+
const selection: Range = {
186+
start: te.selection.start,
187+
end: te.selection.end,
188+
};
189+
// Persist the buffer so SANY and TLAPS operate on the same content.
190+
if (document.isDirty && !await document.save()) {
191+
return;
192+
}
193+
let sanyData;
194+
try {
195+
sanyData = await parseSpec(document.uri);
196+
} catch (err) {
197+
vscode.window.showErrorMessage(`Error parsing module with SANY: ${err}`);
198+
return;
199+
}
200+
applyDCollection(sanyData.dCollection, this.diagnostic);
201+
const hasErrors = sanyData.dCollection.getMessages().some(
202+
(msg) => msg.diagnostic.severity === vscode.DiagnosticSeverity.Error
203+
);
204+
if (hasErrors) {
205+
// SANY's diagnostics already annotate the module (editor markers and
206+
// the Problems view), so don't invoke TLAPS on an invalid module.
207+
return;
208+
}
209+
if (!this.client) {
210+
return;
211+
}
212+
vscode.commands.executeCommand('tlaplus.tlaps.check-step.lsp',
213+
{
214+
uri: document.uri.toString(),
215+
version: document.version
216+
} as VersionedTextDocumentIdentifier,
217+
selection,
218+
);
219+
}
220+
177221
private readConfig(): boolean {
178222
const config = vscode.workspace.getConfiguration();
179223
const configEnabled = config.get<boolean>('tlaplus.tlaps.enabled');

0 commit comments

Comments
 (0)