@informalsystems/quint
Version:
Core tool for the Quint specification language
222 lines • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const mocha_1 = require("mocha");
const chai_1 = require("chai");
const ir_1 = require("../builders/ir");
const printing_1 = require("../../src/effects/printing");
const errorTree_1 = require("../../src/errorTree");
const inferrer_1 = require("../../src/effects/inferrer");
const util_1 = require("../util");
const quintIr_1 = require("../../src/ir/quintIr");
(0, mocha_1.describe)('inferEffects', () => {
const baseDefs = ['const N: int', 'const S: Set[int]', 'var x: int'];
function inferEffectsForDefs(defs) {
const text = `module wrapper { ${baseDefs.concat(defs).join('\n')} }`;
const { modules, table } = (0, util_1.parseMockedModule)(text);
const inferrer = new inferrer_1.EffectInferrer(table);
return inferrer.inferEffects(modules[0].declarations);
}
function effectForDef(defs, effects, defName) {
const module = (0, ir_1.buildModuleWithDecls)(baseDefs.concat(defs));
const result = module.declarations.find(decl => (0, quintIr_1.isDef)(decl) && decl.name === defName);
if (!result) {
throw new Error(`Could not find def with name ${defName}`);
}
return (0, printing_1.effectSchemeToString)(effects.get(result.id));
}
(0, mocha_1.it)('infers simple operator effect', () => {
const defs = [`def a(p) = x' = p`];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "∀ v0 . (Read[v0]) => Read[v0] & Update['x']";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('infers application of multiple arity opertors', () => {
const defs = ['def a(p) = and(p, x)', 'def b(p) = and(p, 1, 2)'];
const [errors, effects] = inferEffectsForDefs(defs);
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), "∀ v0, v1 . (Read[v0] & Temporal[v1]) => Read[v0, 'x'] & Temporal[v1]");
chai_1.assert.deepEqual(effectForDef(defs, effects, 'b'), '∀ v0, v1 . (Read[v0] & Temporal[v1]) => Read[v0] & Temporal[v1]');
});
(0, mocha_1.it)('infers references to operators', () => {
const defs = ['def a(p) = foldl(x, p, iadd)'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "∀ v0, v1 . (Read[v0] & Temporal[v1]) => Read[v0, 'x'] & Temporal[v1]";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('infers references to user-defined operators', () => {
const defs = ['def a(p) = def my_add = iadd { foldl(x, p, my_add) }'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "∀ v0, v1 . (Read[v0] & Temporal[v1]) => Read[v0, 'x'] & Temporal[v1]";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('infers effects for operators defined with let-in', () => {
const defs = ['val b = val m = x { m }'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "Read['x']";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'b'), expectedEffect);
});
(0, mocha_1.it)('infers pure effect for literals and value constants', () => {
const defs = ['val b = N + 1'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = 'Pure';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'b'), expectedEffect);
});
(0, mocha_1.it)('infers arrow effect for operator constants', () => {
const defs = ['const MyOp: int => int', 'val b = MyOp(x)'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "Read['x']";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'b'), expectedEffect);
});
(0, mocha_1.it)('handles underscore', () => {
const defs = ['val b = N.map(_ => 1)'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = 'Pure';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'b'), expectedEffect);
});
(0, mocha_1.it)('infers polymorphic high order operators', () => {
const defs = ['def a(g, p) = g(p)'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = '∀ e0, e1 . ((e0) => e1, e0) => e1';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('infers monomorphic high order operators', () => {
const defs = ['def a(g, p) = g(p) + g(not(p))'];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = '∀ v0, v1, v2, v3 . ((Read[v0] & Temporal[v1]) => Read[v2] & Temporal[v3], Read[v0] & Temporal[v1]) => Read[v2] & Temporal[v3]';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('keeps track of substitutions with nested defs', () => {
const defs = [
'pure def a(p) = and{' +
' val b = p + 1' +
' p + b > 0,' +
' val c = p + 2' +
' p + c > 0,' +
' p > 0,' +
'}',
];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = '∀ v0, v1 . (Read[v0] & Temporal[v1]) => Read[v0] & Temporal[v1]';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'a'), expectedEffect);
});
(0, mocha_1.it)('keeps track of substitutions with lambdas and applications', () => {
const defs = [
`pure def MinBy(__set: Set[a], __f: a => int, __i: a): a = {
__set.fold(
__i,
(__m, __e) => if(__f(__m) < __f(__e)) {__m } else {__e}
)
}`,
];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = '∀ v0, v1 . (Read[v0], (Read[v0]) => Read[v1], Read[v0]) => Read[v0, v1]';
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'MinBy'), expectedEffect);
});
(0, mocha_1.it)('regression on #1091', () => {
const defs = [
'var channel: int',
`action CoolAction(boolean: bool): bool =
any {
all {
boolean,
channel' = channel
},
all {
not(boolean),
channel' = channel
}
}`,
];
const [errors, effects] = inferEffectsForDefs(defs);
const expectedEffect = "∀ v0 . (Read[v0]) => Read[v0, 'channel'] & Update['channel']";
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
chai_1.assert.deepEqual(effectForDef(defs, effects, 'CoolAction'), expectedEffect);
});
(0, mocha_1.it)('avoids invalid cyclical binding error (regression on #1356)', () => {
const defs = [
`pure def foo(s: int, g: int => int): int = {
val r = if (true) s else g(s)
g(r)
}`,
];
const [errors, _] = inferEffectsForDefs(defs);
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
});
(0, mocha_1.it)('returns error when operator signature is not unifiable with args', () => {
const defs = [`def a = S.map(p => x' = p)`];
const [errors] = inferEffectsForDefs(defs);
errors.forEach(v => chai_1.assert.deepEqual(v, {
children: [
{
children: [
{
children: [
{
children: [
{
children: [],
location: "Trying to unify entities ['x'] and []",
message: 'Expected [x] and [] to be the same',
},
],
location: "Trying to unify Read[_v4] & Temporal[_v5] and Update['x']",
},
],
location: "Trying to unify (Pure) => Read[_v4] & Temporal[_v5] and (Read[_v1]) => Read[_v1] & Update['x']",
},
],
location: "Trying to unify (Read[_v2] & Temporal[_v3], (Read[_v2] & Temporal[_v3]) => Read[_v4] & Temporal[_v5]) => Read[_v2, _v4] & Temporal[_v3, _v5] and (Pure, (Read[_v1]) => Read[_v1] & Update['x']) => _e1",
},
],
location: 'Trying to infer effect for operator application in map(S, ((p) => assign(x, p)))',
}));
});
(0, mocha_1.it)('returns error when lambda returns an operator', () => {
const defs = ['pure def f(p) = p', 'pure def myOp = (_) => f'];
const [errors] = inferEffectsForDefs(defs);
chai_1.assert.deepEqual([...errors.values()][0], {
children: [],
location: 'Inferring effect for f',
message: 'Result cannot be an operator',
});
});
(0, mocha_1.it)('returns error when `match` branches update different variables', () => {
const defs = ['type Result = | Some(int) | None', "val foo = match Some(1) { | Some(n) => x' = n | None => true }"];
const [errors] = inferEffectsForDefs(defs);
chai_1.assert.deepEqual([...errors.values()][0].children[0].children[0].children[0].children[0], {
children: [],
location: "Trying to unify entities ['x'] and []",
message: 'Expected [x] and [] to be the same',
});
});
(0, mocha_1.it)('differentiates variables from different instances', () => {
const baseDefs = ['const N: int', 'const S: Set[int]', 'var x: int'];
const text = `
module base { ${baseDefs.join('\n')} }
module wrapper {
import base(N=1) as B1
import base(N=2) as B2
val a = B1::x + B2::x
}`;
const { modules, table } = (0, util_1.parseMockedModule)(text);
const inferrer = new inferrer_1.EffectInferrer(table);
inferrer.inferEffects(modules[0].declarations);
const [errors, effects] = inferrer.inferEffects(modules[1].declarations);
chai_1.assert.isEmpty(errors, `Should find no errors, found: ${[...errors.values()].map(errorTree_1.errorTreeToString)}`);
const def = modules[1].declarations.find(decl => (0, quintIr_1.isDef)(decl) && decl.name === 'a');
const expectedEffect = "Read['wrapper::B1::x', 'wrapper::B2::x']";
chai_1.assert.deepEqual((0, printing_1.effectSchemeToString)(effects.get(def.id)), expectedEffect);
});
});
//# sourceMappingURL=inferrer.test.js.map