Change-Id: I75b33c43456389de6e521b4f0ad46d68bc9e98f6 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11809 Autosubmit: flokli <flokli@flokli.de> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
2 KiB
Recursive attribute sets
The construction behaviour of recursive attribute sets is very specific, and a bit peculiar.
In essence, there are multiple "phases" of scoping that take place during attribute set construction:
-
Every inherited value without an explicit source is inherited only from the outer scope in which the attribute set is enclosed.
-
A new scope is opened in which all recursive keys are evaluated. This only considers statically known keys, attributes can not recurse into dynamic keys in
self!For example, this code is invalid in C++ Nix:
nix-repl> rec { ${"a"+""} = 2; b = a * 10; } error: undefined variable 'a' at (string):1:26 -
Finally, a third scope is opened in which dynamic keys are evaluated.
This behaviour, while possibly a bit strange and unexpected, actually simplifies the implementation of recursive attribute sets in Tvix as well.
Essentially, a recursive attribute set like this:
rec {
inherit a;
b = a * 10;
${"c" + ""} = b * 2;
}
Can be compiled like the following expression:
let
inherit a;
in let
b = a * 10;
in {
inherit a b;
${"c" + ""} = b * 2;
}
Completely deferring the resolution of recursive identifiers to the existing handling of recursive scopes (i.e. deferred access) in let bindings.
In practice, we can further specialise this and compile each scope
directly into the form expected by OpAttrs (that is, leaving
attribute names on the stack) before each value's position.
C++ Nix's Implementation
ExprAttrs(AST representation of attribute sets)ExprAttrs::evaladdAttr(ExprAttrsconstruction in the parser)