1
1
/* ----------------------------------------------------------------------------------
2
- * Copyright 2022 Informal Systems
2
+ * Copyright 2022-2024 Informal Systems
3
3
* Licensed under the Apache License, Version 2.0.
4
4
* See LICENSE in the project root for license information.
5
5
* --------------------------------------------------------------------------------- */
@@ -18,7 +18,7 @@ import {
18
18
QuintAssume ,
19
19
QuintBool ,
20
20
QuintConst ,
21
- QuintDef ,
21
+ QuintDeclaration ,
22
22
QuintEx ,
23
23
QuintInstance ,
24
24
QuintInt ,
@@ -35,7 +35,7 @@ import { expressionToString, rowToString, typeToString } from '../ir/IRprinting'
35
35
import { Either , left , mergeInMany , right } from '@sweet-monads/either'
36
36
import { Error , ErrorTree , buildErrorLeaf , buildErrorTree , errorTreeToString } from '../errorTree'
37
37
import { getSignatures } from './builtinSignatures'
38
- import { Constraint , Signature , TypeScheme , toScheme } from './base'
38
+ import { Constraint , QuantifiedVariables , Signature , TypeScheme , toScheme } from './base'
39
39
import { Substitutions , applySubstitution , compose } from './substitutions'
40
40
import { LookupTable } from '../names/base'
41
41
import {
@@ -95,7 +95,7 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
95
95
}
96
96
}
97
97
98
- protected types : Map < bigint , TypeScheme > = new Map < bigint , TypeScheme > ( )
98
+ protected types : Map < bigint , TypeScheme > = new Map ( )
99
99
protected errors : Map < bigint , ErrorTree > = new Map < bigint , ErrorTree > ( )
100
100
protected freshVarGenerator : FreshVarGenerator
101
101
protected table : LookupTable
@@ -104,13 +104,14 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
104
104
private solvingFunction : SolvingFunctionType
105
105
private builtinSignatures : Map < string , Signature > = getSignatures ( )
106
106
107
+ // A map to save which type variables were free when we started visiting an opdef or an assume
108
+ protected tvs : Map < bigint , QuantifiedVariables > = new Map ( )
109
+ // Temporary type map only for types in scope for a certain declaration
110
+ protected typesInScope : Map < bigint , TypeScheme > = new Map ( )
111
+
107
112
// Track location descriptions for error tree traces
108
113
private location : string = ''
109
114
110
- // A stack of free type variables and row variables for lambda expressions.
111
- // Nested lambdas add new entries to the stack, and pop them when exiting.
112
- private freeNames : { typeVariables : Set < string > ; rowVariables : Set < string > } [ ] = [ ]
113
-
114
115
getResult ( ) : [ Map < bigint , ErrorTree > , Map < bigint , TypeScheme > ] {
115
116
return [ this . errors , this . types ]
116
117
}
@@ -119,18 +120,6 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
119
120
this . location = `Generating constraints for ${ expressionToString ( e ) } `
120
121
}
121
122
122
- exitDef ( def : QuintDef ) {
123
- if ( this . constraints . length > 0 ) {
124
- this . solveConstraints ( ) . map ( subs => {
125
- if ( isAnnotatedDef ( def ) ) {
126
- checkAnnotationGenerality ( subs , def . typeAnnotation ) . mapLeft ( err =>
127
- this . errors . set ( def . typeAnnotation ?. id ?? def . id , err )
128
- )
129
- }
130
- } )
131
- }
132
- }
133
-
134
123
exitVar ( e : QuintVar ) {
135
124
this . addToResults ( e . id , right ( toScheme ( e . typeAnnotation ) ) )
136
125
}
@@ -242,22 +231,14 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
242
231
}
243
232
244
233
enterLambda ( expr : QuintLambda ) {
245
- const lastParamNames = this . currentFreeNames ( )
246
- const paramNames = {
247
- typeVariables : new Set ( lastParamNames . typeVariables ) ,
248
- rowVariables : new Set ( lastParamNames . rowVariables ) ,
249
- }
250
234
expr . params . forEach ( p => {
251
235
const varName = p . name === '_' ? this . freshVarGenerator . freshVar ( '_t' ) : `t_${ p . name } _${ p . id } `
252
- paramNames . typeVariables . add ( varName )
253
236
const paramTypeVar : QuintVarType = { kind : 'var' , name : varName }
254
237
this . addToResults ( p . id , right ( toScheme ( paramTypeVar ) ) )
255
238
if ( p . typeAnnotation ) {
256
239
this . constraints . push ( { kind : 'eq' , types : [ paramTypeVar , p . typeAnnotation ] , sourceId : p . id } )
257
240
}
258
241
} )
259
-
260
- this . freeNames . push ( paramNames )
261
242
}
262
243
263
244
// Γ ∪ {p0: t0, ..., pn: tn} ⊢ e: (te, c) t0, ..., tn are fresh
@@ -281,7 +262,6 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
281
262
} )
282
263
283
264
this . addToResults ( e . id , result )
284
- this . freeNames . pop ( )
285
265
}
286
266
287
267
// Γ ⊢ e1: (t1, c1) s = solve(c1) s(Γ ∪ {n: t1}) ⊢ e2: (t2, c2)
@@ -292,22 +272,58 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
292
272
return
293
273
}
294
274
295
- // TODO: Occurs check on operator body to prevent recursion, see https://github.com/informalsystems/quint/issues/171
296
-
297
275
this . addToResults ( e . id , this . fetchResult ( e . expr . id ) )
298
276
}
299
277
300
- exitOpDef ( e : QuintOpDef ) {
278
+ exitDecl ( _def : QuintDeclaration ) {
279
+ this . typesInScope = new Map ( )
280
+ }
281
+
282
+ enterOpDef ( def : QuintOpDef ) {
283
+ // Save which type variables were free when we started visiting this op def
284
+ const tvs = this . freeNamesInScope ( )
285
+ this . tvs . set ( def . id , tvs )
286
+ }
287
+
288
+ exitOpDef ( def : QuintOpDef ) {
301
289
if ( this . errors . size !== 0 ) {
302
290
return
303
291
}
304
292
305
- this . fetchResult ( e . expr . id ) . map ( t => {
306
- this . addToResults ( e . id , right ( this . quantify ( t . type ) ) )
307
- if ( e . typeAnnotation ) {
308
- this . constraints . push ( { kind : 'eq' , types : [ t . type , e . typeAnnotation ] , sourceId : e . id } )
293
+ this . fetchResult ( def . expr . id ) . map ( t => {
294
+ if ( def . typeAnnotation ) {
295
+ this . constraints . push ( { kind : 'eq' , types : [ t . type , def . typeAnnotation ] , sourceId : def . id } )
309
296
}
310
297
} )
298
+
299
+ const tvs_before = this . tvs . get ( def . id ) !
300
+
301
+ if ( this . constraints . length > 0 ) {
302
+ this . solveConstraints ( ) . map ( subs => {
303
+ // For every free name we are binding in the substitutions, the names occuring in the value of the substitution
304
+ // have to become free as well.
305
+ addBindingsToFreeTypes ( tvs_before , subs )
306
+
307
+ if ( isAnnotatedDef ( def ) ) {
308
+ checkAnnotationGenerality ( subs , def . typeAnnotation ) . mapLeft ( err =>
309
+ this . errors . set ( def . typeAnnotation ?. id ?? def . id , err )
310
+ )
311
+ }
312
+ } )
313
+ }
314
+
315
+ const tvs = this . freeNamesInScope ( )
316
+ // Any new free names, that were not free before, have to be quantified
317
+ const toQuantify = variablesDifference ( tvs , tvs_before )
318
+
319
+ this . fetchResult ( def . expr . id ) . map ( t => {
320
+ this . addToResults ( def . id , right ( quantify ( toQuantify , t . type ) ) )
321
+ } )
322
+ }
323
+
324
+ enterAssume ( e : QuintAssume ) {
325
+ const tvs = this . freeNamesInScope ( )
326
+ this . tvs . set ( e . id , tvs )
311
327
}
312
328
313
329
exitAssume ( e : QuintAssume ) {
@@ -316,15 +332,21 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
316
332
}
317
333
318
334
this . fetchResult ( e . assumption . id ) . map ( t => {
319
- this . addToResults ( e . id , right ( this . quantify ( t . type ) ) )
335
+ const tvs_before = this . tvs . get ( e . id ) !
336
+ const tvs = this . freeNamesInScope ( )
337
+ const toQuantify = variablesDifference ( tvs , tvs_before )
338
+ this . addToResults ( e . id , right ( quantify ( toQuantify , t . type ) ) )
320
339
this . constraints . push ( { kind : 'eq' , types : [ t . type , { kind : 'bool' } ] , sourceId : e . id } )
321
340
} )
322
341
}
323
342
324
343
private addToResults ( exprId : bigint , result : Either < Error , TypeScheme > ) {
325
344
result
326
345
. mapLeft ( err => this . errors . set ( exprId , buildErrorTree ( this . location , err ) ) )
327
- . map ( r => this . types . set ( exprId , r ) )
346
+ . map ( r => {
347
+ this . typesInScope . set ( exprId , r )
348
+ this . types . set ( exprId , r )
349
+ } )
328
350
}
329
351
330
352
private fetchResult ( id : bigint ) : Either < ErrorTree , TypeScheme > {
@@ -348,16 +370,9 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
348
370
return this . solvingFunction ( this . table , constraint )
349
371
. mapLeft ( errors => errors . forEach ( ( err , id ) => this . errors . set ( id , err ) ) )
350
372
. map ( subs => {
351
- // For every free name we are binding in the substitutions, the names occuring in the value of the substitution
352
- // have to become free as well.
353
- this . addBindingsToFreeNames ( subs )
354
-
355
- // Apply substitution to environment
356
- // FIXME: We have to figure out the scope of the constraints/substitutions
357
- // https://github.com/informalsystems/quint/issues/690
358
- this . types . forEach ( ( oldScheme , id ) => {
373
+ this . typesInScope . forEach ( ( oldScheme , id ) => {
359
374
const newType = applySubstitution ( this . table , subs , oldScheme . type )
360
- const newScheme : TypeScheme = this . quantify ( newType )
375
+ const newScheme : TypeScheme = { ... oldScheme , type : newType }
361
376
this . addToResults ( id , right ( newScheme ) )
362
377
} )
363
378
@@ -406,45 +421,18 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
406
421
return applySubstitution ( this . table , subs , t . type )
407
422
}
408
423
409
- private currentFreeNames ( ) : { typeVariables : Set < string > ; rowVariables : Set < string > } {
410
- return (
411
- this . freeNames [ this . freeNames . length - 1 ] ?? {
412
- typeVariables : new Set ( ) ,
413
- rowVariables : new Set ( ) ,
414
- }
424
+ private freeNamesInScope ( ) : QuantifiedVariables {
425
+ return [ ...this . typesInScope . values ( ) ] . reduce (
426
+ ( acc , t ) => {
427
+ const names = freeTypes ( t )
428
+ return {
429
+ typeVariables : new Set ( [ ...names . typeVariables , ...acc . typeVariables ] ) ,
430
+ rowVariables : new Set ( [ ...names . rowVariables , ...acc . rowVariables ] ) ,
431
+ }
432
+ } ,
433
+ { typeVariables : new Set ( ) , rowVariables : new Set ( ) }
415
434
)
416
435
}
417
-
418
- private quantify ( type : QuintType ) : TypeScheme {
419
- const freeNames = this . currentFreeNames ( )
420
- const nonFreeNames = {
421
- typeVariables : new Set ( [ ...typeNames ( type ) . typeVariables ] . filter ( name => ! freeNames . typeVariables . has ( name ) ) ) ,
422
- rowVariables : new Set ( [ ...typeNames ( type ) . rowVariables ] . filter ( name => ! freeNames . rowVariables . has ( name ) ) ) ,
423
- }
424
- return { ...nonFreeNames , type }
425
- }
426
-
427
- private addBindingsToFreeNames ( substitutions : Substitutions ) {
428
- // Assumes substitutions are topologically sorted, i.e. [ t0 |-> (t1, t2), t1 |-> (t3, t4) ]
429
- substitutions . forEach ( s => {
430
- switch ( s . kind ) {
431
- case 'type' :
432
- this . freeNames
433
- . filter ( free => free . typeVariables . has ( s . name ) )
434
- . forEach ( free => {
435
- const names = typeNames ( s . value )
436
- names . typeVariables . forEach ( v => free . typeVariables . add ( v ) )
437
- names . rowVariables . forEach ( v => free . rowVariables . add ( v ) )
438
- } )
439
- return
440
- case 'row' :
441
- this . freeNames
442
- . filter ( free => free . rowVariables . has ( s . name ) )
443
- . forEach ( free => rowNames ( s . value ) . forEach ( v => free . rowVariables . add ( v ) ) )
444
- return
445
- }
446
- } )
447
- }
448
436
}
449
437
450
438
function checkAnnotationGenerality (
@@ -477,3 +465,39 @@ function checkAnnotationGenerality(
477
465
return right ( subs )
478
466
}
479
467
}
468
+
469
+ function quantify ( tvs : QuantifiedVariables , type : QuintType ) : TypeScheme {
470
+ return { ...tvs , type }
471
+ }
472
+
473
+ function freeTypes ( t : TypeScheme ) : QuantifiedVariables {
474
+ const allNames = typeNames ( t . type )
475
+ return variablesDifference ( allNames , { typeVariables : t . typeVariables , rowVariables : t . rowVariables } )
476
+ }
477
+
478
+ function addBindingsToFreeTypes ( free : QuantifiedVariables , substitutions : Substitutions ) : void {
479
+ // Assumes substitutions are topologically sorted, i.e. [ t0 |-> (t1, t2), t1 |-> (t3, t4) ]
480
+ substitutions . forEach ( s => {
481
+ switch ( s . kind ) {
482
+ case 'type' :
483
+ if ( free . typeVariables . has ( s . name ) ) {
484
+ const names = typeNames ( s . value )
485
+ names . typeVariables . forEach ( v => free . typeVariables . add ( v ) )
486
+ names . rowVariables . forEach ( v => free . rowVariables . add ( v ) )
487
+ }
488
+ return
489
+ case 'row' :
490
+ if ( free . rowVariables . has ( s . name ) ) {
491
+ rowNames ( s . value ) . forEach ( v => free . rowVariables . add ( v ) )
492
+ }
493
+ return
494
+ }
495
+ } )
496
+ }
497
+
498
+ function variablesDifference ( a : QuantifiedVariables , b : QuantifiedVariables ) : QuantifiedVariables {
499
+ return {
500
+ typeVariables : new Set ( [ ...a . typeVariables ] . filter ( tv => ! b . typeVariables . has ( tv ) ) ) ,
501
+ rowVariables : new Set ( [ ...a . rowVariables ] . filter ( tv => ! b . rowVariables . has ( tv ) ) ) ,
502
+ }
503
+ }
0 commit comments