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

difftreelog

perf unify function calls

Lach2020-08-23parent: #bc5c3a5.patch.diff
in: master

8 files changed

modifiedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -39,7 +39,7 @@
 								if **name == *"join" || **name == *"manifestJsonEx" ||
 								**name == *"escapeStringJson" || **name == *"equals" ||
 								**name == *"base64" || **name == *"foldl" || **name == *"foldr" ||
-								**name == *"sortImpl" || **name == *"format" || **name == *"range"
+								**name == *"sortImpl" || **name == *"format" || **name == *"range" || **name == *"reverse"
 							)
 						})
 						.collect(),
modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -117,9 +117,7 @@
 			}
 			buf.push('}');
 		}
-		Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
-			throw!(RuntimeError("tried to manifest function".into()))
-		}
+		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
 		Val::Lazy(_) => unreachable!(),
 	};
 	Ok(())
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,5 +1,354 @@
+use crate::{
+	equals,
+	error::{Error::*, Result},
+	evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val,
+	ValType,
+};
+use format::{format_arr, format_obj};
+use jrsonnet_parser::{ArgsDesc, ExprLocation};
+use manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+use std::{path::PathBuf, rc::Rc};
+
 pub mod stdlib;
 pub use stdlib::*;
 
 pub mod format;
 pub mod manifest;
