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

difftreelog

refactor fancier builtin param type

Yaroslav Bolyukin2023-08-06parent: #494be65.patch.diff
in: master

5 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -1,5 +1,4 @@
 use std::{
-	borrow::Cow,
 	ffi::{c_void, CStr},
 	os::raw::{c_char, c_int},
 };
@@ -82,7 +81,7 @@
 		let param = CStr::from_ptr(*raw_params)
 			.to_str()
 			.expect("param name is not utf-8");
-		params.push(Cow::Owned(param.into()));
+		params.push(param.into());
 		raw_params = raw_params.offset(1);
 	}
 
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -1,18 +1,56 @@
 use std::{any::Any, borrow::Cow};
 
 use jrsonnet_gcmodule::Trace;
+use jrsonnet_interner::IStr;
 
 use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
 use crate::{error::Result, gc::TraceBox, tb, Context, Val};
 
-pub type BuiltinParamName = Cow<'static, str>;
+/// 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<Cow<'static, str>>);
+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 as_str(&self) -> Option<&str> {
+		self.0.as_deref()
+	}
+	pub fn is_anonymous(&self) -> bool {
+		self.0.is_none()
+	}
+}
+impl PartialEq<IStr> for ParamName {
+	fn eq(&self, other: &IStr) -> bool {
+		match &self.0 {
+			Some(s) => s.as_bytes() == other.as_bytes(),
+			None => false,
+		}
+	}
+}
 
 #[derive(Clone, Trace)]
 pub struct BuiltinParam {
+	name: ParamName,
+	has_default: bool,
+}
+impl BuiltinParam {
+	pub const fn new(name: ParamName, has_default: bool) -> Self {
+		Self { name, has_default }
+	}
 	/// Parameter name for named call parsing
-	pub name: Option<BuiltinParamName>,
+	pub fn name(&self) -> &ParamName {
+		&self.name
+	}
 	/// Is implementation allowed to return empty value
-	pub has_default: bool,
+	pub fn has_default(&self) -> bool {
+		self.has_default
+	}
 }
 
 /// Description of function defined by native code
@@ -44,12 +82,12 @@
 }
 impl NativeCallback {
 	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
-	pub fn new(params: Vec<Cow<'static, str>>, handler: impl NativeCallbackHandler) -> Self {
+	pub fn new(params: Vec<String>, handler: impl NativeCallbackHandler) -> Self {
 		Self {
 			params: params
 				.into_iter()
 				.map(|n| BuiltinParam {
-					name: Some(n),
+					name: ParamName::new_dynamic(n.to_string()),
 					has_default: false,
 				})
 				.collect(),
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -8,7 +8,7 @@
 
 use self::{
 	arglike::OptionalContext,
-	builtin::{Builtin, StaticBuiltin},
+	builtin::{Builtin, BuiltinParam, ParamName, StaticBuiltin},
 	native::NativeDesc,
 	parse::{parse_default_function_call, parse_function_call},
 };
@@ -113,17 +113,45 @@
 	}
 }
 
+#[allow(clippy::unnecessary_wraps)]
+#[builtin]
+const fn builtin_id(x: Val) -> Val {
+	x
+}
+static ID: &builtin_id = &builtin_id {};
+
 impl FuncVal {
 	pub fn builtin(builtin: impl Builtin) -> Self {
 		Self::Builtin(Cc::new(tb!(builtin)))
 	}
+
+	pub fn params(&self) -> Vec<BuiltinParam> {
+		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),
+						p.1.is_some(),
+					)
+				})
+				.collect(),
+		}
+	}
 	/// Amount of non-default required arguments
 	pub fn params_len(&self) -> usize {
 		match self {
 			Self::Id => 1,
 			Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),
-			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),
-			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
+			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default()).count(),
+			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default()).count(),
 		}
 	}
 	/// Function name, as defined in code.
