about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
Diffstat (limited to 'tvix')
-rw-r--r--tvix/eval/src/builtins/mod.rs26
-rw-r--r--tvix/eval/src/errors.rs12
-rw-r--r--tvix/eval/src/value/mod.rs2
-rw-r--r--tvix/eval/src/value/string.rs9
4 files changed, 48 insertions, 1 deletions
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<PathBuf, ErrorKind> {
+    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<NixString, ErrorKind> {
+        // 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> {