feat(tvix/value): implement nested attribute set literals
With this change, nested attribute sets can now be created from literals. This required some logic for dealing with cases where at a deeper nesting point a literal attribute set was constructed from an optimised representation. For example, this is valid Nix code: ```nix { a = {}; # creates optimised empty representation a.b = 1; # wants to add a `b = 1` to it b = { name = "foo"; value = "bar"; }; # creates optimised K/V repr b.foo = 42; # wants to add an additional `foo = 42` } ``` In these cases, the attribute set must be coerced to a map representation first which is achieved by the new internal NixAttr::map_mut helper. Change-Id: Ia61d3d9d14c4e0f5e207c00f6a2f4daa3265afb2 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6109 Reviewed-by: eta <tvl@eta.st> Tested-by: BuildkiteCI
This commit is contained in:
parent
293fb0ef53
commit
08b4d65fbd
1 changed files with 44 additions and 14 deletions
|
@ -14,7 +14,7 @@ use crate::errors::{Error, EvalResult};
|
|||
use super::string::NixString;
|
||||
use super::Value;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum NixAttrs {
|
||||
Empty,
|
||||
Map(BTreeMap<NixString, Value>),
|
||||
|
@ -55,6 +55,33 @@ impl PartialEq for NixAttrs {
|
|||
}
|
||||
|
||||
impl NixAttrs {
|
||||
/// Retrieve reference to a mutable map inside of an attrs,
|
||||
/// optionally changing the representation if required.
|
||||
fn map_mut(&mut self) -> &mut BTreeMap<NixString, Value> {
|
||||
match self {
|
||||
NixAttrs::Map(m) => m,
|
||||
|
||||
NixAttrs::Empty => {
|
||||
*self = NixAttrs::Map(BTreeMap::new());
|
||||
self.map_mut()
|
||||
}
|
||||
|
||||
NixAttrs::KV { name, value } => {
|
||||
*self = NixAttrs::Map(BTreeMap::from([
|
||||
(
|
||||
NixString("name".into()),
|
||||
std::mem::replace(name, Value::Blackhole),
|
||||
),
|
||||
(
|
||||
NixString("value".into()),
|
||||
std::mem::replace(value, Value::Blackhole),
|
||||
),
|
||||
]));
|
||||
self.map_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement construction logic of an attribute set, to encapsulate
|
||||
/// logic about attribute set optimisations inside of this module.
|
||||
pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> EvalResult<Self> {
|
||||
|
@ -78,7 +105,7 @@ impl NixAttrs {
|
|||
}
|
||||
|
||||
// TODO(tazjin): extend_reserve(count) (rust#72631)
|
||||
let mut attrs: BTreeMap<NixString, Value> = BTreeMap::new();
|
||||
let mut attrs = NixAttrs::Map(BTreeMap::new());
|
||||
|
||||
for _ in 0..count {
|
||||
let value = stack_slice.pop().unwrap();
|
||||
|
@ -107,7 +134,7 @@ impl NixAttrs {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(NixAttrs::Map(attrs))
|
||||
Ok(attrs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,12 +178,9 @@ fn attempt_optimise_kv(slice: &mut [Value]) -> Option<NixAttrs> {
|
|||
}
|
||||
|
||||
// Set an attribute on an in-construction attribute set, while
|
||||
// checking against duplicate key.s
|
||||
fn set_attr(
|
||||
attrs: &mut BTreeMap<NixString, Value>,
|
||||
key: NixString,
|
||||
value: Value,
|
||||
) -> EvalResult<()> {
|
||||
// checking against duplicate keys.
|
||||
fn set_attr(attrs: &mut NixAttrs, key: NixString, value: Value) -> EvalResult<()> {
|
||||
let attrs = attrs.map_mut();
|
||||
let entry = attrs.entry(key);
|
||||
|
||||
match entry {
|
||||
|
@ -180,7 +204,7 @@ fn set_attr(
|
|||
// There is some optimisation potential for this simple implementation
|
||||
// if it becomes a problem.
|
||||
fn set_nested_attr(
|
||||
attrs: &mut BTreeMap<NixString, Value>,
|
||||
attrs: &mut NixAttrs,
|
||||
key: NixString,
|
||||
mut path: Vec<NixString>,
|
||||
value: Value,
|
||||
|
@ -191,6 +215,7 @@ fn set_nested_attr(
|
|||
return set_attr(attrs, key, value);
|
||||
}
|
||||
|
||||
let attrs = attrs.map_mut();
|
||||
let entry = attrs.entry(key);
|
||||
|
||||
// If there is not we go one step further down, in which case we
|
||||
|
@ -202,21 +227,26 @@ fn set_nested_attr(
|
|||
match entry {
|
||||
// Vacant entry -> new attribute set is needed.
|
||||
std::collections::btree_map::Entry::Vacant(entry) => {
|
||||
let mut map = BTreeMap::new();
|
||||
let mut map = NixAttrs::Map(BTreeMap::new());
|
||||
|
||||
// TODO(tazjin): technically recursing further is not
|
||||
// required, we can create the whole hierarchy here, but
|
||||
// it's noisy.
|
||||
set_nested_attr(&mut map, path.pop().expect("next key exists"), path, value)?;
|
||||
|
||||
entry.insert(Value::Attrs(Rc::new(NixAttrs::Map(map))));
|
||||
entry.insert(Value::Attrs(Rc::new(map)));
|
||||
}
|
||||
|
||||
// Occupied entry: Either error out if there is something
|
||||
// other than attrs, or insert the next value.
|
||||
std::collections::btree_map::Entry::Occupied(mut entry) => match entry.get_mut() {
|
||||
Value::Attrs(_attrs) => {
|
||||
todo!("implement mutable attrsets")
|
||||
Value::Attrs(attrs) => {
|
||||
set_nested_attr(
|
||||
Rc::make_mut(attrs),
|
||||
path.pop().expect("next key exists"),
|
||||
path,
|
||||
value,
|
||||
)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
|
|
Loading…
Reference in a new issue