"use strict"; const assert = require("assert"); const types = require("@babel/types"); const template = require("@babel/template").default; const unpackExpression = require("./_unpack-expression"); const NoChange = require("../astformer/actions/no-change"); // FIXME: Add expression parens! let tmplCallLazy = template(` %%wrapper%%() `); let tmplLazyWrapper = template(`( () => %%expression%% )`); let tmplLazyWrapperRecursive = template(`( function() { return %%expression%%; } )`); let tmplExtend = template(`( $$jsNix$extend(this ?? {}, %%object%%) )`); function lazyEvaluationWrapper(args) { let { recursive, ... rest } = args; let wrapper = (recursive) ? tmplLazyWrapperRecursive : tmplLazyWrapper; return unpackExpression(wrapper(rest)); } function isDynamicBinding(binding) { return binding.attrpath.attr[0].type !== "NixAttributeIdentifier"; } module.exports = { name: "attribute-sets", visitors: { Program: (_node, { setContext }) => { setContext(null, "attributeSets_inRecursiveSet", false); return NoChange; }, NixAttributeSelection: (_node, { defer }) => { // TODO: Optimize dynamic attribute access by translating builtins.getAttr to dynamic property access at compile time? This requires scope analysis! return defer((node) => { assert(node.attrpath.type === "NixAttributePath"); return node.attrpath.attr.reduce((last, identifier) => { assert(identifier.type === "NixAttributeIdentifier"); return unpackExpression(tmplCallLazy({ wrapper: types.memberExpression(last, types.identifier(identifier.name)) })); }, node.expression); }); }, NixAttributeSet: (node, { defer, setContext, getContext }) => { if (node.recursive) { setContext(null, "attributeSets_inRecursiveSet", true); } return defer((node) => { let isRecursive = node.recursive; let hasDynamicBindings = node.bind.some((binding) => isDynamicBinding(binding)); if (hasDynamicBindings) { throw new Error(`UNIMPLEMENTED: Dynamic bindings are not supported yet`); } else { let object = types.objectExpression(node.bind.map((binding) => { assert(binding.attrpath.attr.length === 1); // Nested attributes should have been desugared by this point let name = binding.attrpath.attr[0].name; let expression = binding.expression; return types.objectProperty( types.stringLiteral(name), lazyEvaluationWrapper({ recursive: isRecursive, expression: expression }) ); })); // FIXME/MARKER: This is currently broken! console.log({context: getContext("attributeSets_inRecursiveSet")}); // Only do prototypal inheritance for `this` if we're dealing with nested recursive attribute sets if (isRecursive && getContext("attributeSets_inRecursiveSet") === true) { return unpackExpression(tmplExtend({ object: object })); } else { return object; } } }); }, } };