feat(tvix/eval): merge attribute sets in bindings
This is a significant step towards correctly implemented nested bindings. All attribute sets defined within the same binding scope will now be merged as in Nix, if they use the same key. Change-Id: I13e056693d5e73192280043c6dd93b47d1306ed6 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6800 Tested-by: BuildkiteCI Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
parent
0cee44838c
commit
cece9eae4a
5 changed files with 133 additions and 13 deletions
|
@ -4,7 +4,7 @@
|
||||||
//! In the case of recursive scopes these cases share almost all of their
|
//! In the case of recursive scopes these cases share almost all of their
|
||||||
//! (fairly complex) logic.
|
//! (fairly complex) logic.
|
||||||
|
|
||||||
use rnix::ast::AttrpathValue;
|
use rnix::ast::{AttrpathValue, HasEntry};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -27,6 +27,29 @@ impl BindingsKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal representation of an attribute set used for merging sets, or
|
||||||
|
// inserting nested keys.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AttributeSet {
|
||||||
|
/// Original span at which this set was first encountered.
|
||||||
|
span: Span,
|
||||||
|
|
||||||
|
/// Tracks the kind of set (rec or not).
|
||||||
|
kind: BindingsKind,
|
||||||
|
|
||||||
|
/// All inherited entries
|
||||||
|
inherits: Vec<ast::Inherit>,
|
||||||
|
|
||||||
|
/// All internal entries
|
||||||
|
entries: Vec<ast::AttrpathValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSpan for AttributeSet {
|
||||||
|
fn span_for(&self, _: &codemap::File) -> Span {
|
||||||
|
self.span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Data structures to track the bindings observed in the second pass, and
|
// Data structures to track the bindings observed in the second pass, and
|
||||||
// forward the information needed to compile their value.
|
// forward the information needed to compile their value.
|
||||||
enum Binding {
|
enum Binding {
|
||||||
|
@ -39,19 +62,60 @@ enum Binding {
|
||||||
Plain {
|
Plain {
|
||||||
expr: ast::Expr,
|
expr: ast::Expr,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Set(AttributeSet),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Binding {
|
impl Binding {
|
||||||
fn merge(&mut self, _expr: ast::Expr) -> Option<ErrorKind> {
|
fn merge(&mut self, c: &mut Compiler, expr: ast::Expr) {
|
||||||
match self {
|
match self {
|
||||||
Binding::InheritFrom { name, .. } => {
|
Binding::InheritFrom { name, ref span, .. } => {
|
||||||
Some(ErrorKind::UnmergeableInherit { name: name.clone() })
|
c.emit_error(span, ErrorKind::UnmergeableInherit { name: name.clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding::Plain { expr } => match expr {
|
Binding::Plain { expr: existing } => match existing {
|
||||||
ast::Expr::AttrSet(_) => todo!(),
|
ast::Expr::AttrSet(existing) => {
|
||||||
_ => Some(ErrorKind::UnmergeableValue),
|
if let ast::Expr::AttrSet(new) = expr {
|
||||||
|
let merged = AttributeSet {
|
||||||
|
span: c.span_for(existing),
|
||||||
|
|
||||||
|
// Kind of the attrs depends on the first time it is
|
||||||
|
// encountered. We actually believe this to be a Nix
|
||||||
|
// bug: https://github.com/NixOS/nix/issues/7111
|
||||||
|
kind: if existing.rec_token().is_some() {
|
||||||
|
BindingsKind::RecAttrs
|
||||||
|
} else {
|
||||||
|
BindingsKind::Attrs
|
||||||
|
},
|
||||||
|
|
||||||
|
inherits: ast::HasEntry::inherits(existing)
|
||||||
|
.chain(ast::HasEntry::inherits(&new))
|
||||||
|
.collect(),
|
||||||
|
|
||||||
|
entries: ast::HasEntry::attrpath_values(existing)
|
||||||
|
.chain(ast::HasEntry::attrpath_values(&new))
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*self = Binding::Set(merged);
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => c.emit_error(&expr, ErrorKind::UnmergeableValue),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Binding::Set(existing) => {
|
||||||
|
if let ast::Expr::AttrSet(new) = expr {
|
||||||
|
existing.inherits.extend(ast::HasEntry::inherits(&new));
|
||||||
|
existing
|
||||||
|
.entries
|
||||||
|
.extend(ast::HasEntry::attrpath_values(&new));
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,9 +197,7 @@ impl TrackedBindings {
|
||||||
};
|
};
|
||||||
|
|
||||||
// No more excuses ... the binding can be merged!
|
// No more excuses ... the binding can be merged!
|
||||||
if let Some(err) = binding.binding.merge(value) {
|
binding.binding.merge(c, value);
|
||||||
c.emit_error(entry, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -150,6 +212,33 @@ impl TrackedBindings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper around the `ast::HasEntry` trait as that trait can not be
|
||||||
|
/// implemented for custom types.
|
||||||
|
trait HasEntryProxy {
|
||||||
|
fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>>;
|
||||||
|
fn attrpath_values(&self) -> Box<dyn Iterator<Item = ast::AttrpathValue>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: HasEntry> HasEntryProxy for N {
|
||||||
|
fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
|
||||||
|
Box::new(ast::HasEntry::inherits(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attrpath_values(&self) -> Box<dyn Iterator<Item = ast::AttrpathValue>> {
|
||||||
|
Box::new(ast::HasEntry::attrpath_values(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasEntryProxy for AttributeSet {
|
||||||
|
fn inherits(&self) -> Box<dyn Iterator<Item = ast::Inherit>> {
|
||||||
|
Box::new(self.inherits.clone().into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attrpath_values(&self) -> Box<dyn Iterator<Item = ast::AttrpathValue>> {
|
||||||
|
Box::new(self.entries.clone().into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// AST-traversing functions related to bindings.
|
/// AST-traversing functions related to bindings.
|
||||||
impl Compiler<'_> {
|
impl Compiler<'_> {
|
||||||
/// Compile all inherits of a node with entries that do *not* have a
|
/// Compile all inherits of a node with entries that do *not* have a
|
||||||
|
@ -162,7 +251,7 @@ impl Compiler<'_> {
|
||||||
node: &N,
|
node: &N,
|
||||||
) -> Vec<(ast::Expr, SmolStr, Span)>
|
) -> Vec<(ast::Expr, SmolStr, Span)>
|
||||||
where
|
where
|
||||||
N: ToSpan + ast::HasEntry,
|
N: ToSpan + HasEntryProxy,
|
||||||
{
|
{
|
||||||
// Pass over all inherits, resolving only those without namespaces.
|
// Pass over all inherits, resolving only those without namespaces.
|
||||||
// Since they always resolve in a higher scope, we can just compile and
|
// Since they always resolve in a higher scope, we can just compile and
|
||||||
|
@ -309,7 +398,7 @@ impl Compiler<'_> {
|
||||||
bindings: &mut TrackedBindings,
|
bindings: &mut TrackedBindings,
|
||||||
node: &N,
|
node: &N,
|
||||||
) where
|
) where
|
||||||
N: ToSpan + ast::HasEntry,
|
N: ToSpan + HasEntryProxy,
|
||||||
{
|
{
|
||||||
for entry in node.attrpath_values() {
|
for entry in node.attrpath_values() {
|
||||||
if bindings.try_merge(self, &entry) {
|
if bindings.try_merge(self, &entry) {
|
||||||
|
@ -450,6 +539,14 @@ impl Compiler<'_> {
|
||||||
// Binding is "just" a plain expression that needs to be
|
// Binding is "just" a plain expression that needs to be
|
||||||
// compiled.
|
// compiled.
|
||||||
Binding::Plain { expr } => self.compile(binding.value_slot, expr),
|
Binding::Plain { expr } => self.compile(binding.value_slot, expr),
|
||||||
|
|
||||||
|
// Binding is a merged or nested attribute set, and needs to be
|
||||||
|
// recursively compiled as another binding.
|
||||||
|
Binding::Set(set) => self.thunk(binding.value_slot, &set, |c, n, s| {
|
||||||
|
c.scope_mut().begin_scope();
|
||||||
|
c.compile_bindings(binding.value_slot, set.kind, &set);
|
||||||
|
c.scope_mut().end_scope();
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any code after this point will observe the value in the right
|
// Any code after this point will observe the value in the right
|
||||||
|
@ -469,7 +566,7 @@ impl Compiler<'_> {
|
||||||
|
|
||||||
fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
|
fn compile_bindings<N>(&mut self, slot: LocalIdx, kind: BindingsKind, node: &N)
|
||||||
where
|
where
|
||||||
N: ToSpan + ast::HasEntry,
|
N: ToSpan + HasEntryProxy,
|
||||||
{
|
{
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
self.scope_mut().begin_scope();
|
self.scope_mut().begin_scope();
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{ set = { a = 1; b = 2; }; }
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
set = {
|
||||||
|
a = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
set = {
|
||||||
|
b = 2;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{ set = { a = 21; b = 42; }; }
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
set = rec {
|
||||||
|
a = 21;
|
||||||
|
};
|
||||||
|
|
||||||
|
set = {
|
||||||
|
# Fun fact: This might be the only case in Nix where a lexical
|
||||||
|
# resolution of an identifier can only be resolved by looking at
|
||||||
|
# *siblings* in the AST.
|
||||||
|
b = 2 * a;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue