feat(tvix/eval): Give names to builtin arguments
Refactor the arguments of a Builtin to be a vec of a new BuiltinArgument struct, which contains the old strictness boolean and also a static `name` str - this is automatically determined via the ident for the corresponding function argument in the proc-macro case, and passed in in the cases where we're still manually calling Builtin::new. Currently this name is unused, but in the future this can be used as part of a documentation system for builtins. Change-Id: Ib9dadb15b69bf8c9ea1983a4f4f197294a2394a6 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7204 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
This commit is contained in:
parent
dad07a8bc0
commit
a1015ba1d7
6 changed files with 109 additions and 55 deletions
|
@ -5,7 +5,9 @@ use proc_macro2::Span;
|
||||||
use quote::{quote_spanned, ToTokens};
|
use quote::{quote_spanned, ToTokens};
|
||||||
use syn::parse::Parse;
|
use syn::parse::Parse;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{parse_macro_input, parse_quote, FnArg, Ident, Item, ItemMod, LitStr, PatType};
|
use syn::{
|
||||||
|
parse_macro_input, parse_quote, FnArg, Ident, Item, ItemMod, LitStr, Pat, PatIdent, PatType,
|
||||||
|
};
|
||||||
|
|
||||||
struct BuiltinArgs {
|
struct BuiltinArgs {
|
||||||
name: LitStr,
|
name: LitStr,
|
||||||
|
@ -89,31 +91,54 @@ pub fn builtins(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let strictness = f
|
let builtin_arguments = f
|
||||||
.sig
|
.sig
|
||||||
.inputs
|
.inputs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|input| {
|
.map(|arg| {
|
||||||
let mut lazy = false;
|
let mut strict = true;
|
||||||
if let FnArg::Typed(PatType { attrs, .. }) = input {
|
let name = match arg {
|
||||||
attrs.retain(|attr| {
|
FnArg::Receiver(_) => {
|
||||||
attr.path.get_ident().into_iter().any(|id| {
|
return Err(quote_spanned!(arg.span() => {
|
||||||
if id == "lazy" {
|
compile_error!("Unexpected receiver argument in builtin")
|
||||||
lazy = true;
|
}))
|
||||||
false
|
}
|
||||||
} else {
|
FnArg::Typed(PatType { attrs, pat, .. }) => {
|
||||||
true
|
attrs.retain(|attr| {
|
||||||
}
|
attr.path.get_ident().into_iter().any(|id| {
|
||||||
})
|
if id == "lazy" {
|
||||||
});
|
strict = false;
|
||||||
}
|
false
|
||||||
!lazy
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
match pat.as_ref() {
|
||||||
|
Pat::Ident(PatIdent { ident, .. }) => ident.to_string(),
|
||||||
|
_ => "unknown".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(quote_spanned!(arg.span() => {
|
||||||
|
crate::internal::BuiltinArgument {
|
||||||
|
strict: #strict,
|
||||||
|
name: #name,
|
||||||
|
}
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Result<Vec<_>, _>>();
|
||||||
|
|
||||||
|
let builtin_arguments = match builtin_arguments {
|
||||||
|
Ok(args) => args,
|
||||||
|
Err(err) => return err.into(),
|
||||||
|
};
|
||||||
|
|
||||||
let fn_name = f.sig.ident.clone();
|
let fn_name = f.sig.ident.clone();
|
||||||
let args = (0..(f.sig.inputs.len() - 1))
|
let num_args = f.sig.inputs.len() - 1;
|
||||||
|
let args = (0..num_args)
|
||||||
.map(|n| Ident::new(&format!("arg_{n}"), Span::call_site()))
|
.map(|n| Ident::new(&format!("arg_{n}"), Span::call_site()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut reversed_args = args.clone();
|
let mut reversed_args = args.clone();
|
||||||
|
@ -122,7 +147,7 @@ pub fn builtins(_args: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
builtins.push(quote_spanned! { builtin_attr.span() => {
|
builtins.push(quote_spanned! { builtin_attr.span() => {
|
||||||
crate::internal::Builtin::new(
|
crate::internal::Builtin::new(
|
||||||
#name,
|
#name,
|
||||||
&[#(#strictness),*],
|
&[#(#builtin_arguments),*],
|
||||||
|mut args: Vec<crate::Value>, vm: &mut crate::internal::VM| {
|
|mut args: Vec<crate::Value>, vm: &mut crate::internal::VM| {
|
||||||
#(let #reversed_args = args.pop().unwrap();)*
|
#(let #reversed_args = args.pop().unwrap();)*
|
||||||
#fn_name(vm, #(#args),*)
|
#fn_name(vm, #(#args),*)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
compiler::GlobalsMap,
|
compiler::GlobalsMap,
|
||||||
errors::ErrorKind,
|
errors::ErrorKind,
|
||||||
observer::NoOpObserver,
|
observer::NoOpObserver,
|
||||||
value::{Builtin, NixAttrs, Thunk},
|
value::{Builtin, BuiltinArgument, NixAttrs, Thunk},
|
||||||
vm::VM,
|
vm::VM,
|
||||||
SourceCode, Value,
|
SourceCode, Value,
|
||||||
};
|
};
|
||||||
|
@ -111,7 +111,10 @@ pub fn builtins_import(globals: &Weak<GlobalsMap>, source: SourceCode) -> Builti
|
||||||
|
|
||||||
Builtin::new(
|
Builtin::new(
|
||||||
"import",
|
"import",
|
||||||
&[true],
|
&[BuiltinArgument {
|
||||||
|
strict: true,
|
||||||
|
name: "path",
|
||||||
|
}],
|
||||||
move |mut args: Vec<Value>, vm: &mut VM| {
|
move |mut args: Vec<Value>, vm: &mut VM| {
|
||||||
let mut path = super::coerce_value_to_path(&args.pop().unwrap(), vm)?;
|
let mut path = super::coerce_value_to_path(&args.pop().unwrap(), vm)?;
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
use crate::compiler::{GlobalsMap, GlobalsMapFunc};
|
use crate::compiler::{GlobalsMap, GlobalsMapFunc};
|
||||||
use crate::source::SourceCode;
|
use crate::source::SourceCode;
|
||||||
|
use crate::value::BuiltinArgument;
|
||||||
use std::cmp::{self, Ordering};
|
use std::cmp::{self, Ordering};
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -907,7 +908,16 @@ fn placeholders() -> Vec<Builtin> {
|
||||||
vec![
|
vec![
|
||||||
Builtin::new(
|
Builtin::new(
|
||||||
"addErrorContext",
|
"addErrorContext",
|
||||||
&[false, false],
|
&[
|
||||||
|
BuiltinArgument {
|
||||||
|
strict: false,
|
||||||
|
name: "context",
|
||||||
|
},
|
||||||
|
BuiltinArgument {
|
||||||
|
strict: false,
|
||||||
|
name: "value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|mut args: Vec<Value>, vm: &mut VM| {
|
|mut args: Vec<Value>, vm: &mut VM| {
|
||||||
vm.emit_warning(WarningKind::NotImplemented("builtins.addErrorContext"));
|
vm.emit_warning(WarningKind::NotImplemented("builtins.addErrorContext"));
|
||||||
Ok(args.pop().unwrap())
|
Ok(args.pop().unwrap())
|
||||||
|
@ -915,7 +925,10 @@ fn placeholders() -> Vec<Builtin> {
|
||||||
),
|
),
|
||||||
Builtin::new(
|
Builtin::new(
|
||||||
"unsafeDiscardStringContext",
|
"unsafeDiscardStringContext",
|
||||||
&[true],
|
&[BuiltinArgument {
|
||||||
|
strict: true,
|
||||||
|
name: "s",
|
||||||
|
}],
|
||||||
|mut args: Vec<Value>, vm: &mut VM| {
|
|mut args: Vec<Value>, vm: &mut VM| {
|
||||||
vm.emit_warning(WarningKind::NotImplemented(
|
vm.emit_warning(WarningKind::NotImplemented(
|
||||||
"builtins.unsafeDiscardStringContext",
|
"builtins.unsafeDiscardStringContext",
|
||||||
|
@ -923,28 +936,35 @@ fn placeholders() -> Vec<Builtin> {
|
||||||
Ok(args.pop().unwrap())
|
Ok(args.pop().unwrap())
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Builtin::new("derivation", &[true], |args: Vec<Value>, vm: &mut VM| {
|
Builtin::new(
|
||||||
vm.emit_warning(WarningKind::NotImplemented("builtins.derivation"));
|
"derivation",
|
||||||
|
&[BuiltinArgument {
|
||||||
|
strict: true,
|
||||||
|
name: "attrs",
|
||||||
|
}],
|
||||||
|
|args: Vec<Value>, vm: &mut VM| {
|
||||||
|
vm.emit_warning(WarningKind::NotImplemented("builtins.derivation"));
|
||||||
|
|
||||||
// We do not implement derivations yet, so this function sets mock
|
// We do not implement derivations yet, so this function sets mock
|
||||||
// values on the fields that a real derivation would contain.
|
// values on the fields that a real derivation would contain.
|
||||||
//
|
//
|
||||||
// Crucially this means we do not yet *validate* the values either.
|
// Crucially this means we do not yet *validate* the values either.
|
||||||
let attrs = unwrap_or_clone_rc(args[0].to_attrs()?);
|
let attrs = unwrap_or_clone_rc(args[0].to_attrs()?);
|
||||||
let attrs = attrs.update(NixAttrs::from_map(BTreeMap::from([
|
let attrs = attrs.update(NixAttrs::from_map(BTreeMap::from([
|
||||||
(
|
(
|
||||||
"outPath".into(),
|
"outPath".into(),
|
||||||
"/nix/store/00000000000000000000000000000000-mock".into(),
|
"/nix/store/00000000000000000000000000000000-mock".into(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"drvPath".into(),
|
"drvPath".into(),
|
||||||
"/nix/store/00000000000000000000000000000000-mock.drv".into(),
|
"/nix/store/00000000000000000000000000000000-mock.drv".into(),
|
||||||
),
|
),
|
||||||
("type".into(), "derivation".into()),
|
("type".into(), "derivation".into()),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
Ok(Value::Attrs(Rc::new(attrs)))
|
Ok(Value::Attrs(Rc::new(attrs)))
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
// we set TVIX_CURRENT_SYSTEM in build.rs
|
// we set TVIX_CURRENT_SYSTEM in build.rs
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub use crate::vm::run_lambda;
|
||||||
/// Internal-only parts of `tvix-eval`, exported for use in macros, but not part of the public
|
/// Internal-only parts of `tvix-eval`, exported for use in macros, but not part of the public
|
||||||
/// interface of the crate.
|
/// interface of the crate.
|
||||||
pub mod internal {
|
pub mod internal {
|
||||||
pub use crate::value::Builtin;
|
pub use crate::value::{Builtin, BuiltinArgument};
|
||||||
pub use crate::vm::VM;
|
pub use crate::vm::VM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,14 @@ use std::{
|
||||||
pub trait BuiltinFn: Fn(Vec<Value>, &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 {}
|
impl<F: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind>> BuiltinFn for F {}
|
||||||
|
|
||||||
|
/// Description of a single argument passed to a builtin
|
||||||
|
pub struct BuiltinArgument {
|
||||||
|
/// Whether the argument should be forced before the underlying builtin function is called
|
||||||
|
pub strict: bool,
|
||||||
|
/// The name of the argument, to be used in docstrings and error messages
|
||||||
|
pub name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a single built-in function which directly executes Rust
|
/// Represents a single built-in function which directly executes Rust
|
||||||
/// code that operates on a Nix value.
|
/// code that operates on a Nix value.
|
||||||
///
|
///
|
||||||
|
@ -40,10 +48,8 @@ impl<F: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind>> BuiltinFn for F {}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Builtin {
|
pub struct Builtin {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
/// Array reference that describes how many arguments there are (usually 1
|
/// Array of arguments to the builtin.
|
||||||
/// or 2) and whether they need to be forced. `true` causes the
|
arguments: &'static [BuiltinArgument],
|
||||||
/// corresponding argument to be forced before `func` is called.
|
|
||||||
strict_args: &'static [bool],
|
|
||||||
func: Rc<dyn BuiltinFn>,
|
func: Rc<dyn BuiltinFn>,
|
||||||
|
|
||||||
/// Partially applied function arguments.
|
/// Partially applied function arguments.
|
||||||
|
@ -53,12 +59,12 @@ pub struct Builtin {
|
||||||
impl Builtin {
|
impl Builtin {
|
||||||
pub fn new<F: BuiltinFn + 'static>(
|
pub fn new<F: BuiltinFn + 'static>(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
strict_args: &'static [bool],
|
arguments: &'static [BuiltinArgument],
|
||||||
func: F,
|
func: F,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Builtin {
|
Builtin {
|
||||||
name,
|
name,
|
||||||
strict_args,
|
arguments,
|
||||||
func: Rc::new(func),
|
func: Rc::new(func),
|
||||||
partials: vec![],
|
partials: vec![],
|
||||||
}
|
}
|
||||||
|
@ -74,9 +80,9 @@ impl Builtin {
|
||||||
pub fn apply(mut self, vm: &mut VM, arg: Value) -> Result<Value, ErrorKind> {
|
pub fn apply(mut self, vm: &mut VM, arg: Value) -> Result<Value, ErrorKind> {
|
||||||
self.partials.push(arg);
|
self.partials.push(arg);
|
||||||
|
|
||||||
if self.partials.len() == self.strict_args.len() {
|
if self.partials.len() == self.arguments.len() {
|
||||||
for (idx, force) in self.strict_args.iter().enumerate() {
|
for (idx, BuiltinArgument { strict, .. }) in self.arguments.iter().enumerate() {
|
||||||
if *force {
|
if *strict {
|
||||||
self.partials[idx].force(vm)?;
|
self.partials[idx].force(vm)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::errors::ErrorKind;
|
||||||
use crate::opcode::StackIdx;
|
use crate::opcode::StackIdx;
|
||||||
use crate::vm::VM;
|
use crate::vm::VM;
|
||||||
pub use attrs::NixAttrs;
|
pub use attrs::NixAttrs;
|
||||||
pub use builtin::Builtin;
|
pub use builtin::{Builtin, BuiltinArgument};
|
||||||
pub(crate) use function::Formals;
|
pub(crate) use function::Formals;
|
||||||
pub use function::{Closure, Lambda};
|
pub use function::{Closure, Lambda};
|
||||||
pub use list::NixList;
|
pub use list::NixList;
|
||||||
|
|
Loading…
Add table
Reference in a new issue