git.delta.rocks / jrsonnet / refs/commits / 349c41065b93

difftreelog

feat lazy values in builtin

Yaroslav Bolyukin2022-04-20parent: #c7fb888.patch.diff
in: master

8 files changed

modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,4 +1,4 @@
-use crate::function::StaticBuiltin;
+use crate::function::{CallLocation, StaticBuiltin};
 use crate::typed::{Any, PositiveF64, VecVal, M1};
 use crate::{
 	builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
@@ -12,7 +12,6 @@
 use crate::{Either, ObjValue};
 use format::{format_arr, format_obj};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::ExprLocation;
 use serde::Deserialize;
 use serde_yaml::DeserializingQuirks;
 use std::collections::HashMap;
@@ -29,7 +28,7 @@
 
 pub fn std_format(str: IStr, vals: Val) -> Result<String> {
 	push_frame(
-		None,
+		CallLocation::native(),
 		|| format!("std.format of {}", str),
 		|| {
 			Ok(match vals {
@@ -467,9 +466,9 @@
 }
 
 #[jrsonnet_macros::builtin]
-fn builtin_trace(#[location] loc: Option<&ExprLocation>, str: IStr, rest: Any) -> Result<Any> {
+fn builtin_trace(loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
 	eprint!("TRACE:");
-	if let Some(loc) = loc {
+	if let Some(loc) = loc.0 {
 		with_state(|s| {
 			let locs = s.map_source_locations(&loc.0, &[loc.1]);
 			eprint!(
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -4,6 +4,7 @@
 	builtin::{std_slice, BUILTINS},
 	error::Error::*,
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
+	function::CallLocation,
 	gc::TraceBox,
 	push_frame, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal,
 	FutureWrapper, GcHashMap, LazyBinding, LazyVal, LazyValValue, ObjValue, ObjValueBuilder,
@@ -12,8 +13,8 @@
 use gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{
-	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, ForSpecData,
-	IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,
+	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData,
+	LiteralType, LocExpr, Member, ObjBody, ParamsDesc,
 };
 use jrsonnet_types::ValType;
 pub mod operator;
@@ -192,7 +193,7 @@
 	Ok(match field_name {
 		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),
 		jrsonnet_parser::FieldName::Dyn(expr) => push_frame(
-			Some(&expr.1),
+			CallLocation::new(&expr.1),
 			|| "evaluating field name".to_string(),
 			|| {
 				let value = evaluate(context, expr)?;
@@ -442,7 +443,7 @@
 	context: Context,
 	value: &LocExpr,
 	args: &ArgsDesc,
-	loc: Option<&ExprLocation>,
+	loc: CallLocation,
 	tailstrict: bool,
 ) -> Result<Val> {
 	let value = evaluate(context.clone(), value)?;
@@ -463,13 +464,13 @@
 	let value = &assertion.0;
 	let msg = &assertion.1;
 	let assertion_result = push_frame(
-		Some(&value.1),
+		CallLocation::new(&value.1),
 		|| "assertion condition".to_owned(),
 		|| bool::try_from(evaluate(context.clone(), value)?),
 	)?;
 	if !assertion_result {
 		push_frame(
-			Some(&value.1),
+			CallLocation::new(&value.1),
 			|| "assertion failure".to_owned(),
 			|| {
 				if let Some(msg) = msg {
@@ -519,7 +520,7 @@
 		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,
 		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,
 		Var(name) => push_frame(
-			Some(loc),
+			CallLocation::new(loc),
 			|| format!("variable <{}> access", name),
 			|| context.binding(name.clone())?.evaluate(),
 		)?,
@@ -528,7 +529,7 @@
 				(Val::Obj(v), Val::Str(s)) => {
 					let sn = s.clone();
 					push_frame(
-						Some(loc),
+						CallLocation::new(loc),
 						|| format!("field <{}> access", sn),
 						|| {
 							if let Some(v) = v.get(s.clone())? {
@@ -625,7 +626,7 @@
 			&Val::Obj(evaluate_object(context, t)?),
 		)?,
 		Apply(value, args, tailstrict) => {
-			evaluate_apply(context, value, args, Some(loc), *tailstrict)?
+			evaluate_apply(context, value, args, CallLocation::new(loc), *tailstrict)?
 		}
 		Function(params, body) => {
 			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())
@@ -640,7 +641,7 @@
 			evaluate(context, returned)?
 		}
 		ErrorStmt(e) => push_frame(
-			Some(loc),
+			CallLocation::new(loc),
 			|| "error statement".to_owned(),
 			|| throw!(RuntimeError(IStr::try_from(evaluate(context, e)?)?,)),
 		)?,
@@ -650,7 +651,7 @@
 			cond_else,
 		} => {
 			if push_frame(
-				Some(loc),
+				CallLocation::new(loc),
 				|| "if condition".to_owned(),
 				|| bool::try_from(evaluate(context.clone(), &cond.0)?),
 			)? {
@@ -689,7 +690,7 @@
 			let mut import_location = tmp.to_path_buf();
 			import_location.pop();
 			push_frame(
-				Some(loc),
+				CallLocation::new(loc),
 				|| format!("import {:?}", path),
 				|| with_state(|s| s.import_file(&import_location, path)),
 			)?
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -12,6 +12,19 @@
 use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
 use std::{borrow::Cow, collections::HashMap, convert::TryFrom};
 
+#[derive(Clone, Copy)]
+pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
+impl<'l> CallLocation<'l> {
+	pub fn new(loc: &'l ExprLocation) -> Self {
+		Self(Some(loc))
+	}
+}
+impl CallLocation<'static> {
+	pub fn native() -> Self {
+		Self(None)
+	}
+}
+
 #[derive(Trace)]
 struct EvaluateLazyVal {
 	context: Context,
@@ -383,12 +396,7 @@
 pub trait Builtin: Trace {
 	fn name(&self) -> &str;
 	fn params(&self) -> &[BuiltinParam];
-	fn call(
-		&self,
-		context: Context,
-		loc: Option<&ExprLocation>,
-		args: &dyn ArgsLike,
-	) -> Result<Val>;
+	fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
 }
 
 pub trait StaticBuiltin: Builtin + Send + Sync
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -28,7 +28,7 @@
 pub use dynamic::*;
 use error::{Error::*, LocError, Result, StackTraceElement};
 pub use evaluate::*;
-use function::{Builtin, TlaArg};
+use function::{Builtin, CallLocation, TlaArg};
 use gc::{GcHashMap, TraceBox};
 use gcmodule::{Cc, Trace, Weak};
 pub use import::*;
@@ -173,6 +173,7 @@
 	/// Global state is fine here.
 	pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
 }
+
 pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
 	EVAL_STATE.with(|s| {
 		f(s.borrow().as_ref().expect(
@@ -181,7 +182,7 @@
 	})
 }
 pub fn push_frame<T>(
-	e: Option<&ExprLocation>,
+	e: CallLocation,
 	frame_desc: impl FnOnce() -> String,
 	f: impl FnOnce() -> Result<T>,
 ) -> Result<T> {
@@ -345,7 +346,7 @@
 	/// Executes code creating a new stack frame
 	pub fn push<T>(
 		&self,
-		e: Option<&ExprLocation>,
+		e: CallLocation,
 		frame_desc: impl FnOnce() -> String,
 		f: impl FnOnce() -> Result<T>,
 	) -> Result<T> {
@@ -368,7 +369,7 @@
 		}
 		if let Err(mut err) = result {
 			err.trace_mut().0.push(StackTraceElement {
-				location: e.cloned(),
+				location: e.0.cloned(),
 				desc: frame_desc(),
 			});
 			return Err(err);
@@ -512,7 +513,7 @@
 					|| {
 						func.evaluate(
 							self.create_default_context(),
-							None,
+							CallLocation::native(),
 							&self.settings().tla_vars,
 							true,
 						)
modifiedcrates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/native.rs
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -1,11 +1,10 @@
 #![allow(clippy::type_complexity)]
 
-use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam};
+use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam, CallLocation};
 use crate::gc::TraceBox;
 use crate::Context;
 use crate::{error::Result, Val};
 use gcmodule::Trace;
-use jrsonnet_parser::ExprLocation;
 use std::path::Path;
 use std::rc::Rc;
 
@@ -32,18 +31,13 @@
 		&self.params
 	}
 
-	fn call(
-		&self,
-		context: Context,
-		loc: Option<&ExprLocation>,
-		args: &dyn ArgsLike,
-	) -> Result<Val> {
+	fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
 		let args = parse_builtin_call(context, &self.params, args, true)?;
 		let mut out_args = Vec::with_capacity(self.params.len());
 		for p in self.params.iter() {
 			out_args.push(args[&p.name].evaluate()?);
 		}
-		self.handler.call(loc.map(|l| l.0.clone()), &out_args)
+		self.handler.call(loc.0.map(|l| l.0.clone()), &out_args)
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -1,5 +1,6 @@
 use std::convert::{TryFrom, TryInto};
 
+use gcmodule::Cc;
 use jrsonnet_interner::IStr;
 pub use jrsonnet_macros::Typed;
 use jrsonnet_types::{ComplexValType, ValType};
@@ -8,7 +9,7 @@
 	error::{Error::*, LocError, Result},
 	throw,
 	typed::CheckType,
-	ArrValue, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val,
+	ArrValue, FuncDesc, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val,
 };
 
 pub trait TypedObj: Typed {
@@ -431,6 +432,30 @@
 		Ok(Self::Func(value))
 	}
 }
+
+impl Typed for Cc<FuncDesc> {
+	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
+}
+impl TryFrom<Val> for Cc<FuncDesc> {
+	type Error = LocError;
+
+	fn try_from(value: Val) -> Result<Self, Self::Error> {
+		<Self as Typed>::TYPE.check(&value)?;
+		match value {
+			Val::Func(FuncVal::Normal(desc)) => Ok(desc.clone()),
+			Val::Func(_) => throw!(RuntimeError("expected normal function, not builtin".into())),
+			_ => unreachable!(),
+		}
+	}
+}
+impl TryFrom<Cc<FuncDesc>> for Val {
+	type Error = LocError;
+
+	fn try_from(value: Cc<FuncDesc>) -> Result<Self, Self::Error> {
+		Ok(Self::Func(FuncVal::Normal(value)))
+	}
+}
+
 impl Typed for ObjValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::manifest::{3		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,4	},5	cc_ptr_eq,6	error::{Error::*, LocError},7	evaluate,8	function::{9		parse_default_function_call, parse_function_call, ArgsLike, Builtin, StaticBuiltin,10	},11	gc::TraceBox,12	throw, Context, ObjValue, Result,13};14use gcmodule::{Cc, Trace};15use jrsonnet_interner::IStr;16use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc};17use jrsonnet_types::ValType;18use std::{cell::RefCell, fmt::Debug, rc::Rc};1920pub trait LazyValValue: Trace {21	fn get(self: Box<Self>) -> Result<Val>;22}2324#[derive(Trace)]25enum LazyValInternals {26	Computed(Val),27	Errored(LocError),28	Waiting(TraceBox<dyn LazyValValue>),29	Pending,30}3132#[derive(Clone, Trace)]33pub struct LazyVal(Cc<RefCell<LazyValInternals>>);34impl LazyVal {35	pub fn new(f: TraceBox<dyn LazyValValue>) -> Self {36		Self(Cc::new(RefCell::new(LazyValInternals::Waiting(f))))37	}38	pub fn new_resolved(val: Val) -> Self {39		Self(Cc::new(RefCell::new(LazyValInternals::Computed(val))))40	}41	pub fn force(&self) -> Result<()> {42		self.evaluate()?;43		Ok(())44	}45	pub fn evaluate(&self) -> Result<Val> {46		match &*self.0.borrow() {47			LazyValInternals::Computed(v) => return Ok(v.clone()),48			LazyValInternals::Errored(e) => return Err(e.clone()),49			LazyValInternals::Pending => return Err(RecursiveLazyValueEvaluation.into()),50			_ => (),51		};52		let value = if let LazyValInternals::Waiting(value) =53			std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending)54		{55			value56		} else {57			unreachable!()58		};59		let new_value = match value.0.get() {60			Ok(v) => v,61			Err(e) => {62				*self.0.borrow_mut() = LazyValInternals::Errored(e.clone());63				return Err(e);64			}65		};66		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());67		Ok(new_value)68	}69}7071impl Debug for LazyVal {72	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {73		write!(f, "Lazy")74	}75}76impl PartialEq for LazyVal {77	fn eq(&self, other: &Self) -> bool {78		cc_ptr_eq(&self.0, &other.0)79	}80}8182#[derive(Debug, PartialEq, Trace)]83pub struct FuncDesc {84	pub name: IStr,85	pub ctx: Context,86	pub params: ParamsDesc,87	pub body: LocExpr,88}89impl FuncDesc {90	/// Create body context, but fill arguments without defaults with lazy error91	pub fn default_body_context(&self) -> Context {92		parse_default_function_call(self.ctx.clone(), &self.params)93	}9495	/// Create context, with which body code will run96	pub fn call_body_context(97		&self,98		call_ctx: Context,99		args: &dyn ArgsLike,100		tailstrict: bool,101	) -> Result<Context> {102		parse_function_call(call_ctx, self.ctx.clone(), &self.params, args, tailstrict)103	}104}105106#[derive(Trace, Clone)]107pub enum FuncVal {108	/// Plain function implemented in jsonnet109	Normal(Cc<FuncDesc>),110	/// Standard library function111	StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),112	/// User-provided function113	Builtin(Cc<TraceBox<dyn Builtin>>),114}115116impl Debug for FuncVal {117	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {118		match self {119			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),120			Self::StaticBuiltin(arg0) => {121				f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()122			}123			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),124		}125	}126}127128impl PartialEq for FuncVal {129	fn eq(&self, other: &Self) -> bool {130		match (self, other) {131			(Self::Normal(a), Self::Normal(b)) => a == b,132			(Self::StaticBuiltin(an), Self::StaticBuiltin(bn)) => std::ptr::eq(*an, *bn),133			(..) => false,134		}135	}136}137impl FuncVal {138	pub fn args_len(&self) -> usize {139		match self {140			Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),141			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),142			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),143		}144	}145	pub fn name(&self) -> IStr {146		match self {147			Self::Normal(normal) => normal.name.clone(),148			Self::StaticBuiltin(builtin) => builtin.name().into(),149			Self::Builtin(builtin) => builtin.name().into(),150		}151	}152	pub fn evaluate(153		&self,154		call_ctx: Context,155		loc: Option<&ExprLocation>,156		args: &dyn ArgsLike,157		tailstrict: bool,158	) -> Result<Val> {159		match self {160			Self::Normal(func) => {161				let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;162				evaluate(body_ctx, &func.body)163			}164			Self::StaticBuiltin(b) => b.call(call_ctx, loc, args),165			Self::Builtin(b) => b.call(call_ctx, loc, args),166		}167	}168	pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result<Val> {169		self.evaluate(Context::default(), None, args, true)170	}171}172173#[derive(Clone)]174pub enum ManifestFormat {175	YamlStream(Box<ManifestFormat>),176	Yaml(usize),177	Json(usize),178	ToString,179	String,180}181182#[derive(Debug, Clone, Trace)]183#[force_tracking]184pub enum ArrValue {185	Lazy(Cc<Vec<LazyVal>>),186	Eager(Cc<Vec<Val>>),187	Extended(Box<(Self, Self)>),188}189impl ArrValue {190	pub fn new_eager() -> Self {191		Self::Eager(Cc::new(Vec::new()))192	}193194	pub fn len(&self) -> usize {195		match self {196			Self::Lazy(l) => l.len(),197			Self::Eager(e) => e.len(),198			Self::Extended(v) => v.0.len() + v.1.len(),199		}200	}201202	pub fn is_empty(&self) -> bool {203		self.len() == 0204	}205206	pub fn get(&self, index: usize) -> Result<Option<Val>> {207		match self {208			Self::Lazy(vec) => {209				if let Some(v) = vec.get(index) {210					Ok(Some(v.evaluate()?))211				} else {212					Ok(None)213				}214			}215			Self::Eager(vec) => Ok(vec.get(index).cloned()),216			Self::Extended(v) => {217				let a_len = v.0.len();218				if a_len > index {219					v.0.get(index)220				} else {221					v.1.get(index - a_len)222				}223			}224		}225	}226227	pub fn get_lazy(&self, index: usize) -> Option<LazyVal> {228		match self {229			Self::Lazy(vec) => vec.get(index).cloned(),230			Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved),231			Self::Extended(v) => {232				let a_len = v.0.len();233				if a_len > index {234					v.0.get_lazy(index)235				} else {236					v.1.get_lazy(index - a_len)237				}238			}239		}240	}241242	pub fn evaluated(&self) -> Result<Cc<Vec<Val>>> {243		Ok(match self {244			Self::Lazy(vec) => {245				let mut out = Vec::with_capacity(vec.len());246				for item in vec.iter() {247					out.push(item.evaluate()?);248				}249				Cc::new(out)250			}251			Self::Eager(vec) => vec.clone(),252			Self::Extended(_v) => {253				let mut out = Vec::with_capacity(self.len());254				for item in self.iter() {255					out.push(item?);256				}257				Cc::new(out)258			}259		})260	}261262	pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {263		(0..self.len()).map(move |idx| match self {264			Self::Lazy(l) => l[idx].evaluate(),265			Self::Eager(e) => Ok(e[idx].clone()),266			Self::Extended(_) => self.get(idx).map(|e| e.unwrap()),267		})268	}269270	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = LazyVal> + '_ {271		(0..self.len()).map(move |idx| match self {272			Self::Lazy(l) => l[idx].clone(),273			Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()),274			Self::Extended(_) => self.get_lazy(idx).unwrap(),275		})276	}277278	pub fn reversed(self) -> Self {279		match self {280			Self::Lazy(vec) => {281				let mut out = (&vec as &Vec<_>).clone();282				out.reverse();283				Self::Lazy(Cc::new(out))284			}285			Self::Eager(vec) => {286				let mut out = (&vec as &Vec<_>).clone();287				out.reverse();288				Self::Eager(Cc::new(out))289			}290			Self::Extended(b) => Self::Extended(Box::new((b.1.reversed(), b.0.reversed()))),291		}292	}293294	pub fn map(self, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {295		let mut out = Vec::with_capacity(self.len());296297		for value in self.iter() {298			out.push(mapper(value?)?);299		}300301		Ok(Self::Eager(Cc::new(out)))302	}303304	pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {305		let mut out = Vec::with_capacity(self.len());306307		for value in self.iter() {308			let value = value?;309			if filter(&value)? {310				out.push(value);311			}312		}313314		Ok(Self::Eager(Cc::new(out)))315	}316317	pub fn ptr_eq(a: &Self, b: &Self) -> bool {318		match (a, b) {319			(Self::Lazy(a), Self::Lazy(b)) => cc_ptr_eq(a, b),320			(Self::Eager(a), Self::Eager(b)) => cc_ptr_eq(a, b),321			_ => false,322		}323	}324}325326impl From<Vec<LazyVal>> for ArrValue {327	fn from(v: Vec<LazyVal>) -> Self {328		Self::Lazy(Cc::new(v))329	}330}331332impl From<Vec<Val>> for ArrValue {333	fn from(v: Vec<Val>) -> Self {334		Self::Eager(Cc::new(v))335	}336}337338pub enum IndexableVal {339	Str(IStr),340	Arr(ArrValue),341}342343#[derive(Debug, Clone, Trace)]344pub enum Val {345	Bool(bool),346	Null,347	Str(IStr),348	Num(f64),349	Arr(ArrValue),350	Obj(ObjValue),351	Func(FuncVal),352}353354impl Val {355	pub fn as_bool(&self) -> Option<bool> {356		match self {357			Val::Bool(v) => Some(*v),358			_ => None,359		}360	}361	pub fn as_null(&self) -> Option<()> {362		match self {363			Val::Null => Some(()),364			_ => None,365		}366	}367	pub fn as_str(&self) -> Option<IStr> {368		match self {369			Val::Str(s) => Some(s.clone()),370			_ => None,371		}372	}373	pub fn as_num(&self) -> Option<f64> {374		match self {375			Val::Num(n) => Some(*n),376			_ => None,377		}378	}379	pub fn as_arr(&self) -> Option<ArrValue> {380		match self {381			Val::Arr(a) => Some(a.clone()),382			_ => None,383		}384	}385	pub fn as_obj(&self) -> Option<ObjValue> {386		match self {387			Val::Obj(o) => Some(o.clone()),388			_ => None,389		}390	}391	pub fn as_func(&self) -> Option<FuncVal> {392		match self {393			Val::Func(f) => Some(f.clone()),394			_ => None,395		}396	}397398	/// Creates `Val::Num` after checking for numeric overflow.399	/// As numbers are `f64`, we can just check for their finity.400	pub fn new_checked_num(num: f64) -> Result<Self> {401		if num.is_finite() {402			Ok(Self::Num(num))403		} else {404			throw!(RuntimeError("overflow".into()))405		}406	}407408	pub fn try_cast_nullable_num(self, context: &'static str) -> Result<Option<f64>> {409		Ok(match self {410			Val::Null => None,411			Val::Num(num) => Some(num),412			_ => throw!(TypeMismatch(413				context,414				vec![ValType::Null, ValType::Num],415				self.value_type()416			)),417		})418	}419	pub const fn value_type(&self) -> ValType {420		match self {421			Self::Str(..) => ValType::Str,422			Self::Num(..) => ValType::Num,423			Self::Arr(..) => ValType::Arr,424			Self::Obj(..) => ValType::Obj,425			Self::Bool(_) => ValType::Bool,426			Self::Null => ValType::Null,427			Self::Func(..) => ValType::Func,428		}429	}430431	pub fn to_string(&self) -> Result<IStr> {432		Ok(match self {433			Self::Bool(true) => "true".into(),434			Self::Bool(false) => "false".into(),435			Self::Null => "null".into(),436			Self::Str(s) => s.clone(),437			v => manifest_json_ex(438				v,439				&ManifestJsonOptions {440					padding: "",441					mtype: ManifestType::ToString,442					newline: "\n",443					key_val_sep: ": ",444				},445			)?446			.into(),447		})448	}449450	/// Expects value to be object, outputs (key, manifested value) pairs451	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {452		let obj = match self {453			Self::Obj(obj) => obj,454			_ => throw!(MultiManifestOutputIsNotAObject),455		};456		let keys = obj.fields();457		let mut out = Vec::with_capacity(keys.len());458		for key in keys {459			let value = obj460				.get(key.clone())?461				.expect("item in object")462				.manifest(ty)?;463			out.push((key, value));464		}465		Ok(out)466	}467468	/// Expects value to be array, outputs manifested values469	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {470		let arr = match self {471			Self::Arr(a) => a,472			_ => throw!(StreamManifestOutputIsNotAArray),473		};474		let mut out = Vec::with_capacity(arr.len());475		for i in arr.iter() {476			out.push(i?.manifest(ty)?);477		}478		Ok(out)479	}480481	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {482		Ok(match ty {483			ManifestFormat::YamlStream(format) => {484				let arr = match self {485					Self::Arr(a) => a,486					_ => throw!(StreamManifestOutputIsNotAArray),487				};488				let mut out = String::new();489490				match format as &ManifestFormat {491					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),492					ManifestFormat::String => throw!(StreamManifestCannotNestString),493					_ => {}494				};495496				if !arr.is_empty() {497					for v in arr.iter() {498						out.push_str("---\n");499						out.push_str(&v?.manifest(format)?);500						out.push('\n');501					}502					out.push_str("...");503				}504505				out.into()506			}507			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,508			ManifestFormat::Json(padding) => self.to_json(*padding)?,509			ManifestFormat::ToString => self.to_string()?,510			ManifestFormat::String => match self {511				Self::Str(s) => s.clone(),512				_ => throw!(StringManifestOutputIsNotAString),513			},514		})515	}516517	/// For manifestification518	pub fn to_json(&self, padding: usize) -> Result<IStr> {519		manifest_json_ex(520			self,521			&ManifestJsonOptions {522				padding: &" ".repeat(padding),523				mtype: if padding == 0 {524					ManifestType::Minify525				} else {526					ManifestType::Manifest527				},528				newline: "\n",529				key_val_sep: ": ",530			},531		)532		.map(|s| s.into())533	}534535	/// Calls `std.manifestJson`536	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {537		manifest_json_ex(538			self,539			&ManifestJsonOptions {540				padding: &" ".repeat(padding),541				mtype: ManifestType::Std,542				newline: "\n",543				key_val_sep: ": ",544			},545		)546		.map(|s| s.into())547	}548549	pub fn to_yaml(&self, padding: usize) -> Result<IStr> {550		let padding = &" ".repeat(padding);551		manifest_yaml_ex(552			self,553			&ManifestYamlOptions {554				padding,555				arr_element_padding: padding,556				quote_keys: false,557			},558		)559		.map(|s| s.into())560	}561	pub fn into_indexable(self) -> Result<IndexableVal> {562		Ok(match self {563			Val::Str(s) => IndexableVal::Str(s),564			Val::Arr(arr) => IndexableVal::Arr(arr),565			_ => throw!(ValueIsNotIndexable(self.value_type())),566		})567	}568}569570const fn is_function_like(val: &Val) -> bool {571	matches!(val, Val::Func(_))572}573574/// Native implementation of `std.primitiveEquals`575pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {576	Ok(match (val_a, val_b) {577		(Val::Bool(a), Val::Bool(b)) => a == b,578		(Val::Null, Val::Null) => true,579		(Val::Str(a), Val::Str(b)) => a == b,580		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,581		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(582			"primitiveEquals operates on primitive types, got array".into(),583		)),584		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(585			"primitiveEquals operates on primitive types, got object".into(),586		)),587		(a, b) if is_function_like(a) && is_function_like(b) => {588			throw!(RuntimeError("cannot test equality of functions".into()))589		}590		(_, _) => false,591	})592}593594/// Native implementation of `std.equals`595pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {596	if val_a.value_type() != val_b.value_type() {597		return Ok(false);598	}599	match (val_a, val_b) {600		(Val::Arr(a), Val::Arr(b)) => {601			if ArrValue::ptr_eq(a, b) {602				return Ok(true);603			}604			if a.len() != b.len() {605				return Ok(false);606			}607			for (a, b) in a.iter().zip(b.iter()) {608				if !equals(&a?, &b?)? {609					return Ok(false);610				}611			}612			Ok(true)613		}614		(Val::Obj(a), Val::Obj(b)) => {615			if ObjValue::ptr_eq(a, b) {616				return Ok(true);617			}618			let fields = a.fields();619			if fields != b.fields() {620				return Ok(false);621			}622			for field in fields {623				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {624					return Ok(false);625				}626			}627			Ok(true)628		}629		(a, b) => Ok(primitive_equals(a, b)?),630	}631}
after · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::manifest::{3		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,4	},5	cc_ptr_eq,6	error::{Error::*, LocError},7	evaluate,8	function::{9		parse_default_function_call, parse_function_call, ArgsLike, Builtin, CallLocation,10		StaticBuiltin,11	},12	gc::TraceBox,13	throw, Context, ObjValue, Result,14};15use gcmodule::{Cc, Trace};16use jrsonnet_interner::IStr;17use jrsonnet_parser::{LocExpr, ParamsDesc};18use jrsonnet_types::ValType;19use std::{cell::RefCell, fmt::Debug, rc::Rc};2021pub trait LazyValValue: Trace {22	fn get(self: Box<Self>) -> Result<Val>;23}2425#[derive(Trace)]26enum LazyValInternals {27	Computed(Val),28	Errored(LocError),29	Waiting(TraceBox<dyn LazyValValue>),30	Pending,31}3233#[derive(Clone, Trace)]34pub struct LazyVal(Cc<RefCell<LazyValInternals>>);35impl LazyVal {36	pub fn new(f: TraceBox<dyn LazyValValue>) -> Self {37		Self(Cc::new(RefCell::new(LazyValInternals::Waiting(f))))38	}39	pub fn new_resolved(val: Val) -> Self {40		Self(Cc::new(RefCell::new(LazyValInternals::Computed(val))))41	}42	pub fn force(&self) -> Result<()> {43		self.evaluate()?;44		Ok(())45	}46	pub fn evaluate(&self) -> Result<Val> {47		match &*self.0.borrow() {48			LazyValInternals::Computed(v) => return Ok(v.clone()),49			LazyValInternals::Errored(e) => return Err(e.clone()),50			LazyValInternals::Pending => return Err(RecursiveLazyValueEvaluation.into()),51			_ => (),52		};53		let value = if let LazyValInternals::Waiting(value) =54			std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending)55		{56			value57		} else {58			unreachable!()59		};60		let new_value = match value.0.get() {61			Ok(v) => v,62			Err(e) => {63				*self.0.borrow_mut() = LazyValInternals::Errored(e.clone());64				return Err(e);65			}66		};67		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());68		Ok(new_value)69	}70}7172impl Debug for LazyVal {73	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {74		write!(f, "Lazy")75	}76}77impl PartialEq for LazyVal {78	fn eq(&self, other: &Self) -> bool {79		cc_ptr_eq(&self.0, &other.0)80	}81}8283#[derive(Debug, PartialEq, Trace)]84pub struct FuncDesc {85	pub name: IStr,86	pub ctx: Context,87	pub params: ParamsDesc,88	pub body: LocExpr,89}90impl FuncDesc {91	/// Create body context, but fill arguments without defaults with lazy error92	pub fn default_body_context(&self) -> Context {93		parse_default_function_call(self.ctx.clone(), &self.params)94	}9596	/// Create context, with which body code will run97	pub fn call_body_context(98		&self,99		call_ctx: Context,100		args: &dyn ArgsLike,101		tailstrict: bool,102	) -> Result<Context> {103		parse_function_call(call_ctx, self.ctx.clone(), &self.params, args, tailstrict)104	}105}106107#[derive(Trace, Clone)]108pub enum FuncVal {109	/// Plain function implemented in jsonnet110	Normal(Cc<FuncDesc>),111	/// Standard library function112	StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),113	/// User-provided function114	Builtin(Cc<TraceBox<dyn Builtin>>),115}116117impl Debug for FuncVal {118	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {119		match self {120			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),121			Self::StaticBuiltin(arg0) => {122				f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()123			}124			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),125		}126	}127}128129impl PartialEq for FuncVal {130	fn eq(&self, other: &Self) -> bool {131		match (self, other) {132			(Self::Normal(a), Self::Normal(b)) => a == b,133			(Self::StaticBuiltin(an), Self::StaticBuiltin(bn)) => std::ptr::eq(*an, *bn),134			(..) => false,135		}136	}137}138impl FuncVal {139	pub fn args_len(&self) -> usize {140		match self {141			Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),142			Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),143			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),144		}145	}146	pub fn name(&self) -> IStr {147		match self {148			Self::Normal(normal) => normal.name.clone(),149			Self::StaticBuiltin(builtin) => builtin.name().into(),150			Self::Builtin(builtin) => builtin.name().into(),151		}152	}153	pub fn evaluate(154		&self,155		call_ctx: Context,156		loc: CallLocation,157		args: &dyn ArgsLike,158		tailstrict: bool,159	) -> Result<Val> {160		match self {161			Self::Normal(func) => {162				let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;163				evaluate(body_ctx, &func.body)164			}165			Self::StaticBuiltin(b) => b.call(call_ctx, loc, args),166			Self::Builtin(b) => b.call(call_ctx, loc, args),167		}168	}169	pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result<Val> {170		self.evaluate(Context::default(), CallLocation::native(), args, true)171	}172}173174#[derive(Clone)]175pub enum ManifestFormat {176	YamlStream(Box<ManifestFormat>),177	Yaml(usize),178	Json(usize),179	ToString,180	String,181}182183#[derive(Debug, Clone, Trace)]184#[force_tracking]185pub enum ArrValue {186	Lazy(Cc<Vec<LazyVal>>),187	Eager(Cc<Vec<Val>>),188	Extended(Box<(Self, Self)>),189}190impl ArrValue {191	pub fn new_eager() -> Self {192		Self::Eager(Cc::new(Vec::new()))193	}194195	pub fn len(&self) -> usize {196		match self {197			Self::Lazy(l) => l.len(),198			Self::Eager(e) => e.len(),199			Self::Extended(v) => v.0.len() + v.1.len(),200		}201	}202203	pub fn is_empty(&self) -> bool {204		self.len() == 0205	}206207	pub fn get(&self, index: usize) -> Result<Option<Val>> {208		match self {209			Self::Lazy(vec) => {210				if let Some(v) = vec.get(index) {211					Ok(Some(v.evaluate()?))212				} else {213					Ok(None)214				}215			}216			Self::Eager(vec) => Ok(vec.get(index).cloned()),217			Self::Extended(v) => {218				let a_len = v.0.len();219				if a_len > index {220					v.0.get(index)221				} else {222					v.1.get(index - a_len)223				}224			}225		}226	}227228	pub fn get_lazy(&self, index: usize) -> Option<LazyVal> {229		match self {230			Self::Lazy(vec) => vec.get(index).cloned(),231			Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved),232			Self::Extended(v) => {233				let a_len = v.0.len();234				if a_len > index {235					v.0.get_lazy(index)236				} else {237					v.1.get_lazy(index - a_len)238				}239			}240		}241	}242243	pub fn evaluated(&self) -> Result<Cc<Vec<Val>>> {244		Ok(match self {245			Self::Lazy(vec) => {246				let mut out = Vec::with_capacity(vec.len());247				for item in vec.iter() {248					out.push(item.evaluate()?);249				}250				Cc::new(out)251			}252			Self::Eager(vec) => vec.clone(),253			Self::Extended(_v) => {254				let mut out = Vec::with_capacity(self.len());255				for item in self.iter() {256					out.push(item?);257				}258				Cc::new(out)259			}260		})261	}262263	pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {264		(0..self.len()).map(move |idx| match self {265			Self::Lazy(l) => l[idx].evaluate(),266			Self::Eager(e) => Ok(e[idx].clone()),267			Self::Extended(_) => self.get(idx).map(|e| e.unwrap()),268		})269	}270271	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = LazyVal> + '_ {272		(0..self.len()).map(move |idx| match self {273			Self::Lazy(l) => l[idx].clone(),274			Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()),275			Self::Extended(_) => self.get_lazy(idx).unwrap(),276		})277	}278279	pub fn reversed(self) -> Self {280		match self {281			Self::Lazy(vec) => {282				let mut out = (&vec as &Vec<_>).clone();283				out.reverse();284				Self::Lazy(Cc::new(out))285			}286			Self::Eager(vec) => {287				let mut out = (&vec as &Vec<_>).clone();288				out.reverse();289				Self::Eager(Cc::new(out))290			}291			Self::Extended(b) => Self::Extended(Box::new((b.1.reversed(), b.0.reversed()))),292		}293	}294295	pub fn map(self, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {296		let mut out = Vec::with_capacity(self.len());297298		for value in self.iter() {299			out.push(mapper(value?)?);300		}301302		Ok(Self::Eager(Cc::new(out)))303	}304305	pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {306		let mut out = Vec::with_capacity(self.len());307308		for value in self.iter() {309			let value = value?;310			if filter(&value)? {311				out.push(value);312			}313		}314315		Ok(Self::Eager(Cc::new(out)))316	}317318	pub fn ptr_eq(a: &Self, b: &Self) -> bool {319		match (a, b) {320			(Self::Lazy(a), Self::Lazy(b)) => cc_ptr_eq(a, b),321			(Self::Eager(a), Self::Eager(b)) => cc_ptr_eq(a, b),322			_ => false,323		}324	}325}326327impl From<Vec<LazyVal>> for ArrValue {328	fn from(v: Vec<LazyVal>) -> Self {329		Self::Lazy(Cc::new(v))330	}331}332333impl From<Vec<Val>> for ArrValue {334	fn from(v: Vec<Val>) -> Self {335		Self::Eager(Cc::new(v))336	}337}338339pub enum IndexableVal {340	Str(IStr),341	Arr(ArrValue),342}343344#[derive(Debug, Clone, Trace)]345pub enum Val {346	Bool(bool),347	Null,348	Str(IStr),349	Num(f64),350	Arr(ArrValue),351	Obj(ObjValue),352	Func(FuncVal),353}354355impl Val {356	pub fn as_bool(&self) -> Option<bool> {357		match self {358			Val::Bool(v) => Some(*v),359			_ => None,360		}361	}362	pub fn as_null(&self) -> Option<()> {363		match self {364			Val::Null => Some(()),365			_ => None,366		}367	}368	pub fn as_str(&self) -> Option<IStr> {369		match self {370			Val::Str(s) => Some(s.clone()),371			_ => None,372		}373	}374	pub fn as_num(&self) -> Option<f64> {375		match self {376			Val::Num(n) => Some(*n),377			_ => None,378		}379	}380	pub fn as_arr(&self) -> Option<ArrValue> {381		match self {382			Val::Arr(a) => Some(a.clone()),383			_ => None,384		}385	}386	pub fn as_obj(&self) -> Option<ObjValue> {387		match self {388			Val::Obj(o) => Some(o.clone()),389			_ => None,390		}391	}392	pub fn as_func(&self) -> Option<FuncVal> {393		match self {394			Val::Func(f) => Some(f.clone()),395			_ => None,396		}397	}398399	/// Creates `Val::Num` after checking for numeric overflow.400	/// As numbers are `f64`, we can just check for their finity.401	pub fn new_checked_num(num: f64) -> Result<Self> {402		if num.is_finite() {403			Ok(Self::Num(num))404		} else {405			throw!(RuntimeError("overflow".into()))406		}407	}408409	pub fn try_cast_nullable_num(self, context: &'static str) -> Result<Option<f64>> {410		Ok(match self {411			Val::Null => None,412			Val::Num(num) => Some(num),413			_ => throw!(TypeMismatch(414				context,415				vec![ValType::Null, ValType::Num],416				self.value_type()417			)),418		})419	}420	pub const fn value_type(&self) -> ValType {421		match self {422			Self::Str(..) => ValType::Str,423			Self::Num(..) => ValType::Num,424			Self::Arr(..) => ValType::Arr,425			Self::Obj(..) => ValType::Obj,426			Self::Bool(_) => ValType::Bool,427			Self::Null => ValType::Null,428			Self::Func(..) => ValType::Func,429		}430	}431432	pub fn to_string(&self) -> Result<IStr> {433		Ok(match self {434			Self::Bool(true) => "true".into(),435			Self::Bool(false) => "false".into(),436			Self::Null => "null".into(),437			Self::Str(s) => s.clone(),438			v => manifest_json_ex(439				v,440				&ManifestJsonOptions {441					padding: "",442					mtype: ManifestType::ToString,443					newline: "\n",444					key_val_sep: ": ",445				},446			)?447			.into(),448		})449	}450451	/// Expects value to be object, outputs (key, manifested value) pairs452	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {453		let obj = match self {454			Self::Obj(obj) => obj,455			_ => throw!(MultiManifestOutputIsNotAObject),456		};457		let keys = obj.fields();458		let mut out = Vec::with_capacity(keys.len());459		for key in keys {460			let value = obj461				.get(key.clone())?462				.expect("item in object")463				.manifest(ty)?;464			out.push((key, value));465		}466		Ok(out)467	}468469	/// Expects value to be array, outputs manifested values470	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {471		let arr = match self {472			Self::Arr(a) => a,473			_ => throw!(StreamManifestOutputIsNotAArray),474		};475		let mut out = Vec::with_capacity(arr.len());476		for i in arr.iter() {477			out.push(i?.manifest(ty)?);478		}479		Ok(out)480	}481482	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {483		Ok(match ty {484			ManifestFormat::YamlStream(format) => {485				let arr = match self {486					Self::Arr(a) => a,487					_ => throw!(StreamManifestOutputIsNotAArray),488				};489				let mut out = String::new();490491				match format as &ManifestFormat {492					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),493					ManifestFormat::String => throw!(StreamManifestCannotNestString),494					_ => {}495				};496497				if !arr.is_empty() {498					for v in arr.iter() {499						out.push_str("---\n");500						out.push_str(&v?.manifest(format)?);501						out.push('\n');502					}503					out.push_str("...");504				}505506				out.into()507			}508			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,509			ManifestFormat::Json(padding) => self.to_json(*padding)?,510			ManifestFormat::ToString => self.to_string()?,511			ManifestFormat::String => match self {512				Self::Str(s) => s.clone(),513				_ => throw!(StringManifestOutputIsNotAString),514			},515		})516	}517518	/// For manifestification519	pub fn to_json(&self, padding: usize) -> Result<IStr> {520		manifest_json_ex(521			self,522			&ManifestJsonOptions {523				padding: &" ".repeat(padding),524				mtype: if padding == 0 {525					ManifestType::Minify526				} else {527					ManifestType::Manifest528				},529				newline: "\n",530				key_val_sep: ": ",531			},532		)533		.map(|s| s.into())534	}535536	/// Calls `std.manifestJson`537	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {538		manifest_json_ex(539			self,540			&ManifestJsonOptions {541				padding: &" ".repeat(padding),542				mtype: ManifestType::Std,543				newline: "\n",544				key_val_sep: ": ",545			},546		)547		.map(|s| s.into())548	}549550	pub fn to_yaml(&self, padding: usize) -> Result<IStr> {551		let padding = &" ".repeat(padding);552		manifest_yaml_ex(553			self,554			&ManifestYamlOptions {555				padding,556				arr_element_padding: padding,557				quote_keys: false,558			},559		)560		.map(|s| s.into())561	}562	pub fn into_indexable(self) -> Result<IndexableVal> {563		Ok(match self {564			Val::Str(s) => IndexableVal::Str(s),565			Val::Arr(arr) => IndexableVal::Arr(arr),566			_ => throw!(ValueIsNotIndexable(self.value_type())),567		})568	}569}570571const fn is_function_like(val: &Val) -> bool {572	matches!(val, Val::Func(_))573}574575/// Native implementation of `std.primitiveEquals`576pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {577	Ok(match (val_a, val_b) {578		(Val::Bool(a), Val::Bool(b)) => a == b,579		(Val::Null, Val::Null) => true,580		(Val::Str(a), Val::Str(b)) => a == b,581		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,582		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(583			"primitiveEquals operates on primitive types, got array".into(),584		)),585		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(586			"primitiveEquals operates on primitive types, got object".into(),587		)),588		(a, b) if is_function_like(a) && is_function_like(b) => {589			throw!(RuntimeError("cannot test equality of functions".into()))590		}591		(_, _) => false,592	})593}594595/// Native implementation of `std.equals`596pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {597	if val_a.value_type() != val_b.value_type() {598		return Ok(false);599	}600	match (val_a, val_b) {601		(Val::Arr(a), Val::Arr(b)) => {602			if ArrValue::ptr_eq(a, b) {603				return Ok(true);604			}605			if a.len() != b.len() {606				return Ok(false);607			}608			for (a, b) in a.iter().zip(b.iter()) {609				if !equals(&a?, &b?)? {610					return Ok(false);611				}612			}613			Ok(true)614		}615		(Val::Obj(a), Val::Obj(b)) => {616			if ObjValue::ptr_eq(a, b) {617				return Ok(true);618			}619			let fields = a.fields();620			if fields != b.fields() {621				return Ok(false);622			}623			for field in fields {624				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {625					return Ok(false);626				}627			}628			Ok(true)629		}630		(a, b) => Ok(primitive_equals(a, b)?),631	}632}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,5 +1,5 @@
 use proc_macro2::TokenStream;
-use quote::{quote, quote_spanned};
+use quote::quote;
 use syn::{
 	parenthesized,
 	parse::{Parse, ParseStream},
@@ -7,8 +7,8 @@
 	punctuated::Punctuated,
 	spanned::Spanned,
 	token::Comma,
-	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, PatType,
-	Path, PathArguments, Result, Token, Type,
+	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
+	PathArguments, Result, ReturnType, Token, Type,
 };
 
 fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>
@@ -33,49 +33,42 @@
 	Ok(Some(attr))
 }
 
-fn is_location_arg(t: &PatType) -> bool {
-	t.attrs.iter().any(|a| a.path.is_ident("location"))
-}
-fn is_self_arg(t: &PatType) -> bool {
-	t.attrs.iter().any(|a| a.path.is_ident("self"))
+fn path_is(path: &Path, needed: &str) -> bool {
+	path.leading_colon.is_none()
+		&& path.segments.len() >= 1
+		&& path.segments.iter().last().unwrap().ident == needed
 }
 
-trait RetainHad<T> {
-	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;
-}
-impl<T> RetainHad<T> for Vec<T> {
-	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {
-		let before = self.len();
-		self.retain(h);
-		let after = self.len();
-		before != after
+fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {
+	match ty {
+		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {
+			let args = &path.path.segments.iter().last().unwrap().arguments;
+			Some(args)
+		}
+		_ => None,
 	}
 }
 
-fn extract_type_from_option(ty: &Type) -> Option<&Type> {
-	fn path_is_option(path: &Path) -> bool {
-		path.leading_colon.is_none()
-			&& path.segments.len() == 1
-			&& path.segments.iter().next().unwrap().ident == "Option"
-	}
-
-	match ty {
-		Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {
-			// Get the first segment of the path (there is only one, in fact: "Option"):
-			let type_params = &typepath.path.segments.iter().next().unwrap().arguments;
-			// It should have only on angle-bracketed param ("<String>"):
-			let generic_arg = match type_params {
-				PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),
-				_ => panic!("missing option generic"),
-			};
-			// This argument must be a type:
-			match generic_arg {
-				GenericArgument::Type(ty) => Some(ty),
-				_ => panic!("option generic should be a type"),
+fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {
+	Ok(if let Some(args) = type_is_path(ty, "Option") {
+		// It should have only on angle-bracketed param ("<String>"):
+		let generic_arg = match args {
+			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),
+			_ => return Err(Error::new(args.span(), "missing option generic")),
+		};
+		// This argument must be a type:
+		match generic_arg {
+			GenericArgument::Type(ty) => Some(ty),
+			_ => {
+				return Err(Error::new(
+					generic_arg.span(),
+					"option generic should be a type",
+				))
 			}
 		}
-		_ => None,
-	}
+	} else {
+		None
+	})
 }
 
 struct Field {
@@ -101,7 +94,7 @@
 
 struct EmptyAttr;
 impl Parse for EmptyAttr {
-	fn parse(input: ParseStream) -> Result<Self> {
+	fn parse(_input: ParseStream) -> Result<Self> {
 		Ok(Self)
 	}
 }
@@ -124,107 +117,158 @@
 	}
 }
 
+enum ArgInfo {
+	Normal {
+		ty: Type,
+		is_option: bool,
+		name: String,
+		// ident: Ident,
+	},
+	Lazy {
+		is_option: bool,
+		name: String,
+	},
+	Location,
+	This,
+}
+
+impl ArgInfo {
+	fn parse(arg: &FnArg) -> Result<Self> {
+		let typed = match arg {
+			FnArg::Receiver(_) => unreachable!(),
+			FnArg::Typed(a) => a,
+		};
+		let ident = match &typed.pat as &Pat {
+			Pat::Ident(i) => i.ident.clone(),
+			_ => {
+				return Err(Error::new(
+					typed.pat.span(),
+					"arg should be plain identifier",
+				))
+			}
+		};
+		let ty = &typed.ty as &Type;
+		if type_is_path(&ty, "CallLocation").is_some() {
+			return Ok(Self::Location);
+		} else if type_is_path(&ty, "Self").is_some() {
+			return Ok(Self::This);
+		} else if type_is_path(&ty, "LazyVal").is_some() {
+			return Ok(Self::Lazy {
+				is_option: false,
+				name: ident.to_string(),
+			});
+		}
+
+		let (is_option, ty) = if let Some(ty) = extract_type_from_option(&ty)? {
+			if type_is_path(&ty, "LazyVal").is_some() {
+				return Ok(Self::Lazy {
+					is_option: true,
+					name: ident.to_string(),
+				});
+			}
+
+			(true, ty.clone())
+		} else {
+			(false, ty.clone())
+		};
+
+		Ok(Self::Normal {
+			ty,
+			is_option,
+			name: ident.to_string(),
+			// ident,
+		})
+	}
+}
+
 #[proc_macro_attribute]
 pub fn builtin(
 	attr: proc_macro::TokenStream,
 	item: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
-	let attrs = parse_macro_input!(attr as BuiltinAttrs);
-	let mut fun: ItemFn = parse_macro_input!(item);
+	let attr = parse_macro_input!(attr as BuiltinAttrs);
+	let item: ItemFn = parse_macro_input!(item);
+
+	match builtin_inner(attr, item) {
+		Ok(v) => v.into(),
+		Err(e) => e.into_compile_error().into(),
+	}
+}
 
+fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {
 	let result = match fun.sig.output {
-		syn::ReturnType::Default => {
-			return quote_spanned! { fun.sig.span() =>
-				compile_error!("builtins should return something");
-			}
-			.into()
+		ReturnType::Default => {
+			return Err(Error::new(
+				fun.sig.span(),
+				"builtin should return something",
+			))
 		}
-		syn::ReturnType::Type(_, ref ty) => ty.clone(),
+		ReturnType::Type(_, ref ty) => ty.clone(),
 	};
 
-	let params = fun
+	let args = fun
 		.sig
 		.inputs
 		.iter()
-		.map(|i| match i {
-			FnArg::Receiver(_) => unreachable!(),
-			FnArg::Typed(t) => t,
-		})
-		.filter(|a| !is_location_arg(a) && !is_self_arg(a))
-		.map(|t| {
-			let ident = match &t.pat as &Pat {
-				Pat::Ident(i) => i.ident.to_string(),
-				_ => {
-					return quote_spanned! { t.pat.span() =>
-						compile_error!("args should be plain identifiers")
-					}
-					.into()
-				}
-			};
-			let optional = extract_type_from_option(&t.ty).is_some();
-			quote! {
-				BuiltinParam {
-					name: std::borrow::Cow::Borrowed(#ident),
-					has_default: #optional,
-				}
+		.map(|a| ArgInfo::parse(a))
+		.collect::<Result<Vec<_>>>()?;
+
+	let params_desc = args.iter().flat_map(|a| match a {
+		ArgInfo::Normal {
+			is_option, name, ..
+		}
+		| ArgInfo::Lazy { is_option, name } => Some(quote! {
+			BuiltinParam {
+				name: std::borrow::Cow::Borrowed(#name),
+				has_default: #is_option,
 			}
-		})
-		.collect::<Vec<_>>();
+		}),
+		ArgInfo::Location => None,
+		ArgInfo::This => None,
+	});
 
-	let args = fun
-		.sig
-		.inputs
-		.iter_mut()
-		.map(|i| match i {
-			FnArg::Receiver(_) => unreachable!(),
-			FnArg::Typed(t) => t,
-		})
-		.map(|t| {
-			if t.attrs.retain_had(|a| !a.path.is_ident("location")) {
-				quote! {{
-					loc
+	let pass = args.iter().map(|a| match a {
+		ArgInfo::Normal {
+			ty,
+			is_option,
+			name,
+			// ident,
+		} => {
+			let eval = quote! {::jrsonnet_evaluator::push_description_frame(
+				|| format!("argument <{}> evaluation", #name),
+				|| <#ty>::try_from(value.evaluate()?),
+			)?};
+			if *is_option {
+				quote! {if let Some(value) = parsed.get(#name) {
+					Some(#eval)
+				} else {
+					None
 				}}
-			} else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {
+			} else {
 				quote! {{
-					self
+					let value = parsed.get(#name).expect("args shape is checked");
+					#eval
 				}}
-			} else {
-				let ident = match &t.pat as &Pat {
-					Pat::Ident(i) => i.ident.to_string(),
-					_ => {
-						return quote_spanned! { t.pat.span() =>
-							compile_error!("args should be plain identifiers")
-						}
-						.into()
-					}
-				};
-				let ty = &t.ty;
-				if let Some(opt_ty) = extract_type_from_option(&t.ty) {
-					quote! {{
-						if let Some(value) = parsed.get(#ident) {
-							Some(::jrsonnet_evaluator::push_description_frame(
-								|| format!("argument <{}> evaluation", #ident),
-								|| <#opt_ty>::try_from(value.evaluate()?),
-							)?)
-						} else {
-							None
-						}
-					}}
+			}
+		}
+		ArgInfo::Lazy { is_option, name } => {
+			if *is_option {
+				quote! {if let Some(value) = parsed.get(#name) {
+					Some(value.clone())
 				} else {
-					quote! {{
-						let value = parsed.get(#ident).unwrap();
-
-						::jrsonnet_evaluator::push_description_frame(
-							|| format!("argument <{}> evaluation", #ident),
-							|| <#ty>::try_from(value.evaluate()?),
-						)?
-					}}
+					None
+				}}
+			} else {
+				quote! {
+					parsed.get(#name).expect("args shape is correct").clone()
 				}
 			}
-		})
-		.collect::<Vec<_>>();
+		}
+		ArgInfo::Location => quote! {location},
+		ArgInfo::This => quote! {self},
+	});
 
-	let fields = attrs.fields.iter().map(|field| {
+	let fields = attr.fields.iter().map(|field| {
 		let name = &field.name;
 		let ty = &field.ty;
 		quote! {
@@ -234,7 +278,7 @@
 
 	let name = &fun.sig.ident;
 	let vis = &fun.vis;
-	let static_ext = if attrs.fields.is_empty() {
+	let static_ext = if attr.fields.is_empty() {
 		quote! {
 			impl #name {
 				pub const INST: &'static dyn StaticBuiltin = &#name {};
@@ -244,13 +288,13 @@
 	} else {
 		quote! {}
 	};
-	let static_derive_copy = if attrs.fields.is_empty() {
+	let static_derive_copy = if attr.fields.is_empty() {
 		quote! {, Copy}
 	} else {
 		quote! {}
 	};
 
-	(quote! {
+	Ok(quote! {
 		#fun
 		#[doc(hidden)]
 		#[allow(non_camel_case_types)]
@@ -260,12 +304,12 @@
 		}
 		const _: () = {
 			use ::jrsonnet_evaluator::{
-				function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
+				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
 				error::Result, Context,
 				parser::ExprLocation,
 			};
 			const PARAMS: &'static [BuiltinParam] = &[
-				#(#params),*
+				#(#params_desc),*
 			];
 
 			#static_ext
@@ -279,17 +323,16 @@
 				fn params(&self) -> &[BuiltinParam] {
 					PARAMS
 				}
-				fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {
+				fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
 					let parsed = parse_builtin_call(context, &PARAMS, args, false)?;
 
-					let result: #result = #name(#(#args),*);
+					let result: #result = #name(#(#pass),*);
 					let result = result?;
 					result.try_into()
 				}
 			}
 		};
 	})
-	.into()
 }
 
 #[derive(Default)]
@@ -366,15 +409,6 @@
 		)
 	}
 
-	fn expand_shallow_field(&self) -> Option<TokenStream> {
-		if self.is_option() {
-			return None;
-		}
-		let name = self.name()?;
-		Some(quote! {
-			(#name, ComplexValType::Any)
-		})
-	}
 	fn expand_field(&self) -> Option<TokenStream> {
 		if self.is_option() {
 			return None;
@@ -450,7 +484,7 @@
 	}
 
 	fn as_option(&self) -> Option<&Type> {
-		extract_type_from_option(&self.0.ty)
+		extract_type_from_option(&self.0.ty).unwrap()
 	}
 	fn is_option(&self) -> bool {
 		self.as_option().is_some()
@@ -460,25 +494,25 @@
 #[proc_macro_derive(Typed, attributes(typed))]
 pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
 	let input = parse_macro_input!(item as DeriveInput);
+
+	match derive_typed_inner(input) {
+		Ok(v) => v.into(),
+		Err(e) => e.to_compile_error().into(),
+	}
+}
+
+fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {
 	let data = match &input.data {
 		syn::Data::Struct(s) => s,
-		_ => {
-			return syn::Error::new(input.span(), "only structs supported")
-				.to_compile_error()
-				.into()
-		}
+		_ => return Err(Error::new(input.span(), "only structs supported")),
 	};
 
 	let ident = &input.ident;
-	let fields = match data
+	let fields = data
 		.fields
 		.iter()
 		.map(TypedField::try_new)
-		.collect::<Result<Vec<_>>>()
-	{
-		Ok(v) => v,
-		Err(e) => return e.to_compile_error().into(),
-	};
+		.collect::<Result<Vec<_>>>()?;
 
 	let typed = {
 		let fields = fields
@@ -499,7 +533,7 @@
 	let fields_parse = fields.iter().map(TypedField::expand_parse);
 	let fields_serialize = fields.iter().map(TypedField::expand_serialize);
 
-	quote! {
+	Ok(quote! {
 		const _: () = {
 			use ::jrsonnet_evaluator::{
 				typed::{ComplexValType, Typed, TypedObj, CheckType},
@@ -540,6 +574,5 @@
 			}
 			()
 		};
-	}
-	.into()
+	})
 }