about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-09-24T12·42+0300
committertazjin <tazjin@tvl.su>2022-09-29T11·47+0000
commit949897651e826598f2011611e0cc03619426fcc2 (patch)
treea0512b521c43a15ac451859ecb1d292bbf3d83f1
parentb593f6922c3b31f2456cb8885d80af3789a380b3 (diff)
feat(tvix/eval): implement dynamic keys in recursive attrs r/4988
This wires up the new bindings setup logic to be able to thread
through & compile dynamic attributes in recursive attrs.

It seems like we don't actually need to retain the phasing of Nix
exactly, as we can use the phantom mechanism to declare all locals
without making the dynamic ones accessible.

Change-Id: Ic2d43dd8fd97d7ccd56d8c6adf2ff97274cd837a
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6781
Reviewed-by: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
-rw-r--r--tvix/eval/src/compiler/bindings.rs62
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix5
3 files changed, 39 insertions, 29 deletions
diff --git a/tvix/eval/src/compiler/bindings.rs b/tvix/eval/src/compiler/bindings.rs
index 94dae5fb95..86245270f2 100644
--- a/tvix/eval/src/compiler/bindings.rs
+++ b/tvix/eval/src/compiler/bindings.rs
@@ -22,14 +22,14 @@ enum Binding {
 
 enum KeySlot {
     /// There is no key slot (`let`-expressions do not emit their key).
-    None,
+    None { name: SmolStr },
 
     /// The key is statically known and has a slot.
     Static { slot: LocalIdx, name: SmolStr },
 
     /// The key is dynamic, i.e. only known at runtime, and must be compiled
     /// into its slot.
-    Dynamic { slot: LocalIdx, expr: ast::Expr },
+    Dynamic { slot: LocalIdx, attr: ast::Attr },
 }
 
 struct TrackedBinding {
@@ -180,7 +180,7 @@ impl Compiler<'_> {
                     name: name.clone(),
                 }
             } else {
-                KeySlot::None
+                KeySlot::None { name: name.clone() }
             };
 
             let value_slot = match kind {
@@ -221,22 +221,25 @@ impl Compiler<'_> {
         for entry in node.attrpath_values() {
             *count += 1;
 
-            let path = entry.attrpath().unwrap().attrs().collect::<Vec<_>>();
+            let mut path = entry.attrpath().unwrap().attrs().collect::<Vec<_>>();
             if path.len() != 1 {
                 self.emit_error(&entry, ErrorKind::NotImplemented("nested bindings :("));
                 continue;
             }
 
-            let name = match self.expr_static_attr_str(&path[0]) {
-                Some(name) => name,
+            let key_span = self.span_for(&path[0]);
+            let key_slot = match self.expr_static_attr_str(&path[0]) {
+                Some(name) if kind.is_attrs() => KeySlot::Static {
+                    name,
+                    slot: self.scope_mut().declare_phantom(key_span, false),
+                },
 
-                None if kind.is_attrs() => {
-                    self.emit_error(
-                        &entry,
-                        ErrorKind::NotImplemented("dynamic keys in `rec` sets"),
-                    );
-                    continue;
-                }
+                Some(name) => KeySlot::None { name },
+
+                None if kind.is_attrs() => KeySlot::Dynamic {
+                    attr: path.pop().unwrap(),
+                    slot: self.scope_mut().declare_phantom(key_span, false),
+                },
 
                 None => {
                     self.emit_error(&path[0], ErrorKind::DynamicKeyInScope("let-expression"));
@@ -244,20 +247,20 @@ impl Compiler<'_> {
                 }
             };
 
-            let key_span = self.span_for(&path[0]);
-            let key_slot = if kind.is_attrs() {
-                KeySlot::Static {
-                    name: name.clone(),
-                    slot: self.scope_mut().declare_phantom(key_span, false),
-                }
-            } else {
-                KeySlot::None
-            };
-
             let value_slot = match kind {
-                // In recursive scopes, the value needs to be accessible on the
-                // stack.
-                BindingsKind::LetIn | BindingsKind::RecAttrs => self.declare_local(&key_span, name),
+                BindingsKind::LetIn | BindingsKind::RecAttrs => match &key_slot {
+                    // In recursive scopes, the value needs to be accessible on the
+                    // stack if it is statically known
+                    KeySlot::None { name } | KeySlot::Static { name, .. } => {
+                        self.declare_local(&key_span, name.as_str())
+                    }
+
+                    // Dynamic values are never resolvable (as their names are
+                    // of course only known at runtime).
+                    //
+                    // Note: This branch is unreachable in `let`-expressions.
+                    KeySlot::Dynamic { .. } => self.scope_mut().declare_phantom(key_span, false),
+                },
 
                 // In non-recursive attribute sets, the value is inaccessible
                 // (only consumed by `OpAttrs`).
@@ -439,7 +442,7 @@ impl Compiler<'_> {
             value_indices.push(binding.value_slot);
 
             match binding.key_slot {
-                KeySlot::None => {} // nothing to do here
+                KeySlot::None { .. } => {} // nothing to do here
 
                 KeySlot::Static { slot, name } => {
                     let span = self.scope()[slot].span;
@@ -447,8 +450,9 @@ impl Compiler<'_> {
                     self.scope_mut().mark_initialised(slot);
                 }
 
-                KeySlot::Dynamic { .. } => {
-                    todo!("dynamic keys not ye timplemented")
+                KeySlot::Dynamic { slot, attr } => {
+                    self.compile_attr(slot, attr);
+                    self.scope_mut().mark_initialised(slot);
                 }
             }
 
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp
new file mode 100644
index 0000000000..ac8d062a69
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp
@@ -0,0 +1 @@
+{ barbaz = 42; foobar = 42; val = 21; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix
new file mode 100644
index 0000000000..8d7a8cef8e
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix
@@ -0,0 +1,5 @@
+rec {
+  val = 21;
+  ${"foo" + "bar"} = 42;
+  ${"bar" + "baz"} = val * 2;
+}