+pub mod sort;
+
+pub fn call_builtin(
+	context: Context,
+	loc: &Option<ExprLocation>,
+	ns: &str,
+	name: &str,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	Ok(match (ns, &name as &str) {
+		// arr/string/function
+		("std", "length") => parse_args!(context, "std.length", args, 1, [
+			0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];
+		], {
+			Ok(match x {
+				Val::Str(n) => Val::Num(n.chars().count() as f64),
+				Val::Arr(i) => Val::Num(i.len() as f64),
+				Val::Obj(o) => Val::Num(
+					o.fields_visibility()
+						.into_iter()
+						.filter(|(_k, v)| *v)
+						.count() as f64,
+				),
+				_ => unreachable!(),
+			})
+		})?,
+		// any
+		("std", "type") => parse_args!(context, "std.type", args, 1, [
+			0, x, vec![];
+		], {
+			Ok(Val::Str(x.value_type()?.name().into()))
+		})?,
+		// length, idx=>any
+		("std", "makeArray") => parse_args!(context, "std.makeArray", args, 2, [
+			0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];
+			1, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+		], {
+			if sz < 0.0 {
+				throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));
+			}
+			let mut out = Vec::with_capacity(sz as usize);
+			for i in 0..sz as usize {
+				out.push(func.evaluate_values(
+					Context::new(),
+					&[Val::Num(i as f64)]
+				)?)
+			}
+			Ok(Val::Arr(Rc::new(out)))
+		})?,
+		// string
+		("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [
+			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			assert!(
+				str.chars().count() == 1,
+				"std.codepoint should receive single char string"
+			);
+			Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))
+		})?,
+		// object, includeHidden
+		("std", "objectFieldsEx") => parse_args!(context, "std.objectFieldsEx",args, 2, [
+			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
+			1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
+		], {
+			let mut out = obj.fields_visibility()
+				.into_iter()
+				.filter(|(_k, v)| *v || inc_hidden)
+				.map(|(k, _v)|k)
+				.collect::<Vec<_>>();
+			out.sort();
+			Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))
+		})?,
+		// object, field, includeHidden
+		("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [
+			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
+			1, f: [Val::Str]!!Val::Str, vec![ValType::Str];
+			2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
+		], {
+			Ok(Val::Bool(
+				obj.fields_visibility()
+					.into_iter()
+					.filter(|(_k, v)| *v || inc_hidden)
+					.any(|(k, _v)| *k == *f),
+			))
+		})?,
+		("std", "primitiveEquals") => parse_args!(context, "std.primitiveEquals", args, 2, [
+			0, a, vec![];
+			1, b, vec![];
+		], {
+			Ok(Val::Bool(primitive_equals(&a, &b)?))
+		})?,
+		// faster
+		("std", "equals") => parse_args!(context, "std.equals", args, 2, [
+			0, a, vec![];
+			1, b, vec![];
+		], {
+			Ok(Val::Bool(equals(&a, &b)?))
+		})?,
+		("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [
+			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
+			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			Ok(Val::Num(a % b))
+		})?,
+		("std", "floor") => parse_args!(context, "std.floor", args, 1, [
+			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			Ok(Val::Num(x.floor()))
+		})?,
+		("std", "log") => parse_args!(context, "std.log", args, 2, [
+			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			Ok(Val::Num(n.ln()))
+		})?,
+		("std", "trace") => parse_args!(context, "std.trace", args, 2, [
+			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+			1, rest, vec![];
+		], {
+			eprint!("TRACE:");
+			if let Some(loc) = loc {
+				with_state(|s|{
+					let locs = s.map_source_locations(&loc.0, &[loc.1]);
+					eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);
+				});
+			}
+			eprintln!(" {}", str);
+			Ok(rest)
+		})?,
+		("std", "pow") => parse_args!(context, "std.modulo", args, 2, [
+			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
+			1, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			Ok(Val::Num(x.powf(n)))
+		})?,
+		("std", "extVar") => parse_args!(context, "std.extVar", args, 1, [
+			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
+				|| UndefinedExternalVariable(x),
+			)?)
+		})?,
+		("std", "native") => parse_args!(context, "std.native", args, 1, [
+			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(FuncVal::NativeExt(x.clone(), v))).ok_or_else(
+				|| UndefinedExternalFunction(x),
+			)?)
+		})?,
+		("std", "filter") => parse_args!(context, "std.filter", args, 2, [
+			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+		], {
+			Ok(Val::Arr(Rc::new(
+				arr.iter()
+					.cloned()
+					.filter(|e| {
+						func
+							.evaluate_values(context.clone(), &[e.clone()])
+							.unwrap()
+							.try_cast_bool("filter predicate")
+							.unwrap()
+					})
+					.collect(),
+			)))
+		})?,
+		// faster
+		("std", "foldl") => parse_args!(context, "std.foldl", args, 3, [
+			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+			2, init, vec![];
+		], {
+			let mut acc = init;
+			for i in arr.iter().cloned() {
+				acc = func.evaluate_values(context.clone(), &[acc, i])?;
+			}
+			Ok(acc)
+		})?,
+		// faster
+		("std", "foldr") => parse_args!(context, "std.foldr", args, 3, [
+			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+			2, init, vec![];
+		], {
+			let mut acc = init;
+			for i in arr.iter().rev().cloned() {
+				acc = func.evaluate_values(context.clone(), &[acc, i])?;
+			}
+			Ok(acc)
+		})?,
+		// faster
+		#[allow(non_snake_case)]
+		("std", "sortImpl") => parse_args!(context, "std.sort", args, 2, [
+			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+			1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];
+		], {
+			if arr.len() <= 1 {
+				return Ok(Val::Arr(arr))
+			}
+			Ok(Val::Arr(sort::sort(context, arr, keyF)?))
+		})?,
+		// faster
+		("std", "format") => parse_args!(context, "std.format", args, 2, [
+			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+			1, vals, vec![]
+		], {
+			push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{
+				Ok(match vals {
+					Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),
+					Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),
+					o => Val::Str(format_arr(&str, &[o])?.into()),
+				})
+			})
+		})?,
+		// faster
+		("std", "range") => parse_args!(context, "std.range", args, 2, [
+			0, from: [Val::Num]!!Val::Num, vec![ValType::Num];
+			1, to: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));
+			for i in from as usize..=to as usize {
+				out.push(Val::Num(i as f64));
+			}
+			Ok(Val::Arr(Rc::new(out)))
+		})?,
+		("std", "char") => parse_args!(context, "std.char", args, 1, [
+			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+		], {
+			let mut out = String::new();
+			out.push(std::char::from_u32(n as u32).ok_or_else(||
+				InvalidUnicodeCodepointGot(n as u32)
+			)?);
+			Ok(Val::Str(out.into()))
+		})?,
+		("std", "encodeUTF8") => parse_args!(context, "std.encodeUtf8", args, 1, [
+			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))
+		})?,
+		("std", "md5") => parse_args!(context, "std.md5", args, 1, [
+			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))
+		})?,
+		// faster
+		("std", "base64") => parse_args!(context, "std.base64", args, 1, [
+			0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];
+		], {
+			Ok(Val::Str(match input {
+				Val::Str(s) => {
+					base64::encode(s.bytes().collect::<Vec<_>>()).into()
+				},
+				Val::Arr(a) => {
+					base64::encode(a.iter().map(|v| {
+						Ok(v.clone().try_cast_num("base64 array")? as u8)
+					}).collect::<Result<Vec<_>>>()?).into()
+				},
+				_ => unreachable!()
+			}))
+		})?,
+		// faster
+		("std", "join") => parse_args!(context, "std.join", args, 2, [
+			0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];
+			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+		], {
+			Ok(match sep {
+				Val::Arr(joiner_items) => {
+					let mut out = Vec::new();
+
+					let mut first = true;
+					for item in arr.iter().cloned() {
+						if let Val::Arr(items) = item.unwrap_if_lazy()? {
+							if !first {
+								out.reserve(joiner_items.len());
+								out.extend(joiner_items.iter().cloned());
+							}
+							first = false;
+							out.reserve(items.len());
+							out.extend(items.iter().cloned());
+						} else {
+							throw!(RuntimeError("in std.join all items should be arrays".into()));
+						}
+					}
+
+					Val::Arr(Rc::new(out))
+				},
+				Val::Str(sep) => {
+					let mut out = String::new();
+
+					let mut first = true;
+					for item in arr.iter().cloned() {
+						if let Val::Str(item) = item.unwrap_if_lazy()? {
+							if !first {
+								out += &sep;
+							}
+							first = false;
+							out += &item;
+						} else {
+							throw!(RuntimeError("in std.join all items should be strings".into()));
+						}
+					}
+
+					Val::Str(out.into())
+				},
+				_ => unreachable!()
+			})
+		})?,
+		// Faster
+		("std", "escapeStringJson") => parse_args!(context, "std.escapeStringJson", args, 1, [
+			0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(Val::Str(escape_string_json(&str_).into()))
+		})?,
+		// Faster
+		("std", "manifestJsonEx") => parse_args!(context, "std.manifestJsonEx", args, 2, [
+			0, value, vec![];
+			1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];
+		], {
+			Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {
+				padding: &indent,
+				mtype: ManifestType::Std,
+			})?.into()))
+		})?,
+		// Faster
+		("std", "reverse") => parse_args!(context, "std.reverse", args, 1, [
+			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+		], {
+			let mut marr = arr;
+			Rc::make_mut(&mut marr).reverse();
+			Ok(Val::Arr(marr))
+		})?,
+		("std", "id") => parse_args!(context, "std.id", args, 1, [
+			0, v, vec![];
+		], {
+			Ok(v)
+		})?,
+		(ns, name) => throw!(IntristicNotFound(ns.into(), name.into())),
+	})
+}
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,4 +1,7 @@
-use crate::{builtin::format::FormatError, ValType};
+use crate::{
+	builtin::{format::FormatError, sort::SortError},
+	ValType,
+};
 use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType};
 use std::{path::PathBuf, rc::Rc};
 
