git.delta.rocks / jrsonnet / refs/commits / 6bf55d21bf51

difftreelog

feat default params in function description

Yaroslav Bolyukin2024-06-18parent: #b5d51b9.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -10,7 +10,12 @@
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
-use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};
+use crate::{
+	function::{builtin::ParamDefault, CallLocation},
+	stdlib::format::FormatError,
+	typed::TypeLocError,
+	ObjValue,
+};
 
 pub(crate) fn format_found(list: &[IStr], what: &str) -> String {
 	if list.is_empty() {
@@ -43,7 +48,7 @@
 	if sig.is_empty() {
 		out.push_str("/*no arguments*/");
 	} else {
-		for (i, (name, has_default)) in sig.iter().enumerate() {
+		for (i, (name, default)) in sig.iter().enumerate() {
 			if i != 0 {
 				out.push_str(", ");
 			}
@@ -52,8 +57,13 @@
 			} else {
 				out.push_str("<unnamed>");
 			}
-			if *has_default {
-				out.push_str(" = <default>");
+			match default {
+				ParamDefault::None => {}
+				ParamDefault::Exists => out.push_str(" = <default>"),
+				ParamDefault::Literal(lit) => {
+					out.push_str(" = ");
+					out.push_str(lit);
+				}
 			}
 		}
 	}
@@ -88,7 +98,7 @@
 	heap.into_iter().map(|v| v.1).collect()
 }
 
-type FunctionSignature = Vec<(Option<IStr>, bool)>;
+type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;
 
 /// Possible errors
 #[allow(missing_docs)]
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -33,22 +33,40 @@
 	}
 }
 
+#[derive(Clone, Copy, Debug, Trace)]
+pub enum ParamDefault {
+	None,
+	Exists,
+	Literal(&'static str),
+}
+impl ParamDefault {
+	pub const fn exists(is_exists: bool) -> Self {
+		if is_exists {
+			Self::Exists
+		} else {
+			Self::None
+		}
+	}
+}
+
 #[derive(Clone, Trace)]
 pub struct BuiltinParam {
 	name: ParamName,
-	has_default: bool,
+	default: ParamDefault,
 }
 impl BuiltinParam {
-	pub const fn new(name: ParamName, has_default: bool) -> Self {
-		Self { name, has_default }
+	pub const fn new(name: ParamName, default: ParamDefault) -> Self {
+		Self { name, default }
 	}
 	/// Parameter name for named call parsing
 	pub fn name(&self) -> &ParamName {
 		&self.name
 	}
-	/// Is implementation allowed to return empty value
+	pub fn default(&self) -> ParamDefault {
+		self.default
+	}
 	pub fn has_default(&self) -> bool {
-		self.has_default
+		!matches!(self.default, ParamDefault::None)
 	}
 }
 
@@ -87,7 +105,7 @@
 				.into_iter()
 				.map(|n| BuiltinParam {
 					name: ParamName::new_dynamic(n),
-					has_default: false,
+					default: ParamDefault::Exists,
 				})
 				.collect(),
 			handler: tb!(handler),
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, BuiltinParam, ParamName, StaticBuiltin},
+	builtin::{Builtin, BuiltinParam, ParamDefault, ParamName, StaticBuiltin},
 	native::NativeDesc,
 	parse::{parse_default_function_call, parse_function_call},
 };
@@ -142,7 +142,7 @@
 							.as_ref()
 							.map(IStr::to_string)
 							.map_or(ParamName::ANONYMOUS, ParamName::new_dynamic),
-						p.1.is_some(),
+						ParamDefault::exists(p.1.is_some()),
 					)
 				})
 				.collect(),
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	bail,10	destructure::destruct,11	error::{ErrorKind::*, Result},12	evaluate_named,13	gc::GcHashMap,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		bail!(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			bail!(UnknownFunctionParameter((name as &str).to_owned()));75		}76		if passed_args.insert(name.clone(), value).is_some() {77			bail!(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					bail!(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		bail!(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			bail!(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					bail!(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}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -253,10 +253,14 @@
 			let name = name
 				.as_ref()
 				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
-			let is_optional = optionality.is_optional();
+			let default = match optionality {
+				Optionality::Required => quote!(ParamDefault::None),
+				Optionality::Optional => quote!(ParamDefault::Exists),
+				Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),
+			};
 			Some(quote! {
 				#(#cfg_attrs)*
-				BuiltinParam::new(#name, #is_optional),
+				BuiltinParam::new(#name, #default),
 			})
 		}
 		ArgInfo::Lazy { is_option, name } => {
@@ -264,7 +268,7 @@
 				.as_ref()
 				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
 			Some(quote! {
-				BuiltinParam::new(#name, #is_option),
+				BuiltinParam::new(#name, ParamDefault::exists(#is_option)),
 			})
 		}
 		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,
@@ -375,7 +379,7 @@
 		const _: () = {
 			use ::jrsonnet_evaluator::{
 				State, Val,
-				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},
+				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call},
 				Result, Context, typed::Typed,
 				parser::ExprLocation,
 			};