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

difftreelog

feat allow unnamed builtin arguments

Yaroslav Bolyukin2022-10-11parent: #1178a0b.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,11 +1,12 @@
 use std::{
+	borrow::Cow,
 	ffi::{c_void, CStr},
 	os::raw::{c_char, c_int},
 };
 
 use jrsonnet_evaluator::{
 	error::{Error, LocError},
-	function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
+	function::builtin::{NativeCallback, NativeCallbackHandler},
 	tb,
 	typed::Typed,
 	IStr, State, Val,
@@ -87,10 +88,7 @@
 		let param = CStr::from_ptr(*raw_params)
 			.to_str()
 			.expect("param name is not utf-8");
-		params.push(BuiltinParam {
-			name: param.into(),
-			has_default: false,
-		});
+		params.push(Cow::Owned(param.into()));
 		raw_params = raw_params.offset(1);
 	}
 
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/error.rs
1use std::{fmt::Debug, path::PathBuf};23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, SourcePath, UnaryOpType};6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{stdlib::format::FormatError, typed::TypeLocError};1011fn format_found(list: &[IStr], what: &str) -> String {12	if list.is_empty() {13		return String::new();14	}15	let mut out = String::new();16	out.push_str("\nThere is ");17	out.push_str(what);18	if list.len() > 1 {19		out.push('s');20	}21	out.push_str(" with similar name");22	if list.len() > 1 {23		out.push('s');24	}25	out.push_str(" present: ");26	for (i, v) in list.iter().enumerate() {27		if i != 0 {28			out.push_str(", ");29		}30		out.push_str(v as &str);31	}32	out33}3435fn format_signature(sig: &FunctionSignature) -> String {36	let mut out = String::new();37	out.push_str("\nFunction has the following signature: ");38	out.push('(');39	if sig.is_empty() {40		out.push_str("/*no arguments*/");41	} else {42		for (i, (name, has_default)) in sig.iter().enumerate() {43			if i != 0 {44				out.push_str(", ");45			}46			if let Some(name) = name {47				out.push_str(name);48			} else {49				out.push_str("<unnamed>");50			}51			if *has_default {52				out.push_str(" = <default>");53			}54		}55	}56	out.push(')');57	out58}5960const fn format_empty_str(str: &str) -> &str {61	if str.is_empty() {62		"\"\" (empty string)"63	} else {64		str65	}66}6768type FunctionSignature = Vec<(Option<IStr>, bool)>;6970#[derive(Error, Debug, Clone, Trace)]71pub enum Error {72	#[error("intrinsic not found: {0}")]73	IntrinsicNotFound(IStr),7475	#[error("operator {0} does not operate on type {1}")]76	UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),77	#[error("binary operation {1} {0} {2} is not implemented")]78	BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),7980	#[error("no top level object in this context")]81	NoTopLevelObjectFound,82	#[error("self is only usable inside objects")]83	CantUseSelfOutsideOfObject,84	#[error("no super found")]85	NoSuperFound,8687	#[error("for loop can only iterate over arrays")]88	InComprehensionCanOnlyIterateOverArray,8990	#[error("array out of bounds: {0} is not within [0,{1})")]91	ArrayBoundsError(usize, usize),92	#[error("string out of bounds: {0} is not within [0,{1})")]93	StringBoundsError(usize, usize),9495	#[error("assert failed: {}", format_empty_str(.0))]96	AssertionFailed(IStr),9798	#[error("variable is not defined: {0}{}", format_found(.1, "variable"))]99	VariableIsNotDefined(IStr, Vec<IStr>),100	#[error("duplicate local var: {0}")]101	DuplicateLocalVar(IStr),102103	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join(", "))]104	TypeMismatch(&'static str, Vec<ValType>, ValType),105	#[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]106	NoSuchField(IStr, Vec<IStr>),107108	#[error("only functions can be called, got {0}")]109	OnlyFunctionsCanBeCalledGot(ValType),110	#[error("parameter {0} is not defined")]111	UnknownFunctionParameter(String),112	#[error("argument {0} is already bound")]113	BindingParameterASecondTime(IStr),114	#[error("too many args, function has {0}{}", format_signature(.1))]115	TooManyArgsFunctionHas(usize, FunctionSignature),116	#[error("function argument is not passed: {0}{}", format_signature(.1))]117	FunctionParameterNotBoundInCall(IStr, FunctionSignature),118119	#[error("external variable is not defined: {0}")]120	UndefinedExternalVariable(IStr),121122	#[error("field name should be string, got {0}")]123	FieldMustBeStringGot(ValType),124	#[error("duplicate field name: {}", format_empty_str(.0))]125	DuplicateFieldName(IStr),126127	#[error("attempted to index array with string {}", format_empty_str(.0))]128	AttemptedIndexAnArrayWithString(IStr),129	#[error("{0} index type should be {1}, got {2}")]130	ValueIndexMustBeTypeGot(ValType, ValType, ValType),131	#[error("cant index into {0}")]132	CantIndexInto(ValType),133	#[error("{0} is not indexable")]134	ValueIsNotIndexable(ValType),135136	#[error("super can't be used standalone")]137	StandaloneSuper,138139	#[error("can't resolve {1} from {0}")]140	ImportFileNotFound(SourcePath, String),141	#[error("can't resolve absolute {0}")]142	AbsoluteImportFileNotFound(PathBuf),143	#[error("resolved file not found: {:?}", .0)]144	ResolvedFileNotFound(SourcePath),145	#[error("can't import {0}: is a directory")]146	ImportIsADirectory(SourcePath),147	#[error("imported file is not valid utf-8: {0:?}")]148	ImportBadFileUtf8(SourcePath),149	#[error("import io error: {0}")]150	ImportIo(String),151	#[error("tried to import {1} from {0}, but imports are not supported")]152	ImportNotSupported(SourcePath, String),153	#[error("tried to import {0}, but absolute imports are not supported")]154	AbsoluteImportNotSupported(PathBuf),155	#[error("can't import from virtual file")]156	CantImportFromVirtualFile,157	#[error(158		"syntax error: expected {}, got {:?}",159		.error.expected,160		.path.code().chars().nth(error.location.offset)161		.map_or_else(|| "EOF".into(), |c| c.to_string())162	)]163	ImportSyntaxError {164		path: Source,165		#[trace(skip)]166		error: Box<jrsonnet_parser::ParseError>,167	},168169	#[error("runtime error: {}", format_empty_str(.0))]170	RuntimeError(IStr),171	#[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]172	StackOverflow,173	#[error("infinite recursion detected")]174	InfiniteRecursionDetected,175	#[error("tried to index by fractional value")]176	FractionalIndex,177	#[error("attempted to divide by zero")]178	DivisionByZero,179180	#[error("string manifest output is not an string")]181	StringManifestOutputIsNotAString,182	#[error("stream manifest output is not an array")]183	StreamManifestOutputIsNotAArray,184	#[error("multi manifest output is not an object")]185	MultiManifestOutputIsNotAObject,186187	#[error("cant recurse stream manifest")]188	StreamManifestOutputCannotBeRecursed,189	#[error("stream manifest output cannot consist of raw strings")]190	StreamManifestCannotNestString,191192	#[error("{}", format_empty_str(.0))]193	ImportCallbackError(String),194	#[error("invalid unicode codepoint: {0}")]195	InvalidUnicodeCodepointGot(u32),196197	#[error("format error: {0}")]198	Format(#[from] FormatError),199	#[error("type error: {0}")]200	TypeError(TypeLocError),201202	#[cfg(feature = "anyhow-error")]203	#[error(transparent)]204	Other(Rc<anyhow::Error>),205}206207#[cfg(feature = "anyhow-error")]208impl From<anyhow::Error> for LocError {209	fn from(e: anyhow::Error) -> Self {210		Self::new(Error::Other(Rc::new(e)))211	}212}213214impl From<Error> for LocError {215	fn from(e: Error) -> Self {216		Self::new(e)217	}218}219220#[derive(Clone, Debug, Trace)]221pub struct StackTraceElement {222	pub location: Option<ExprLocation>,223	pub desc: String,224}225#[derive(Debug, Clone, Trace)]226pub struct StackTrace(pub Vec<StackTraceElement>);227228#[derive(Clone, Trace)]229pub struct LocError(Box<(Error, StackTrace)>);230impl LocError {231	pub fn new(e: Error) -> Self {232		Self(Box::new((e, StackTrace(vec![]))))233	}234235	pub const fn error(&self) -> &Error {236		&(self.0).0237	}238	pub fn error_mut(&mut self) -> &mut Error {239		&mut (self.0).0240	}241	pub const fn trace(&self) -> &StackTrace {242		&(self.0).1243	}244	pub fn trace_mut(&mut self) -> &mut StackTrace {245		&mut (self.0).1246	}247}248impl Debug for LocError {249	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {250		writeln!(f, "{}", self.0 .0)?;251		for el in &self.0 .1 .0 {252			writeln!(f, "\t{:?}", el)?;253		}254		Ok(())255	}256}257258pub type Result<V, E = LocError> = std::result::Result<V, E>;259260#[macro_export]261macro_rules! throw {262	($e: expr) => {263		return Err($e.into())264	};265}266267#[macro_export]268macro_rules! throw_runtime {269	($($tt:tt)*) => {270		return Err($crate::error::Error::RuntimeError(format!($($tt)*).into()).into())271	};272}
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -9,7 +9,9 @@
 
 #[derive(Clone, Trace)]
 pub struct BuiltinParam {
-	pub name: BuiltinParamName,
+	/// Parameter name for named call parsing
+	pub name: Option<BuiltinParamName>,
+	/// Is implementation allowed to return empty value
 	pub has_default: bool,
 }
 