@@ -69,6 +72,7 @@
 	InvalidUnicodeCodepointGot(u32),
 
 	Format(FormatError),
+	Sort(SortError),
 }
 impl From<Error> for LocError {
 	fn from(e: Error) -> Self {
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -1,13 +1,7 @@
 use crate::{
-	builtin::{
-		format::{format_arr, format_obj},
-		manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType},
-	},
-	context_creator, equals,
-	error::Error::*,
-	future_wrapper, lazy_val, parse_args, parse_function_call, primitive_equals, push, throw,
-	with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember,
-	ObjValue, Result, Val, ValType,
+	context_creator, error::Error::*, future_wrapper, lazy_val, push, throw, with_state, Context,
+	ContextCreator, FuncDesc, FuncVal, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val,
+	ValType,
 };
 use closure::closure;
 use jrsonnet_parser::{
@@ -15,7 +9,7 @@
 	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
 	Visibility,
 };
-use std::{cmp::Ordering, collections::HashMap, path::PathBuf, rc::Rc};
+use std::{collections::HashMap, rc::Rc};
 
 pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {
 	let b = b.clone();
@@ -51,12 +45,12 @@
 }
 
 pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {
-	Val::Func(Rc::new(FuncDesc {
+	Val::Func(FuncVal::Normal(Rc::new(FuncDesc {
 		name,
 		ctx,
 		params,
 		body,
-	}))
+	})))
 }
 
 pub fn evaluate_field_name(
@@ -389,21 +383,6 @@
 	})
 }
 
