feat(tvix/eval): implement inherit in attribute set literals

Straightforward implementation, evaluating the elements of an inherit
and preparing the stack so that `OpAttrs` sees all relevant values
when constructing the attribute set itself.

The emitted instructions for inheriting a lot of values from the same
attribute set are inefficient, but it's too early to say whether this
actually matters.

Change-Id: Icb55a20936d4ef77173f34433811c5fa5d2c9ecc
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6214
Reviewed-by: grfn <grfn@gws.fyi>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2022-08-14 20:12:20 +03:00 committed by tazjin
parent 7db4f8d774
commit 8c1c9aee3c
5 changed files with 39 additions and 12 deletions

View file

@ -152,10 +152,7 @@ impl Compiler {
fn compile_with_literal_ident(&mut self, node: rnix::SyntaxNode) -> EvalResult<()> { fn compile_with_literal_ident(&mut self, node: rnix::SyntaxNode) -> EvalResult<()> {
if node.kind() == rnix::SyntaxKind::NODE_IDENT { if node.kind() == rnix::SyntaxKind::NODE_IDENT {
let ident = rnix::types::Ident::cast(node).unwrap(); let ident = rnix::types::Ident::cast(node).unwrap();
let idx = self self.emit_literal_ident(&ident);
.chunk
.push_constant(Value::String(ident.as_str().into()));
self.chunk.push_op(OpCode::OpConstant(idx));
return Ok(()); return Ok(());
} }
@ -363,17 +360,31 @@ impl Compiler {
// inherit "from the outside"). // inherit "from the outside").
for inherit in node.inherits() { for inherit in node.inherits() {
match inherit.from() { match inherit.from() {
Some(_from) => todo!("inherit from attrs not implemented"), Some(from) => {
None => {
for ident in inherit.idents() { for ident in inherit.idents() {
count += 1; count += 1;
// Leave the identifier on the stack (never // First emit the identifier itself
// nested in case of inherits!) self.emit_literal_ident(&ident);
let idx = self
.chunk // Then emit the node that we're inheriting
.push_constant(Value::String(ident.as_str().into())); // from.
self.chunk.push_op(OpCode::OpConstant(idx)); //
// 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.
self.compile(from.inner().unwrap())?;
self.emit_literal_ident(&ident);
self.chunk.push_op(OpCode::OpAttrsSelect);
}
}
None => {
for ident in inherit.idents() {
count += 1;
self.emit_literal_ident(&ident);
match self.resolve_local(ident.as_str()) { match self.resolve_local(ident.as_str()) {
Some(idx) => self.chunk.push_op(OpCode::OpGetLocal(idx)), Some(idx) => self.chunk.push_op(OpCode::OpGetLocal(idx)),
@ -729,6 +740,16 @@ impl Compiler {
Ok(()) Ok(())
} }
// Emit the literal string value of an identifier. Required for
// several operations related to attribute sets, where identifiers
// are used as string keys.
fn emit_literal_ident(&mut self, ident: &rnix::types::Ident) {
let idx = self
.chunk
.push_constant(Value::String(ident.as_str().into()));
self.chunk.push_op(OpCode::OpConstant(idx));
}
fn patch_jump(&mut self, idx: CodeIdx) { fn patch_jump(&mut self, idx: CodeIdx) {
let offset = self.chunk.code.len() - 1 - idx.0; let offset = self.chunk.code.len() - 1 - idx.0;

View file

@ -0,0 +1,2 @@
# the 'from' part of an `inherit` can be any expression.
{ inherit ({a = 15;}) a; }.a

View file

@ -0,0 +1 @@
{ a = 15; }

View file

@ -0,0 +1,2 @@
let a = 15;
in { inherit a; }