@@ -40,8 +42,20 @@
 }
 impl NativeCallback {
 	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
-	pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
-		Self { params, handler }
+	pub fn new(
+		params: Vec<Cow<'static, str>>,
+		handler: TraceBox<dyn NativeCallbackHandler>,
+	) -> Self {
+		Self {
+			params: params
+				.into_iter()
+				.map(|n| BuiltinParam {
+					name: Some(n),
+					has_default: false,
+				})
+				.collect(),
+			handler,
+		}
 	}
 }
 
@@ -58,11 +72,12 @@
 
 	fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
 		let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
-		let mut out_args = Vec::with_capacity(self.params.len());
-		for p in &self.params {
-			out_args.push(args[&p.name].evaluate(s.clone())?);
-		}
-		self.handler.call(s, &out_args)
+		let args = args
+			.into_iter()
+			.map(|a| a.expect("legacy natives have no default params"))
+			.map(|a| a.evaluate(s.clone()))
+			.collect::<Result<Vec<Val>>>()?;
+		self.handler.call(s, &args)
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,11 +1,10 @@
+use std::mem::replace;
+
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{LocExpr, ParamsDesc};
 
-use super::{
-	arglike::ArgsLike,
-	builtin::{BuiltinParam, BuiltinParamName},
-};
+use super::{arglike::ArgsLike, builtin::BuiltinParam};
 use crate::{
 	destructure::destruct,
 	error::{Error::*, Result},
@@ -125,11 +124,7 @@
 				});
 				if !found {
 					throw!(FunctionParameterNotBoundInCall(
-						param
-							.0
-							.clone()
-							.name()
-							.unwrap_or_else(|| "<destruct>".into()),
+						param.0.clone().name(),
 						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
 					));
 				}
