From 949897651e826598f2011611e0cc03619426fcc2 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Sat, 24 Sep 2022 15:42:32 +0300 Subject: feat(tvix/eval): implement dynamic keys in recursive attrs 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 Tested-by: BuildkiteCI --- tvix/eval/src/compiler/bindings.rs | 62 ++++++++++++---------- .../tvix_tests/eval-okay-rec-dynamic-keys.exp | 1 + .../tvix_tests/eval-okay-rec-dynamic-keys.nix | 5 ++ 3 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.exp create mode 100644 tvix/eval/src/tests/tvix_tests/eval-okay-rec-dynamic-keys.nix (limited to 'tvix/eval') 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::>(); + let mut path = entry.attrpath().unwrap().attrs().collect::>(); 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; +} -- cgit 1.4.1