diff options
-rw-r--r-- | tvix/eval/src/compiler/attrs.rs | 132 | ||||
-rw-r--r-- | tvix/eval/src/compiler/mod.rs | 1 |
2 files changed, 101 insertions, 32 deletions
diff --git a/tvix/eval/src/compiler/attrs.rs b/tvix/eval/src/compiler/attrs.rs index c9897daac211..db8410fcdb8b 100644 --- a/tvix/eval/src/compiler/attrs.rs +++ b/tvix/eval/src/compiler/attrs.rs @@ -20,42 +20,82 @@ impl Compiler<'_, '_> { } } - /// Compile attribute set literals into equivalent bytecode. - /// - /// This is complicated by a number of features specific to Nix - /// attribute sets, most importantly: - /// - /// 1. Keys can be dynamically constructed through interpolation. - /// 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"), - ); - } + /// Compile the statically known entries of an attribute set. Which + /// keys are which is not known from the iterator, so discovered + /// dynamic keys are returned from here. + fn compile_static_attr_entries( + &mut self, + count: &mut usize, + entries: AstChildren<ast::AttrpathValue>, + ) -> Vec<ast::AttrpathValue> { + let mut dynamic_attrs: Vec<ast::AttrpathValue> = vec![]; - // Open a scope to track the positions of the temporaries used - // by the `OpAttrs` instruction. - self.scope_mut().begin_scope(); + 'entries: for kv in entries { + // Attempt to turn the attrpath into a list of static + // strings, but abort this process if any dynamic + // fragments are encountered. + let static_attrpath: Option<Vec<String>> = kv + .attrpath() + .unwrap() + .attrs() + .map(|a| self.expr_static_attr_str(&a)) + .collect(); - let mut count = self.compile_inherit_attrs(slot, node.inherits()); + let fragments = match static_attrpath { + Some(fragments) => fragments, + None => { + dynamic_attrs.push(kv); + continue 'entries; + } + }; + + // At this point we can increase the counter because we + // know that this particular attribute is static and can + // thus be processed here. + *count += 1; + + let key_count = fragments.len(); + for fragment in fragments.into_iter() { + self.emit_constant(Value::String(fragment.into()), &kv.attrpath().unwrap()); + } + + // We're done with the key if there was only one fragment, + // otherwise we need to emit an instruction to construct + // the attribute path. + if key_count > 1 { + self.push_op( + OpCode::OpAttrPath(Count(key_count)), + &kv.attrpath().unwrap(), + ); + } + + // The value is just compiled as normal so that its + // resulting value is on the stack when the attribute set + // is constructed at runtime. + let value_span = self.span_for(&kv.value().unwrap()); + let value_slot = self.scope_mut().declare_phantom(value_span, false); + self.compile(value_slot, kv.value().unwrap()); + self.scope_mut().mark_initialised(value_slot); + } - for kv in node.attrpath_values() { - count += 1; + dynamic_attrs + } + + /// Compile the dynamic entries of an attribute set, where keys + /// are only known at runtime. + fn compile_dynamic_attr_entries( + &mut self, + count: &mut usize, + entries: Vec<ast::AttrpathValue>, + ) { + for entry in entries.into_iter() { + *count += 1; - // Because attribute set literals can contain nested keys, - // there is potentially more than one key fragment. If - // this is the case, a special operation to construct a - // runtime value representing the attribute path is - // emitted. let mut key_count = 0; - let key_span = self.span_for(&kv.attrpath().unwrap()); + let key_span = self.span_for(&entry.attrpath().unwrap()); let key_idx = self.scope_mut().declare_phantom(key_span, false); - for fragment in kv.attrpath().unwrap().attrs() { + for fragment in entry.attrpath().unwrap().attrs() { // Key fragments can contain dynamic expressions, // which makes accounting for their stack slots very // tricky. @@ -86,7 +126,7 @@ impl Compiler<'_, '_> { if key_count > 1 { self.push_op( OpCode::OpAttrPath(Count(key_count)), - &kv.attrpath().unwrap(), + &entry.attrpath().unwrap(), ); // Close the temporary scope that was set up for the @@ -97,11 +137,39 @@ impl Compiler<'_, '_> { // The value is just compiled as normal so that its // resulting value is on the stack when the attribute set // is constructed at runtime. - let value_span = self.span_for(&kv.value().unwrap()); + let value_span = self.span_for(&entry.value().unwrap()); let value_slot = self.scope_mut().declare_phantom(value_span, false); - self.compile(value_slot, kv.value().unwrap()); + self.compile(value_slot, entry.value().unwrap()); self.scope_mut().mark_initialised(value_slot); } + } + + /// Compile attribute set literals into equivalent bytecode. + /// + /// This is complicated by a number of features specific to Nix + /// attribute sets, most importantly: + /// + /// 1. Keys can be dynamically constructed through interpolation. + /// 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()); + + let dynamic_entries = self.compile_static_attr_entries(&mut count, node.attrpath_values()); + + self.compile_dynamic_attr_entries(&mut count, dynamic_entries); self.push_op(OpCode::OpAttrs(Count(count)), &node); diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index 15ff0ea309d3..4a5966ae83a8 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -1257,6 +1257,7 @@ impl Compiler<'_, '_> { /// Convert the provided `ast::Attr` into a statically known /// string if possible. + // TODO(tazjin): these should probably be SmolStr fn expr_static_attr_str(&self, node: &ast::Attr) -> Option<String> { match node { ast::Attr::Ident(ident) => Some(ident.ident_token().unwrap().text().into()), |