@@ -160,14 +155,14 @@
 	params: &[BuiltinParam],
 	args: &dyn ArgsLike,
 	tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {
-	let mut passed_args = GcHashMap::with_capacity(params.len());
+) -> Result<Vec<Option<Thunk<Val>>>> {
+	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];
 	if args.unnamed_len() > params.len() {
 		throw!(TooManyArgsFunctionHas(
 			params.len(),
 			params
 				.iter()
-				.map(|p| (Some(p.name.as_ref().into()), p.has_default))
+				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))
 				.collect()
 		))
 	}
@@ -175,19 +170,23 @@
 	let mut filled_args = 0;
 
 	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
-		let name = params[id].name.clone();
-		passed_args.insert(name, arg);
+		passed_args[id] = Some(arg);
 		filled_args += 1;
 		Ok(())
 	})?;
 
 	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
 		// FIXME: O(n) for arg existence check
-		let p = params
+		let id = params
 			.iter()
-			.find(|p| p.name == name as &str)
+			.position(|p| {
+				p.name
+					.as_ref()
+					.map(|v| &v as &str == name as &str)
+					.unwrap_or(false)
+			})
 			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
-		if passed_args.insert(p.name.clone(), arg).is_some() {
+		if replace(&mut passed_args[id], Some(arg)).is_some() {
 			throw!(BindingParameterASecondTime(name.clone()));
 		}
 		filled_args += 1;
@@ -195,8 +194,8 @@
 	})?;
 
 	if filled_args < params.len() {
-		for param in params.iter().filter(|p| p.has_default) {
-			if passed_args.contains_key(&param.name) {
+		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {
+			if passed_args[id].is_some() {
 				continue;
 			}
 			filled_args += 1;
@@ -207,16 +206,21 @@
 			for param in params.iter().skip(args.unnamed_len()) {
 				let mut found = false;
 				args.named_names(&mut |name| {
-					if name as &str == &param.name as &str {
+					if param
+						.name
+						.as_ref()
+						.map(|v| &v as &str == name as &str)
+						.unwrap_or(false)
+					{
 						found = true;
 					}
 				});
 				if !found {
 					throw!(FunctionParameterNotBoundInCall(
-						param.name.clone().into(),
+						param.name.as_ref().map(|v| v.as_ref().into()),
 						params
 							.iter()
-							.map(|p| (Some(p.name.as_ref().into()), p.has_default))
+							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))
 							.collect()
 					));
 				}
@@ -236,7 +240,7 @@
 		type Output = Val;
 		fn get(self: Box<Self>, _: State) -> Result<Val> {
 			Err(FunctionParameterNotBoundInCall(
-				self.0.clone(),
+				Some(self.0.clone()),
 				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),
 			)
 			.into())
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -122,13 +122,13 @@
 	Normal {
 		ty: Box<Type>,
 		is_option: bool,
-		name: String,
+		name: Option<String>,
 		cfg_attrs: Vec<Attribute>,
 		// ident: Ident,
 	},
 	Lazy {
 		is_option: bool,
-		name: String,
+		name: Option<String>,
 	},
 	State,
 	Location,
@@ -142,8 +142,8 @@
 			FnArg::Typed(a) => a,
 		};
 		let ident = match &arg.pat as &Pat {
-			Pat::Ident(i) => i.ident.clone(),
-			_ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),
+			Pat::Ident(i) => Some(i.ident.clone()),
+			_ => None,
 		};
 		let ty = &arg.ty;
 		if type_is_path(ty, "State").is_some() {
@@ -153,7 +153,7 @@
 		} else if type_is_path(ty, "Thunk").is_some() {
 			return Ok(Self::Lazy {
 				is_option: false,
-				name: ident.to_string(),
+				name: ident.map(|v| v.to_string()),
 			});
 		}
 
@@ -166,7 +166,7 @@
 			if type_is_path(ty, "Thunk").is_some() {
 				return Ok(Self::Lazy {
 					is_option: true,
-					name: ident.to_string(),
+					name: ident.map(|v| v.to_string()),
 				});
 			}
 
@@ -185,7 +185,7 @@
 		Ok(Self::Normal {
 			ty,
 			is_option,
-			name: ident.to_string(),
+			name: ident.map(|v| v.to_string()),
 			cfg_attrs,
 		})
 	}
@@ -248,69 +248,95 @@
 			name,
 			cfg_attrs,
 			..
-		} => Some(quote! {
-			#(#cfg_attrs)*
-			BuiltinParam {
-				name: std::borrow::Cow::Borrowed(#name),
-				has_default: #is_option,
-			},
-		}),
-		ArgInfo::Lazy { is_option, name } => Some(quote! {
-			BuiltinParam {
-				name: std::borrow::Cow::Borrowed(#name),
-				has_default: #is_option,
-			},
-		}),
+		} => {
+			let name = name
+				.as_ref()
+				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+				.unwrap_or_else(|| quote! {None});
+			Some(quote! {
+				#(#cfg_attrs)*
+				BuiltinParam {
+					name: #name,
+					has_default: #is_option,
+				},
+			})
+		}
+		ArgInfo::Lazy { is_option, name } => {
+			let name = name
+				.as_ref()
+				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+				.unwrap_or_else(|| quote! {None});
+			Some(quote! {
+				BuiltinParam {
+					name: #name,
+					has_default: #is_option,
+				},
+			})
+		}
 		ArgInfo::State => None,
 		ArgInfo::Location => None,
 		ArgInfo::This => None,
 	});
 
