nix::parser::State::addAttr: Deep attribute merging
Closes #845 Change-Id: Ie14d0e5a7a9fb778325c4ad30d1e1bd73c60b4f9
This commit is contained in:
@@ -214,11 +214,20 @@ inline void State::mergeAttrs(AttrPath & attrPath, ExprSet * source, ExprSet * t
|
||||
target->inheritFromExprs = std::make_unique<std::list<std::unique_ptr<Expr>>>();
|
||||
}
|
||||
for (auto & [insertKey, insertDef] : source->attrs) {
|
||||
if (auto collision = target->attrs.find(insertKey);
|
||||
collision != target->attrs.end()) // Attr already defined in target, error.
|
||||
{
|
||||
if (auto collision = target->attrs.find(insertKey); collision != target->attrs.end()) {
|
||||
// Attr already defined in target, recurse merge if possible otherwise error.
|
||||
auto * collisionInsert = dynamic_cast<ExprSet *>(insertDef.e.get());
|
||||
auto * collisionTarget = dynamic_cast<ExprSet *>(collision->second.e.get());
|
||||
if (!collisionInsert || !collisionTarget) {
|
||||
attrPath.push_back(AttrName(insertDef.pos, insertKey));
|
||||
return dupAttr(attrPath, insertDef.pos, collision->second.pos);
|
||||
}
|
||||
|
||||
// Push insertKey to the attrPath for error propagation (pop afterwards), then recurse
|
||||
// merge
|
||||
attrPath.push_back(AttrName(insertDef.pos, insertKey));
|
||||
return dupAttr(attrPath, insertDef.pos, collision->second.pos);
|
||||
mergeAttrs(attrPath, collisionInsert, collisionTarget);
|
||||
attrPath.pop_back();
|
||||
}
|
||||
if (insertDef.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
|
||||
auto & sel = dynamic_cast<ExprSelect &>(*insertDef.e);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ foo = { bar = { baz = 1; qux = 2; }; }; }
|
||||
@@ -0,0 +1,20 @@
|
||||
# Deep recursive attr merges
|
||||
# Regression test for https://git.lix.systems/lix-project/lix/issues/845 / https://github.com/NixOS/nix/issues/11268
|
||||
|
||||
# Also this is an eval test and not a parser test because the printed value is easier to inspect than the AST,
|
||||
# though we are primarily testing parser functionality here
|
||||
let
|
||||
reference = { a.b.c = 1; a.b.d = 2; };
|
||||
in
|
||||
# Test cases courtesy of @rhendric
|
||||
assert { a = { b = { c = 1; }; }; a = { b = { d = 2; }; }; } == reference;
|
||||
assert { a.b = { c = 1; }; a.b = { d = 2; }; } == reference;
|
||||
assert { a = { b.c = 1; }; a = { b.d = 2; }; } == reference;
|
||||
assert { a = { b = { c = 1; }; }; a.b.d = 2; } == reference;
|
||||
assert { a.b.c = 1; a = { b = { d = 2; }; }; } == reference;
|
||||
{
|
||||
foo.bar.baz = 1;
|
||||
foo = {
|
||||
bar.qux = 2;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
error: attribute 'x.y' already defined at /pwd/in.nix:2:3
|
||||
error: attribute 'x.y.y' already defined at /pwd/in.nix:2:3
|
||||
at /pwd/in.nix:3:9:
|
||||
2| x.y.y = 3;
|
||||
3| x = { y.y= 3; z = 3; };
|
||||
|
||||
Reference in New Issue
Block a user