--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -11,18 +11,7 @@ use self::destructure::destruct; use crate::{ - arr::ArrValue, - bail, - destructure::evaluate_dest, - error::{suggest_object_fields, ErrorKind::*}, - evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, - function::{CallLocation, FuncDesc, FuncVal}, - gc::WithCapacityExt as _, - in_frame, - typed::Typed, - val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, - with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, - ResultExt, SupThis, Unbound, Val, + Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, SupThis, Unbound, Val, arr::ArrValue, bail, destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal, builtin::{ParamDefault, ParamName, ParamParse}}, gc::WithCapacityExt as _, in_frame, typed::Typed, val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, with_state }; pub mod destructure; pub mod operator; @@ -88,6 +77,15 @@ Val::Func(FuncVal::Normal(Cc::new(FuncDesc { name, ctx, + params_parse: params + .iter() + .map(|p| { + ParamParse::new( + p.0.name().map_or(ParamName::ANONYMOUS, ParamName::new), + ParamDefault::exists(p.1.is_some()), + ) + }) + .collect(), params, body, }))) --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -1,22 +1,17 @@ -use std::{any::Any, borrow::Cow}; +use std::any::Any; -use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox}; +use jrsonnet_gcmodule::{cc_dyn, Acyclic, Trace, TraceBox}; use jrsonnet_interner::IStr; use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation}; use crate::{Context, Result, Val}; -/// Can't have `str` | `IStr`, because constant `BuiltinParam` causes -/// `E0492: constant functions cannot refer to interior mutable data` -#[derive(Clone, Trace)] -pub struct ParamName(Option>); +#[derive(Clone, Acyclic)] +pub struct ParamName(Option); impl ParamName { pub const ANONYMOUS: Self = Self(None); - pub const fn new_static(name: &'static str) -> Self { - Self(Some(Cow::Borrowed(name))) - } - pub fn new_dynamic(name: String) -> Self { - Self(Some(Cow::Owned(name))) + pub fn new(name: IStr) -> Self { + Self(Some(name)) } pub fn as_str(&self) -> Option<&str> { self.0.as_deref() @@ -33,7 +28,7 @@ } } -#[derive(Clone, Copy, Debug, Trace)] +#[derive(Clone, Copy, Debug, Acyclic)] pub enum ParamDefault { None, Exists, @@ -49,13 +44,26 @@ } } -#[derive(Clone, Trace)] -pub struct BuiltinParam { +#[macro_export] +macro_rules! params { + (@name unnamed) => { ParamName::ANONYMOUS }; + (@name named $name:literal) => { ParamName::new($crate::IStr::from($name)) }; + ($($(#[$meta:meta])* [$kind:ident $(($lit:literal))? => $default:expr]),* $(,)?) => { + thread_local! { + static PARAMS: [ParamParse; { const N: usize = <[u8]>::len(&[$($(#[$meta])* 0u8),*]); N }] = [ + $($(#[$meta])* ParamParse::new(params!(@name $kind $($lit)?), $default)),* + ]; + } + }; +} + +#[derive(Clone, Acyclic)] +pub struct ParamParse { name: ParamName, default: ParamDefault, } -impl BuiltinParam { - pub const fn new(name: ParamName, default: ParamDefault) -> Self { +impl ParamParse { + pub fn new(name: ParamName, default: ParamDefault) -> Self { Self { name, default } } /// Parameter name for named call parsing @@ -81,7 +89,7 @@ self.0.name() } - fn params(&self) -> &[BuiltinParam] { + fn params(&self) -> &[ParamParse] { self.0.params() } @@ -101,7 +109,7 @@ /// Function name to be used in stack traces fn name(&self) -> &str; /// Parameter names for named calls - fn params(&self) -> &[BuiltinParam]; + fn params(&self) -> &[ParamParse]; /// Call the builtin fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result; @@ -118,7 +126,7 @@ #[derive(Trace)] pub struct NativeCallback { - pub(crate) params: Vec, + pub(crate) params: Vec, handler: TraceBox, } impl NativeCallback { @@ -127,8 +135,8 @@ Self { params: params .into_iter() - .map(|n| BuiltinParam { - name: ParamName::new_dynamic(n), + .map(|n| ParamParse { + name: ParamName::new(n.into()), default: ParamDefault::None, }) .collect(), @@ -144,7 +152,7 @@ "" } - fn params(&self) -> &[BuiltinParam] { + fn params(&self) -> &[ParamParse] { &self.params } --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -1,6 +1,7 @@ use std::{fmt::Debug, rc::Rc}; pub use arglike::{ArgLike, ArgsLike, TlaArg}; +use educe::Educe; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; pub use jrsonnet_macros::builtin; @@ -8,7 +9,7 @@ use self::{ arglike::OptionalContext, - builtin::{Builtin, BuiltinParam, ParamDefault, ParamName, StaticBuiltin}, + builtin::{Builtin, ParamParse, StaticBuiltin}, native::NativeDesc, parse::{parse_default_function_call, parse_function_call}, }; @@ -40,7 +41,8 @@ } /// Represents Jsonnet function defined in code. -#[derive(Debug, Trace, PartialEq)] +#[derive(Trace, Educe)] +#[educe(Debug, PartialEq)] pub struct FuncDesc { /// # Example /// @@ -67,6 +69,9 @@ pub params: ParamsDesc, /// Function body pub body: Rc>, + + #[educe(PartialEq = false, Debug = false)] + pub(crate) params_parse: Vec, } impl FuncDesc { /// Create body context, but fill arguments without defaults with lazy error @@ -134,25 +139,13 @@ Self::StaticBuiltin(static_builtin) } - pub fn params(&self) -> Vec { + pub fn params(&self) -> &[ParamParse] { match self { - Self::Id => ID.params().to_vec(), - Self::StaticBuiltin(i) => i.params().to_vec(), - Self::Builtin(i) => i.params().to_vec(), - Self::Normal(p) => p - .params - .iter() - .map(|p| { - BuiltinParam::new( - p.0.name() - .as_ref() - .map(IStr::to_string) - .map_or(ParamName::ANONYMOUS, ParamName::new_dynamic), - ParamDefault::exists(p.1.is_some()), - ) - }) - .collect(), - Self::Thunk(_) => vec![], + Self::Id => ID.params(), + Self::StaticBuiltin(i) => i.params(), + Self::Builtin(i) => i.params(), + Self::Normal(p) => &p.params_parse, + Self::Thunk(_) => &[], } } /// Amount of non-default required arguments --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -4,7 +4,7 @@ use jrsonnet_parser::ParamsDesc; use rustc_hash::FxHashMap; -use super::{arglike::ArgsLike, builtin::BuiltinParam}; +use super::{arglike::ArgsLike, builtin::ParamParse}; use crate::{ bail, destructure::destruct, @@ -147,7 +147,7 @@ /// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily pub fn parse_builtin_call( ctx: Context, - params: &[BuiltinParam], + params: &[ParamParse], args: &dyn ArgsLike, tailstrict: bool, ) -> Result>>> { --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -239,9 +239,7 @@ cfg_attrs, .. } => { - let name = name - .as_ref() - .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)}); + let name = name.as_ref().map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); let default = match optionality { Optionality::Required => quote!(ParamDefault::None), Optionality::Optional => quote!(ParamDefault::Exists), @@ -249,15 +247,13 @@ }; Some(quote! { #(#cfg_attrs)* - BuiltinParam::new(#name, #default), + [#name => #default], }) } ArgInfo::Lazy { is_option, name } => { - let name = name - .as_ref() - .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)}); + let name = name.as_ref().map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)}); Some(quote! { - BuiltinParam::new(#name, ParamDefault::exists(#is_option)), + [#name => ParamDefault::exists(#is_option)], }) } ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None, @@ -368,13 +364,13 @@ const _: () = { use ::jrsonnet_evaluator::{ State, Val, - function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call}, + function::{builtin::{Builtin, StaticBuiltin, ParamParse, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call}, Result, Context, typed::Typed, - parser::Span, + parser::Span, params, }; - const PARAMS: &'static [BuiltinParam] = &[ + params!( #(#params_desc)* - ]; + ); #static_ext impl Builtin for #name @@ -384,12 +380,15 @@ fn name(&self) -> &str { stringify!(#name) } - fn params(&self) -> &[BuiltinParam] { - PARAMS + fn params(&self) -> &[ParamParse] { + /// Safety: ParamParse contains IStr, which is thread-local, thus neither Send or Sync + /// The result of this transmute can not outlive the thread, thus 'static here is equivalent to the + /// nightly-only 'thread + PARAMS.with(|p| unsafe { std::mem::transmute::<&[ParamParse], &'static [ParamParse]>(p.as_slice()) }) } #[allow(unused_variables)] fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result { - let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?; + let parsed = parse_builtin_call(ctx.clone(), self.params(), args, false)?; let result: #result = #name(#(#pass)*); <_ as Typed>::into_result(result)