-/// Extracts code block and disables inlining for them
-/// Fixes WASM to java bytecode compilation failing because of very large method
-#[cfg(feature = "unstable")]
-macro_rules! noinline {
-	($e:expr) => {
-		(#![inline(never)] move || $e)()
-	};
-}
-#[cfg(not(feature = "unstable"))]
-macro_rules! noinline {
-	($e:expr) => {
-		(move || $e)()
-	};
-}
-
 pub fn evaluate_apply(
 	context: Context,
 	value: &LocExpr,
@@ -414,397 +393,12 @@
 	let lazy = evaluate(context.clone(), value)?;
 	let value = lazy.unwrap_if_lazy()?;
 	Ok(match value {
-		Val::Intristic(ns, name) => push(
-			loc,
-			|| format!("intristic <{}.{}> call", ns, name),
-			|| {
-				Ok(match (&ns as &str, &name as &str) {
-					// arr/string/function
-					("std", "length") => parse_args!(context, "std.length", args, 1, [
-						0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];
-					], {
-						Ok(match x {
-							Val::Str(n) => Val::Num(n.chars().count() as f64),
-							Val::Arr(i) => Val::Num(i.len() as f64),
-							Val::Obj(o) => Val::Num(
-								o.fields_visibility()
-									.into_iter()
-									.filter(|(_k, v)| *v)
-									.count() as f64,
-							),
-							_ => unreachable!(),
-						})
-					})?,
-					// any
-					("std", "type") => parse_args!(context, "std.type", args, 1, [
-						0, x, vec![];
-					], {
-						Ok(Val::Str(x.value_type()?.name().into()))
-					})?,
-					// length, idx=>any
-					("std", "makeArray") => {
-						noinline!(parse_args!(context, "std.makeArray", args, 2, [
-							0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];
-							1, func: [Val::Func]!!Val::Func, vec![ValType::Func];
-						], {
-							if sz < 0.0 {
-								throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));
-							}
-							let mut out = Vec::with_capacity(sz as usize);
-							for i in 0..sz as usize {
-								out.push(func.evaluate_values(
-									Context::new(),
-									&[Val::Num(i as f64)]
-								)?)
-							}
-							Ok(Val::Arr(Rc::new(out)))
-						}))?
-					}
-					// string
-					("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [
-						0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-					], {
-						assert!(
-							str.chars().count() == 1,
-							"std.codepoint should receive single char string"
-						);
-						Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))
-					})?,
-					// object, includeHidden
-					("std", "objectFieldsEx") => {
-						noinline!(parse_args!(context, "std.objectFieldsEx",args, 2, [
-							0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
-							1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
-						], {
-							let mut out = obj.fields_visibility()
-								.into_iter()
-								.filter(|(_k, v)| *v || inc_hidden)
-								.map(|(k, _v)|k)
-								.collect::<Vec<_>>();
-							out.sort();
-							Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))
-						}))?
-					}
-					// object, field, includeHidden
-					("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [
-						0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
-						1, f: [Val::Str]!!Val::Str, vec![ValType::Str];
-						2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
-					], {
-						Ok(Val::Bool(
-							obj.fields_visibility()
-								.into_iter()
-								.filter(|(_k, v)| *v || inc_hidden)
-								.any(|(k, _v)| *k == *f),
-						))
-					})?,
-					("std", "primitiveEquals") => {
-						parse_args!(context, "std.primitiveEquals", args, 2, [
-							0, a, vec![];
-							1, b, vec![];
-						], {
-							Ok(Val::Bool(primitive_equals(&a, &b)?))
-						})?
-					}
-					// faster
-					("std", "equals") => parse_args!(context, "std.equals", args, 2, [
-						0, a, vec![];
-						1, b, vec![];
-					], {
-						Ok(Val::Bool(equals(&a, &b)?))
-					})?,
-					("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [
-						0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
-						1, b: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						Ok(Val::Num(a % b))
-					})?,
-					("std", "floor") => parse_args!(context, "std.floor", args, 1, [
-						0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						Ok(Val::Num(x.floor()))
-					})?,
-					("std", "log") => parse_args!(context, "std.log", args, 2, [
-						0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						Ok(Val::Num(n.ln()))
-					})?,
-					("std", "trace") => parse_args!(context, "std.trace", args, 2, [
-						0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-						1, rest, vec![];
-					], {
-						eprint!("TRACE:");
-						if let Some(loc) = loc {
-							with_state(|s|{
-								let locs = s.map_source_locations(&loc.0, &[loc.1]);
-								eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);
-							});
-						}
-						eprintln!(" {}", str);
-						Ok(rest)
-					})?,
-					("std", "pow") => parse_args!(context, "std.modulo", args, 2, [
-						0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
-						1, n: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						Ok(Val::Num(x.powf(n)))
-					})?,
-					("std", "extVar") => parse_args!(context, "std.extVar", args, 1, [
-						0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
-					], {
-						Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
-							|| UndefinedExternalVariable(x),
-						)?)
-					})?,
-					("std", "native") => parse_args!(context, "std.native", args, 1, [
-						0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
-					], {
-						Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::NativeExt(x.clone(), v)).ok_or_else(
-							|| UndefinedExternalFunction(x),
-						)?)
-					})?,
-					("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [
-						0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
-						1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
-					], {
-						Ok(Val::Arr(Rc::new(
-							arr.iter()
-								.cloned()
-								.filter(|e| {
-									func
-										.evaluate_values(context.clone(), &[e.clone()])
-										.unwrap()
-										.try_cast_bool("filter predicate")
-										.unwrap()
-								})
-								.collect(),
-						)))
-					}))?,
-					// faster
-					("std", "foldl") => noinline!(parse_args!(context, "std.foldl", args, 3, [
-						0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
-						1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
-						2, init, vec![];
-					], {
-						let mut acc = init;
-						for i in arr.iter().cloned() {
-							acc = func.evaluate_values(context.clone(), &[acc, i])?;
-						}
-						Ok(acc)
-					}))?,
-					// faster
-					("std", "foldr") => noinline!(parse_args!(context, "std.foldr", args, 3, [
-						0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
-						1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
-						2, init, vec![];
-					], {
-						let mut acc = init;
-						for i in arr.iter().rev().cloned() {
-							acc = func.evaluate_values(context.clone(), &[acc, i])?;
-						}
-						Ok(acc)
-					}))?,
-					// faster
-					#[allow(non_snake_case)]
-					("std", "sortImpl") => noinline!(parse_args!(context, "std.sort", args, 2, [
-						0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
-						1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];
-					], {
-						if arr.len() <= 1 {
-							return Ok(Val::Arr(arr))
-						}
-						let mut new_arr = arr.iter().cloned().collect::<Vec<_>>();
-						match keyF.evaluate_values(context.clone(), &[new_arr[0].clone()])? {
-							Val::Str(_) => {
-								let mut err = None;
-								new_arr.sort_by_cached_key(|k| {
-									match keyF.evaluate_values(context.clone(), &[k.clone()]) {
-										Ok(Val::Str(v)) => v,
-										Ok(_) => {
-											err = Some(LocError::new(RuntimeError("types of all array elements should equal".into())));
-											"".into()
-										}
-										Err(e) => {
-											err = Some(e);
-											"".into()
-										}
-									}
-								});
-								if let Some(e) = err {
-									return Err(e);
-								}
-							},
-							Val::Num(_) => {
-								let mut err = None;
-								new_arr.sort_unstable_by(|a, b| {
-									match (keyF.evaluate_values(context.clone(), &[a.clone()]), keyF.evaluate_values(context.clone(), &[b.clone()])) {
-										(Ok(Val::Num(a)), Ok(Val::Num(b))) => a.partial_cmp(&b).unwrap(),
-										(Ok(_a), Ok(_b)) => {
-											err = Some(RuntimeError("types of all array elements should equal".into()).into());
-											Ordering::Equal
-										}
-										(Err(e), _) | (_, Err(e)) => {
-											err = Some(e);
-											Ordering::Equal
-										}
-									}
-								});
-								if let Some(e) = err {
-									return Err(e);
-								}
-							},
-							_ => throw!(RuntimeError("keys should be number or string".into()))
-						}
-						Ok(Val::Arr(Rc::new(new_arr)))
-					}))?,
-					// faster
-					("std", "format") => parse_args!(context, "std.format", args, 2, [
-						0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-						1, vals, vec![]
-					], {
-						push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{
-							Ok(match vals {
-								Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),
-								Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),
-								o => Val::Str(format_arr(&str, &[o])?.into()),
-							})
-						})
-					})?,
-					// faster
-					("std", "range") => parse_args!(context, "std.range", args, 2, [
-						0, from: [Val::Num]!!Val::Num, vec![ValType::Num];
-						1, to: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));
-						for i in from as usize..=to as usize {
-							out.push(Val::Num(i as f64));
-						}
-						Ok(Val::Arr(Rc::new(out)))
-					})?,
-					("std", "char") => parse_args!(context, "std.char", args, 1, [
-						0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
-					], {
-						let mut out = String::new();
-						out.push(std::char::from_u32(n as u32).ok_or_else(||
-							InvalidUnicodeCodepointGot(n as u32)
-						)?);
-						Ok(Val::Str(out.into()))
-					})?,
-					("std", "encodeUTF8") => parse_args!(context, "std.encodeUtf8", args, 1, [
-						0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-					], {
-						Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))
-					})?,
-					("std", "md5") => noinline!(parse_args!(context, "std.md5", args, 1, [
-						0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-					], {
-						Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))
-					}))?,
-					// faster
-					("std", "base64") => parse_args!(context, "std.base64", args, 1, [
-						0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];
-					], {
-						Ok(Val::Str(match input {
-							Val::Str(s) => {
-								base64::encode(s.bytes().collect::<Vec<_>>()).into()
-							},
-							Val::Arr(a) => {
-								base64::encode(a.iter().map(|v| {
-									Ok(v.clone().try_cast_num("base64 array")? as u8)
-								}).collect::<Result<Vec<_>>>()?).into()
-							},
-							_ => unreachable!()
-						}))
-					})?,
-					// faster
-					("std", "join") => noinline!(parse_args!(context, "std.join", args, 2, [
-						0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];
-						1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
-					], {
-						Ok(match sep {
-							Val::Arr(joiner_items) => {
-								let mut out = Vec::new();
-
-								let mut first = true;
-								for item in arr.iter().cloned() {
-									if let Val::Arr(items) = item.unwrap_if_lazy()? {
-										if !first {
-											out.reserve(joiner_items.len());
-											out.extend(joiner_items.iter().cloned());
-										}
-										first = false;
-										out.reserve(items.len());
-										out.extend(items.iter().cloned());
-									} else {
-										throw!(RuntimeError("in std.join all items should be arrays".into()));
-									}
-								}
-
-								Val::Arr(Rc::new(out))
-							},
-							Val::Str(sep) => {
-								let mut out = String::new();
-
-								let mut first = true;
-								for item in arr.iter().cloned() {
-									if let Val::Str(item) = item.unwrap_if_lazy()? {
-										if !first {
-											out += &sep;
-										}
-										first = false;
-										out += &item;
-									} else {
-										throw!(RuntimeError("in std.join all items should be strings".into()));
-									}
-								}
-
-								Val::Str(out.into())
-							},
-							_ => unreachable!()
-						})
-					}))?,
-					// Faster
-					("std", "escapeStringJson") => {
-						parse_args!(context, "std.escapeStringJson", args, 1, [
-							0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];
-						], {
-							Ok(Val::Str(escape_string_json(&str_).into()))
-						})?
-					}
-					// Faster
-					("std", "manifestJsonEx") => {
-						parse_args!(context, "std.manifestJsonEx", args, 2, [
-							0, value, vec![];
-							1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];
-						], {
-							Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {
-								padding: &indent,
-								mtype: ManifestType::Std,
-							})?.into()))
-						})?
-					}
-					(ns, name) => throw!(IntristicNotFound(ns.into(), name.into())),
-				})
-			},
-		)?,
-		Val::NativeExt(n, f) => push(
-			loc,
-			|| format!("native <{}> call", n),
-			|| {
-				let args = parse_function_call(context, None, &f.params, args, true)?;
-				let mut out_args = Vec::with_capacity(f.params.len());
-				for p in f.params.0.iter() {
-					out_args.push(args.binding(p.0.clone())?.evaluate()?);
-				}
-				Ok(f.call(&out_args)?)
-			},
-		)?,
 		Val::Func(f) => {
-			let body = || f.evaluate(context, args, tailstrict);
+			let body = || f.evaluate(context, loc, args, tailstrict);
 			if tailstrict {
 				body()?
 			} else {
-				push(loc, || format!("function <{}> call", f.name), body)?
+				push(loc, || format!("function <{}> call", f.name()), body)?
 			}
 		}
 		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)),
