You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.2 KiB
JavaScript
105 lines
3.2 KiB
JavaScript
"use strict";
|
|
|
|
|
|
const unreachable = require("@joepie91/unreachable")("jsNix");
|
|
const NoChange = require("astformer/actions/no-change"); // FIXME
|
|
|
|
const { NixIdentifier, NixAttributeSet, NixBinding } = require("./util/nix-types");
|
|
|
|
function isAttributeSet(node) {
|
|
return (node.type === "NixAttributeSet");
|
|
}
|
|
|
|
function mergeAttributeSets(a, b) {
|
|
return NixAttributeSet([ ... a.binding, ... b.binding ]);
|
|
}
|
|
|
|
function mergeBindings(name, a, b) {
|
|
let attributes = mergeAttributeSets(unpackBindingValue(a), unpackBindingValue(b));
|
|
|
|
return NixBinding(
|
|
[ NixIdentifier(name) ],
|
|
attributes
|
|
);
|
|
}
|
|
|
|
function unpackBindingValue(binding) {
|
|
// binding -> value for top-level attribute of the binding
|
|
if (binding.attrpath.attr.length > 1) {
|
|
// Nested attribute path syntax, synthesize a new attribute set with the first attribute snipped off - this new attribute set represents the *value* of the binding
|
|
return NixAttributeSet([
|
|
NixBinding(binding.attrpath.attr.slice(1), binding.expression)
|
|
]);
|
|
} else {
|
|
return binding.expression;
|
|
}
|
|
}
|
|
|
|
/* FIXME: Cases to test/verify:
|
|
{ a = 4; a.b = 5; }
|
|
{ a = {}; a.b = 5; }
|
|
{ a.c = 4; a.b = 5; }
|
|
{ a = 4; a = 5; }
|
|
*/
|
|
|
|
module.exports = {
|
|
name: "desugar-attrsets",
|
|
visitors: {
|
|
NixAttributeSet: (node) => {
|
|
let neededDesugaring = false;
|
|
|
|
let newStaticBindings = {};
|
|
let dynamicBindings = [];
|
|
|
|
for (let binding of node.binding) {
|
|
if (binding.type === "NixBinding") {
|
|
if (binding.attrpath.attr.length > 1) {
|
|
neededDesugaring = true;
|
|
}
|
|
|
|
let firstAttribute = binding.attrpath.attr[0];
|
|
let isStaticAttribute = firstAttribute.type === "NixIdentifier";
|
|
|
|
if (isStaticAttribute) {
|
|
let attributeName = firstAttribute.name;
|
|
let existingBinding = newStaticBindings[attributeName];
|
|
|
|
if (existingBinding == null) {
|
|
newStaticBindings[attributeName] = NixBinding(
|
|
[ firstAttribute ],
|
|
unpackBindingValue(binding)
|
|
);
|
|
} else {
|
|
if (isAttributeSet(existingBinding.expression) && isAttributeSet(binding.expression)) {
|
|
neededDesugaring = true;
|
|
|
|
newStaticBindings[attributeName] = mergeBindings(attributeName, existingBinding, binding);
|
|
} else {
|
|
throw new Error(`Key '${attributeName}' was specified twice, but this is only allowed when both values are an attribute set`);
|
|
}
|
|
}
|
|
} else {
|
|
// FIXME: Needs runtime check, need to *always* construct objects at runtime when dynamic bindings are involved
|
|
// FIXME: Still just desugar here? And expect another transformer to deal with the runtime checks
|
|
// desugar but do not merge, dynamic bindings cannot be combined with attribute paths on the same key
|
|
dynamicBindings.push(binding);
|
|
}
|
|
} else {
|
|
unreachable(`unrecognized binding type: ${binding.type}`);
|
|
}
|
|
}
|
|
|
|
let staticBindingList = Object.entries(newStaticBindings).map(([ _key, value ]) => {
|
|
return value;
|
|
});
|
|
|
|
// We only check this here because there are multiple things that could cause a need for desugaring, and this is simpler for now than having a separate 'check first' implementation
|
|
if (neededDesugaring) {
|
|
return NixAttributeSet([ ... staticBindingList, ... dynamicBindings ], node.recursive);
|
|
} else {
|
|
return NoChange;
|
|
}
|
|
}
|
|
}
|
|
};
|