UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

365 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const mocha_1 = require("mocha"); const chai_1 = require("chai"); const constraintSolver_1 = require("../../src/types/constraintSolver"); const parser_1 = require("../../src/types/parser"); const printing_1 = require("../../src/types/printing"); const quintTypes_1 = require("../../src/ir/quintTypes"); const errorTree_1 = require("../../src/errorTree"); const table = new Map([ // A type alias (id 1n) [3n, { kind: 'typedef', name: 'MY_ALIAS', type: { kind: 'int' }, id: 1n }], // An uniterpreted type (id 2n) [4n, { kind: 'typedef', name: 'MY_UNINTERPRETED', id: 2n }], [5n, { kind: 'typedef', name: 'MY_UNINTERPRETED', id: 2n }], [ 6n, { kind: 'typedef', name: 'SumType', id: 7n, type: (0, quintTypes_1.sumType)([ ['A', { kind: 'int' }], ['B', { kind: 'str' }], ]), }, ], ]); (0, mocha_1.describe)('solveConstraint', () => { (0, mocha_1.it)('solves simple equality', () => { const constraint = { kind: 'eq', types: [(0, parser_1.parseTypeOrThrow)('a'), (0, parser_1.parseTypeOrThrow)('int')], sourceId: 1n, }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> int ]')); }); (0, mocha_1.it)('solves conjunctions', () => { const constraint1 = { kind: 'eq', types: [(0, parser_1.parseTypeOrThrow)('a'), (0, parser_1.parseTypeOrThrow)('int')], sourceId: 1n, }; const constraint2 = { kind: 'eq', types: [(0, parser_1.parseTypeOrThrow)('b'), (0, parser_1.parseTypeOrThrow)('a')], sourceId: 2n, }; const constraint = { kind: 'conjunction', constraints: [constraint1, constraint2], sourceId: 3n, }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> int, b |-> int ]')); }); (0, mocha_1.it)('solves isDefined constraint when unifiable type is defined', () => { const constraint = { kind: 'isDefined', type: (0, quintTypes_1.sumType)([['A', { kind: 'int' }]], 'a'), sourceId: 1n, }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> { B: str } ]')); }); (0, mocha_1.it)('isDefined constraint fails when type is not defined', () => { const constraint = { kind: 'isDefined', type: (0, quintTypes_1.sumType)([['NotDefined', { kind: 'int' }]], 'a'), sourceId: 1n, }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(errs => { const err = errs.get(1n); chai_1.assert.deepEqual(err?.location, 'Looking for defined type unifying with (NotDefined(int))'); chai_1.assert.deepEqual(err?.message, 'Expected type is not defined'); }); }); (0, mocha_1.it)('solves empty constraint', () => { const constraint = { kind: 'empty' }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.sameDeepMembers(subs, [])); }); (0, mocha_1.it)('fails to solve equality constraint between incompatible types', () => { const constraint1 = { kind: 'eq', types: [(0, parser_1.parseTypeOrThrow)('bool'), (0, parser_1.parseTypeOrThrow)('int')], sourceId: 1n, }; const constraint2 = { kind: 'eq', types: [(0, parser_1.parseTypeOrThrow)('Set[a]'), (0, parser_1.parseTypeOrThrow)('List[a]')], sourceId: 2n, }; const constraint = { kind: 'conjunction', constraints: [constraint1, constraint2], sourceId: 3n, }; const result = (0, constraintSolver_1.solveConstraint)(table, constraint); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(errors => { chai_1.assert.sameDeepMembers([...errors.entries()], [ [ 1n, { message: "Couldn't unify bool and int", location: 'Trying to unify bool and int', children: [], }, ], [ 2n, { message: "Couldn't unify set and list", location: 'Trying to unify Set[a] and List[a]', children: [], }, ], ]); }); }); }); (0, mocha_1.describe)('unify', () => { (0, mocha_1.it)('unifies variable with other type', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('a'), (0, parser_1.parseTypeOrThrow)('(Set[b]) => List[b]')); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> (Set[b]) => List[b] ]')); }); (0, mocha_1.it)('returns empty substitution for equal types', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('(Set[b]) => List[b]'), (0, parser_1.parseTypeOrThrow)('(Set[b]) => List[b]')); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.sameDeepMembers(subs, [])); }); (0, mocha_1.it)('returns empty substitution for equal types with alias', () => { const result = (0, constraintSolver_1.unify)(table, { kind: 'const', name: 'MY_ALIAS', id: 3n }, { kind: 'int' }); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.sameDeepMembers(subs, [])); }); (0, mocha_1.it)('returns empty substitution for equal uninterpreted types', () => { const result = (0, constraintSolver_1.unify)(table, { kind: 'const', name: 'MY_UNINTERPRETED', id: 4n }, { kind: 'const', name: 'MY_UNINTERPRETED', id: 5n }); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.sameDeepMembers(subs, [])); }); (0, mocha_1.it)('returns error when uninterpreted type is unified with other type', () => { const result = (0, constraintSolver_1.unify)(table, { kind: 'const', name: 'MY_UNINTERPRETED', id: 4n }, { kind: 'int' }); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(err => chai_1.assert.deepEqual(err, { message: "Couldn't unify uninterpreted type MY_UNINTERPRETED with different type", location: 'Trying to unify MY_UNINTERPRETED and int', children: [], })); }); (0, mocha_1.it)('returns error when type alias is not found', () => { const result = (0, constraintSolver_1.unify)(table, { kind: 'const', name: 'UNEXISTING_ALIAS', id: 999n }, { kind: 'int' }); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(err => chai_1.assert.deepEqual(err, { message: "Couldn't find type alias UNEXISTING_ALIAS", location: 'Trying to unify UNEXISTING_ALIAS and int', children: [], })); }); (0, mocha_1.it)('unifies args and results of arrow and function types', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('(a) => int -> bool'), (0, parser_1.parseTypeOrThrow)('((Set[b]) => List[b]) => b -> c')); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> (Set[int]) => List[int], c |-> bool, b |-> int ]')); }); (0, mocha_1.it)('unifies elements of tuples, set and list types', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('(Set[a], List[b])'), (0, parser_1.parseTypeOrThrow)('(Set[int], List[bool])')); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> int, b |-> bool ]')); }); (0, mocha_1.it)('unifies sum-type', () => { const result = (0, constraintSolver_1.unify)(table, (0, quintTypes_1.sumType)([ ['A', { kind: 'var', name: 'a' }], ['B', { kind: 'int' }], ['C', (0, quintTypes_1.unitType)(0n)], ]), (0, quintTypes_1.sumType)([ ['C', (0, quintTypes_1.unitType)(0n)], ['A', { kind: 'str' }], ['B', { kind: 'var', name: 'b' }], ])); chai_1.assert.isTrue(result.isRight()); result.map(subs => chai_1.assert.deepEqual((0, printing_1.substitutionsToString)(subs), '[ a |-> str, b |-> int ]')); }); (0, mocha_1.it)("returns error when variable occurs in the other type's body", () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('a'), (0, parser_1.parseTypeOrThrow)('Set[a]')); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(err => chai_1.assert.deepEqual(err, { message: "Can't bind a to Set[a]: cyclical binding", location: 'Trying to unify a and Set[a]', children: [], })); }); (0, mocha_1.it)('returns error when unifying operator with different number of args', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('(a, b) => c'), (0, parser_1.parseTypeOrThrow)('(int) => c')); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(err => chai_1.assert.deepEqual(err, { message: 'Expected 2 arguments, got 1', location: 'Trying to unify (a, b) => c and (int) => c', children: [], })); }); (0, mocha_1.it)('returns error when unifying tuples with different number of args', () => { const result = (0, constraintSolver_1.unify)(table, (0, parser_1.parseTypeOrThrow)('(a, b, c)'), (0, parser_1.parseTypeOrThrow)('(int, bool)')); chai_1.assert.isTrue(result.isLeft()); result.mapLeft(err => chai_1.assert.deepEqual(err, { location: 'Trying to unify (a, b, c) and (int, bool)', children: [ { location: 'Trying to unify { 0: a, 1: b, 2: c } and { 0: int, 1: bool }', children: [ { message: "Couldn't unify row and empty", location: 'Trying to unify { 2: c } and {}', children: [], }, ], }, ], })); }); }); (0, mocha_1.describe)('unifyRows', () => { (0, mocha_1.it)('unifies empty row with non-empty', () => { const row1 = (0, parser_1.parseRowOrThrow)(''); const row2 = (0, parser_1.parseRowOrThrow)('| a'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); const expectedSubs = [{ kind: 'row', name: 'a', value: { kind: 'empty' } }]; result.map(subs => chai_1.assert.sameDeepMembers(subs, expectedSubs)).mapLeft(err => chai_1.assert.fail((0, errorTree_1.errorTreeToString)(err))); }); (0, mocha_1.it)('unifies row var with row with fields', () => { const row1 = (0, parser_1.parseRowOrThrow)('f: int'); const row2 = (0, parser_1.parseRowOrThrow)('| a'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); const expectedSubs = [{ kind: 'row', name: 'a', value: row1 }]; result.map(subs => chai_1.assert.sameDeepMembers(subs, expectedSubs)).mapLeft(err => chai_1.assert.fail((0, errorTree_1.errorTreeToString)(err))); }); (0, mocha_1.it)('unifies two partial rows', () => { const row1 = (0, parser_1.parseRowOrThrow)('f1: int, f2: str | a'); const row2 = (0, parser_1.parseRowOrThrow)('f3: bool | b'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); const expectedSubs = [ { kind: 'row', name: 'a', value: { kind: 'row', fields: [{ fieldName: 'f3', fieldType: { kind: 'bool', id: 1n } }], other: { kind: 'var', name: '$a$b' }, }, }, { kind: 'row', name: 'b', value: { kind: 'row', fields: [ { fieldName: 'f1', fieldType: { kind: 'int', id: 1n } }, { fieldName: 'f2', fieldType: { kind: 'str', id: 2n } }, ], other: { kind: 'var', name: '$a$b' }, }, }, ]; result.map(subs => chai_1.assert.sameDeepMembers(subs, expectedSubs)).mapLeft(err => chai_1.assert.fail((0, errorTree_1.errorTreeToString)(err))); }); (0, mocha_1.it)('unifies partial row with complete row', () => { const row1 = (0, parser_1.parseRowOrThrow)('f1: int, f2: str | a'); const row2 = (0, parser_1.parseRowOrThrow)('f3: bool, f2: str, f1: int'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); const expectedSubs = [ { kind: 'row', name: 'a', value: (0, parser_1.parseRowOrThrow)('f3: bool'), }, ]; result.map(subs => chai_1.assert.sameDeepMembers(subs, expectedSubs)).mapLeft(err => chai_1.assert.fail((0, errorTree_1.errorTreeToString)(err))); }); (0, mocha_1.it)('unifies two row variables', () => { const row1 = (0, parser_1.parseRowOrThrow)('| a'); const row2 = (0, parser_1.parseRowOrThrow)('| b'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); const expectedSubs = [{ kind: 'row', name: 'a', value: { kind: 'var', name: 'b' } }]; result.map(subs => chai_1.assert.sameDeepMembers(subs, expectedSubs)).mapLeft(err => chai_1.assert.fail((0, errorTree_1.errorTreeToString)(err))); }); (0, mocha_1.it)('fails at unifying rows with incompatible fields', () => { const row1 = (0, parser_1.parseRowOrThrow)('f1: int'); const row2 = (0, parser_1.parseRowOrThrow)('f1: str'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); result .mapLeft(err => chai_1.assert.deepEqual(err, { location: 'Trying to unify { f1: int } and { f1: str }', children: [ { message: "Couldn't unify int and str", location: 'Trying to unify int and str', children: [], }, ], })) .map(subs => chai_1.assert.fail('Expected error, got substitutions: ' + (0, printing_1.substitutionsToString)(subs))); }); (0, mocha_1.it)('fails at unifying complete rows with distinct fields', () => { const row1 = (0, parser_1.parseRowOrThrow)('shared: bool, f1: int'); const row2 = (0, parser_1.parseRowOrThrow)('shared: bool, f2: str'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); result .mapLeft(err => chai_1.assert.deepEqual(err, { location: 'Trying to unify { shared: bool, f1: int } and { shared: bool, f2: str }', children: [ { message: 'Incompatible tails for rows with disjoint fields: {} and {}', location: 'Trying to unify { f1: int } and { f2: str }', children: [], }, ], })) .map(subs => chai_1.assert.fail('Expected error, got substitutions: ' + (0, printing_1.substitutionsToString)(subs))); }); (0, mocha_1.it)('fails at unifying rows with cyclical references', () => { const row1 = (0, parser_1.parseRowOrThrow)('| a'); const row2 = (0, parser_1.parseRowOrThrow)('f1: str | a'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); result .mapLeft(err => chai_1.assert.deepEqual(err, { message: "Can't bind a to { f1: str | a }: cyclical binding", location: 'Trying to unify { | a } and { f1: str | a }', children: [], })) .map(subs => chai_1.assert.fail('Expected error, got substitutions: ' + (0, printing_1.substitutionsToString)(subs))); }); (0, mocha_1.it)('fails at unifying rows with cyclical references on tail', () => { const row1 = (0, parser_1.parseRowOrThrow)('f1: str | a'); const row2 = (0, parser_1.parseRowOrThrow)('f2: int | a'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); result .mapLeft(err => chai_1.assert.deepEqual(err, { location: 'Trying to unify { f1: str | a } and { f2: int | a }', message: 'Incompatible tails for rows with disjoint fields: { | a } and { | a }', children: [], })) .map(subs => chai_1.assert.fail('Expected error, got substitutions: ' + (0, printing_1.substitutionsToString)(subs))); }); (0, mocha_1.it)('fails at unifying incompatible rows', () => { const row1 = (0, parser_1.parseRowOrThrow)(''); const row2 = (0, parser_1.parseRowOrThrow)('f1: str | a'); const result = (0, constraintSolver_1.unifyRows)(table, row1, row2); result .mapLeft(err => chai_1.assert.deepEqual(err, { message: "Couldn't unify empty and row", location: 'Trying to unify {} and { f1: str | a }', children: [], })) .map(subs => chai_1.assert.fail('Expected error, got substitutions: ' + (0, printing_1.substitutionsToString)(subs))); }); }); //# sourceMappingURL=constraintSolver.test.js.map