NixOS/nix — pull request #134 by shlevy (2013-06-17 13:26:39+00:00)
Title: Dynamic attrs sugar

This adds new syntax for attribute names:

* `attrs.${name}` => `getAttr name attrs`
* `attrs ? ${name}` => `isAttrs attrs && hasAttr attrs name`
* `attrs.${name} or def` => `if attrs ? ${name} then attrs.${name} else def`
* { ${name} = value; } => `listToAttrs [{ inherit name value; }]`

Of course, it's a bit more complicated than that. The attribute chains
can be arbitrarily long and contain combinations of static and dynamic
parts (e.g. `attrs.${foo}.bar.${baz} or qux`), which is relatively
straightforward for the getAttrs/hasAttrs cases but is more complex for
the listToAttrs case due to rules about duplicate attribute definitions.

For attribute sets with dynamic attribute names, duplicate static
attributes are detected at parse time while duplicate dynamic attributes
are detected when the attribute set is forced. So, for example, `{ a =
{}; a.b = null; ${"c"} = true; }` will be a parse-time error, while `{ a
= {}; ${"a"}.b = null; c = true; }` will be an eval-time error
(technically that case could theoretically be detected at parse time,
but the general case would require full evaluation). The rules for
duplicate attributes are the same in both cases, though: if some
sub-chain of an attribute path already has a value, that's an error (for
example, `{ a.b = true; a.b.c = false; }` would cause an error when
parsing the `a.b.c` since `a.b`, a sub-chain, has a value. `{ a.b.d =
true; a.b.c = false; }`, on the other hand, is fine as neither is a
subchain of the other.

Finally, recursive attribute sets with dynamic attributes have the
static attributes in scope but not the dynamic ones. So `rec { a = true;
${"b"} = a; }` is equivalent to `{ a = true; b = true; }` but `rec {
${"a"} = true; b = a; }` would be an error or use `a` from the
surrounding scope if it exists.

This solution includes shlevy/nix@96816995a86af46c8bd0d90dff001855411d5b93, which fixes an inconsistency in nix's parsing but causes some existing cases to break. Without that commit, `{ a = {}; a.b = null; }` is equivalent to `{ a = { b = null; }; }`, but `{ a.b = null; a = {}; }` and `let attrs = {}; in { a = attrs; a.b = null; }` are parse errors. I explain in that commit message why I think the first behavior is wrong and what the cost of the change is.

This is an alternative solution to #132