@@ -874,7 +468,7 @@
 							} else if let Some(Val::Str(n)) =
 								v.get("__intristic_namespace__".into())?
 							{
-								Ok(Val::Intristic(n, s))
+								Ok(Val::Func(FuncVal::Intristic(n, s)))
 							} else {
 								throw!(NoSuchField(s))
 							}
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -40,9 +40,7 @@
 				}
 				Value::Object(out)
 			}
-			Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
-				throw!(RuntimeError("tried to manifest function".into()))
-			}
+			Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
 		})
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3	error::Error::*,4	evaluate,5	function::{parse_function_call, parse_function_call_map, place_args},6	native::NativeCallback,7	throw, with_state, Context, ObjValue, Result,8};9use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};10use std::{11	cell::RefCell,12	collections::HashMap,13	fmt::{Debug, Display},14	rc::Rc,15};1617enum LazyValInternals {18	Computed(Val),19	Waiting(Box<dyn Fn() -> Result<Val>>),20}21#[derive(Clone)]22pub struct LazyVal(Rc<RefCell<LazyValInternals>>);23impl LazyVal {24	pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {25		LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))26	}27	pub fn new_resolved(val: Val) -> Self {28		LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))29	}30	pub fn evaluate(&self) -> Result<Val> {31		let new_value = match &*self.0.borrow() {32			LazyValInternals::Computed(v) => return Ok(v.clone()),33			LazyValInternals::Waiting(f) => f()?,34		};35		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());36		Ok(new_value)37	}38}3940#[macro_export]41macro_rules! lazy_val {42	($f: expr) => {43		$crate::LazyVal::new(Box::new($f))44	};45}46#[macro_export]47macro_rules! resolved_lazy_val {48	($f: expr) => {49		$crate::LazyVal::new_resolved($f)50	};51}52impl Debug for LazyVal {53	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {54		write!(f, "Lazy")55	}56}57impl PartialEq for LazyVal {58	fn eq(&self, other: &Self) -> bool {59		Rc::ptr_eq(&self.0, &other.0)60	}61}6263#[derive(Debug, PartialEq)]64pub struct FuncDesc {65	pub name: Rc<str>,66	pub ctx: Context,67	pub params: ParamsDesc,68	pub body: LocExpr,69}70impl FuncDesc {71	/// This function is always inlined to make tailstrict work72	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {73		let ctx = parse_function_call(74			call_ctx,75			Some(self.ctx.clone()),76			&self.params,77			args,78			tailstrict,79		)?;80		evaluate(ctx, &self.body)81	}8283	pub fn evaluate_map(84		&self,85		call_ctx: Context,86		args: &HashMap<Rc<str>, Val>,87		tailstrict: bool,88	) -> Result<Val> {89		let ctx = parse_function_call_map(90			call_ctx,91			Some(self.ctx.clone()),92			&self.params,93			args,94			tailstrict,95		)?;96		evaluate(ctx, &self.body)97	}9899	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {100		let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;101		evaluate(ctx, &self.body)102	}103}104105#[derive(Debug, Clone, Copy, PartialEq)]106pub enum ValType {107	Bool,108	Null,109	Str,110	Num,111	Arr,112	Obj,113	Func,114}115impl ValType {116	pub fn name(&self) -> &'static str {117		use ValType::*;118		match self {119			Bool => "boolean",120			Null => "null",121			Str => "string",122			Num => "number",123			Arr => "array",124			Obj => "object",125			Func => "function",126		}127	}128}129impl Display for ValType {130	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131		write!(f, "{}", self.name())132	}133}134135#[derive(Clone)]136pub enum ManifestFormat {137	YamlStream(Box<ManifestFormat>),138	Yaml(usize),139	Json(usize),140	String,141}142143#[derive(Debug, Clone)]144pub enum Val {145	Bool(bool),146	Null,147	Str(Rc<str>),148	Num(f64),149	Lazy(LazyVal),150	Arr(Rc<Vec<Val>>),151	Obj(ObjValue),152	Func(Rc<FuncDesc>),153154	// Library functions implemented in native155	Intristic(Rc<str>, Rc<str>),156	NativeExt(Rc<str>, Rc<NativeCallback>),157}158macro_rules! matches_unwrap {159	($e: expr, $p: pat, $r: expr) => {160		match $e {161			$p => $r,162			_ => panic!("no match"),163			}164	};165}166impl Val {167	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity168	pub fn new_checked_num(num: f64) -> Result<Val> {169		if num.is_finite() {170			Ok(Val::Num(num))171		} else {172			throw!(RuntimeError("overflow".into()))173		}174	}175176	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {177		let this_type = self.value_type()?;178		if this_type != val_type {179			throw!(TypeMismatch(context, vec![val_type], this_type))180		} else {181			Ok(())182		}183	}184	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {185		self.assert_type(context, ValType::Bool)?;186		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))187	}188	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {189		self.assert_type(context, ValType::Str)?;190		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))191	}192	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {193		self.assert_type(context, ValType::Num)?;194		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))195	}196	pub fn unwrap_if_lazy(&self) -> Result<Self> {197		Ok(if let Val::Lazy(v) = self {198			v.evaluate()?.unwrap_if_lazy()?199		} else {200			self.clone()201		})202	}203	pub fn value_type(&self) -> Result<ValType> {204		Ok(match self {205			Val::Str(..) => ValType::Str,206			Val::Num(..) => ValType::Num,207			Val::Arr(..) => ValType::Arr,208			Val::Obj(..) => ValType::Obj,209			Val::Bool(_) => ValType::Bool,210			Val::Null => ValType::Null,211			Val::Func(..) | Val::Intristic(_, _) | Val::NativeExt(_, _) => ValType::Func,212			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,213		})214	}215216	pub fn to_string(&self) -> Result<Rc<str>> {217		Ok(match self.unwrap_if_lazy()? {218			Val::Bool(true) => "true".into(),219			Val::Bool(false) => "false".into(),220			Val::Null => "null".into(),221			Val::Str(s) => s,222			v => manifest_json_ex(223				&v,224				&ManifestJsonOptions {225					padding: &"",226					mtype: ManifestType::ToString,227				},228			)?229			.into(),230		})231	}232233	/// Expects value to be object, outputs (key, manifested value) pairs234	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(Rc<str>, Rc<str>)>> {235		let obj = match self {236			Val::Obj(obj) => obj,237			_ => throw!(MultiManifestOutputIsNotAObject),238		};239		let keys = obj.visible_fields();240		let mut out = Vec::with_capacity(keys.len());241		for key in keys {242			let value = obj243				.get(key.clone())?244				.expect("item in object")245				.manifest(ty)?;246			out.push((key, value));247		}248		Ok(out)249	}250251	/// Expects value to be array, outputs manifested values252	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<Rc<str>>> {253		let arr = match self {254			Val::Arr(a) => a,255			_ => throw!(StreamManifestOutputIsNotAArray),256		};257		let mut out = Vec::with_capacity(arr.len());258		for i in arr.iter() {259			out.push(i.manifest(ty)?);260		}261		Ok(out)262	}263264	pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {265		Ok(match ty {266			ManifestFormat::YamlStream(format) => {267				let arr = match self {268					Val::Arr(a) => a,269					_ => throw!(StreamManifestOutputIsNotAArray),270				};271				let mut out = String::new();272273				match format as &ManifestFormat {274					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),275					ManifestFormat::String => throw!(StreamManifestCannotNestString),276					_ => {}277				};278279				if !arr.is_empty() {280					for v in arr.iter() {281						out.push_str("---\n");282						out.push_str(&v.manifest(format)?);283						out.push_str("\n");284					}285					out.push_str("...");286				}287288				out.into()289			}290			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,291			ManifestFormat::Json(padding) => self.to_json(*padding)?,292			ManifestFormat::String => match self {293				Val::Str(s) => s.clone(),294				_ => throw!(StringManifestOutputIsNotAString),295			},296		})297	}298299	/// For manifestification300	pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {301		manifest_json_ex(302			self,303			&ManifestJsonOptions {304				padding: &" ".repeat(padding),305				mtype: if padding == 0 {306					ManifestType::Minify307				} else {308					ManifestType::Manifest309				},310			},311		)312		.map(|s| s.into())313	}314315	/// Calls std.manifestJson316	#[cfg(feature = "faster")]317	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {318		manifest_json_ex(319			&self,320			&ManifestJsonOptions {321				padding: &" ".repeat(padding),322				mtype: ManifestType::Std,323			},324		)325		.map(|s| s.into())326	}327328	/// Calls std.manifestJson329	#[cfg(not(feature = "faster"))]330	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {331		with_state(|s| {332			let ctx = s333				.create_default_context()?334				.with_var("__tmp__to_json__".into(), self.clone())?;335			Ok(evaluate(336				ctx,337				&el!(Expr::Apply(338					el!(Expr::Index(339						el!(Expr::Var("std".into())),340						el!(Expr::Str("manifestJsonEx".into()))341					)),342					ArgsDesc(vec![343						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),344						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))345					]),346					false347				)),348			)?349			.try_cast_str("to json")?)350		})351	}352	pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {353		with_state(|s| {354			let ctx = s355				.create_default_context()?356				.with_var("__tmp__to_json__".into(), self.clone());357			Ok(evaluate(358				ctx,359				&el!(Expr::Apply(360					el!(Expr::Index(361						el!(Expr::Var("std".into())),362						el!(Expr::Str("manifestYamlDoc".into()))363					)),364					ArgsDesc(vec![365						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),366						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))367					]),368					false369				)),370			)?371			.try_cast_str("to json")?)372		})373	}374}375376fn is_function_like(val: &Val) -> bool {377	matches!(val, Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _))378}379380/// Implements std.primitiveEquals builtin381pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {382	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {383		(Val::Bool(a), Val::Bool(b)) => a == b,384		(Val::Null, Val::Null) => true,385		(Val::Str(a), Val::Str(b)) => a == b,386		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,387		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(388			"primitiveEquals operates on primitive types, got array".into(),389		)),390		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(391			"primitiveEquals operates on primitive types, got object".into(),392		)),393		(a, b) if is_function_like(&a) && is_function_like(&b) => {394			throw!(RuntimeError("cannot test equality of functions".into()))395		}396		(_, _) => false,397	})398}399400/// Native implementation of std.equals401pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {402	let val_a = val_a.unwrap_if_lazy()?;403	let val_b = val_b.unwrap_if_lazy()?;404405	if val_a.value_type()? != val_b.value_type()? {406		return Ok(false);407	}408	match (val_a, val_b) {409		// Cant test for ptr equality, because all fields needs to be evaluated410		(Val::Arr(a), Val::Arr(b)) => {411			if a.len() != b.len() {412				return Ok(false);413			}414			for (a, b) in a.iter().zip(b.iter()) {415				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {416					return Ok(false);417				}418			}419			Ok(true)420		}421		(Val::Obj(a), Val::Obj(b)) => {422			let fields = a.visible_fields();423			if fields != b.visible_fields() {424				return Ok(false);425			}426			for field in fields {427				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {428					return Ok(false);429				}430			}431			Ok(true)432		}433		(a, b) => Ok(primitive_equals(&a, &b)?),434	}435}
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -2,7 +2,7 @@
   __intristic_namespace__:: 'std',
 
   local std = self,
-  local id = function(x) x,
+  local id = std.id,
 
   isString(v):: std.type(v) == 'string',
   isNumber(v):: std.type(v) == 'number',