-	let pass = args.iter().map(|a| match a {
-		ArgInfo::Normal {
-			ty,
-			is_option,
-			name,
-			cfg_attrs,
-		} => {
-			let eval = quote! {s.push_description(
-				|| format!("argument <{}> evaluation", #name),
-				|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
-			)?};
-			let value = if *is_option {
-				quote! {if let Some(value) = parsed.get(#name) {
-					Some(#eval)
+	let mut id = 0usize;
+	let pass = args
+		.iter()
+		.map(|a| match a {
+			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {
+				let cid = id;
+				id += 1;
+				(quote! {#cid}, a)
+			}
+			ArgInfo::State | ArgInfo::Location | ArgInfo::This => {
+				(quote! {compile_error!("should not use id")}, a)
+			}
+		})
+		.map(|(id, a)| match a {
+			ArgInfo::Normal {
+				ty,
+				is_option,
+				name,
+				cfg_attrs,
+			} => {
+				let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");
+				let eval = quote! {s.push_description(
+					|| format!("argument <{}> evaluation", #name),
+					|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
+				)?};
+				let value = if *is_option {
+					quote! {if let Some(value) = &parsed[#id] {
+						Some(#eval)
+					} else {
+						None
+					},}
 				} else {
-					None
-				},}
-			} else {
-				quote! {{
-					let value = parsed.get(#name).expect("args shape is checked");
-					#eval
-				},}
-			};
-			quote! {
-				#(#cfg_attrs)*
-				#value
+					quote! {{
+						let value = parsed[#id].as_ref().expect("args shape is checked");
+						#eval
+					},}
+				};
+				quote! {
+					#(#cfg_attrs)*
+					#value
+				}
 			}
-		}
-		ArgInfo::Lazy { is_option, name } => {
-			if *is_option {
-				quote! {if let Some(value) = parsed.get(#name) {
-					Some(value.clone())
+			ArgInfo::Lazy { is_option, .. } => {
+				if *is_option {
+					quote! {if let Some(value) = &parsed[#id] {
+						Some(value.clone())
+					} else {
+						None
+					}}
 				} else {
-					None
-				}}
-			} else {
-				quote! {
-					parsed.get(#name).expect("args shape is correct").clone(),
+					quote! {
+						parsed[#id].as_ref().expect("args shape is correct").clone(),
+					}
 				}
 			}
-		}
-		ArgInfo::State => quote! {s.clone(),},
-		ArgInfo::Location => quote! {location,},
-		ArgInfo::This => quote! {self,},
-	});
+			ArgInfo::State => quote! {s.clone(),},
+			ArgInfo::Location => quote! {location,},
+			ArgInfo::This => quote! {self,},
+		});
 
 	let fields = attr.fields.iter().map(|field| {
 		let name = &field.name;