//! This module implements compiler logic related to attribute sets
//! (construction, access operators, ...).
use super::*;
impl Compiler<'_> {
pub(super) fn compile_attr(&mut self, slot: LocalIdx, node: ast::Attr) {
match node {
ast::Attr::Dynamic(dynamic) => {
self.compile(slot, dynamic.expr().unwrap());
self.emit_force(&dynamic.expr().unwrap());
}
ast::Attr::Str(s) => {
self.compile_str(slot, s.clone());
self.emit_force(&s);
}
ast::Attr::Ident(ident) => self.emit_literal_ident(&ident),
}
}
/// Compiles inherited values in an attribute set. Inherited
/// values are *always* inherited from the outer scope, even if
/// there is a matching name within a recursive attribute set.
fn compile_inherit_attrs(
&mut self,
slot: LocalIdx,
inherits: AstChildren<ast::Inherit>,
) -> usize {
// Count the number of inherited values, so that the outer
// constructor can emit the correct number of pairs when
// constructing attribute sets.
let mut count = 0;
for inherit in inherits {
match inherit.from() {
Some(from) => {
for attr in inherit.attrs() {
count += 1;
let name = match self.expr_static_attr_str(&attr) {
Some(name) => name,
None => {
// TODO(tazjin): error variant for dynamic
// key in *inherit* (or generalise it)
self.emit_error(&attr, ErrorKind::DynamicKeyInLet);
continue;
}
};
let name_span = self.span_for(&attr);
// First emit the identifier itself (this
// becomes the new key).
self.emit_constant(Value::String(SmolStr::new(&name).into()), &attr);
self.scope_mut().declare_phantom(name_span, true);
// Then emit the node that we're inheriting
// from.
//
// TODO: Likely significant optimisation
// potential in having a multi-select
// instruction followed by a merge, rather
// than pushing/popping the same attrs
// potentially a lot of times.
let val_idx = self.scope_mut().declare_phantom(name_span, false);
self.compile(val_idx, from.expr().unwrap());
self.emit_force(&from.expr().unwrap());
self.emit_constant(Value::String(name.into()), &attr);
self.push_op(OpCode::OpAttrsSelect, &attr);
self.scope_mut().mark_initialised(val_idx);
}
}
None => {
for attr in inherit.attrs() {
count += 1;
// Emit the key to use for OpAttrs
let name = match self.expr_static_attr_str(&attr) {
Some(name) => name,
None => {
// TODO(tazjin): error variant for dynamic
// key in *inherit* (or generalise it)
self.emit_error(&attr, ErrorKind::DynamicKeyInLet);
continue;
}
};
let name_span = self.span_for(&attr);
self.emit_constant(Value::String(SmolStr::new(&name).into()), &attr);
self.scope_mut().declare_phantom(name_span, true);
// Emit the value.
self.compile_identifier_access(slot, &name, &attr);
self.scope_mut().declare_phantom(name_span, true);
}
}
}
}
count
}
/// 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![];
'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 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);
}
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;
let mut key_count = 0;
let key_span = self.span_for(&entry.attrpath().unwrap());
let key_idx = self.scope_mut().declare_phantom(key_span, false);
for fragment in entry.attrpath().unwrap().attrs() {
// Key fragments can contain dynamic expressions,
// which makes accounting for their stack slots very
// tricky.
//
// In order to ensure the locals are correctly cleaned
// up, we essentially treat any key fragment after the
// first one (which has a locals index that will
// become that of the final key itself) as being part
// of a separate scope, which results in the somewhat
// annoying setup logic below.
let fragment_slot = match key_count {
0 => key_idx,
1 => {
self.scope_mut().begin_scope();
self.scope_mut().declare_phantom(key_span, false)
}
_ => self.scope_mut().declare_phantom(key_span, false),
};
key_count += 1;
self.compile_attr(fragment_slot, fragment);
self.scope_mut().mark_initialised(fragment_slot);
}
// 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)),
&entry.attrpath().unwrap(),
);
// Close the temporary scope that was set up for the
// key fragments.
self.scope_mut().end_scope();
}
// 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(&entry.value().unwrap());
let value_slot = self.scope_mut().declare_phantom(value_span, false);
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) {
// Open a scope to track the positions of the temporaries used
// by the `OpAttrs` instruction.
self.scope_mut().begin_scope();
if node.rec_token().is_some() {
let count = self.compile_recursive_scope(slot, true, &node);
self.push_op(OpCode::OpAttrs(Count(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());
self.compile_dynamic_attr_entries(&mut count, dynamic_entries);
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).
self.scope_mut().end_scope();
}
pub(super) fn compile_has_attr(&mut self, slot: LocalIdx, node: ast::HasAttr) {
// Put the attribute set on the stack.
self.compile(slot, node.expr().unwrap());
self.emit_force(&node);
// Push all path fragments with an operation for fetching the
// next nested element, for all fragments except the last one.
for (count, fragment) in node.attrpath().unwrap().attrs().enumerate() {
if count > 0 {
self.push_op(OpCode::OpAttrsTrySelect, &fragment);
self.emit_force(&fragment);
}
self.compile_attr(slot, fragment);
}
// After the last fragment, emit the actual instruction that
// leaves a boolean on the stack.
self.push_op(OpCode::OpHasAttr, &node);
}
pub(super) fn compile_select(&mut self, slot: LocalIdx, node: ast::Select) {
let set = node.expr().unwrap();
let path = node.attrpath().unwrap();
if node.or_token().is_some() {
self.compile_select_or(slot, set, path, node.default_expr().unwrap());
return;
}
// Push the set onto the stack
self.compile(slot, set.clone());
// Compile each key fragment and emit access instructions.
//
// TODO: multi-select instruction to avoid re-pushing attrs on
// nested selects.
for fragment in path.attrs() {
// Force the current set value.
self.emit_force(&fragment);
self.compile_attr(slot, fragment.clone());
self.push_op(OpCode::OpAttrsSelect, &fragment);
}
}
/// Compile an `or` expression into a chunk of conditional jumps.
///
/// If at any point during attribute set traversal a key is
/// missing, the `OpAttrOrNotFound` instruction will leave a
/// special sentinel value on the stack.
///
/// After each access, a conditional jump evaluates the top of the
/// stack and short-circuits to the default value if it sees the
/// sentinel.
///
/// Code like `{ a.b = 1; }.a.c or 42` yields this bytecode and
/// runtime stack:
///
/// ```notrust
/// Bytecode Runtime stack
/// ┌────────────────────────────┐ ┌─────────────────────────┐
/// │ ... │ │ ... │
/// │ 5 OP_ATTRS(1) │ → │ 5 [ { a.b = 1; } ] │
/// │ 6 OP_CONSTANT("a") │ → │ 6 [ { a.b = 1; } "a" ] │
/// │ 7 OP_ATTR_OR_NOT_FOUND │ → │ 7 [ { b = 1; } ] │
/// │ 8 JUMP_IF_NOT_FOUND(13) │ → │ 8 [ { b = 1; } ] │
/// │ 9 OP_CONSTANT("C") │ → │ 9 [ { b = 1; } "c" ] │
/// │ 10 OP_ATTR_OR_NOT_FOUND │ → │ 10 [ NOT_FOUND ] │
/// │ 11 JUMP_IF_NOT_FOUND(13) │ → │ 11 [ ] │
/// │ 12 JUMP(14) │ │ .. jumped over │
/// │ 13 CONSTANT(42) │ → │ 12 [ 42 ] │
/// │ 14 ... │ │ .. .... │
/// └────────────────────────────┘ └─────────────────────────┘
/// ```
fn compile_select_or(
&mut self,
slot: LocalIdx,
set: ast::Expr,
path: ast::Attrpath,
default: ast::Expr,
) {
self.compile(slot, set.clone());
let mut jumps = vec![];
for fragment in path.attrs() {
self.emit_force(&fragment);
self.compile_attr(slot, fragment.clone());
self.push_op(OpCode::OpAttrsTrySelect, &fragment);
jumps.push(self.push_op(OpCode::OpJumpIfNotFound(JumpOffset(0)), &fragment));
}
let final_jump = self.push_op(OpCode::OpJump(JumpOffset(0)), &path);
for jump in jumps {
self.patch_jump(jump);
}
// Compile the default value expression and patch the final
// jump to point *beyond* it.
self.compile(slot, default);
self.patch_jump(final_jump);
}
}