@@ -146,16 +174,7 @@
 		tailstrict: bool,
 	) -> Result<Val> {
 		match self {
-			Self::Id => {
-				#[allow(clippy::unnecessary_wraps)]
-				#[builtin]
-				const fn builtin_id(x: Val) -> Val {
-					x
-				}
-				static ID: &builtin_id = &builtin_id {};
-
-				ID.call(call_ctx, loc, args)
-			}
+			Self::Id => ID.call(call_ctx, loc, args),
 			Self::Normal(func) => {
 				let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;
 				evaluate(body_ctx, &func.body)
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/parse.rs
1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9	destructure::destruct,10	error::{ErrorKind::*, Result},11	evaluate_named,12	gc::GcHashMap,13	throw,14	val::ThunkValue,15	Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20	ctx: Pending<Context>,21	name: IStr,22	value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26	type Output = Val;27	fn get(self: Box<Self>) -> Result<Val> {28		evaluate_named(self.ctx.unwrap(), &self.value, self.name)29	}30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41	ctx: Context,42	body_ctx: Context,43	params: &ParamsDesc,44	args: &dyn ArgsLike,45	tailstrict: bool,46) -> Result<Context> {47	let mut passed_args =48		GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());49	if args.unnamed_len() > params.len() {50		throw!(TooManyArgsFunctionHas(51			params.len(),52			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()53		))54	}5556	let mut filled_named = 0;57	let mut filled_positionals = 0;5859	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {60		let name = params[id].0.clone();61		destruct(62			&name,63			arg,64			Pending::new_filled(ctx.clone()),65			&mut passed_args,66		)?;67		filled_positionals += 1;68		Ok(())69	})?;7071	args.named_iter(ctx, tailstrict, &mut |name, value| {72		// FIXME: O(n) for arg existence check73		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {74			throw!(UnknownFunctionParameter((name as &str).to_owned()));75		}76		if passed_args.insert(name.clone(), value).is_some() {77			throw!(BindingParameterASecondTime(name.clone()));78		}79		filled_named += 1;80		Ok(())81	})?;8283	if filled_named + filled_positionals < params.len() {84		// Some args are unset, but maybe we have defaults for them85		// Default values should be created in newly created context86		let fctx = Context::new_future();87		let mut defaults = GcHashMap::with_capacity(88			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()89				- filled_named - filled_positionals,90		);9192		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {93			if let Some(name) = param.0.name() {94				if passed_args.contains_key(&name) {95					continue;96				}97			} else if idx < filled_positionals {98				continue;99			}100101			destruct(102				&param.0,103				Thunk::new(EvaluateNamedThunk {104					ctx: fctx.clone(),105					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),106					value: param.1.clone().expect("default exists"),107				}),108				fctx.clone(),109				&mut defaults,110			)?;111			if param.0.name().is_some() {112				filled_named += 1;113			} else {114				filled_positionals += 1;115			}116		}117118		// Some args still weren't filled119		if filled_named + filled_positionals != params.len() {120			for param in params.iter().skip(args.unnamed_len()) {121				let mut found = false;122				args.named_names(&mut |name| {123					if Some(name) == param.0.name().as_ref() {124						found = true;125					}126				});127				if !found {128					throw!(FunctionParameterNotBoundInCall(129						param.0.clone().name(),130						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()131					));132				}133			}134			unreachable!();135		}136137		Ok(body_ctx138			.extend(passed_args, None, None, None)139			.extend(defaults, None, None, None)140			.into_future(fctx))141	} else {142		let body_ctx = body_ctx.extend(passed_args, None, None, None);143		Ok(body_ctx)144	}145}146147/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead148///149/// ## Parameters150/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)151/// * `params`: function parameters' definition152/// * `args`: passed function arguments153/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily154pub fn parse_builtin_call(155	ctx: Context,156	params: &[BuiltinParam],157	args: &dyn ArgsLike,158	tailstrict: bool,159) -> Result<Vec<Option<Thunk<Val>>>> {160	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];161	if args.unnamed_len() > params.len() {162		throw!(TooManyArgsFunctionHas(163			params.len(),164			params165				.iter()166				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))167				.collect()168		))169	}170171	let mut filled_args = 0;172173	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {174		passed_args[id] = Some(arg);175		filled_args += 1;176		Ok(())177	})?;178179	args.named_iter(ctx, tailstrict, &mut |name, arg| {180		// FIXME: O(n) for arg existence check181		let id = params182			.iter()183			.position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))184			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;185		if replace(&mut passed_args[id], Some(arg)).is_some() {186			throw!(BindingParameterASecondTime(name.clone()));187		}188		filled_args += 1;189		Ok(())190	})?;191192	if filled_args < params.len() {193		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {194			if passed_args[id].is_some() {195				continue;196			}197			filled_args += 1;198		}199200		// Some args still wasn't filled201		if filled_args != params.len() {202			for param in params.iter().skip(args.unnamed_len()) {203				let mut found = false;204				args.named_names(&mut |name| {205					if param206						.name207						.as_ref()208						.map_or(false, |v| v as &str == name as &str)209					{210						found = true;211					}212				});213				if !found {214					throw!(FunctionParameterNotBoundInCall(215						param.name.as_ref().map(|v| v.as_ref().into()),216						params217							.iter()218							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))219							.collect()220					));221				}222			}223			unreachable!();224		}225	}226	Ok(passed_args)227}228229/// Creates Context, which has all argument default values applied230/// and with unbound values causing error to be returned231pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {232	#[derive(Trace)]233	struct DependsOnUnbound(IStr, ParamsDesc);234	impl ThunkValue for DependsOnUnbound {235		type Output = Val;236		fn get(self: Box<Self>) -> Result<Val> {237			Err(FunctionParameterNotBoundInCall(238				Some(self.0.clone()),239				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),240			)241			.into())242		}243	}244245	let fctx = Context::new_future();246247	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());248249	for param in params.iter() {250		if let Some(v) = &param.1 {251			destruct(252				&param.0.clone(),253				Thunk::new(EvaluateNamedThunk {254					ctx: fctx.clone(),255					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),256					value: v.clone(),257				}),258				fctx.clone(),259				&mut bindings,260			)?;261		} else {262			destruct(263				&param.0,264				Thunk::new(DependsOnUnbound(265					param.0.name().unwrap_or_else(|| "<destruct>".into()),266					params.clone(),267				)),268				fctx.clone(),269				&mut bindings,270			)?;271		}272	}273274	Ok(body_ctx275		.extend(bindings, None, None, None)276		.into_future(fctx))277}
after · crates/jrsonnet-evaluator/src/function/parse.rs
1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9	destructure::destruct,10	error::{ErrorKind::*, Result},11	evaluate_named,12	gc::GcHashMap,13	throw,14	val::ThunkValue,15	Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20	ctx: Pending<Context>,21	name: IStr,22	value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26	type Output = Val;27	fn get(self: Box<Self>) -> Result<Val> {28		evaluate_named(self.ctx.unwrap(), &self.value, self.name)29	}30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41	ctx: Context,42	body_ctx: Context,43	params: &ParamsDesc,44	args: &dyn ArgsLike,45	tailstrict: bool,46) -> Result<Context> {47	let mut passed_args =48		GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());49	if args.unnamed_len() > params.len() {50		throw!(TooManyArgsFunctionHas(51			params.len(),52			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()53		))54	}5556	let mut filled_named = 0;57	let mut filled_positionals = 0;5859	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {60		let name = params[id].0.clone();61		destruct(62			&name,63			arg,64			Pending::new_filled(ctx.clone()),65			&mut passed_args,66		)?;67		filled_positionals += 1;68		Ok(())69	})?;7071	args.named_iter(ctx, tailstrict, &mut |name, value| {72		// FIXME: O(n) for arg existence check73		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {74			throw!(UnknownFunctionParameter((name as &str).to_owned()));75		}76		if passed_args.insert(name.clone(), value).is_some() {77			throw!(BindingParameterASecondTime(name.clone()));78		}79		filled_named += 1;80		Ok(())81	})?;8283	if filled_named + filled_positionals < params.len() {84		// Some args are unset, but maybe we have defaults for them85		// Default values should be created in newly created context86		let fctx = Context::new_future();87		let mut defaults = GcHashMap::with_capacity(88			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()89				- filled_named - filled_positionals,90		);9192		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {93			if let Some(name) = param.0.name() {94				if passed_args.contains_key(&name) {95					continue;96				}97			} else if idx < filled_positionals {98				continue;99			}100101			destruct(102				&param.0,103				Thunk::new(EvaluateNamedThunk {104					ctx: fctx.clone(),105					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),106					value: param.1.clone().expect("default exists"),107				}),108				fctx.clone(),109				&mut defaults,110			)?;111			if param.0.name().is_some() {112				filled_named += 1;113			} else {114				filled_positionals += 1;115			}116		}117118		// Some args still weren't filled119		if filled_named + filled_positionals != params.len() {120			for param in params.iter().skip(args.unnamed_len()) {121				let mut found = false;122				args.named_names(&mut |name| {123					if Some(name) == param.0.name().as_ref() {124						found = true;125					}126				});127				if !found {128					throw!(FunctionParameterNotBoundInCall(129						param.0.clone().name(),130						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()131					));132				}133			}134			unreachable!();135		}136137		Ok(body_ctx138			.extend(passed_args, None, None, None)139			.extend(defaults, None, None, None)140			.into_future(fctx))141	} else {142		let body_ctx = body_ctx.extend(passed_args, None, None, None);143		Ok(body_ctx)144	}145}146147/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead148///149/// ## Parameters150/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)151/// * `params`: function parameters' definition152/// * `args`: passed function arguments153/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily154pub fn parse_builtin_call(155	ctx: Context,156	params: &[BuiltinParam],157	args: &dyn ArgsLike,158	tailstrict: bool,159) -> Result<Vec<Option<Thunk<Val>>>> {160	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];161	if args.unnamed_len() > params.len() {162		throw!(TooManyArgsFunctionHas(163			params.len(),164			params165				.iter()166				.map(|p| (p.name().as_str().map(IStr::from), p.has_default()))167				.collect()168		))169	}170171	let mut filled_args = 0;172173	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {174		passed_args[id] = Some(arg);175		filled_args += 1;176		Ok(())177	})?;178179	args.named_iter(ctx, tailstrict, &mut |name, arg| {180		// FIXME: O(n) for arg existence check181		let id = params182			.iter()183			.position(|p| p.name() == name)184			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;185		if replace(&mut passed_args[id], Some(arg)).is_some() {186			throw!(BindingParameterASecondTime(name.clone()));187		}188		filled_args += 1;189		Ok(())190	})?;191192	if filled_args < params.len() {193		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default()) {194			if passed_args[id].is_some() {195				continue;196			}197			filled_args += 1;198		}199200		// Some args still wasn't filled201		if filled_args != params.len() {202			for param in params.iter().skip(args.unnamed_len()) {203				let mut found = false;204				args.named_names(&mut |name| {205					if param.name() == name {206						found = true;207					}208				});209				if !found {210					throw!(FunctionParameterNotBoundInCall(211						param.name().as_str().map(IStr::from),212						params213							.iter()214							.map(|p| (p.name().as_str().map(IStr::from), p.has_default()))215							.collect()216					));217				}218			}219			unreachable!();220		}221	}222	Ok(passed_args)223}224225/// Creates Context, which has all argument default values applied226/// and with unbound values causing error to be returned227pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {228	#[derive(Trace)]229	struct DependsOnUnbound(IStr, ParamsDesc);230	impl ThunkValue for DependsOnUnbound {231		type Output = Val;232		fn get(self: Box<Self>) -> Result<Val> {233			Err(FunctionParameterNotBoundInCall(234				Some(self.0.clone()),235				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),236			)237			.into())238		}239	}240241	let fctx = Context::new_future();242243	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());244245	for param in params.iter() {246		if let Some(v) = &param.1 {247			destruct(248				&param.0.clone(),249				Thunk::new(EvaluateNamedThunk {250					ctx: fctx.clone(),251					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),252					value: v.clone(),253				}),254				fctx.clone(),255				&mut bindings,256			)?;257		} else {258			destruct(259				&param.0,260				Thunk::new(DependsOnUnbound(261					param.0.name().unwrap_or_else(|| "<destruct>".into()),262					params.clone(),263				)),264				fctx.clone(),265				&mut bindings,266			)?;267		}268	}269270	Ok(body_ctx271		.extend(bindings, None, None, None)272		.into_future(fctx))273}
modifiedtests/tests/common.rsdiffbeforeafterboth
--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -1,5 +1,3 @@
-use std::borrow::Cow;
-
 use jrsonnet_evaluator::{
 	error::Result,
 	function::{builtin, FuncVal},
@@ -66,22 +64,12 @@
 		FuncVal::StaticBuiltin(b) => b
 			.params()
 			.iter()
-			.map(|p| {
-				p.name
-					.as_ref()
-					.unwrap_or(&Cow::Borrowed("<unnamed>"))
-					.to_string()
-			})
+			.map(|p| p.name().as_str().unwrap_or(&"<unnamed>").to_string())
 			.collect(),
 		FuncVal::Builtin(b) => b
 			.params()
 			.iter()
-			.map(|p| {
-				p.name
-					.as_ref()
-					.unwrap_or(&Cow::Borrowed("<unnamed>"))
-					.to_string()
-			})
+			.map(|p| p.name().as_str().unwrap_or(&"<unnamed>").to_string())
 			.collect(),
 	}
 }