UNPKG

@informalsystems/quint

Version:

Core tool for the Quint specification language

132 lines 6.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = require("chai"); const mocha_1 = require("mocha"); const textUtils_1 = require("../textUtils"); const idGenerator_1 = require("../../src/idGenerator"); const quintParserFrontend_1 = require("../../src/parsing/quintParserFrontend"); const sourceResolver_1 = require("../../src/parsing/sourceResolver"); const callgraph_1 = require("../../src/static/callgraph"); const IRVisitor_1 = require("../../src/ir/IRVisitor"); const lodash_1 = require("lodash"); (0, mocha_1.describe)('compute call graph', () => { // Parse Quint code without, stopping after name resolution function parse3phases(code) { const idGen = (0, idGenerator_1.newIdGenerator)(); const fakePath = { normalizedPath: 'fake_path', toSourceName: () => 'fake_path', }; // we are calling parse phases directly instead of `parse`, // as the call graph will be computed at parse phase 4 const resolver = (0, sourceResolver_1.fileSourceResolver)(); const { modules, table, errors } = (0, lodash_1.flow)([ () => (0, quintParserFrontend_1.parsePhase1fromText)(idGen, code, fakePath.normalizedPath), phase1Data => (0, quintParserFrontend_1.parsePhase2sourceResolution)(idGen, resolver, fakePath, phase1Data), quintParserFrontend_1.parsePhase3importAndNameResolution, ])(); chai_1.assert.isEmpty(errors); return [modules, table]; } function findDef(module, name) { const d = module.declarations.find(d => (d.kind === 'def' || d.kind === 'const' || d.kind === 'var' || d.kind === 'typedef') && d.name === name); (0, chai_1.assert)(d, `Definition ${name} not found`); return d; } function findImport(module, pred) { const d = module.declarations.find(d => d.kind === 'import' && pred(d)); (0, chai_1.assert)(d, `Import not found in ${module.name}`); return d; } function findInstance(module, pred) { const d = module.declarations.find(d => d.kind === 'instance' && pred(d)); (0, chai_1.assert)(d, `Instance not found in ${module.name}`); return d; } function findExport(module, pred) { const d = module.declarations.find(d => d.kind === 'export' && pred(d)); (0, chai_1.assert)(d, `Export not found in ${module.name}`); return d; } it('computes a call graph of const, var, and operator definitions', () => { const code = (0, textUtils_1.dedent)(`module main { | const N: int | var w: int | pure def plus(x, y) = x + y | pure def double(x) = plus(x, x) | pure def triple(x) = plus(x, double(x, x)) | val getW = w + N |}`); const [modules, table] = parse3phases(code); const main = modules.find(m => m.name === 'main'); const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules)); (0, IRVisitor_1.walkModule)(visitor, main); const graph = visitor.graph; const plus = findDef(main, 'plus'); const double = findDef(main, 'double'); const triple = findDef(main, 'triple'); const w = findDef(main, 'w'); const N = findDef(main, 'N'); const getW = findDef(main, 'getW'); (0, chai_1.expect)(graph.get(plus.id)?.size).to.equal(2); (0, chai_1.expect)(graph.get(double.id)?.toArray()).to.include.members([plus.id]); (0, chai_1.expect)(graph.get(triple.id)?.toArray()).to.include.members([plus.id, double.id]); (0, chai_1.expect)(graph.get(getW.id)?.toArray()).to.include.members([w.id, N.id]); }); it('computes a "uses" graph of typedefs', () => { const code = (0, textUtils_1.dedent)(`module main { | type BagOfApples = Set[int] | type BoxOfApples = Set[BagOfApples] | var x: BoxOfApples |}`); const [modules, table] = parse3phases(code); const main = modules.find(m => m.name === 'main'); const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules)); (0, IRVisitor_1.walkModule)(visitor, main); const graph = visitor.graph; const bagOfApples = findDef(main, 'BagOfApples'); const boxOfApples = findDef(main, 'BoxOfApples'); const x = findDef(main, 'x'); (0, chai_1.expect)(graph.get(bagOfApples.id)).to.be.undefined; (0, chai_1.expect)(graph.get(boxOfApples.id)?.toArray()).to.eql([bagOfApples.id]); (0, chai_1.expect)(graph.get(x.id)?.toArray()).to.eql([boxOfApples.id]); }); it('computes a "uses" graph of imports and defs', () => { // the following definitions should always come in this order: const code = (0, textUtils_1.dedent)(`module A { | pure def sqr(x) = x * x |} |module B { | const M: int | pure val doubleM = M + M |} |module main { | import A.* | pure val myM = sqr(3) | import B(M = myM) as B1 | pure val quadM = 2 * B1::doubleM | export B1.* |}`); const [modules, table] = parse3phases(code); const findModule = (name) => modules.find(m => m.name === name); const [A, B, main] = ['A', 'B', 'main'].map(findModule); const visitor = new callgraph_1.CallGraphVisitor(table, (0, callgraph_1.mkCallGraphContext)(modules)); (0, IRVisitor_1.walkModule)(visitor, main); const graph = visitor.graph; // uncomment to debug the graph structure //visitor.print(console.log) const sqr = findDef(A, 'sqr'); const importA = findImport(main, imp => imp.protoName === 'A'); const myM = findDef(main, 'myM'); const importB = findInstance(main, imp => imp.protoName === 'B'); const quadM = findDef(main, 'quadM'); const doubleM = findDef(B, 'doubleM'); const exportB1 = findExport(main, exp => exp.protoName === 'B1'); (0, chai_1.expect)(graph.get(importA.id)?.toArray()).eql([A.id]); (0, chai_1.expect)(graph.get(myM.id)?.toArray()).to.eql([sqr.id, importA.id]); (0, chai_1.expect)(graph.get(importB.id)?.toArray()).to.eql([B.id, myM.id]); (0, chai_1.expect)(graph.get(quadM.id)?.toArray()).to.eql([doubleM.id, importB.id]); (0, chai_1.expect)(graph.get(exportB1.id)?.toArray()).to.eql([importB.id]); }); }); //# sourceMappingURL=callgraph.test.js.map