@@ -34,6 +34,7 @@ import {
34
34
addNamespacesToDef ,
35
35
builtinNames ,
36
36
copyNames ,
37
+ getTopLevelDef ,
37
38
} from './base'
38
39
import {
39
40
moduleNotFoundError ,
@@ -42,7 +43,7 @@ import {
42
43
paramNotFoundError ,
43
44
selfReferenceError ,
44
45
} from './importErrors'
45
- import { compact } from 'lodash'
46
+ import { cloneDeep , compact } from 'lodash'
46
47
47
48
/**
48
49
* Collects all top-level definitions in Quint modules. Used internally by
@@ -118,7 +119,7 @@ export class NameCollector implements IRVisitor {
118
119
return
119
120
}
120
121
121
- const instanceTable = new Map ( [ ... moduleTable . entries ( ) ] )
122
+ const instanceTable = cloneDeep ( moduleTable )
122
123
if ( decl . qualifiedName ) {
123
124
// Add the qualifier to `definitionsMyModule` map with a copy of the
124
125
// definitions, so if there is an export of that qualifier, we know which
@@ -129,7 +130,8 @@ export class NameCollector implements IRVisitor {
129
130
// For each override, check if the name exists in the instantiated module and is a constant.
130
131
// If so, update the value definition to point to the expression being overriden
131
132
decl . overrides . forEach ( ( [ param , ex ] ) => {
132
- const constDef = instanceTable . get ( param . name )
133
+ // Constants are always top-level
134
+ const constDef = getTopLevelDef ( instanceTable , param . name )
133
135
134
136
if ( ! constDef ) {
135
137
this . errors . push ( paramNotFoundError ( decl , param ) )
@@ -142,13 +144,14 @@ export class NameCollector implements IRVisitor {
142
144
}
143
145
144
146
// Update the definition to point to the expression being overriden
145
- instanceTable . set ( param . name , { ...constDef , id : ex . id } )
147
+ constDef . id = ex . id
148
+ constDef . hidden = false
146
149
} )
147
150
148
151
// All names from the instanced module should be acessible with the instance namespace
149
152
// So, copy them to the current module's lookup table
150
153
const newDefs = copyNames ( instanceTable , decl . qualifiedName , true )
151
- this . collectDefinitions ( newDefs , decl )
154
+ this . collectTopLevelDefinitions ( newDefs , decl )
152
155
}
153
156
154
157
enterImport ( decl : QuintImport ) : void {
@@ -158,7 +161,7 @@ export class NameCollector implements IRVisitor {
158
161
return
159
162
}
160
163
161
- const moduleTable = this . definitionsByModule . get ( decl . protoName )
164
+ const moduleTable = cloneDeep ( this . definitionsByModule . get ( decl . protoName ) )
162
165
163
166
if ( ! moduleTable ) {
164
167
// Importing non-existing module
@@ -178,12 +181,12 @@ export class NameCollector implements IRVisitor {
178
181
179
182
if ( ! decl . defName || decl . defName === '*' ) {
180
183
// Imports all definitions
181
- this . collectDefinitions ( importableDefinitions , decl )
184
+ this . collectTopLevelDefinitions ( importableDefinitions , decl )
182
185
return
183
186
}
184
187
185
188
// Tries to find a specific definition, reporting an error if not found
186
- const newDef = importableDefinitions . get ( decl . defName )
189
+ const newDef = getTopLevelDef ( importableDefinitions , decl . defName )
187
190
if ( ! newDef ) {
188
191
this . errors . push ( nameNotFoundError ( decl ) )
189
192
return
@@ -203,7 +206,7 @@ export class NameCollector implements IRVisitor {
203
206
return
204
207
}
205
208
206
- const moduleTable = this . definitionsByModule . get ( decl . protoName )
209
+ const moduleTable = cloneDeep ( this . definitionsByModule . get ( decl . protoName ) )
207
210
if ( ! moduleTable ) {
208
211
// Exporting non-existing module
209
212
this . errors . push ( moduleNotFoundError ( decl ) )
@@ -214,12 +217,12 @@ export class NameCollector implements IRVisitor {
214
217
215
218
if ( ! decl . defName || decl . defName === '*' ) {
216
219
// Export all definitions
217
- this . collectDefinitions ( exportableDefinitions , decl )
220
+ this . collectTopLevelDefinitions ( exportableDefinitions , decl )
218
221
return
219
222
}
220
223
221
224
// Tries to find a specific definition, reporting an error if not found
222
- const newDef = exportableDefinitions . get ( decl . defName )
225
+ const newDef = getTopLevelDef ( exportableDefinitions , decl . defName )
223
226
224
227
if ( ! newDef ) {
225
228
this . errors . push ( nameNotFoundError ( decl ) )
@@ -257,14 +260,36 @@ export class NameCollector implements IRVisitor {
257
260
return
258
261
}
259
262
260
- if ( this . definitionsByName . has ( identifier ) && this . definitionsByName . get ( identifier ) ! . id != def . id ) {
261
- // Conflict with a previous definition
262
- this . recordConflict ( identifier , this . definitionsByName . get ( identifier ) ! . id , source )
263
+ def . depth ??= 0
264
+ const namespaces = importedFrom ? this . namespaces ( importedFrom ) : [ ]
265
+
266
+ if ( ! this . definitionsByName . has ( identifier ) ) {
267
+ // No existing defs with this name. Create an entry with a single def.
268
+ this . definitionsByName . set ( identifier , [ { ...addNamespacesToDef ( def , namespaces ) , importedFrom } ] )
263
269
return
264
270
}
265
271
266
- const namespaces = importedFrom ? this . namespaces ( importedFrom ) : [ ]
267
- this . definitionsByName . set ( identifier , { ...addNamespacesToDef ( def , namespaces ) , importedFrom } )
272
+ // Else: There are exiting defs. We need to check for conflicts
273
+ const existingEntries = this . definitionsByName . get ( identifier ) !
274
+ // Entries conflict if they have different ids, but the same depth.
275
+ // Entries with different depths are ok, because one is shadowing the
276
+ // other.
277
+ const conflictingEntries = existingEntries . filter ( entry => entry . id !== def . id && entry . depth === def . depth )
278
+
279
+ // Record potential errors and move on
280
+ conflictingEntries . forEach ( existingEntry => {
281
+ this . recordConflict ( identifier , existingEntry . id , source )
282
+ } )
283
+
284
+ // Keep entries with different ids. DON'T keep the whole
285
+ // `existingEntries` since those may contain the same exact defs, but
286
+ // hidden.
287
+ this . definitionsByName . set (
288
+ identifier ,
289
+ existingEntries
290
+ . filter ( entry => entry . id !== def . id )
291
+ . concat ( [ { ...addNamespacesToDef ( def , namespaces ) , importedFrom } ] )
292
+ )
268
293
}
269
294
270
295
/**
@@ -273,19 +298,25 @@ export class NameCollector implements IRVisitor {
273
298
* @param identifier - The identifier of the definition to delete.
274
299
*/
275
300
deleteDefinition ( identifier : string ) : void {
276
- this . definitionsByName . delete ( identifier )
301
+ this . definitionsByName . get ( identifier ) ?. pop ( )
302
+ return
277
303
}
278
304
279
305
/**
280
- * Obtains a collected definition.
306
+ * Gets the definition with the given name, in the current (visiting) scope
281
307
*
282
308
* @param identifier - The identifier of the definition to retrieve.
283
309
*
284
310
* @returns The definition object for the given identifier, or undefined if a
285
311
* definitions with that identifier was never collected.
286
312
*/
287
313
getDefinition ( identifier : string ) : LookupDefinition | undefined {
288
- return this . definitionsByName . get ( identifier )
314
+ const defs = this . definitionsByName . get ( identifier )
315
+ if ( defs === undefined || defs . length === 0 ) {
316
+ return
317
+ }
318
+
319
+ return defs [ defs . length - 1 ]
289
320
}
290
321
291
322
private namespaces ( decl : QuintImport | QuintInstance | QuintExport ) : string [ ] {
@@ -297,18 +328,33 @@ export class NameCollector implements IRVisitor {
297
328
return namespace ? [ namespace ] : [ ]
298
329
}
299
330
300
- private collectDefinitions (
331
+ private collectTopLevelDefinitions (
301
332
newDefs : DefinitionsByName ,
302
333
importedFrom ?: QuintImport | QuintExport | QuintInstance
303
334
) : void {
304
335
const namespaces = importedFrom ? this . namespaces ( importedFrom ) : [ ]
305
- const newEntries : [ string , LookupDefinition ] [ ] = [ ...newDefs . entries ( ) ] . map ( ( [ identifier , def ] ) => {
306
- const existingEntry = this . definitionsByName . get ( identifier )
307
- if ( existingEntry && existingEntry . id !== def . id ) {
308
- this . recordConflict ( identifier , existingEntry . id , def . id )
309
- }
310
- return [ identifier , { ...addNamespacesToDef ( def , namespaces ) , importedFrom } ]
311
- } )
336
+ const newEntries : [ string , LookupDefinition [ ] ] [ ] = compact (
337
+ [ ...newDefs . keys ( ) ] . map ( identifier => {
338
+ const def = getTopLevelDef ( newDefs , identifier )
339
+ if ( ! def ) {
340
+ return
341
+ }
342
+
343
+ const existingEntries = this . definitionsByName . get ( identifier )
344
+ if ( existingEntries ) {
345
+ const conflictingEntries = existingEntries . filter ( entry => entry . id !== def . id )
346
+ conflictingEntries . forEach ( existingEntry => {
347
+ this . recordConflict ( identifier , existingEntry . id , def . id )
348
+ } )
349
+
350
+ // Keep conflicting entries and add the new one. DON'T keep the whole
351
+ // `existingEntries` since those may contain the same exact defs, but
352
+ // hidden.
353
+ return [ identifier , conflictingEntries . concat ( [ { ...addNamespacesToDef ( def , namespaces ) , importedFrom } ] ) ]
354
+ }
355
+ return [ identifier , [ { ...addNamespacesToDef ( def , namespaces ) , importedFrom } ] ]
356
+ } )
357
+ )
312
358
313
359
this . definitionsByName = new Map ( [ ...this . definitionsByName . entries ( ) , ...newEntries ] )
314
360
}
0 commit comments