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
}
@@ -244,22 +233,14 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
244
233
}
245
234
246
235
enterLambda ( expr : QuintLambda ) {
247
- const lastParamNames = this . currentFreeNames ( )
248
- const paramNames = {
249
- typeVariables : new Set ( lastParamNames . typeVariables ) ,
250
- rowVariables : new Set ( lastParamNames . rowVariables ) ,
251
- }
252
236
expr . params . forEach ( p => {
253
237
const varName = p . name === '_' ? this . freshVarGenerator . freshVar ( '_t' ) : `t_${ p . name } _${ p . id } `
254
- paramNames . typeVariables . add ( varName )
255
238
const paramTypeVar : QuintVarType = { kind : 'var' , name : varName }
256
239
this . addToResults ( p . id , right ( toScheme ( paramTypeVar ) ) )
257
240
if ( p . typeAnnotation ) {
258
241
this . constraints . push ( { kind : 'eq' , types : [ paramTypeVar , p . typeAnnotation ] , sourceId : p . id } )
259
242
}
260
243
} )
261
-
262
- this . freeNames . push ( paramNames )
263
244
}
264
245
265
246
// Γ ∪ {p0: t0, ..., pn: tn} ⊢ e: (te, c) t0, ..., tn are fresh
@@ -283,7 +264,6 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
283
264
} )
284
265
285
266
this . addToResults ( e . id , result )
286
- this . freeNames . pop ( )
287
267
}
288
268
289
269
// Γ ⊢ e1: (t1, c1) s = solve(c1) s(Γ ∪ {n: t1}) ⊢ e2: (t2, c2)
@@ -294,22 +274,58 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
294
274
return
295
275
}
296
276
297
- // TODO: Occurs check on operator body to prevent recursion, see https://github.com/informalsystems/quint/issues/171
298
-
299
277
this . addToResults ( e . id , this . fetchResult ( e . expr . id ) )
300
278
}
301
279
302
- exitOpDef ( e : QuintOpDef ) {
280
+ exitDecl ( _def : QuintDeclaration ) {
281
+ this . typesInScope = new Map ( )
282
+ }
283
+
284
+ enterOpDef ( def : QuintOpDef ) {
285
+ // Save which type variables were free when we started visiting this op def
286
+ const tvs = this . freeNamesInScope ( )
287
+ this . tvs . set ( def . id , tvs )
288
+ }
289
+
290
+ exitOpDef ( def : QuintOpDef ) {
303
291
if ( this . errors . size !== 0 ) {
304
292
return
305
293
}
306
294
307
- this . fetchResult ( e . expr . id ) . map ( t => {
308
- this . addToResults ( e . id , right ( this . quantify ( t . type ) ) )
309
- if ( e . typeAnnotation ) {
310
- this . constraints . push ( { kind : 'eq' , types : [ t . type , e . typeAnnotation ] , sourceId : e . id } )
295
+ this . fetchResult ( def . expr . id ) . map ( t => {
296
+ if ( def . typeAnnotation ) {
297
+ this . constraints . push ( { kind : 'eq' , types : [ t . type , def . typeAnnotation ] , sourceId : def . id } )
311
298
}
312
299
} )
300
+
301
+ const tvs_before = this . tvs . get ( def . id ) !
302
+
303
+ if ( this . constraints . length > 0 ) {
304
+ this . solveConstraints ( ) . map ( subs => {
305
+ // For every free name we are binding in the substitutions, the names occuring in the value of the substitution
306
+ // have to become free as well.
307
+ addBindingsToFreeTypes ( tvs_before , subs )
308
+
309
+ if ( isAnnotatedDef ( def ) ) {
310
+ checkAnnotationGenerality ( subs , def . typeAnnotation ) . mapLeft ( err =>
311
+ this . errors . set ( def . typeAnnotation ?. id ?? def . id , err )
312
+ )
313
+ }
314
+ } )
315
+ }
316
+
317
+ const tvs = this . freeNamesInScope ( )
318
+ // Any new free names, that were not free before, have to be quantified
319
+ const toQuantify = variablesDifference ( tvs , tvs_before )
320
+
321
+ this . fetchResult ( def . expr . id ) . map ( t => {
322
+ this . addToResults ( def . id , right ( quantify ( toQuantify , t . type ) ) )
323
+ } )
324
+ }
325
+
326
+ enterAssume ( e : QuintAssume ) {
327
+ const tvs = this . freeNamesInScope ( )
328
+ this . tvs . set ( e . id , tvs )
313
329
}
314
330
315
331
exitAssume ( e : QuintAssume ) {
@@ -318,15 +334,21 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
318
334
}
319
335
320
336
this . fetchResult ( e . assumption . id ) . map ( t => {
321
- this . addToResults ( e . id , right ( this . quantify ( t . type ) ) )
337
+ const tvs_before = this . tvs . get ( e . id ) !
338
+ const tvs = this . freeNamesInScope ( )
339
+ const toQuantify = variablesDifference ( tvs , tvs_before )
340
+ this . addToResults ( e . id , right ( quantify ( toQuantify , t . type ) ) )
322
341
this . constraints . push ( { kind : 'eq' , types : [ t . type , { kind : 'bool' } ] , sourceId : e . id } )
323
342
} )
324
343
}
325
344
326
345
private addToResults ( exprId : bigint , result : Either < Error , TypeScheme > ) {
327
346
result
328
347
. mapLeft ( err => this . errors . set ( exprId , buildErrorTree ( this . location , err ) ) )
329
- . map ( r => this . types . set ( exprId , r ) )
348
+ . map ( r => {
349
+ this . typesInScope . set ( exprId , r )
350
+ this . types . set ( exprId , r )
351
+ } )
330
352
}
331
353
332
354
private fetchResult ( id : bigint ) : Either < ErrorTree , TypeScheme > {
@@ -350,16 +372,9 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
350
372
return this . solvingFunction ( this . table , constraint )
351
373
. mapLeft ( errors => errors . forEach ( ( err , id ) => this . errors . set ( id , err ) ) )
352
374
. map ( subs => {
353
- // For every free name we are binding in the substitutions, the names occuring in the value of the substitution
354
- // have to become free as well.
355
- this . addBindingsToFreeNames ( subs )
356
-
357
- // Apply substitution to environment
358
- // FIXME: We have to figure out the scope of the constraints/substitutions
359
- // https://github.com/informalsystems/quint/issues/690
360
- this . types . forEach ( ( oldScheme , id ) => {
375
+ this . typesInScope . forEach ( ( oldScheme , id ) => {
361
376
const newType = applySubstitution ( this . table , subs , oldScheme . type )
362
- const newScheme : TypeScheme = this . quantify ( newType )
377
+ const newScheme : TypeScheme = { ... oldScheme , type : newType }
363
378
this . addToResults ( id , right ( newScheme ) )
364
379
} )
365
380
@@ -408,45 +423,18 @@ export class ConstraintGeneratorVisitor implements IRVisitor {
408
423
return applySubstitution ( this . table , subs , t . type )
409
424
}
410
425
411
- private currentFreeNames ( ) : { typeVariables : Set < string > ; rowVariables : Set < string > } {
412
- return (
413
- this . freeNames [ this . freeNames . length - 1 ] ?? {
414
- typeVariables : new Set ( ) ,
415
- rowVariables : new Set ( ) ,
416
- }
426
+ private freeNamesInScope ( ) : QuantifiedVariables {
427
+ return [ ...this . typesInScope . values ( ) ] . reduce (
428
+ ( acc , t ) => {
429
+ const names = freeTypes ( t )
430
+ return {
431
+ typeVariables : new Set ( [ ...names . typeVariables , ...acc . typeVariables ] ) ,
432
+ rowVariables : new Set ( [ ...names . rowVariables , ...acc . rowVariables ] ) ,
433
+ }
434
+ } ,
435
+ { typeVariables : new Set ( ) , rowVariables : new Set ( ) }
417
436
)
418
437
}
419
-
420
- private quantify ( type : QuintType ) : TypeScheme {
421
- const freeNames = this . currentFreeNames ( )
422
- const nonFreeNames = {
423
- typeVariables : new Set ( [ ...typeNames ( type ) . typeVariables ] . filter ( name => ! freeNames . typeVariables . has ( name ) ) ) ,
424
- rowVariables : new Set ( [ ...typeNames ( type ) . rowVariables ] . filter ( name => ! freeNames . rowVariables . has ( name ) ) ) ,
425
- }
426
- return { ...nonFreeNames , type }
427
- }
428
-
429
- private addBindingsToFreeNames ( substitutions : Substitutions ) {
430
- // Assumes substitutions are topologically sorted, i.e. [ t0 |-> (t1, t2), t1 |-> (t3, t4) ]
431
- substitutions . forEach ( s => {
432
- switch ( s . kind ) {
433
- case 'type' :
434
- this . freeNames
435
- . filter ( free => free . typeVariables . has ( s . name ) )
436
- . forEach ( free => {
437
- const names = typeNames ( s . value )
438
- names . typeVariables . forEach ( v => free . typeVariables . add ( v ) )
439
- names . rowVariables . forEach ( v => free . rowVariables . add ( v ) )
440
- } )
441
- return
442
- case 'row' :
443
- this . freeNames
444
- . filter ( free => free . rowVariables . has ( s . name ) )
445
- . forEach ( free => rowNames ( s . value ) . forEach ( v => free . rowVariables . add ( v ) ) )
446
- return
447
- }
448
- } )
449
- }
450
438
}
451
439
452
440
function checkAnnotationGenerality (
@@ -479,3 +467,39 @@ function checkAnnotationGenerality(
479
467
return right ( subs )
480
468
}
481
469
}
470
+
471
+ function quantify ( tvs : QuantifiedVariables , type : QuintType ) : TypeScheme {
472
+ return { ...tvs , type }
473
+ }
474
+
475
+ function freeTypes ( t : TypeScheme ) : QuantifiedVariables {
476
+ const allNames = typeNames ( t . type )
477
+ return variablesDifference ( allNames , { typeVariables : t . typeVariables , rowVariables : t . rowVariables } )
478
+ }
479
+
480
+ function addBindingsToFreeTypes ( free : QuantifiedVariables , substitutions : Substitutions ) : void {
481
+ // Assumes substitutions are topologically sorted, i.e. [ t0 |-> (t1, t2), t1 |-> (t3, t4) ]
482
+ substitutions . forEach ( s => {
483
+ switch ( s . kind ) {
484
+ case 'type' :
485
+ if ( free . typeVariables . has ( s . name ) ) {
486
+ const names = typeNames ( s . value )
487
+ names . typeVariables . forEach ( v => free . typeVariables . add ( v ) )
488
+ names . rowVariables . forEach ( v => free . rowVariables . add ( v ) )
489
+ }
490
+ return
491
+ case 'row' :
492
+ if ( free . rowVariables . has ( s . name ) ) {
493
+ rowNames ( s . value ) . forEach ( v => free . rowVariables . add ( v ) )
494
+ }
495
+ return
496
+ }
497
+ } )
498
+ }
499
+
500
+ function variablesDifference ( a : QuantifiedVariables , b : QuantifiedVariables ) : QuantifiedVariables {
501
+ return {
502
+ typeVariables : new Set ( [ ...a . typeVariables ] . filter ( tv => ! b . typeVariables . has ( tv ) ) ) ,
503
+ rowVariables : new Set ( [ ...a . rowVariables ] . filter ( tv => ! b . rowVariables . has ( tv ) ) ) ,
504
+ }
505
+ }
0 commit comments