@@ -81,11 +81,11 @@ export interface StateVariable {
81
81
* entity variable or a combination of other entities.
82
82
*/
83
83
export type Entity =
84
- /* A list of state variables */
84
+ /* A set of state variables, represented as a list */
85
85
| { kind : 'concrete' ; stateVariables : StateVariable [ ] }
86
- /* A variable representing some entity */
86
+ /* A variable representing some set of entities */
87
87
| { kind : 'variable' ; name : string ; reference ?: bigint }
88
- /* A combination of entities */
88
+ /* The union of sets of entities, represented as a list */
89
89
| { kind : 'union' ; entities : Entity [ ] }
90
90
91
91
/*
@@ -177,11 +177,36 @@ function bindEffect(name: string, effect: Effect): Either<string, Substitutions>
177
177
}
178
178
}
179
179
180
- function bindEntity ( name : string , entity : Entity ) : Either < string , Substitutions > {
181
- if ( entityNames ( entity ) . includes ( name ) ) {
182
- return left ( `Can't bind ${ name } to ${ entityToString ( entity ) } : cyclical binding` )
183
- } else {
184
- return right ( [ { kind : 'entity' , name, value : entity } ] )
180
+ function bindEntity ( name : string , entity : Entity ) : Substitutions {
181
+ switch ( entity . kind ) {
182
+ case 'concrete' :
183
+ case 'variable' :
184
+ return [ { kind : 'entity' , name, value : entity } ]
185
+ case 'union' :
186
+ if ( entityNames ( entity ) . includes ( name ) ) {
187
+ // An entity variable (which always stands for a set of state variables)
188
+ // unifies with the union of n sets of entities that may include itself,
189
+ // iff it unifies with each set.
190
+ //
191
+ // I.e.:
192
+ //
193
+ // (v1 =.= v1 ∪ ... ∪ vn) <=> (v1 =.= ... =.= vn)
194
+ //
195
+ // We call this function recursively because, in the general case,
196
+ // occurrences of `v1` may be nested, as in:
197
+ //
198
+ // v1 =.= v1 ∪ (v2 ∪ (v3 ∪ v1)) ∪ v4
199
+ //
200
+ // In practice, we are flattening unions before we call this function,
201
+ // but solving the general case ensures we preserve correct behavior
202
+ // even if this function is used on its own, without incurring any
203
+ // notable overhead when `entities` is already flat.
204
+ return entity . entities . flatMap ( e => bindEntity ( name , e ) )
205
+ } else {
206
+ // Otherwise, the variable may be bound to the union of the entities
207
+ // without qualification.
208
+ return [ { kind : 'entity' , name, value : entity } ]
209
+ }
185
210
}
186
211
}
187
212
@@ -321,9 +346,9 @@ export function unifyEntities(va: Entity, vb: Entity): Either<ErrorTree, Substit
321
346
} else if ( v1 . kind === 'variable' && v2 . kind === 'variable' && v1 . name === v2 . name ) {
322
347
return right ( [ ] )
323
348
} else if ( v1 . kind === 'variable' ) {
324
- return bindEntity ( v1 . name , v2 ) . mapLeft ( msg => buildErrorLeaf ( location , msg ) )
349
+ return right ( bindEntity ( v1 . name , v2 ) )
325
350
} else if ( v2 . kind === 'variable' ) {
326
- return bindEntity ( v2 . name , v1 ) . mapLeft ( msg => buildErrorLeaf ( location , msg ) )
351
+ return right ( bindEntity ( v2 . name , v1 ) )
327
352
} else if ( isEqual ( v1 , v2 ) ) {
328
353
return right ( [ ] )
329
354
} else if ( v1 . kind === 'union' && v2 . kind === 'concrete' ) {
0 commit comments