@informalsystems/quint
Version:
Core tool for the Quint specification language
365 lines • 18.7 kB
JavaScript
"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