about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/builtins/mod.rs186
-rw-r--r--tvix/eval/src/value/builtin.rs22
2 files changed, 115 insertions, 93 deletions
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index 61ff60fa05..ad3ab807e3 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -54,12 +54,12 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new(
             "add",
             &[false, false],
-            |args, vm| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, +),
+            |args: Vec<Value>, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, +),
         ),
-        Builtin::new("abort", &[true], |args, _| {
+        Builtin::new("abort", &[true], |args: Vec<Value>, _: &mut VM| {
             Err(ErrorKind::Abort(args[0].to_str()?.to_string()))
         }),
-        Builtin::new("all", &[true, true], |args, vm| {
+        Builtin::new("all", &[true, true], |args: Vec<Value>, vm: &mut VM| {
             for value in args[1].to_list()?.into_iter() {
                 let pred_result = {
                     vm.push(value);
@@ -73,7 +73,7 @@ fn pure_builtins() -> Vec<Builtin> {
 
             Ok(Value::Bool(true))
         }),
-        Builtin::new("any", &[true, true], |args, vm| {
+        Builtin::new("any", &[true, true], |args: Vec<Value>, vm: &mut VM| {
             for value in args[1].to_list()?.into_iter() {
                 let pred_result = {
                     vm.push(value);
@@ -87,7 +87,7 @@ fn pure_builtins() -> Vec<Builtin> {
 
             Ok(Value::Bool(false))
         }),
-        Builtin::new("attrNames", &[true], |args, _| {
+        Builtin::new("attrNames", &[true], |args: Vec<Value>, _: &mut VM| {
             let xs = args[0].to_attrs()?;
             let mut output = Vec::with_capacity(xs.len());
 
@@ -97,7 +97,7 @@ fn pure_builtins() -> Vec<Builtin> {
 
             Ok(Value::List(NixList::construct(output.len(), output)))
         }),
-        Builtin::new("attrValues", &[true], |args, _| {
+        Builtin::new("attrValues", &[true], |args: Vec<Value>, _: &mut VM| {
             let xs = args[0].to_attrs()?;
             let mut output = Vec::with_capacity(xs.len());
 
@@ -107,42 +107,50 @@ fn pure_builtins() -> Vec<Builtin> {
 
             Ok(Value::List(NixList::construct(output.len(), output)))
         }),
-        Builtin::new("bitAnd", &[true, true], |args, _| {
+        Builtin::new("bitAnd", &[true, true], |args: Vec<Value>, _: &mut VM| {
             Ok(Value::Integer(args[0].as_int()? & args[1].as_int()?))
         }),
-        Builtin::new("bitOr", &[true, true], |args, _| {
+        Builtin::new("bitOr", &[true, true], |args: Vec<Value>, _: &mut VM| {
             Ok(Value::Integer(args[0].as_int()? | args[1].as_int()?))
         }),
-        Builtin::new("bitXor", &[true, true], |args, _| {
+        Builtin::new("bitXor", &[true, true], |args: Vec<Value>, _: &mut VM| {
             Ok(Value::Integer(args[0].as_int()? ^ args[1].as_int()?))
         }),
-        Builtin::new("catAttrs", &[true, true], |args, vm| {
-            let key = args[0].to_str()?;
-            let list = args[1].to_list()?;
-            let mut output = vec![];
-
-            for item in list.into_iter() {
-                let set = item.force(vm)?.to_attrs()?;
-                if let Some(value) = set.select(key.as_str()) {
-                    output.push(value.clone());
+        Builtin::new(
+            "catAttrs",
+            &[true, true],
+            |args: Vec<Value>, vm: &mut VM| {
+                let key = args[0].to_str()?;
+                let list = args[1].to_list()?;
+                let mut output = vec![];
+
+                for item in list.into_iter() {
+                    let set = item.force(vm)?.to_attrs()?;
+                    if let Some(value) = set.select(key.as_str()) {
+                        output.push(value.clone());
+                    }
                 }
-            }
 
-            Ok(Value::List(NixList::construct(output.len(), output)))
-        }),
-        Builtin::new("compareVersions", &[true, true], |args, _| {
-            let s1 = args[0].to_str()?;
-            let s1 = VersionPartsIter::new_for_cmp(s1.as_str());
-            let s2 = args[1].to_str()?;
-            let s2 = VersionPartsIter::new_for_cmp(s2.as_str());
-
-            match s1.cmp(s2) {
-                std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
-                std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
-                std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
-            }
-        }),
-        Builtin::new("concatLists", &[true], |args, vm| {
+                Ok(Value::List(NixList::construct(output.len(), output)))
+            },
+        ),
+        Builtin::new(
+            "compareVersions",
+            &[true, true],
+            |args: Vec<Value>, _: &mut VM| {
+                let s1 = args[0].to_str()?;
+                let s1 = VersionPartsIter::new_for_cmp(s1.as_str());
+                let s2 = args[1].to_str()?;
+                let s2 = VersionPartsIter::new_for_cmp(s2.as_str());
+
+                match s1.cmp(s2) {
+                    std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
+                    std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
+                    std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
+                }
+            },
+        ),
+        Builtin::new("concatLists", &[true], |args: Vec<Value>, vm: &mut VM| {
             let list = args[0].to_list()?;
             let lists = list
                 .into_iter()
@@ -159,9 +167,9 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new(
             "div",
             &[false, false],
-            |args, vm| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, /),
+            |args: Vec<Value>, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, /),
         ),
-        Builtin::new("elemAt", &[true, true], |args, _| {
+        Builtin::new("elemAt", &[true, true], |args: Vec<Value>, _: &mut VM| {
             let xs = args[0].to_list()?;
             let i = args[1].as_int()?;
             if i < 0 {
@@ -173,7 +181,7 @@ fn pure_builtins() -> Vec<Builtin> {
                 }
             }
         }),
-        Builtin::new("filter", &[true, true], |args, vm| {
+        Builtin::new("filter", &[true, true], |args: Vec<Value>, vm: &mut VM| {
             let list: NixList = args[1].to_list()?;
 
             list.into_iter()
@@ -202,7 +210,7 @@ fn pure_builtins() -> Vec<Builtin> {
                 .map(|list| Value::List(NixList::from(list)))
                 .map_err(Into::into)
         }),
-        Builtin::new("getAttr", &[true, true], |args, _| {
+        Builtin::new("getAttr", &[true, true], |args: Vec<Value>, _: &mut VM| {
             let k = args[0].to_str()?;
             let xs = args[1].to_attrs()?;
 
@@ -213,10 +221,10 @@ fn pure_builtins() -> Vec<Builtin> {
                 }),
             }
         }),
-        Builtin::new("length", &[true], |args, _| {
+        Builtin::new("length", &[true], |args: Vec<Value>, _: &mut VM| {
             Ok(Value::Integer(args[0].to_list()?.len() as i64))
         }),
-        Builtin::new("map", &[true, true], |args, vm| {
+        Builtin::new("map", &[true, true], |args: Vec<Value>, vm: &mut VM| {
             let list: NixList = args[1].to_list()?;
 
             list.into_iter()
@@ -233,64 +241,66 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new(
             "lessThan",
             &[false, false],
-            |args, vm| cmp_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, <),
+            |args: Vec<Value>, vm: &mut VM| cmp_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, <),
         ),
-        Builtin::new("hasAttr", &[true, true], |args, _| {
+        Builtin::new("hasAttr", &[true, true], |args: Vec<Value>, _: &mut VM| {
             let k = args[0].to_str()?;
             let xs = args[1].to_attrs()?;
 
             Ok(Value::Bool(xs.contains(k.as_str())))
         }),
-        Builtin::new("head", &[true], |args, _| match args[0].to_list()?.get(0) {
-            Some(x) => Ok(x.clone()),
-            None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
+        Builtin::new("head", &[true], |args: Vec<Value>, _: &mut VM| {
+            match args[0].to_list()?.get(0) {
+                Some(x) => Ok(x.clone()),
+                None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
+            }
         }),
         // For `is*` predicates we force manually, as Value::force also unwraps any Thunks
-        Builtin::new("isAttrs", &[false], |args, vm| {
+        Builtin::new("isAttrs", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Attrs(_))))
         }),
-        Builtin::new("isBool", &[false], |args, vm| {
+        Builtin::new("isBool", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Bool(_))))
         }),
-        Builtin::new("isFloat", &[false], |args, vm| {
+        Builtin::new("isFloat", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Float(_))))
         }),
-        Builtin::new("isFunction", &[false], |args, vm| {
+        Builtin::new("isFunction", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(
                 *value,
                 Value::Closure(_) | Value::Builtin(_)
             )))
         }),
-        Builtin::new("isInt", &[false], |args, vm| {
+        Builtin::new("isInt", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Integer(_))))
         }),
-        Builtin::new("isList", &[false], |args, vm| {
+        Builtin::new("isList", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::List(_))))
         }),
-        Builtin::new("isNull", &[false], |args, vm| {
+        Builtin::new("isNull", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Null)))
         }),
-        Builtin::new("isPath", &[false], |args, vm| {
+        Builtin::new("isPath", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::Path(_))))
         }),
-        Builtin::new("isString", &[false], |args, vm| {
+        Builtin::new("isString", &[false], |args: Vec<Value>, vm: &mut VM| {
             let value = args[0].force(vm)?;
             Ok(Value::Bool(matches!(*value, Value::String(_))))
         }),
         Builtin::new(
             "mul",
             &[false, false],
-            |args, vm| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, *),
+            |args: Vec<Value>, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, *),
         ),
-        Builtin::new("splitVersion", &[true], |args, _| {
+        Builtin::new("splitVersion", &[true], |args: Vec<Value>, _: &mut VM| {
             let s = args[0].to_str()?;
             let s = VersionPartsIter::new(s.as_str());
 
@@ -304,7 +314,7 @@ fn pure_builtins() -> Vec<Builtin> {
                 .collect::<Vec<Value>>();
             Ok(Value::List(NixList::construct(parts.len(), parts)))
         }),
-        Builtin::new("stringLength", &[false], |args, vm| {
+        Builtin::new("stringLength", &[false], |args: Vec<Value>, vm: &mut VM| {
             // also forces the value
             let s = args[0].coerce_to_string(CoercionKind::Weak, vm)?;
             Ok(Value::Integer(s.as_str().len() as i64))
@@ -312,37 +322,41 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new(
             "sub",
             &[false, false],
-            |args, vm| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, -),
+            |args: Vec<Value>, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, -),
         ),
-        Builtin::new("substring", &[true, true, true], |args, _| {
-            let beg = args[0].as_int()?;
-            let len = args[1].as_int()?;
-            let x = args[2].to_str()?;
-
-            if beg < 0 {
-                return Err(ErrorKind::IndexOutOfBounds { index: beg });
-            }
-            let beg = beg as usize;
+        Builtin::new(
+            "substring",
+            &[true, true, true],
+            |args: Vec<Value>, _: &mut VM| {
+                let beg = args[0].as_int()?;
+                let len = args[1].as_int()?;
+                let x = args[2].to_str()?;
+
+                if beg < 0 {
+                    return Err(ErrorKind::IndexOutOfBounds { index: beg });
+                }
+                let beg = beg as usize;
 
-            // Nix doesn't assert that the length argument is
-            // non-negative when the starting index is GTE the
-            // string's length.
-            if beg >= x.as_str().len() {
-                return Ok(Value::String("".into()));
-            }
+                // Nix doesn't assert that the length argument is
+                // non-negative when the starting index is GTE the
+                // string's length.
+                if beg >= x.as_str().len() {
+                    return Ok(Value::String("".into()));
+                }
 
-            if len < 0 {
-                return Err(ErrorKind::NegativeLength { length: len });
-            }
+                if len < 0 {
+                    return Err(ErrorKind::NegativeLength { length: len });
+                }
 
-            let len = len as usize;
-            let end = cmp::min(beg + len, x.as_str().len());
+                let len = len as usize;
+                let end = cmp::min(beg + len, x.as_str().len());
 
-            Ok(Value::String(
-                x.as_str()[(beg as usize)..(end as usize)].into(),
-            ))
-        }),
-        Builtin::new("tail", &[true], |args, _| {
+                Ok(Value::String(
+                    x.as_str()[(beg as usize)..(end as usize)].into(),
+                ))
+            },
+        ),
+        Builtin::new("tail", &[true], |args: Vec<Value>, _: &mut VM| {
             let xs = args[0].to_list()?;
 
             if xs.len() == 0 {
@@ -352,16 +366,16 @@ fn pure_builtins() -> Vec<Builtin> {
                 Ok(Value::List(NixList::construct(output.len(), output)))
             }
         }),
-        Builtin::new("throw", &[true], |args, _| {
+        Builtin::new("throw", &[true], |args: Vec<Value>, _: &mut VM| {
             Err(ErrorKind::Throw(args[0].to_str()?.to_string()))
         }),
         // coerce_to_string forces for us
-        Builtin::new("toString", &[false], |args, vm| {
+        Builtin::new("toString", &[false], |args: Vec<Value>, vm: &mut VM| {
             args[0]
                 .coerce_to_string(CoercionKind::Strong, vm)
                 .map(Value::String)
         }),
-        Builtin::new("typeOf", &[false], |args, vm| {
+        Builtin::new("typeOf", &[false], |args: Vec<Value>, vm: &mut VM| {
             // We force manually here because it also unwraps the Thunk
             // representation, if any.
             // TODO(sterni): it'd be nice if we didn't have to worry about this
diff --git a/tvix/eval/src/value/builtin.rs b/tvix/eval/src/value/builtin.rs
index ca9924c5d5..fc80431762 100644
--- a/tvix/eval/src/value/builtin.rs
+++ b/tvix/eval/src/value/builtin.rs
@@ -7,10 +7,13 @@ use crate::{errors::ErrorKind, vm::VM};
 
 use super::Value;
 
-use std::fmt::{Debug, Display};
+use std::{
+    fmt::{Debug, Display},
+    rc::Rc,
+};
 
-/// Function pointer type for builtins implemented directly by backing
-/// Rust code.
+/// Trait for closure types of builtins implemented directly by
+/// backing Rust code.
 ///
 /// Builtins declare their arity and are passed a vector with the
 /// right number of arguments. Additionally, as they might have to
@@ -19,7 +22,8 @@ use std::fmt::{Debug, Display};
 ///
 /// Errors returned from a builtin will be annotated with the location
 /// of the call to the builtin.
-pub type BuiltinFn = fn(arg: Vec<Value>, vm: &mut VM) -> Result<Value, ErrorKind>;
+pub trait BuiltinFn: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind> {}
+impl<F: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind>> BuiltinFn for F {}
 
 /// Represents a single built-in function which directly executes Rust
 /// code that operates on a Nix value.
@@ -40,18 +44,22 @@ pub struct Builtin {
     /// or 2) and whether they need to be forced. `true` causes the
     /// corresponding argument to be forced before `func` is called.
     strict_args: &'static [bool],
-    func: BuiltinFn,
+    func: Rc<dyn BuiltinFn>,
 
     /// Partially applied function arguments.
     partials: Vec<Value>,
 }
 
 impl Builtin {
-    pub fn new(name: &'static str, strict_args: &'static [bool], func: BuiltinFn) -> Self {
+    pub fn new<F: BuiltinFn + 'static>(
+        name: &'static str,
+        strict_args: &'static [bool],
+        func: F,
+    ) -> Self {
         Builtin {
             name,
             strict_args,
-            func,
+            func: Rc::new(func),
             partials: vec![],
         }
     }