diff options
Diffstat (limited to 'tvix')
-rw-r--r-- | tvix/eval/src/compiler/attrs.rs | 22 | ||||
-rw-r--r-- | tvix/eval/src/compiler/mod.rs | 89 |
2 files changed, 77 insertions, 34 deletions
diff --git a/tvix/eval/src/compiler/attrs.rs b/tvix/eval/src/compiler/attrs.rs index b5ebfe23998f..e2e932953b7c 100644 --- a/tvix/eval/src/compiler/attrs.rs +++ b/tvix/eval/src/compiler/attrs.rs @@ -215,25 +215,23 @@ impl Compiler<'_, '_> { /// 2. Keys can refer to nested attribute sets. /// 3. Attribute sets can (optionally) be recursive. pub(super) fn compile_attr_set(&mut self, slot: LocalIdx, node: ast::AttrSet) { - if node.rec_token().is_some() { - let span = self.span_for(&node); - self.emit_warning( - span, - WarningKind::NotImplemented("recursive attribute sets"), - ); - } - // Open a scope to track the positions of the temporaries used // by the `OpAttrs` instruction. self.scope_mut().begin_scope(); - let mut count = self.compile_inherit_attrs(slot, node.inherits()); + if node.rec_token().is_some() { + self.compile_recursive_scope(slot, true, &node); + self.push_op(OpCode::OpAttrs(Count(node.entries().count())), &node); + } else { + let mut count = self.compile_inherit_attrs(slot, node.inherits()); - let dynamic_entries = self.compile_static_attr_entries(&mut count, node.attrpath_values()); + let dynamic_entries = + self.compile_static_attr_entries(&mut count, node.attrpath_values()); - self.compile_dynamic_attr_entries(&mut count, dynamic_entries); + self.compile_dynamic_attr_entries(&mut count, dynamic_entries); - self.push_op(OpCode::OpAttrs(Count(count)), &node); + self.push_op(OpCode::OpAttrs(Count(count)), &node); + } // Remove the temporary scope, but do not emit any additional // cleanup (OpAttrs consumes all of these locals). diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index 25ddb080686c..90bc263bc8be 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -19,6 +19,7 @@ mod scope; use path_clean::PathClean; use rnix::ast::{self, AstToken, HasEntry}; use rowan::ast::{AstChildren, AstNode}; +use smol_str::SmolStr; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -520,7 +521,7 @@ impl Compiler<'_, '_> { self.patch_jump(else_idx); // patch jump *over* else body } - fn compile_recursive_scope<N>(&mut self, slot: LocalIdx, node: &N) + fn compile_recursive_scope<N>(&mut self, slot: LocalIdx, rec_attrs: bool, node: &N) where N: AstNode + ast::HasEntry, { @@ -539,25 +540,33 @@ impl Compiler<'_, '_> { // Within a `let` binding, inheriting from the outer // scope is a no-op *if* the identifier can be // statically resolved. - None if !self.scope().has_with() => { + None if !rec_attrs && !self.scope().has_with() => { self.emit_warning(self.span_for(&inherit), WarningKind::UselessInherit); continue; } None => { for ident in inherit.idents() { - // If the identifier resolves statically, it - // has precedence over dynamic bindings, and - // the inherit is useless. - if matches!( - self.scope_mut() - .resolve_local(ident.ident_token().unwrap().text()), - LocalPosition::Known(_) - ) { + // If the identifier resolves statically in a + // `let`, it has precedence over dynamic + // bindings, and the inherit is useless. + if !rec_attrs + && matches!( + self.scope_mut() + .resolve_local(ident.ident_token().unwrap().text()), + LocalPosition::Known(_) + ) + { self.emit_warning(self.span_for(&ident), WarningKind::UselessInherit); continue; } + if rec_attrs { + self.emit_literal_ident(&ident); + let span = self.span_for(&ident); + self.scope_mut().declare_phantom(span, true); + } + self.compile_ident(slot, ident.clone()); let idx = self.declare_local(&ident, ident.ident_token().unwrap().text()); self.scope_mut().mark_initialised(idx); @@ -586,8 +595,13 @@ impl Compiler<'_, '_> { }, } + struct KeySlot { + slot: LocalIdx, + name: SmolStr, + } + struct TrackedBinding { - key_slot: Option<LocalIdx>, + key_slot: Option<KeySlot>, value_slot: LocalIdx, kind: BindingKind, } @@ -600,11 +614,21 @@ impl Compiler<'_, '_> { // Begin with the inherit (from)s since they all become a thunk anyway for (from, ident) in inherit_froms { - let idx = self.declare_local(&ident, ident.ident_token().unwrap().text()); + let key_slot = if rec_attrs { + let span = self.span_for(&ident); + Some(KeySlot { + slot: self.scope_mut().declare_phantom(span, false), + name: SmolStr::new(ident.ident_token().unwrap().text()), + }) + } else { + None + }; + + let value_slot = self.declare_local(&ident, ident.ident_token().unwrap().text()); bindings.push(TrackedBinding { - key_slot: None, - value_slot: idx, + key_slot, + value_slot, kind: BindingKind::InheritFrom { ident, namespace: from, @@ -626,16 +650,26 @@ impl Compiler<'_, '_> { let span = self.span_for(&entry); self.emit_error( span, - ErrorKind::NotImplemented("nested bindings in let expressions :("), + ErrorKind::NotImplemented("nested bindings in recursive scope :("), ); continue; } - let idx = self.declare_local(&entry.attrpath().unwrap(), path.pop().unwrap()); + let key_slot = if rec_attrs { + let span = self.span_for(&entry.attrpath().unwrap()); + Some(KeySlot { + slot: self.scope_mut().declare_phantom(span, false), + name: SmolStr::new(&path[0]), + }) + } else { + None + }; + + let value_slot = self.declare_local(&entry.attrpath().unwrap(), path.pop().unwrap()); bindings.push(TrackedBinding { - key_slot: None, - value_slot: idx, + key_slot, + value_slot, kind: BindingKind::Plain { expr: entry.value().unwrap(), }, @@ -643,9 +677,20 @@ impl Compiler<'_, '_> { } // Third pass to place the values in the correct stack slots. - let mut indices: Vec<LocalIdx> = vec![]; + let mut value_indices: Vec<LocalIdx> = vec![]; for binding in bindings.into_iter() { - indices.push(binding.value_slot); + value_indices.push(binding.value_slot); + + if let Some(key_slot) = binding.key_slot { + // TODO: emit_constant should be able to take a span directly + let span = self.scope()[key_slot.slot].span; + let idx = self + .chunk() + .push_constant(Value::String(key_slot.name.into())); + + self.chunk().push_op(OpCode::OpConstant(idx), span); + self.scope_mut().mark_initialised(key_slot.slot); + } match binding.kind { // This entry is an inherit (from) expr. The value is @@ -673,7 +718,7 @@ impl Compiler<'_, '_> { } // Fourth pass to emit finaliser instructions if necessary. - for idx in indices { + for idx in value_indices { if self.scope()[idx].needs_finaliser { let stack_idx = self.scope().stack_index(idx); self.push_op(OpCode::OpFinalise(stack_idx), node); @@ -687,7 +732,7 @@ impl Compiler<'_, '_> { /// simply pushed on the stack and their indices noted in the /// entries vector. fn compile_let_in(&mut self, slot: LocalIdx, node: ast::LetIn) { - self.compile_recursive_scope(slot, &node); + self.compile_recursive_scope(slot, false, &node); // Deal with the body, then clean up the locals afterwards. self.compile(slot, node.body().unwrap()); |