git.delta.rocks / jrsonnet / refs/commits / df5053db3f0e

difftreelog

refactor do not allocate for BuiltinParam vec

mqrttluxYaroslav Bolyukin2026-03-19parent: #6a2bca3.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- 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,
 	})))
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/builtin.rs
1use std::{any::Any, borrow::Cow};23use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox};4use jrsonnet_interner::IStr;56use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};7use crate::{Context, Result, Val};89/// Can't have `str` | `IStr`, because constant `BuiltinParam` causes10/// `E0492: constant functions cannot refer to interior mutable data`11#[derive(Clone, Trace)]12pub struct ParamName(Option<Cow<'static, str>>);13impl ParamName {14	pub const ANONYMOUS: Self = Self(None);15	pub const fn new_static(name: &'static str) -> Self {16		Self(Some(Cow::Borrowed(name)))17	}18	pub fn new_dynamic(name: String) -> Self {19		Self(Some(Cow::Owned(name)))20	}21	pub fn as_str(&self) -> Option<&str> {22		self.0.as_deref()23	}24	pub fn is_anonymous(&self) -> bool {25		self.0.is_none()26	}27}28impl PartialEq<IStr> for ParamName {29	fn eq(&self, other: &IStr) -> bool {30		self.031			.as_ref()32			.map_or(false, |s| s.as_bytes() == other.as_bytes())33	}34}3536#[derive(Clone, Copy, Debug, Trace)]37pub enum ParamDefault {38	None,39	Exists,40	Literal(&'static str),41}42impl ParamDefault {43	pub const fn exists(is_exists: bool) -> Self {44		if is_exists {45			Self::Exists46		} else {47			Self::None48		}49	}50}5152#[derive(Clone, Trace)]53pub struct BuiltinParam {54	name: ParamName,55	default: ParamDefault,56}57impl BuiltinParam {58	pub const fn new(name: ParamName, default: ParamDefault) -> Self {59		Self { name, default }60	}61	/// Parameter name for named call parsing62	pub fn name(&self) -> &ParamName {63		&self.name64	}65	pub fn default(&self) -> ParamDefault {66		self.default67	}68	pub fn has_default(&self) -> bool {69		!matches!(self.default, ParamDefault::None)70	}71}7273cc_dyn!(74	#[derive(Clone)]75	BuiltinFunc,76	Builtin,77	pub(crate) fn new() {...}78);79impl Builtin for BuiltinFunc {80	fn name(&self) -> &str {81		self.0.name()82	}8384	fn params(&self) -> &[BuiltinParam] {85		self.0.params()86	}8788	fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {89		self.0.call(ctx, loc, args)90	}9192	fn as_any(&self) -> &dyn Any {93		self.0.as_any()94	}95}9697/// Description of function defined by native code98///99/// Prefer to use #[builtin] macro, instead of manual implementation of this trait100pub trait Builtin: Trace {101	/// Function name to be used in stack traces102	fn name(&self) -> &str;103	/// Parameter names for named calls104	fn params(&self) -> &[BuiltinParam];105	/// Call the builtin106	fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val>;107108	fn as_any(&self) -> &dyn Any;109}110111pub trait StaticBuiltin: Builtin + Send + Sync112where113	Self: 'static,114{115	// In impl, to make it object safe:116	// const INST: &'static Self;117}118119#[derive(Trace)]120pub struct NativeCallback {121	pub(crate) params: Vec<BuiltinParam>,122	handler: TraceBox<dyn NativeCallbackHandler>,123}124impl NativeCallback {125	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]126	pub fn new(params: Vec<String>, handler: impl NativeCallbackHandler) -> Self {127		Self {128			params: params129				.into_iter()130				.map(|n| BuiltinParam {131					name: ParamName::new_dynamic(n),132					default: ParamDefault::None,133				})134				.collect(),135			handler: TraceBox(Box::new(handler)),136		}137	}138}139140impl Builtin for NativeCallback {141	fn name(&self) -> &str {142		// TODO: standard natives gets their names from definition143		// But builitins should already have them144		"<native>"145	}146147	fn params(&self) -> &[BuiltinParam] {148		&self.params149	}150151	fn call(&self, ctx: Context, _loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {152		let args = parse_builtin_call(ctx, &self.params, args, true)?;153		let args = args154			.into_iter()155			.map(|a| a.expect("legacy natives have no default params"))156			.map(|a| a.evaluate())157			.collect::<Result<Vec<Val>>>()?;158		self.handler.call(&args)159	}160161	fn as_any(&self) -> &dyn Any {162		self163	}164}165166pub trait NativeCallbackHandler: Trace {167	fn call(&self, args: &[Val]) -> Result<Val>;168}
after · crates/jrsonnet-evaluator/src/function/builtin.rs
1use std::any::Any;23use jrsonnet_gcmodule::{cc_dyn, Acyclic, Trace, TraceBox};4use jrsonnet_interner::IStr;56use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};7use crate::{Context, Result, Val};89#[derive(Clone, Acyclic)]10pub struct ParamName(Option<IStr>);11impl ParamName {12	pub const ANONYMOUS: Self = Self(None);13	pub fn new(name: IStr) -> Self {14		Self(Some(name))15	}16	pub fn as_str(&self) -> Option<&str> {17		self.0.as_deref()18	}19	pub fn is_anonymous(&self) -> bool {20		self.0.is_none()21	}22}23impl PartialEq<IStr> for ParamName {24	fn eq(&self, other: &IStr) -> bool {25		self.026			.as_ref()27			.map_or(false, |s| s.as_bytes() == other.as_bytes())28	}29}3031#[derive(Clone, Copy, Debug, Acyclic)]32pub enum ParamDefault {33	None,34	Exists,35	Literal(&'static str),36}37impl ParamDefault {38	pub const fn exists(is_exists: bool) -> Self {39		if is_exists {40			Self::Exists41		} else {42			Self::None43		}44	}45}4647#[macro_export]48macro_rules! params {49	(@name unnamed) => { ParamName::ANONYMOUS };50	(@name named $name:literal) => { ParamName::new($crate::IStr::from($name)) };51	($($(#[$meta:meta])* [$kind:ident $(($lit:literal))? => $default:expr]),* $(,)?) => {52		thread_local! {53			static PARAMS: [ParamParse; { const N: usize = <[u8]>::len(&[$($(#[$meta])* 0u8),*]); N }] = [54				$($(#[$meta])* ParamParse::new(params!(@name $kind $($lit)?), $default)),*55			];56		}57	};58}5960#[derive(Clone, Acyclic)]61pub struct ParamParse {62	name: ParamName,63	default: ParamDefault,64}65impl ParamParse {66	pub fn new(name: ParamName, default: ParamDefault) -> Self {67		Self { name, default }68	}69	/// Parameter name for named call parsing70	pub fn name(&self) -> &ParamName {71		&self.name72	}73	pub fn default(&self) -> ParamDefault {74		self.default75	}76	pub fn has_default(&self) -> bool {77		!matches!(self.default, ParamDefault::None)78	}79}8081cc_dyn!(82	#[derive(Clone)]83	BuiltinFunc,84	Builtin,85	pub(crate) fn new() {...}86);87impl Builtin for BuiltinFunc {88	fn name(&self) -> &str {89		self.0.name()90	}9192	fn params(&self) -> &[ParamParse] {93		self.0.params()94	}9596	fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {97		self.0.call(ctx, loc, args)98	}99100	fn as_any(&self) -> &dyn Any {101		self.0.as_any()102	}103}104105/// Description of function defined by native code106///107/// Prefer to use #[builtin] macro, instead of manual implementation of this trait108pub trait Builtin: Trace {109	/// Function name to be used in stack traces110	fn name(&self) -> &str;111	/// Parameter names for named calls112	fn params(&self) -> &[ParamParse];113	/// Call the builtin114	fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val>;115116	fn as_any(&self) -> &dyn Any;117}118119pub trait StaticBuiltin: Builtin + Send + Sync120where121	Self: 'static,122{123	// In impl, to make it object safe:124	// const INST: &'static Self;125}126127#[derive(Trace)]128pub struct NativeCallback {129	pub(crate) params: Vec<ParamParse>,130	handler: TraceBox<dyn NativeCallbackHandler>,131}132impl NativeCallback {133	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]134	pub fn new(params: Vec<String>, handler: impl NativeCallbackHandler) -> Self {135		Self {136			params: params137				.into_iter()138				.map(|n| ParamParse {139					name: ParamName::new(n.into()),140					default: ParamDefault::None,141				})142				.collect(),143			handler: TraceBox(Box::new(handler)),144		}145	}146}147148impl Builtin for NativeCallback {149	fn name(&self) -> &str {150		// TODO: standard natives gets their names from definition151		// But builitins should already have them152		"<native>"153	}154155	fn params(&self) -> &[ParamParse] {156		&self.params157	}158159	fn call(&self, ctx: Context, _loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {160		let args = parse_builtin_call(ctx, &self.params, args, true)?;161		let args = args162			.into_iter()163			.map(|a| a.expect("legacy natives have no default params"))164			.map(|a| a.evaluate())165			.collect::<Result<Vec<Val>>>()?;166		self.handler.call(&args)167	}168169	fn as_any(&self) -> &dyn Any {170		self171	}172}173174pub trait NativeCallbackHandler: Trace {175	fn call(&self, args: &[Val]) -> Result<Val>;176}
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- 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<Spanned<Expr>>,
+
+	#[educe(PartialEq = false, Debug = false)]
+	pub(crate) params_parse: Vec<ParamParse>,
 }
 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<BuiltinParam> {
+	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
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- 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<Vec<Option<Thunk<Val>>>> {
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- 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<Val> {
-					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)