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 <sternenseemann@systemli.org>
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
sterni 2022-09-13 20:11:07 +02:00 committed by clbot
parent e834a2cbc4
commit 067f2b16f6
4 changed files with 48 additions and 1 deletions

View file

@ -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).

View file

@ -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",
}
}

View file

@ -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)?;
}

View file

@ -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> {