@@ -28,6 +28,8 @@ import {
2828 InitResponseCapabilitiesExperimental ,
2929} from './model/paths' ;
3030import { moduleSearchPaths , TLAPS } from './paths' ;
31+ import { parseSpec } from './commands/parseModule' ;
32+ import { applyDCollection } from './diagnostic' ;
3133
3234export 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,69 @@ 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+ // SANY itself failed to run (not a parse error). Let the user decide
198+ // whether to proceed without the SANY check; default to aborting.
199+ const continueLabel = 'Run TLAPS anyway' ;
200+ const choice = await vscode . window . showErrorMessage (
201+ `Error parsing module with SANY: ${ err } ` ,
202+ continueLabel
203+ ) ;
204+ if ( choice === continueLabel ) {
205+ this . runCheckStep ( document , selection ) ;
206+ }
207+ return ;
208+ }
209+ applyDCollection ( sanyData . dCollection , this . diagnostic ) ;
210+ const hasErrors = sanyData . dCollection . getMessages ( ) . some (
211+ ( msg ) => msg . diagnostic . severity === vscode . DiagnosticSeverity . Error
212+ ) ;
213+ if ( hasErrors ) {
214+ // SANY's diagnostics already annotate the module (editor markers and
215+ // the Problems view), so don't invoke TLAPS on an invalid module.
216+ return ;
217+ }
218+ this . runCheckStep ( document , selection ) ;
219+ }
220+
221+ private runCheckStep ( document : vscode . TextDocument , selection : Range ) {
222+ if ( ! this . client ) {
223+ return ;
224+ }
225+ vscode . commands . executeCommand ( 'tlaplus.tlaps.check-step.lsp' ,
226+ {
227+ uri : document . uri . toString ( ) ,
228+ version : document . version
229+ } as VersionedTextDocumentIdentifier ,
230+ selection ,
231+ ) ;
232+ }
233+
177234 private readConfig ( ) : boolean {
178235 const config = vscode . workspace . getConfiguration ( ) ;
179236 const configEnabled = config . get < boolean > ( 'tlaplus.tlaps.enabled' ) ;
0 commit comments