UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

319 lines 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const mocha_1 = require("mocha"); const chai_1 = require("chai"); const base_1 = require("../../src/effects/base"); const parser_1 = require("../../src/effects/parser"); const src_1 = require("../../src"); (0, mocha_1.describe)('unify', () => { (0, mocha_1.describe)('simple effects', () => { (0, mocha_1.it)('unifies temporal effects', () => { const e1 = (0, parser_1.parseEffectOrThrow)('Temporal[t1]'); const e2 = (0, parser_1.parseEffectOrThrow)("Temporal['x']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [ { kind: 'entity', name: 't1', value: { kind: 'concrete', stateVariables: [{ name: 'x', reference: 1n }] } }, ])); }); (0, mocha_1.it)('returns error unifying temporal and update effects', () => { const e1 = (0, parser_1.parseEffectOrThrow)("Update['x']"); const e2 = (0, parser_1.parseEffectOrThrow)("Temporal['y']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify Update['x'] and Temporal['y']", children: [ { location: "Trying to unify entities ['x'] and []", message: 'Expected [x] and [] to be the same', children: [], }, ], })); }); (0, mocha_1.it)('returns error unifying temporal and pure effects', () => { const e1 = (0, parser_1.parseEffectOrThrow)('Pure'); const e2 = (0, parser_1.parseEffectOrThrow)("Temporal['y']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify Pure and Temporal['y']", children: [ { location: "Trying to unify entities ['y'] and []", message: 'Expected [y] and [] to be the same', children: [], }, ], })); }); (0, mocha_1.it)('unifies entities with different orders', () => { const e1 = (0, parser_1.parseEffectOrThrow)("Read['x', 'y']"); const e2 = (0, parser_1.parseEffectOrThrow)("Read['y', 'x']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [])); }); (0, mocha_1.it)('flattens any nested unions', () => { const e1 = (0, parser_1.parseEffectOrThrow)('E'); const e2 = { kind: 'concrete', components: [ { kind: 'read', entity: { kind: 'union', entities: [ { kind: 'union', entities: [ { kind: 'variable', name: 'r1' }, { kind: 'variable', name: 'r2' }, ], }, { kind: 'concrete', stateVariables: [ { name: 'x', reference: 1n }, { name: 'y', reference: 2n }, ], }, ], }, }, ], }; const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [ { kind: 'effect', name: 'E', value: (0, parser_1.parseEffectOrThrow)("Read[r1, r2, 'x', 'y']"), }, ])); }); (0, mocha_1.it)('unifies unions when they are the exact same for temporal', () => { const e = (0, parser_1.parseEffectOrThrow)('Temporal[v1, v2]'); const result = (0, base_1.unify)(e, e); result.map(r => chai_1.assert.deepEqual(r, [])); chai_1.assert.isTrue(result.isRight()); }); (0, mocha_1.it)('unifies unions when they are the exact same for updates', () => { const e = (0, parser_1.parseEffectOrThrow)('Update[v1, v2]'); const result = (0, base_1.unify)(e, e); result.map(r => chai_1.assert.deepEqual(r, [])); chai_1.assert.isTrue(result.isRight()); }); }); (0, mocha_1.describe)('simple arrow effects', () => { (0, mocha_1.it)('unifies effects with parameters', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[v]) => Update[v]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['x']) => E"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [ { kind: 'entity', name: 'v', value: { kind: 'concrete', stateVariables: [{ name: 'x', reference: 1n }] } }, { kind: 'effect', name: 'E', value: (0, parser_1.parseEffectOrThrow)("Update['x']") }, ])); }); (0, mocha_1.it)('results in the same effect regardless of unpacked projection', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[r1], Read[r2]) => Read[r1]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['x', 'y']) => E"); const e3 = (0, parser_1.parseEffectOrThrow)('(Read[r1], Read[r2]) => Read[r2]'); const e4 = (0, parser_1.parseEffectOrThrow)("(Read['x', 'y']) => E"); const result1 = (0, base_1.unify)(e1, e2); const result2 = (0, base_1.unify)(e3, e4); result1.map(r1 => result2.map(r2 => chai_1.assert.deepEqual((0, src_1.substitutionsToString)(r1), (0, src_1.substitutionsToString)(r2)))); }); (0, mocha_1.it)('returns error when there are not enough parameters', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[v]) => Update[v]'); const e2 = (0, parser_1.parseEffectOrThrow)("() => Update['y']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify (Read[v]) => Update[v] and () => Update['y']", message: 'Expected 1 arguments, got 0', children: [], })); }); (0, mocha_1.it)('returns error when trying to unify with non-arrow effect', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[v]) => Update[v]'); const e2 = (0, parser_1.parseEffectOrThrow)("Update['y']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify (Read[v]) => Update[v] and Update['y']", message: "Can't unify different types of effects", children: [], })); }); }); (0, mocha_1.describe)('nested arrow effects', () => { (0, mocha_1.it)('unifies effects with parameters', () => { const e1 = (0, parser_1.parseEffectOrThrow)('((Pure) => E) => E'); const e2 = (0, parser_1.parseEffectOrThrow)("((Pure) => Read['x']) => Read['x']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [{ kind: 'effect', name: 'E', value: (0, parser_1.parseEffectOrThrow)("Read['x']") }])); }); }); (0, mocha_1.describe)('effects with multiple variable entities', () => { (0, mocha_1.it)('unfies with concrete and unifiable effect', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['x'] & Update['x'], Read['y', 'z'] & Update['x']) => Read['x', 'y', 'z'] & Update['x']"); const result = (0, base_1.unify)(e1, e2); const reversedResult = (0, base_1.unify)(e2, e1); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [ { kind: 'entity', name: 'r1', value: { kind: 'concrete', stateVariables: [{ name: 'x', reference: 1n }] } }, { kind: 'entity', name: 'r2', value: { kind: 'concrete', stateVariables: [ { name: 'y', reference: 3n }, { name: 'z', reference: 4n }, ], }, }, { kind: 'entity', name: 'u', value: { kind: 'concrete', stateVariables: [{ name: 'x', reference: 2n }] } }, ])); chai_1.assert.deepEqual(result, reversedResult, 'Result should be the same regardless of the effect order in parameters'); }); (0, mocha_1.it)('returns error with incompatible effect', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['x'] & Update['x'], Read['y'] & Update['y']) => E"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify (Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u] and (Read['x'] & Update['x'], Read['y'] & Update['y']) => E", children: [ { location: "Trying to unify Read[r2] & Update['x'] and Read['y'] & Update['y']", children: [ { location: "Trying to unify entities ['x'] and ['y']", message: 'Expected [x] and [y] to be the same', children: [], }, ], }, ], })); }); (0, mocha_1.it)('returns partial bindings when unifying with another variable effect', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['x'] & Update[v], Read['y'] & Update[v]) => Read['x', 'y'] & Update[v]"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [ { kind: 'entity', name: 'r1', value: { kind: 'concrete', stateVariables: [{ name: 'x', reference: 1n }] } }, { kind: 'entity', name: 'r2', value: { kind: 'concrete', stateVariables: [{ name: 'y', reference: 2n }] } }, { kind: 'entity', name: 'u', value: { kind: 'variable', name: 'v' } }, ])); }); (0, mocha_1.it)('simplifies unions of entities before giving up on unifying them', () => { const e1 = (0, parser_1.parseEffectOrThrow)("Read[r1, r2, 'x']"); const e2 = (0, parser_1.parseEffectOrThrow)("Read[r1, 'x']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isRight()); result.map(r => chai_1.assert.sameDeepMembers(r, [{ kind: 'entity', name: 'r2', value: { kind: 'concrete', stateVariables: [] } }])); }); (0, mocha_1.it)('returns error with effect with incompatible entity variables', () => { const e1 = (0, parser_1.parseEffectOrThrow)('(Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u]'); const e2 = (0, parser_1.parseEffectOrThrow)("(Read['y'] & Update['x'], Read['z'] & Update['x']) => Read['y'] & Update[u]"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify (Read[r1] & Update[u], Read[r2] & Update[u]) => Read[r1, r2] & Update[u] and (Read['y'] & Update['x'], Read['z'] & Update['x']) => Read['y'] & Update[u]", children: [ { location: "Trying to unify Read['y', 'z'] & Update['x'] and Read['y'] & Update['x']", children: [ { location: "Trying to unify entities ['y', 'z'] and ['y']", message: 'Expected [y,z] and [y] to be the same', children: [], }, ], }, ], })); }); (0, mocha_1.it)('returns error when unifying union with another union', () => { const e1 = (0, parser_1.parseEffectOrThrow)('Read[r1, r2]'); const e2 = (0, parser_1.parseEffectOrThrow)("Read[r, 'x', 'y']"); const result = (0, base_1.unify)(e1, e2); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(r => chai_1.assert.deepEqual(r, { location: "Trying to unify Read[r1, r2] and Read[r, 'x', 'y']", children: [ { location: "Trying to unify entities [r1, r2] and [r, 'x', 'y']", message: 'Unification for unions of entities is not implemented', children: [], }, ], })); }); (0, mocha_1.it)('returns error when effect names are cyclical', () => { const e1 = (0, parser_1.parseEffectOrThrow)('e1'); const e2 = (0, parser_1.parseEffectOrThrow)('() => e1'); const result = (0, base_1.unify)(e1, e2); result.mapLeft(e => chai_1.assert.deepEqual(e, { location: 'Trying to unify e1 and () => e1', message: "Can't bind e1 to () => e1: cyclical binding", children: [], })); chai_1.assert.isTrue(result.isLeft()); }); (0, mocha_1.it)('returns error when effect names are cyclical in other way', () => { const e1 = (0, parser_1.parseEffectOrThrow)('() => e1'); const e2 = (0, parser_1.parseEffectOrThrow)('e1'); const result = (0, base_1.unify)(e1, e2); result.mapLeft(e => chai_1.assert.deepEqual(e, { location: 'Trying to unify () => e1 and e1', message: "Can't bind e1 to () => e1: cyclical binding", children: [], })); chai_1.assert.isTrue(result.isLeft()); }); (0, mocha_1.it)('can unify entities when a single variable in the lhs appears in a union on the rhs', () => { // E.g., given the unification problem // // v1 =.= v1 ∪ v2 // // We should be able to form a valid substitution iff v1 =.= v2, since // this then simplifies to // // v1 =.= v1 =.= v2 // // NOTE: This test was inverted after an incorrect occurs check was // causing erroneous effect checking failures, as reported in // https://github.com/informalsystems/quint/issues/1356 // // Occurs checks are called for to prevent the attempt to unify a free // variable with a term that includes that variable as a subterm. E.g., `X // =.= foo(a, X)`, which can never be resolved into a ground term. // However, despite appearances, the unification of so called "entity // unions", as in the example above is not such a case. Each "entity // variable" stands for a set of possible state variables. As such, the // unification problem above can be expanded to // // v1 ∪ {} =.= v1 ∪ v2 ∪ {} // // Which helps make clear why the unification succeeds iff v1 =.= v2. const read1 = (0, parser_1.parseEffectOrThrow)('Read[v1]'); const read2 = (0, parser_1.parseEffectOrThrow)('Read[v1, v2]'); chai_1.assert.isTrue((0, base_1.unify)(read1, read2).isRight()); // Check the symmetrical case. const temporal1 = (0, parser_1.parseEffectOrThrow)('Temporal[v1, v2]'); const temporal2 = (0, parser_1.parseEffectOrThrow)('Temporal[v1]'); chai_1.assert.isTrue((0, base_1.unify)(temporal1, temporal2).isRight()); }); }); }); //# sourceMappingURL=base.test.js.map