From 067f2b16f6f5d8fce73b8420a53b51a15c90f589 Mon Sep 17 00:00:00 2001 From: sterni Date: Tue, 13 Sep 2022 20:11:07 +0200 Subject: feat(tvix/eval): implement Value::coerce_to_path() This function is necessary for all builtins that expect some form of path as an argument. It is merely a wrapper around coerce_to_string that can shortcut if we already have a path. The absolute path check is done in the same way as in C++ Nix for compatibility, although it should probably be revised in the long term (think about Windows, for example). Since coercing to a path is not an operation possible in the language directly, this function can live in the builtins module as the only place it is required. Change-Id: I69ed5455c00d193fea88b8fa83e28907a761cab5 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6574 Autosubmit: sterni Reviewed-by: tazjin Tested-by: BuildkiteCI --- tvix/eval/src/builtins/mod.rs | 26 ++++++++++++++++++++++++++ tvix/eval/src/errors.rs | 12 ++++++++++++ tvix/eval/src/value/mod.rs | 2 ++ tvix/eval/src/value/string.rs | 9 ++++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs index 33abfe492d10..7a2f46e2d47d 100644 --- a/tvix/eval/src/builtins/mod.rs +++ b/tvix/eval/src/builtins/mod.rs @@ -5,12 +5,14 @@ use std::{ collections::{BTreeMap, HashMap}, + path::PathBuf, rc::Rc, }; use crate::{ errors::ErrorKind, value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value}, + vm::VM, }; use crate::arithmetic_op; @@ -36,6 +38,30 @@ macro_rules! force { }; } +/// Coerce a Nix Value to a plain path, e.g. in order to access the file it +/// points to in an I/O builtin. This coercion can _never_ be performed in +/// a Nix program directly (i.e. the trick `path: /. + path` to convert from +/// a string to a path wouldn't hit this code), so the target file +/// doesn't need to be realised or imported into the Nix store. +pub fn coerce_value_to_path(v: &Value, vm: &mut VM) -> Result { + force!(vm, v, value, { + match value { + Value::Thunk(t) => coerce_value_to_path(&t.value(), vm), + Value::Path(p) => Ok(p.clone()), + _ => value + .coerce_to_string(CoercionKind::Weak, vm) + .map(|s| PathBuf::from(s.as_str())) + .and_then(|path| { + if path.is_absolute() { + Ok(path) + } else { + Err(ErrorKind::NotAnAbsolutePath(path)) + } + }), + } + }) +} + /// Return all pure builtins, that is all builtins that do not rely on /// I/O outside of the VM and which can be used in any contexts (e.g. /// WASM). diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs index a84c931d0a4e..23ac6abbf4de 100644 --- a/tvix/eval/src/errors.rs +++ b/tvix/eval/src/errors.rs @@ -1,5 +1,6 @@ use crate::value::CoercionKind; use std::fmt::Display; +use std::path::PathBuf; use codemap::{CodeMap, Span}; use codemap_diagnostic::{Diagnostic, Emitter, Level, SpanLabel, SpanStyle}; @@ -73,6 +74,9 @@ pub enum ErrorKind { kind: CoercionKind, }, + /// The given string doesn't represent an absolute path + NotAnAbsolutePath(PathBuf), + /// Tvix internal warning for features triggered by users that are /// not actually implemented yet, and without which eval can not /// proceed. @@ -189,6 +193,13 @@ to a missing value in the attribute set(s) included via `with`."#, format!("cannot ({kindly}) coerce {from} to a string{hint}") } + ErrorKind::NotAnAbsolutePath(given) => { + format!( + "string {} doesn't represent an absolute path", + given.to_string_lossy() + ) + } + ErrorKind::NotImplemented(feature) => { format!("feature not yet implemented in Tvix: {}", feature) } @@ -218,6 +229,7 @@ to a missing value in the attribute set(s) included via `with`."#, ErrorKind::ThunkForce(_) => "E017", ErrorKind::NotCoercibleToString { .. } => "E018", ErrorKind::IndexOutOfBounds { .. } => "E019", + ErrorKind::NotAnAbsolutePath(_) => "E020", ErrorKind::NotImplemented(_) => "E999", } } diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs index f8fb9c7b40b7..47096dd40912 100644 --- a/tvix/eval/src/value/mod.rs +++ b/tvix/eval/src/value/mod.rs @@ -101,6 +101,8 @@ impl Value { kind: CoercionKind, vm: &mut VM, ) -> Result { + // TODO: eventually, this will need to handle string context and importing + // files into the Nix store depending on what context the coercion happens in if let Value::Thunk(t) = self { t.force(vm)?; } diff --git a/tvix/eval/src/value/string.rs b/tvix/eval/src/value/string.rs index aa542181f9b1..058c7f87dda8 100644 --- a/tvix/eval/src/value/string.rs +++ b/tvix/eval/src/value/string.rs @@ -2,7 +2,7 @@ //! backing implementations. use smol_str::SmolStr; use std::hash::Hash; -use std::{borrow::Cow, fmt::Display}; +use std::{borrow::Cow, fmt::Display, str::Chars}; #[derive(Clone, Debug)] enum StringRepr { @@ -97,6 +97,13 @@ impl NixString { s.push_str(other.as_str()); NixString(StringRepr::Heap(s)) } + + pub fn chars(&self) -> Chars<'_> { + match &self.0 { + StringRepr::Heap(h) => h.chars(), + StringRepr::Smol(s) => s.chars(), + } + } } fn nix_escape_char(ch: char, next: Option<&char>) -> Option<&'static str> { -- cgit 1.4.1