diff --git a/packages/transform-eventual-send/package.json b/packages/transform-eventual-send/package.json index de9cf8fb8cd..2073712d560 100644 --- a/packages/transform-eventual-send/package.json +++ b/packages/transform-eventual-send/package.json @@ -1,6 +1,6 @@ { "name": "@agoric/transform-eventual-send", - "version": "1.0.4", + "version": "1.1.0", "description": "transform-eventual-send", "main": "dist/transform-eventual-send.cjs.js", "module": "dist/transform-eventual-send.esm.js", @@ -9,15 +9,18 @@ "test": "tape -r esm 'test/**/*.js'", "lint-fix": "eslint --fix '**/*.{js,jsx}'", "lint-check": "eslint '**/*.{js,jsx}'", - "build": "rollup -c rollup.config.js" + "build": "node -r esm ./scripts/build-esend.js && rollup -c rollup.config.js" }, "devDependencies": { "@agoric/acorn-eventual-send": "^2.0.0", "@agoric/babel-parser": "^7.6.4", + "@agoric/bundle-source": "^1.0.4", "@agoric/eventual-send": "^0.5.0", + "@agoric/harden": "^0.0.4", "@babel/generator": "^7.5.0", "astring": "^1.4.0", "esm": "^3.2.5", + "rollup": "^1.16.6", "rollup-plugin-node-resolve": "^5.2.0", "ses": "^0.6.3", "tap-spec": "^5.0.0", diff --git a/packages/transform-eventual-send/scripts/build-esend.js b/packages/transform-eventual-send/scripts/build-esend.js new file mode 100755 index 00000000000..abf9c5687b5 --- /dev/null +++ b/packages/transform-eventual-send/scripts/build-esend.js @@ -0,0 +1,27 @@ +#! /usr/bin/env node +import fs from 'fs'; +import process from 'process'; +import bundleSource from '@agoric/bundle-source'; + +async function main() { + const esend = require.resolve(`@agoric/eventual-send`); + const bundle = await bundleSource(esend); + const fileContents = `export default ${JSON.stringify(bundle)};`; + try { + await fs.promises.mkdir('src/bundles'); + } catch (e) { + if (!e || e.code !== 'EEXIST') { + throw e; + } + } + await fs.promises.writeFile('src/bundles/eventual-send.js', fileContents); +} + +main().then( + _ => process.exit(0), + err => { + console.log('error creating src/bundles/eventual-send.js:'); + console.log(err); + process.exit(1); + }, +); diff --git a/packages/transform-eventual-send/src/.gitignore b/packages/transform-eventual-send/src/.gitignore new file mode 100644 index 00000000000..68ac6de1f83 --- /dev/null +++ b/packages/transform-eventual-send/src/.gitignore @@ -0,0 +1 @@ +bundles diff --git a/packages/transform-eventual-send/src/index.js b/packages/transform-eventual-send/src/index.js index de4c9fc1228..bd16807989d 100644 --- a/packages/transform-eventual-send/src/index.js +++ b/packages/transform-eventual-send/src/index.js @@ -1,9 +1,42 @@ +import eventualSendBundle from './bundles/eventual-send'; + function makeEventualSendTransformer(parser, generate) { + let HandledPromise; + let evaluateProgram; const transform = { + closeOverSES(s) { + // FIXME: This should be replaced with ss.evaluateProgram support in SES. + evaluateProgram = src => s.evaluate(src); + }, rewrite(ss) { - // Parse with eventualSend enabled, rewriting to - // HandledPromise.got/set/apply/applyMethod/delete(...) const source = ss.src; + if (!source.includes(`${'~'}.`)) { + // Short circuit: no instance of tildot. + return ss; + } + const endowments = ss.endowments || {}; + if (!('HandledPromise' in endowments)) { + // Use a getter to postpone initialization. + Object.defineProperty(endowments, 'HandledPromise', { + get() { + if (!HandledPromise) { + // Get a HandledPromise endowment for the evaluator. + // It will be hardened in the evaluator's context. + const { source, moduleFormat } = eventualSendBundle; + if (moduleFormat === 'getExport') { + const ns = (evaluateProgram || ss.evaluateProgram)(`(${source})()`); + HandledPromise = ns.HandledPromise; + } else { + throw Error(`Unrecognized moduleFormat ${moduleFormat}`); + } + } + return HandledPromise; + }, + }); + } + + // Parse with eventualSend enabled, rewriting to + // HandledPromise.get/applyFunction/applyMethod(...) const parseFunc = parser.parse; const ast = (parseFunc || parser)(source, { plugins: ['eventualSend'], @@ -23,6 +56,7 @@ function makeEventualSendTransformer(parser, generate) { return { ...ss, ast, + endowments, src: actualSource, }; }, diff --git a/packages/transform-eventual-send/test/ses-test.js b/packages/transform-eventual-send/test/ses-test.js index fc41511b91d..310abc6f656 100644 --- a/packages/transform-eventual-send/test/ses-test.js +++ b/packages/transform-eventual-send/test/ses-test.js @@ -3,8 +3,6 @@ import { test } from 'tape-promise/tape'; import SES from 'ses'; -import { makeHandledPromise } from '@agoric/eventual-send'; - import * as babelParser from '@agoric/babel-parser'; import babelGenerate from '@babel/generator'; @@ -14,7 +12,10 @@ import * as astring from 'astring'; import makeEventualSendTransformer from '../src'; -const shims = [`this.HandledPromise = (${makeHandledPromise})(Promise)`]; +// FIXME: This should be unnecessary when SES has support +// for passing `evaluateProgram` through to the rewriter state. +const closeOverSES = (transforms, ses) => + transforms.forEach(t => t.closeOverSES && t.closeOverSES(ses)); const AcornParser = acorn.Parser.extend(eventualSend(acorn)); const acornParser = { @@ -56,7 +57,6 @@ test('eventual send is disabled by default', t => { test('expression source is parsable', async t => { try { const s = SES.makeSESRootRealm({ - shims, transforms: makeEventualSendTransformer(babelParser, babelGenerate), }); t.equal( @@ -90,25 +90,23 @@ test('expression source is parsable', async t => { test('eventual send can be enabled twice', async t => { try { - const s = SES.makeSESRootRealm({ - shims, - transforms: [ - ...makeEventualSendTransformer(babelParser, babelGenerate), - ...makeEventualSendTransformer(babelParser, babelGenerate), - ], - }); + const transforms = [ + ...makeEventualSendTransformer(babelParser, babelGenerate), + ...makeEventualSendTransformer(babelParser, babelGenerate), + ]; + const s = SES.makeSESRootRealm({ transforms }); + closeOverSES(transforms, s); t.equal( await s.evaluate('"abc"~.[2]'), 'c', `babel double transform works`, ); - const s2 = SES.makeSESRootRealm({ - shims, - transforms: [ - ...makeEventualSendTransformer(acornParser, acornGenerate), - ...makeEventualSendTransformer(acornParser, acornGenerate), - ], - }); + const transforms2 = [ + ...makeEventualSendTransformer(acornParser, acornGenerate), + ...makeEventualSendTransformer(acornParser, acornGenerate), + ]; + const s2 = SES.makeSESRootRealm({ transforms: transforms2 }); + closeOverSES(transforms2, s2); t.equal( await s2.evaluate('"abc"~.[2]'), 'c', @@ -127,10 +125,12 @@ test('eventual send can be enabled', async t => { ['babel', babelParser, babelGenerate], ['acorn', acornParser, acornGenerate], ]) { - const s = SES.makeSESRootRealm({ - shims, - transforms: makeEventualSendTransformer(parser, generate), - }); + const transforms = [ + ...makeEventualSendTransformer(parser, generate), + ]; + const s = SES.makeSESRootRealm({ transforms }); + closeOverSES(transforms, s); + // console.log(parser('"abc"~.length', { plugins: ['eventualSend'] })); t.equals(await s.evaluate(`"abc"~.length`), 3, `${name} .get() works`); t.equals(