diff options
Diffstat (limited to 'tvix')
-rw-r--r-- | tvix/eval/src/value/attrs.rs | 26 | ||||
-rw-r--r-- | tvix/eval/src/value/mod.rs | 32 |
2 files changed, 34 insertions, 24 deletions
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs index 2b29c8396ff5..e4584e3aa2f7 100644 --- a/tvix/eval/src/value/attrs.rs +++ b/tvix/eval/src/value/attrs.rs @@ -15,9 +15,11 @@ use serde::{Deserialize, Serialize}; use super::string::NixString; use super::thunk::ThunkSet; +use super::CoercionKind; use super::TotalDisplay; use super::Value; use crate::errors::ErrorKind; +use crate::generators::{self, GenCo}; lazy_static! { static ref NAME_S: NixString = "name".into(); @@ -396,6 +398,30 @@ impl NixAttrs { pub(crate) fn from_kv(name: Value, value: Value) -> Self { NixAttrs(AttrsRep::KV { name, value }) } + + /// Attempt to coerce an attribute set with a `__toString` + /// attribute to a string. + pub(crate) async fn try_to_string(&self, co: &GenCo, kind: CoercionKind) -> Option<NixString> { + if let Some(to_string) = self.select("__toString") { + let callable = generators::request_force(&co, to_string.clone()).await; + + // Leave the attribute set on the stack as an argument + // to the function call. + generators::request_stack_push(&co, Value::Attrs(Box::new(self.clone()))).await; + + // Call the callable ... + let result = generators::request_call(&co, callable).await; + + // Recurse on the result, as attribute set coercion + // actually works recursively, e.g. you can even return + // /another/ set with a __toString attr. + let s = generators::request_string_coerce(&co, result, kind).await; + + return Some(s); + } + + None + } } /// In Nix, name/value attribute pairs are frequently constructed from diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs index 4acdd135f4bb..4e93b36d55d3 100644 --- a/tvix/eval/src/value/mod.rs +++ b/tvix/eval/src/value/mod.rs @@ -265,32 +265,16 @@ impl Value { // set itself or an `outPath` attribute which should be a string. // `__toString` is preferred. (Value::Attrs(attrs), kind) => { - match (attrs.select("__toString"), attrs.select("outPath")) { - (None, None) => Err(ErrorKind::NotCoercibleToString { from: "set", kind }), - - (Some(f), _) => { - let callable = generators::request_force(&co, f.clone()).await; - - // Leave the attribute set on the stack as an argument - // to the function call. - generators::request_stack_push(&co, Value::Attrs(attrs)).await; - - // Call the callable ... - let result = generators::request_call(&co, callable).await; - - // Recurse on the result, as attribute set coercion - // actually works recursively, e.g. you can even return - // /another/ set with a __toString attr. - let s = generators::request_string_coerce(&co, result, kind).await; - Ok(Value::String(s)) - } + if let Some(s) = attrs.try_to_string(&co, kind).await { + return Ok(Value::String(s)); + } - // Similarly to `__toString` we also coerce recursively for `outPath` - (None, Some(s)) => { - let s = generators::request_string_coerce(&co, s.clone(), kind).await; - Ok(Value::String(s)) - } + if let Some(out_path) = attrs.select("outPath") { + let s = generators::request_string_coerce(&co, out_path.clone(), kind).await; + return Ok(Value::String(s)); } + + Err(ErrorKind::NotCoercibleToString { from: "set", kind }) } // strong coercions |