NixOS/nix — pull request #167 by shlevy (2013-09-21 03:50:40+00:00)
Title: Dynamic attrs sugar 2

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 =
null; 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.

Note that the error checking for attribute sets is pretty hairy, in part
because I wanted to leave the non-dynamic code path as untouched as I
could. Essentially, this creates a list of lists of attribute paths (
e.g. `{ a.b.c = 1; a.d = 2; }` would go to `[ [ "a" "b" "c" ] [ "a" "d"] ]`
) for all of the attributes, static and dynamic, and then as each
dynamic attribute is added to the set its attribute path is checked
against that list. Possibly this could be cleaned up a bit.

This is an alternative solution to #134