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

difftreelog

Merge pull request #29 from CertainLach/type-safety

Yaroslav Bolyukin2021-01-25parents: #847c11f #c93dcb4.patch.diff
in: master
Jsonnet type system

36 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -167,26 +167,34 @@
  "bincode",
  "closure",
  "indexmap",
+ "jrsonnet-interner",
  "jrsonnet-parser",
  "jrsonnet-stdlib",
+ "jrsonnet-types",
  "md5",
  "pathdiff",
  "rustc-hash",
  "serde",
  "serde_json",
- "structdump",
  "thiserror",
 ]
 
 [[package]]
+name = "jrsonnet-interner"
+version = "0.3.3"
+dependencies = [
+ "rustc-hash",
+ "serde",
+]
+
+[[package]]
 name = "jrsonnet-parser"
 version = "0.3.3"
 dependencies = [
+ "jrsonnet-interner",
  "jrsonnet-stdlib",
  "peg",
  "serde",
- "structdump",
- "structdump-derive",
  "unescape",
 ]
 
@@ -195,10 +203,18 @@
 version = "0.3.3"
 
 [[package]]
+name = "jrsonnet-types"
+version = "0.3.3"
+dependencies = [
+ "peg",
+]
+
+[[package]]
 name = "jsonnet"
 version = "0.3.3"
 dependencies = [
  "jrsonnet-evaluator",
+ "jrsonnet-interner",
  "jrsonnet-parser",
 ]
 
@@ -370,23 +386,6 @@
 version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
-[[package]]
-name = "structdump"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e16ec33a0342fdb67d13913b4ffae6527ebccfa04b5d7da174bdc7a31db29b8"
-
-[[package]]
-name = "structdump-derive"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06c337fdc077e02ccbfcc62af0090564a4af342975c3b7be09705efab90c1888"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
 
 [[package]]
 name = "syn"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,11 +1,13 @@
 [workspace]
 members = [
+	"crates/jrsonnet-interner",
 	"crates/jrsonnet-parser",
 	"crates/jrsonnet-evaluator",
 	"crates/jrsonnet-stdlib",
 	"crates/jrsonnet-cli",
+	"crates/jrsonnet-types",
 	"bindings/jsonnet",
-	"cmds/jrsonnet"
+	"cmds/jrsonnet",
 ]
 
 [profile.test]
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -5,6 +5,7 @@
 edition = "2018"
 
 [dependencies]
+jrsonnet-interner = { path = "../../crates/jrsonnet-interner", version = "0.3.3" }
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.3" }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.3" }
 
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -4,6 +4,7 @@
 	error::{Error::*, Result},
 	throw, EvaluationState, ImportResolver,
 };
+use jrsonnet_interner::IStr;
 use std::{
 	any::Any,
 	cell::RefCell,
@@ -30,7 +31,7 @@
 	cb: JsonnetImportCallback,
 	ctx: *mut c_void,
 
-	out: RefCell<HashMap<PathBuf, Rc<str>>>,
+	out: RefCell<HashMap<PathBuf, IStr>>,
 }
 impl ImportResolver for CallbackImportResolver {
 	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {
@@ -75,7 +76,7 @@
 
 		Ok(Rc::new(found_here_buf))
 	}
-	fn load_file_contents(&self, resolved: &PathBuf) -> Result<Rc<str>> {
+	fn load_file_contents(&self, resolved: &PathBuf) -> Result<IStr> {
 		Ok(self.out.borrow().get(resolved).unwrap().clone())
 	}
 	unsafe fn as_any(&self) -> &dyn Any {
@@ -124,7 +125,7 @@
 			throw!(ImportFileNotFound(from.clone(), path.clone()))
 		}
 	}
-	fn load_file_contents(&self, id: &PathBuf) -> Result<Rc<str>> {
+	fn load_file_contents(&self, id: &PathBuf) -> Result<IStr> {
 		let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.clone()))?;
 		let mut out = String::new();
 		file.read_to_string(&mut out)
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -8,6 +8,7 @@
 
 use import::NativeImportResolver;
 use jrsonnet_evaluator::{EvaluationState, ManifestFormat, Val};
+use jrsonnet_interner::IStr;
 use std::{
 	alloc::Layout,
 	ffi::{CStr, CString},
@@ -161,7 +162,7 @@
 	})
 }
 
-fn multi_to_raw(multi: Vec<(Rc<str>, Rc<str>)>) -> *const c_char {
+fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char {
 	let mut out = Vec::new();
 	for (i, (k, v)) in multi.iter().enumerate() {
 		if i != 0 {
@@ -237,7 +238,7 @@
 	})
 }
 
-fn stream_to_raw(multi: Vec<Rc<str>>) -> *const c_char {
+fn stream_to_raw(multi: Vec<IStr>) -> *const c_char {
 	let mut out = Vec::new();
 	for (i, v) in multi.iter().enumerate() {
 		if i != 0 {
modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -35,7 +35,7 @@
 
 	vm.add_native(
 		name,
-		Rc::new(NativeCallback::new(params, move |args| {
+		Rc::new(NativeCallback::new(params, move |_caller, args| {
 			let mut n_args = Vec::new();
 			for a in args {
 				n_args.push(Some(Box::new(a.clone())));
modifiedbindings/jsonnet/src/val_extract.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_extract.rs
+++ b/bindings/jsonnet/src/val_extract.rs
@@ -9,7 +9,7 @@
 
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_string(_vm: &EvaluationState, v: &Val) -> *mut c_char {
-	match v.unwrap_if_lazy().unwrap() {
+	match v {
 		Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(),
 		_ => std::ptr::null_mut(),
 	}
@@ -20,9 +20,9 @@
 	v: &Val,
 	out: &mut c_double,
 ) -> c_int {
-	match v.unwrap_if_lazy().unwrap() {
+	match v {
 		Val::Num(n) => {
-			*out = n;
+			*out = *n;
 			1
 		}
 		_ => 0,
@@ -30,7 +30,7 @@
 }
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_bool(_vm: &EvaluationState, v: &Val) -> c_int {
-	match v.unwrap_if_lazy().unwrap() {
+	match v {
 		Val::Bool(false) => 0,
 		Val::Bool(true) => 1,
 		_ => 2,
@@ -38,7 +38,7 @@
 }
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_null(_vm: &EvaluationState, v: &Val) -> c_int {
-	match v.unwrap_if_lazy().unwrap() {
+	match v {
 		Val::Null => 1,
 		_ => 0,
 	}
modifiedbindings/jsonnet/src/val_make.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_make.rs
+++ b/bindings/jsonnet/src/val_make.rs
@@ -1,6 +1,6 @@
 //! Create values in VM
 
-use jrsonnet_evaluator::{EvaluationState, ObjValue, Val};
+use jrsonnet_evaluator::{ArrValue, EvaluationState, ObjValue, Val};
 use std::{
 	ffi::CStr,
 	os::raw::{c_char, c_double, c_int},
@@ -38,7 +38,7 @@
 
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_array(_vm: &EvaluationState) -> *mut Val {
-	Box::into_raw(Box::new(Val::Arr(Rc::new(Vec::new()))))
+	Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Rc::new(Vec::new())))))
 }
 
 #[no_mangle]
modifiedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -2,7 +2,9 @@
 //! Only tested with variables, which haven't altered by code before appearing here
 //! In jrsonnet every value is immutable, and this code is probally broken
 
-use jrsonnet_evaluator::{EvaluationState, LazyBinding, LazyVal, ObjMember, ObjValue, Val};
+use jrsonnet_evaluator::{
+	ArrValue, EvaluationState, LazyBinding, LazyVal, ObjMember, ObjValue, Val,
+};
 use jrsonnet_parser::Visibility;
 use std::{collections::HashMap, ffi::CStr, os::raw::c_char, rc::Rc};
 
@@ -12,14 +14,17 @@
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_json_array_append(
 	_vm: &EvaluationState,
-	arr: *mut Val,
+	arr: &mut Val,
 	val: &Val,
 ) {
-	match *Box::from_raw(arr) {
+	match arr {
 		Val::Arr(old) => {
-			let mut new = Rc::try_unwrap(old).expect("arr with no refs");
-			new.push(val.clone());
-			*arr = Val::Arr(Rc::new(new));
+			let mut new = Vec::new();
+			for item in old.iter_lazy() {
+				new.push(item);
+			}
+			new.push(LazyVal::new_resolved(val.clone()));
+			*arr = Val::Arr(ArrValue::Lazy(Rc::new(new)));
 		}
 		_ => panic!("should receive array"),
 	}
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -12,8 +12,6 @@
 serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]
 # Allow to convert Val into serde_json::Value and backwards
 serde-json = ["serde", "serde_json"]
-# Same as above, but with generated code instead of serde. Reduces memory usage, but increases binary size and compilation time
-codegenerated-stdlib = []
 # Replace some standard library functions with faster implementations (I.e manifestJsonEx)
 # Library works fine without this feature, but requires more memory and time for std function calls
 faster = []
@@ -24,8 +22,10 @@
 unstable = []
 
 [dependencies]
+jrsonnet-interner = { path = "../jrsonnet-interner" }
 jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.3.3" }
 jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }
+jrsonnet-types = { path = "../jrsonnet-types", version = "0.3.3" }
 pathdiff = "0.2.0"
 
 closure = "0.3.0"
@@ -57,8 +57,7 @@
 optional = true
 
 [build-dependencies]
-jrsonnet-parser = { path = "../jrsonnet-parser", features = ["dump", "serialize", "deserialize"], version = "0.3.3" }
+jrsonnet-parser = { path = "../jrsonnet-parser", features = ["serialize", "deserialize"], version = "0.3.3" }
 jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }
-structdump = "0.1.2"
 serde = "1.0"
 bincode = "1.3.1"
modifiedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -10,7 +10,6 @@
 	path::{Path, PathBuf},
 	rc::Rc,
 };
-use structdump::CodegenResult;
 
 fn main() {
 	let parsed = parse(
@@ -36,11 +35,12 @@
 									name: FieldName::Fixed(name),
 									..
 								})
-								if **name == *"join" || **name == *"manifestJsonEx" ||
-								**name == *"escapeStringJson" || **name == *"equals" ||
-								**name == *"base64" || **name == *"foldl" || **name == *"foldr" ||
-								**name == *"sortImpl" || **name == *"format" || **name == *"range" ||
-								**name == *"reverse" || **name == *"slice" || **name == *"mod"
+								if name == "join" || name == "manifestJsonEx" ||
+								name == "escapeStringJson" || name == "equals" ||
+								name == "base64" || name == "foldl" || name == "foldr" ||
+								name == "sortImpl" || name == "format" || name == "range" ||
+								name == "reverse" || name == "slice" || name == "mod" ||
+								name == "strReplace"
 							)
 						})
 						.collect(),
@@ -52,15 +52,6 @@
 	} else {
 		parsed
 	};
-	{
-		let mut codegen = CodegenResult::default();
-		let code = codegen.codegen(&parsed);
-
-		let out_dir = env::var("OUT_DIR").unwrap();
-		let dest_path = Path::new(&out_dir).join("stdlib.rs");
-		let mut f = File::create(&dest_path).unwrap();
-		f.write_all(&code.as_bytes()).unwrap();
-	}
 	{
 		let out_dir = env::var("OUT_DIR").unwrap();
 		let dest_path = Path::new(&out_dir).join("stdlib.bincode");
modifiedcrates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/format.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/format.rs
@@ -1,7 +1,9 @@
 //! faster std.format impl
 #![allow(clippy::too_many_arguments)]
 
-use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType};
+use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val};
+use jrsonnet_interner::IStr;
+use jrsonnet_types::ValType;
 use thiserror::Error;
 
 #[derive(Debug, Clone, Error)]
@@ -19,7 +21,7 @@
 	#[error("mapping keys required")]
 	MappingKeysRequired,
 	#[error("no such format field: {0}")]
-	NoSuchFormatField(Rc<str>),
+	NoSuchFormatField(IStr),
 }
 
 impl From<FormatError> for LocError {
@@ -28,7 +30,6 @@
 	}
 }
 
-use std::rc::Rc;
 use FormatError::*;
 
 type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
@@ -573,7 +574,7 @@
 				);
 			}
 		}
-		ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {
+		ConvTypeV::Char => match value.clone() {
 			Val::Num(n) => tmp_out.push(
 				std::char::from_u32(n as u32)
 					.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
@@ -590,7 +591,7 @@
 				throw!(TypeMismatch(
 					"%c requires number/string",
 					vec![ValType::Num, ValType::Str],
-					value.value_type()?,
+					value.value_type(),
 				));
 			}
 		},
@@ -679,7 +680,7 @@
 			}
 			Element::Code(c) => {
 				// TODO: Operate on ref
-				let f: Rc<str> = c.mkey.into();
+				let f: IStr = c.mkey.into();
 				let width = match c.width {
 					Width::Star => {
 						throw!(CannotUseStarWidthWithObject);
modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -33,16 +33,16 @@
 ) -> Result<()> {
 	use std::fmt::Write;
 	let mtype = options.mtype;
-	match val.unwrap_if_lazy()? {
+	match val {
 		Val::Bool(v) => {
-			if v {
+			if *v {
 				buf.push_str("true");
 			} else {
 				buf.push_str("false");
 			}
 		}
 		Val::Null => buf.push_str("null"),
-		Val::Str(s) => buf.push_str(&escape_string_json(&s)),
+		Val::Str(s) => buf.push_str(&escape_string_json(s)),
 		Val::Num(n) => write!(buf, "{}", n).unwrap(),
 		Val::Arr(items) => {
 			buf.push('[');
@@ -63,7 +63,7 @@
 						}
 					}
 					buf.push_str(cur_padding);
-					manifest_json_ex_buf(item, buf, cur_padding, options)?;
+					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;
 				}
 				cur_padding.truncate(old_len);
 
@@ -118,7 +118,6 @@
 			buf.push('}');
 		}
 		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,28 +1,31 @@
 use crate::{
 	equals,
 	error::{Error::*, Result},
-	evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val,
-	ValType,
+	parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, FuncVal, LazyVal,
+	Val,
 };
 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};
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{ArgsDesc, BinaryOpType, ExprLocation};
+use jrsonnet_types::ty;
+use std::{collections::HashMap, path::PathBuf, rc::Rc};
 
 pub mod stdlib;
 pub use stdlib::*;
 
+use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+
 pub mod format;
 pub mod manifest;
 pub mod sort;
 
-fn std_format(str: Rc<str>, vals: Val) -> Result<Val> {
+fn std_format(str: IStr, vals: Val) -> Result<Val> {
 	push(
-		&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)),
+		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::Arr(vals) => Val::Str(format_arr(&str, &vals.evaluated()?)?.into()),
 				Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),
 				o => Val::Str(format_arr(&str, &[o])?.into()),
 			})
@@ -30,379 +33,566 @@
 	)
 }
 
-#[allow(clippy::cognitive_complexity)]
-pub fn call_builtin(
+type Builtin = fn(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val>;
+
+type BuiltinsType = HashMap<Box<str>, Builtin>;
+
+thread_local! {
+	static BUILTINS: BuiltinsType = {
+		[
+			("length".into(), builtin_length as Builtin),
+			("type".into(), builtin_type),
+			("makeArray".into(), builtin_make_array),
+			("codepoint".into(), builtin_codepoint),
+			("objectFieldsEx".into(), builtin_object_fields_ex),
+			("objectHasEx".into(), builtin_object_has_ex),
+			("slice".into(), builtin_slice),
+			("primitiveEquals".into(), builtin_primitive_equals),
+			("equals".into(), builtin_equals),
+			("modulo".into(), builtin_modulo),
+			("mod".into(), builtin_mod),
+			("floor".into(), builtin_floor),
+			("log".into(), builtin_log),
+			("pow".into(), builtin_pow),
+			("extVar".into(), builtin_ext_var),
+			("native".into(), builtin_native),
+			("filter".into(), builtin_filter),
+			("foldl".into(), builtin_foldl),
+			("foldr".into(), builtin_foldr),
+			("sortImpl".into(), builtin_sort_impl),
+			("format".into(), builtin_format),
+			("range".into(), builtin_range),
+			("char".into(), builtin_char),
+			("encodeUTF8".into(), builtin_encode_utf8),
+			("md5".into(), builtin_md5),
+			("base64".into(), builtin_base64),
+			("trace".into(), builtin_trace),
+			("join".into(), builtin_join),
+			("escapeStringJson".into(), builtin_escape_string_json),
+			("manifestJsonEx".into(), builtin_manifest_json_ex),
+			("reverse".into(), builtin_reverse),
+			("id".into(), builtin_id),
+			("strReplace".into(), builtin_str_replace),
+		].iter().cloned().collect()
+	};
+}
+
+fn builtin_length(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "length", args, 1, [
+		0, x: ty!((string | object | array));
+	], {
+		Ok(match x {
+			Val::Str(n) => Val::Num(n.chars().count() as f64),
+			Val::Arr(a) => Val::Num(a.len() as f64),
+			Val::Obj(o) => Val::Num(
+				o.fields_visibility()
+					.into_iter()
+					.filter(|(_k, v)| *v)
+					.count() as f64,
+			),
+			_ => unreachable!(),
+		})
+	})
+}
+
+fn builtin_type(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "type", args, 1, [
+		0, x: ty!(any);
+	], {
+		Ok(Val::Str(x.value_type().name().into()))
+	})
+}
+
+fn builtin_make_array(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "makeArray", args, 2, [
+		0, sz: ty!(BoundedNumber<(Some(0.0)), (None)>) => Val::Num;
+		1, func: ty!(function) => Val::Func;
+	], {
+		let mut out = Vec::with_capacity(sz as usize);
+		for i in 0..sz as usize {
+			out.push(LazyVal::new_resolved(func.evaluate_values(
+				Context::new(),
+				&[Val::Num(i as f64)]
+			)?))
+		}
+		Ok(Val::Arr(out.into()))
+	})
+}
+
+fn builtin_codepoint(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "codepoint", args, 1, [
+		0, str: ty!(char) => Val::Str;
+	], {
+		Ok(Val::Num(str.chars().next().unwrap() as u32 as f64))
+	})
+}
+
+fn builtin_object_fields_ex(
 	context: Context,
-	loc: &Option<ExprLocation>,
-	name: &str,
+	_loc: Option<&ExprLocation>,
 	args: &ArgsDesc,
 ) -> Result<Val> {
-	Ok(match name as &str {
-		// arr/string/function
-		"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
-		"type" => parse_args!(context, "std.type", args, 1, [
-			0, x, vec![];
-		], {
-			Ok(Val::Str(x.value_type()?.name().into()))
-		})?,
-		// length, idx=>any
-		"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
-		"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
-		"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()
+	parse_args!(context, "objectFieldsEx", args, 2, [
+		0, obj: ty!(object) => Val::Obj;
+		1, inc_hidden: ty!(boolean) => Val::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(out.into_iter().map(Val::Str).collect::<Vec<_>>().into()))
+	})
+}
+
+fn builtin_object_has_ex(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "objectHasEx", args, 3, [
+		0, obj: ty!(object) => Val::Obj;
+		1, f: ty!(string) => Val::Str;
+		2, inc_hidden: ty!(boolean) => Val::Bool;
+	], {
+		Ok(Val::Bool(
+			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
-		"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),
-			))
-		})?,
+				.any(|(k, _v)| *k == *f),
+		))
+	})
+}
 
-		// faster
-		"slice" => parse_args!(context, "slice", args, 4, [
-			0, indexable: [Val::Str | Val::Arr], vec![ValType::Str, ValType::Arr];
-			1, index, vec![ValType::Num, ValType::Null];
-			2, end, vec![ValType::Num, ValType::Null];
-			3, step, vec![ValType::Num, ValType::Null];
-		], {
-			let index = match index {
-				Val::Num(v) => v as usize,
-				Val::Null => 0,
-				_ => unreachable!(),
-			};
-			let end = match end {
-				Val::Num(v) => v as usize,
-				Val::Null => match &indexable {
-					Val::Str(s) => s.chars().count(),
-					Val::Arr(v) => v.len(),
-					_ => unreachable!()
-				},
+// faster
+fn builtin_slice(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "slice", args, 4, [
+		0, indexable: ty!((string | array));
+		1, index: ty!((number | null));
+		2, end: ty!((number | null));
+		3, step: ty!((number | null));
+	], {
+		let index = match index {
+			Val::Num(v) => v as usize,
+			Val::Null => 0,
+			_ => unreachable!(),
+		};
+		let end = match end {
+			Val::Num(v) => v as usize,
+			Val::Null => match &indexable {
+				Val::Str(s) => s.chars().count(),
+				Val::Arr(v) => v.len(),
 				_ => unreachable!()
-			};
-			let step = match step {
-				Val::Num(v) => v as usize,
-				Val::Null => 1,
-				_ => unreachable!()
-			};
-			match &indexable {
-				Val::Str(s) => {
-					Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::<String>()).into()))
-				}
-				Val::Arr(arr) => {
-					Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).cloned().collect::<Vec<Val>>()).into()))
-				}
-				_ => unreachable!()
+			},
+			_ => unreachable!()
+		};
+		let step = match step {
+			Val::Num(v) => v as usize,
+			Val::Null => 1,
+			_ => unreachable!()
+		};
+		match &indexable {
+			Val::Str(s) => {
+				Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::<String>()).into()))
 			}
-		})?,
-		"primitiveEquals" => parse_args!(context, "std.primitiveEquals", args, 2, [
-			0, a, vec![];
-			1, b, vec![];
-		], {
-			Ok(Val::Bool(primitive_equals(&a, &b)?))
-		})?,
-		// faster
-		"equals" => parse_args!(context, "std.equals", args, 2, [
-			0, a, vec![];
-			1, b, vec![];
-		], {
-			Ok(Val::Bool(equals(&a, &b)?))
-		})?,
-		"mod" => parse_args!(context, "std.mod", args, 2, [
-			0, a: [Val::Num | Val::Str], vec![ValType::Num, ValType::Str];
-			1, b, vec![];
-		], {
-			match (a, b) {
-				(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)),
-				(Val::Str(str), vals) => std_format(str, vals),
-				(a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(jrsonnet_parser::BinaryOpType::Mod, a.value_type()?, b.value_type()?))
+			Val::Arr(arr) => {
+				Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).collect::<Result<Vec<Val>>>()?).into()))
 			}
-		})?,
-		"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))
-		})?,
-		"floor" => parse_args!(context, "std.floor", args, 1, [
-			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
-		], {
-			Ok(Val::Num(x.floor()))
-		})?,
-		"log" => parse_args!(context, "std.log", args, 2, [
-			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
-		], {
-			Ok(Val::Num(n.ln()))
-		})?,
-		"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)
-		})?,
-		"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)))
-		})?,
-		"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(UndefinedExternalVariable(x))?)
-		})?,
-		"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(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?)
-		})?,
-		"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
-		"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
-		"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)]
-		"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
-		"format" => parse_args!(context, "std.format", args, 2, [
-			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-			1, vals, vec![]
-		], {
-			std_format(str, vals)
-		})?,
-		// faster
-		"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];
-		], {
-			if to < from {
-				return Ok(Val::Arr(Rc::new(Vec::new())))
-			}
-			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)))
-		})?,
-		"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()))
-		})?,
-		"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())))
-		})?,
-		"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
-		"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
-		"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();
+			_ => unreachable!()
+		}
+	})
+}
+
+// faster
+fn builtin_primitive_equals(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "primitiveEquals", args, 2, [
+		0, a: ty!(any);
+		1, b: ty!(any);
+	], {
+		Ok(Val::Bool(primitive_equals(&a, &b)?))
+	})
+}
+
+// faster
+fn builtin_equals(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "equals", args, 2, [
+		0, a: ty!(any);
+		1, b: ty!(any);
+	], {
+		Ok(Val::Bool(equals(&a, &b)?))
+	})
+}
+
+fn builtin_modulo(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "modulo", args, 2, [
+		0, a: ty!(number) => Val::Num;
+		1, b: ty!(number) => Val::Num;
+	], {
+		Ok(Val::Num(a % b))
+	})
+}
+
+fn builtin_mod(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "mod", args, 2, [
+		0, a: ty!((number | string));
+		1, b: ty!(any);
+	], {
+		match (a, b) {
+			(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)),
+			(Val::Str(str), vals) => std_format(str, vals),
+			(a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(BinaryOpType::Mod, a.value_type(), b.value_type()))
+		}
+	})
+}
+
+fn builtin_floor(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "floor", args, 1, [
+		0, x: ty!(number) => Val::Num;
+	], {
+		Ok(Val::Num(x.floor()))
+	})
+}
+
+fn builtin_log(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "log", args, 1, [
+		0, n: ty!(number) => Val::Num;
+	], {
+		Ok(Val::Num(n.ln()))
+	})
+}
+
+fn builtin_pow(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "pow", args, 2, [
+		0, x: ty!(number) => Val::Num;
+		1, n: ty!(number) => Val::Num;
+	], {
+		Ok(Val::Num(x.powf(n)))
+	})
+}
+
+fn builtin_ext_var(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "extVar", args, 1, [
+		0, x: ty!(string) => Val::Str;
+	], {
+		Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or(UndefinedExternalVariable(x))?)
+	})
+}
+
+fn builtin_native(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "native", args, 1, [
+		0, x: ty!(string) => Val::Str;
+	], {
+		Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or(UndefinedExternalFunction(x))?)
+	})
+}
+
+fn builtin_filter(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "filter", args, 2, [
+		0, func: ty!(function) => Val::Func;
+		1, arr: ty!(array) => Val::Arr;
+	], {
+		let mut out = Vec::new();
+		for item in arr.iter() {
+			let item = item?;
+			if func
+						.evaluate_values(context.clone(), &[item.clone()])?
+						.try_cast_bool("filter predicate")? {
+							out.push(item);
+						}
+		}
+		Ok(Val::Arr(out.into()))
+	})
+}
+
+fn builtin_foldl(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "foldl", args, 3, [
+		0, func: ty!(function) => Val::Func;
+		1, arr: ty!(array) => Val::Arr;
+		2, init: ty!(any);
+	], {
+		let mut acc = init;
+		for i in arr.iter() {
+			acc = func.evaluate_values(context.clone(), &[acc, i?])?;
+		}
+		Ok(acc)
+	})
+}
+
+fn builtin_foldr(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "foldr", args, 3, [
+		0, func: ty!(function) => Val::Func;
+		1, arr: ty!(array) => Val::Arr;
+		2, init: ty!(any);
+	], {
+		let mut acc = init;
+		for i in arr.iter().rev() {
+			acc = func.evaluate_values(context.clone(), &[acc, i?])?;
+		}
+		Ok(acc)
+	})
+}
+
+#[allow(non_snake_case)]
+fn builtin_sort_impl(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "sort", args, 2, [
+		0, arr: ty!(array) => Val::Arr;
+		1, keyF: ty!(function) => Val::Func;
+	], {
+		if arr.len() <= 1 {
+			return Ok(Val::Arr(arr))
+		}
+		Ok(Val::Arr(ArrValue::Eager(sort::sort(context, arr.evaluated()?, &keyF)?)))
+	})
+}
+
+// faster
+fn builtin_format(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "format", args, 2, [
+		0, str: ty!(string) => Val::Str;
+		1, vals: ty!(any)
+	], {
+		std_format(str, vals)
+	})
+}
+
+fn builtin_range(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "range", args, 2, [
+		0, from: ty!(number) => Val::Num;
+		1, to: ty!(number) => Val::Num;
+	], {
+		if to < from {
+			return Ok(Val::Arr(ArrValue::new_eager()))
+		}
+		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(out.into()))
+	})
+}
+
+fn builtin_char(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "char", args, 1, [
+		0, n: ty!(number) => Val::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()))
+	})
+}
 
-					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());
+fn builtin_encode_utf8(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "encodeUTF8", args, 1, [
+		0, str: ty!(string) => Val::Str;
+	], {
+		Ok(Val::Arr((str.bytes().map(|b| Val::Num(b as f64)).collect::<Vec<Val>>()).into()))
+	})
+}
+
+fn builtin_md5(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "md5", args, 1, [
+		0, str: ty!(string) => Val::Str;
+	], {
+		Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))
+	})
+}
+
+fn builtin_trace(context: Context, loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "trace", args, 2, [
+		0, str: ty!(string) => Val::Str;
+		1, rest: ty!(any);
+	], {
+		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)
+	})
+}
+
+fn builtin_base64(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "base64", args, 1, [
+		0, input: ty!((string | (Array<number>)));
+	], {
+		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?.unwrap_num()? as u8)
+				}).collect::<Result<Vec<_>>>()?).into()
+			},
+			_ => unreachable!()
+		}))
+	})
+}
+
+fn builtin_join(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "join", args, 2, [
+		0, sep: ty!((string | array));
+		1, arr: ty!(array) => Val::Arr;
+	], {
+		Ok(match sep {
+			Val::Arr(joiner_items) => {
+				let mut out = Vec::new();
+
+				let mut first = true;
+				for item in arr.iter() {
+					let item = item?.clone();
+					if let Val::Arr(items) = item {
+						if !first {
+							out.reserve(joiner_items.len());
+							// TODO: extend
+							for item in joiner_items.iter() {
+								out.push(item?);
 							}
-							first = false;
-							out.reserve(items.len());
-							out.extend(items.iter().cloned());
-						} else {
-							throw!(RuntimeError("in std.join all items should be arrays".into()));
 						}
+						first = false;
+						out.reserve(items.len());
+						// TODO: extend
+						for item in items.iter() {
+							out.push(item?);
+						}
+					} 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();
+				Val::Arr(out.into())
+			},
+			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()));
+				let mut first = true;
+				for item in arr.iter() {
+					let item = item?.clone();
+					if let Val::Str(item) = item {
+						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
-		"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
-		"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
-		"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))
-		})?,
-		"id" => parse_args!(context, "std.id", args, 1, [
-			0, v, vec![];
-		], {
-			Ok(v)
-		})?,
-		name => throw!(IntrinsicNotFound(name.into())),
+				Val::Str(out.into())
+			},
+			_ => unreachable!()
+		})
+	})
+}
+
+// faster
+fn builtin_escape_string_json(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "escapeStringJson", args, 1, [
+		0, str_: ty!(string) => Val::Str;
+	], {
+		Ok(Val::Str(escape_string_json(&str_).into()))
+	})
+}
+
+// faster
+fn builtin_manifest_json_ex(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "manifestJsonEx", args, 2, [
+		0, value: ty!(any);
+		1, indent: ty!(string) => Val::Str;
+	], {
+		Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {
+			padding: &indent,
+			mtype: ManifestType::Std,
+		})?.into()))
+	})
+}
+
+// faster
+fn builtin_reverse(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "reverse", args, 1, [
+		0, value: ty!(array) => Val::Arr;
+	], {
+		Ok(Val::Arr(value.reversed()))
+	})
+}
+
+fn builtin_id(context: Context, _loc: Option<&ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	parse_args!(context, "id", args, 1, [
+		0, v: ty!(any);
+	], {
+		Ok(v)
+	})
+}
+
+// faster
+fn builtin_str_replace(
+	context: Context,
+	_loc: Option<&ExprLocation>,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	parse_args!(context, "strReplace", args, 3, [
+		0, str: ty!(string) => Val::Str;
+		1, from: ty!(string) => Val::Str;
+		2, to: ty!(string) => Val::Str;
+	], {
+		let mut out = String::new();
+		let mut last_idx = 0;
+		while let Some(idx) = (&str[last_idx..]).find(&from as &str) {
+			out.push_str(&str[last_idx..last_idx+idx]);
+			out.push_str(&to);
+			last_idx += idx + from.len();
+		}
+		if last_idx == 0 {
+			return Ok(Val::Str(str))
+		}
+		out.push_str(&str[last_idx..]);
+		Ok(Val::Str(out.into()))
 	})
 }
+
+pub fn call_builtin(
+	context: Context,
+	loc: Option<&ExprLocation>,
+	name: &str,
+	args: &ArgsDesc,
+) -> Result<Val> {
+	if let Some(f) = BUILTINS.with(|builtins| builtins.get(name).copied()) {
+		return Ok(f(context, loc, args)?);
+	}
+	throw!(IntrinsicNotFound(name.into()))
+}
modifiedcrates/jrsonnet-evaluator/src/builtin/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/sort.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs
@@ -46,7 +46,6 @@
 	let mut sort_type = SortKeyType::Unknown;
 	for i in values.iter_mut() {
 		let i = key_getter(i);
-		i.inplace_unwrap()?;
 		match (i, sort_type) {
 			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
 			(Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -2,6 +2,7 @@
 	error::Error::*, future_wrapper, map::LayeredHashMap, rc_fn_helper, resolved_lazy_val,
 	LazyBinding, LazyVal, ObjValue, Result, Val,
 };
+use jrsonnet_interner::IStr;
 use rustc_hash::FxHashMap;
 use std::hash::BuildHasherDefault;
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
@@ -18,7 +19,7 @@
 	dollar: Option<ObjValue>,
 	this: Option<ObjValue>,
 	super_obj: Option<ObjValue>,
-	bindings: LayeredHashMap<Rc<str>, LazyVal>,
+	bindings: LayeredHashMap<LazyVal>,
 }
 impl Debug for ContextInternals {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -57,7 +58,7 @@
 		}))
 	}
 
-	pub fn binding(&self, name: Rc<str>) -> Result<LazyVal> {
+	pub fn binding(&self, name: IStr) -> Result<LazyVal> {
 		Ok(self
 			.0
 			.bindings
@@ -72,7 +73,7 @@
 		ctx.unwrap()
 	}
 
-	pub fn with_var(self, name: Rc<str>, value: Val) -> Self {
+	pub fn with_var(self, name: IStr, value: Val) -> Self {
 		let mut new_bindings =
 			FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default());
 		new_bindings.insert(name, resolved_lazy_val!(value));
@@ -81,7 +82,7 @@
 
 	pub fn extend(
 		self,
-		new_bindings: FxHashMap<Rc<str>, LazyVal>,
+		new_bindings: FxHashMap<IStr, LazyVal>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
@@ -123,7 +124,7 @@
 	}
 	pub fn extend_unbound(
 		self,
-		new_bindings: HashMap<Rc<str>, LazyBinding>,
+		new_bindings: HashMap<IStr, LazyBinding>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,15 +1,17 @@
 use crate::{
 	builtin::{format::FormatError, sort::SortError},
-	ValType,
+	typed::TypeLocError,
 };
+use jrsonnet_interner::IStr;
 use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType};
+use jrsonnet_types::ValType;
 use std::{path::PathBuf, rc::Rc};
 use thiserror::Error;
 
 #[derive(Error, Debug, Clone)]
 pub enum Error {
 	#[error("intrinsic not found: {0}")]
-	IntrinsicNotFound(Rc<str>),
+	IntrinsicNotFound(IStr),
 	#[error("argument reordering in intrisics not supported yet")]
 	IntrinsicArgumentReorderingIsNotSupportedYet,
 
@@ -32,36 +34,36 @@
 	ArrayBoundsError(usize, usize),
 
 	#[error("assert failed: {0}")]
-	AssertionFailed(Rc<str>),
+	AssertionFailed(IStr),
 
 	#[error("variable is not defined: {0}")]
-	VariableIsNotDefined(Rc<str>),
+	VariableIsNotDefined(IStr),
 	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join(", "))]
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
 	#[error("no such field: {0}")]
-	NoSuchField(Rc<str>),
+	NoSuchField(IStr),
 
 	#[error("only functions can be called, got {0}")]
 	OnlyFunctionsCanBeCalledGot(ValType),
 	#[error("parameter {0} is not defined")]
 	UnknownFunctionParameter(String),
 	#[error("argument {0} is already bound")]
-	BindingParameterASecondTime(Rc<str>),
+	BindingParameterASecondTime(IStr),
 	#[error("too many args, function has {0}")]
 	TooManyArgsFunctionHas(usize),
 	#[error("founction argument is not passed: {0}")]
-	FunctionParameterNotBoundInCall(Rc<str>),
+	FunctionParameterNotBoundInCall(IStr),
 
 	#[error("external variable is not defined: {0}")]
-	UndefinedExternalVariable(Rc<str>),
+	UndefinedExternalVariable(IStr),
 	#[error("native is not defined: {0}")]
-	UndefinedExternalFunction(Rc<str>),
+	UndefinedExternalFunction(IStr),
 
 	#[error("field name should be string, got {0}")]
 	FieldMustBeStringGot(ValType),
 
 	#[error("attempted to index array with string {0}")]
-	AttemptedIndexAnArrayWithString(Rc<str>),
+	AttemptedIndexAnArrayWithString(IStr),
 	#[error("{0} index type should be {1}, got {2}")]
 	ValueIndexMustBeTypeGot(ValType, ValType, ValType),
 	#[error("cant index into {0}")]
@@ -85,12 +87,12 @@
 	)]
 	ImportSyntaxError {
 		path: Rc<PathBuf>,
-		source_code: Rc<str>,
+		source_code: IStr,
 		error: Box<jrsonnet_parser::ParseError>,
 	},
 
 	#[error("runtime error: {0}")]
-	RuntimeError(Rc<str>),
+	RuntimeError(IStr),
 	#[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]
 	StackOverflow,
 	#[error("tried to index by fractional value")]
@@ -117,6 +119,8 @@
 
 	#[error("format error: {0}")]
 	Format(#[from] FormatError),
+	#[error("type error: {0}")]
+	TypeError(TypeLocError),
 	#[error("sort error: {0}")]
 	Sort(#[from] SortError),
 }
@@ -128,7 +132,7 @@
 
 #[derive(Clone, Debug)]
 pub struct StackTraceElement {
-	pub location: ExprLocation,
+	pub location: Option<ExprLocation>,
 	pub desc: String,
 }
 #[derive(Debug, Clone)]
@@ -144,6 +148,9 @@
 	pub const fn error(&self) -> &Error {
 		&(self.0).0
 	}
+	pub fn error_mut(&mut self) -> &mut Error {
+		&mut (self.0).0
+	}
 	pub const fn trace(&self) -> &StackTrace {
 		&(self.0).1
 	}
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -1,18 +1,19 @@
 use crate::{
 	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_interner::IStr;
 use jrsonnet_parser::{
 	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember,
 	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
 	Visibility,
 };
+use jrsonnet_types::ValType;
 use rustc_hash::FxHashMap;
 use std::{collections::HashMap, rc::Rc};
 
-pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {
+pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, LazyBinding) {
 	let b = b.clone();
 	if let Some(params) = &b.params {
 		let params = params.clone();
@@ -45,7 +46,7 @@
 	}
 }
 
-pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {
+pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {
 	Val::Func(Rc::new(FuncVal::Normal(FuncDesc {
 		name,
 		ctx,
@@ -57,12 +58,11 @@
 pub fn evaluate_field_name(
 	context: Context,
 	field_name: &jrsonnet_parser::FieldName,
-) -> Result<Option<Rc<str>>> {
+) -> Result<Option<IStr>> {
 	Ok(match field_name {
 		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),
 		jrsonnet_parser::FieldName::Dyn(expr) => {
-			let lazy = evaluate(context, expr)?;
-			let value = lazy.unwrap_if_lazy()?;
+			let value = evaluate(context, expr)?;
 			if matches!(value, Val::Null) {
 				None
 			} else {
@@ -74,11 +74,10 @@
 
 pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {
 	Ok(match (op, b) {
-		(o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()?)?,
 		(UnaryOpType::Not, Val::Bool(v)) => Val::Bool(!v),
 		(UnaryOpType::Minus, Val::Num(n)) => Val::Num(-*n),
 		(UnaryOpType::BitNot, Val::Num(n)) => Val::Num(!(*n as i32) as f64),
-		(op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type()?)),
+		(op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),
 	})
 }
 
@@ -94,12 +93,17 @@
 		(o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().to_string()?, s).into()),
 
 		(Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
-		(Val::Arr(a), Val::Arr(b)) => Val::Arr(Rc::new([&a[..], &b[..]].concat())),
+		(Val::Arr(a), Val::Arr(b)) => {
+			let mut out = Vec::with_capacity(a.len() + b.len());
+			out.extend(a.iter_lazy());
+			out.extend(b.iter_lazy());
+			Val::Arr(out.into())
+		}
 		(Val::Num(v1), Val::Num(v2)) => Val::new_checked_num(v1 + v2)?,
 		_ => throw!(BinaryOperatorDoesNotOperateOnValues(
 			BinaryOpType::Add,
-			a.value_type()?,
-			b.value_type()?,
+			a.value_type(),
+			b.value_type(),
 		)),
 	})
 }
@@ -110,15 +114,11 @@
 	op: BinaryOpType,
 	b: &LocExpr,
 ) -> Result<Val> {
-	Ok(
-		match (evaluate(context.clone(), a)?.unwrap_if_lazy()?, op, b) {
-			(Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true),
-			(Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false),
-			(a, op, eb) => {
-				evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?.unwrap_if_lazy()?)?
-			}
-		},
-	)
+	Ok(match (evaluate(context.clone(), a)?, op, b) {
+		(Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true),
+		(Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false),
+		(a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?)?,
+	})
 }
 
 pub fn evaluate_binary_op_normal(a: &Val, op: BinaryOpType, b: &Val) -> Result<Val> {
@@ -177,13 +177,13 @@
 
 		_ => throw!(BinaryOperatorDoesNotOperateOnValues(
 			op,
-			a.value_type()?,
-			b.value_type()?,
+			a.value_type(),
+			b.value_type(),
 		)),
 	})
 }
 
-future_wrapper!(HashMap<Rc<str>, LazyBinding>, FutureNewBindings);
+future_wrapper!(HashMap<IStr, LazyBinding>, FutureNewBindings);
 future_wrapper!(ObjValue, FutureObjValue);
 
 pub fn evaluate_comp<T>(
@@ -200,23 +200,20 @@
 				None
 			}
 		}
-		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {
-			match evaluate(context.clone(), expr)?.unwrap_if_lazy()? {
-				Val::Arr(list) => {
-					let mut out = Vec::new();
-					for item in list.iter() {
-						let item = item.unwrap_if_lazy()?;
-						out.push(evaluate_comp(
-							context.clone().with_var(var.clone(), item.clone()),
-							value,
-							&specs[1..],
-						)?);
-					}
-					Some(out.into_iter().flatten().flatten().collect())
+		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(context.clone(), expr)? {
+			Val::Arr(list) => {
+				let mut out = Vec::new();
+				for item in list.iter() {
+					out.push(evaluate_comp(
+						context.clone().with_var(var.clone(), item?.clone()),
+						value,
+						&specs[1..],
+					)?);
 				}
-				_ => throw!(InComprehensionCanOnlyIterateOverArray),
+				Some(out.into_iter().flatten().flatten().collect())
 			}
-		}
+			_ => throw!(InComprehensionCanOnlyIterateOverArray),
+		},
 	})
 }
 
@@ -234,7 +231,7 @@
 		})
 	);
 	{
-		let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+		let mut bindings: HashMap<IStr, LazyBinding> = HashMap::new();
 		for (n, b) in members
 			.iter()
 			.filter_map(|m| match m {
@@ -338,7 +335,7 @@
 							)?)
 						})
 					);
-					let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+					let mut bindings: HashMap<IStr, LazyBinding> = HashMap::new();
 					for (n, b) in obj
 						.pre_locals
 						.iter()
@@ -375,7 +372,7 @@
 							},
 						);
 					}
-					v => throw!(FieldMustBeStringGot(v.value_type()?)),
+					v => throw!(FieldMustBeStringGot(v.value_type())),
 				}
 			}
 
@@ -388,11 +385,10 @@
 	context: Context,
 	value: &LocExpr,
 	args: &ArgsDesc,
-	loc: &Option<ExprLocation>,
+	loc: Option<&ExprLocation>,
 	tailstrict: bool,
 ) -> Result<Val> {
-	let lazy = evaluate(context.clone(), value)?;
-	let value = lazy.unwrap_if_lazy()?;
+	let value = evaluate(context.clone(), value)?;
 	Ok(match value {
 		Val::Func(f) => {
 			let body = || f.evaluate(context, loc, args, tailstrict);
@@ -402,11 +398,11 @@
 				push(loc, || format!("function <{}> call", f.name()), body)?
 			}
 		}
-		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)),
+		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),
 	})
 }
 
-pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: Rc<str>) -> Result<Val> {
+pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {
 	use Expr::*;
 	let LocExpr(expr, _loc) = lexpr;
 	Ok(match &**expr {
@@ -434,9 +430,9 @@
 		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(
-			loc,
+			loc.as_ref(),
 			|| format!("variable <{}>", name),
-			|| Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?),
+			|| Ok(context.binding(name.clone())?.evaluate()?),
 		)?,
 		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {
 			let name = evaluate(context.clone(), index)?.try_cast_str("object index")?;
@@ -444,22 +440,19 @@
 				.super_obj()
 				.clone()
 				.expect("no super found")
-				.get_raw(name, &context.this().clone().expect("no this found"))?
+				.get_raw(name, Some(&context.this().clone().expect("no this found")))?
 				.expect("value not found")
 		}
 		Index(value, index) => {
-			match (
-				evaluate(context.clone(), value)?.unwrap_if_lazy()?,
-				evaluate(context, index)?,
-			) {
+			match (evaluate(context.clone(), value)?, evaluate(context, index)?) {
 				(Val::Obj(v), Val::Str(s)) => {
 					let sn = s.clone();
 					push(
-						loc,
+						loc.as_ref(),
 						|| format!("field <{}> access", sn),
 						|| {
 							if let Some(v) = v.get(s.clone())? {
-								Ok(v.unwrap_if_lazy()?)
+								Ok(v)
 							} else if v.get("__intrinsic_namespace__".into())?.is_some() {
 								Ok(Val::Func(Rc::new(FuncVal::Intrinsic(s))))
 							} else {
@@ -471,23 +464,21 @@
 				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(
 					ValType::Obj,
 					ValType::Str,
-					n.value_type()?,
+					n.value_type(),
 				)),
 
 				(Val::Arr(v), Val::Num(n)) => {
 					if n.fract() > f64::EPSILON {
 						throw!(FractionalIndex)
 					}
-					v.get(n as usize)
+					v.get(n as usize)?
 						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?
-						.clone()
-						.unwrap_if_lazy()?
 				}
 				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),
 				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(
 					ValType::Arr,
 					ValType::Num,
-					n.value_type()?,
+					n.value_type(),
 				)),
 
 				(Val::Str(s), Val::Num(n)) => Val::Str(
@@ -500,14 +491,14 @@
 				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(
 					ValType::Str,
 					ValType::Num,
-					n.value_type()?,
+					n.value_type(),
 				)),
 
-				(v, _) => throw!(CantIndexInto(v.value_type()?)),
+				(v, _) => throw!(CantIndexInto(v.value_type())),
 			}
 		}
 		LocalExpr(bindings, returned) => {
-			let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+			let mut new_bindings: HashMap<IStr, LazyBinding> = HashMap::new();
 			let future_context = Context::new_future();
 
 			let context_creator = context_creator!(
@@ -529,31 +520,35 @@
 		Arr(items) => {
 			let mut out = Vec::with_capacity(items.len());
 			for item in items {
-				out.push(Val::Lazy(lazy_val!(
+				out.push(LazyVal::new(Box::new(
 					closure!(clone context, clone item, || {
 						evaluate(context.clone(), &item)
-					})
+					}),
 				)));
 			}
-			Val::Arr(Rc::new(out))
+			Val::Arr(out.into())
 		}
 		ArrComp(expr, comp_specs) => Val::Arr(
 			// First comp_spec should be for_spec, so no "None" possible here
-			Rc::new(evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)?.unwrap()),
+			evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)?
+				.unwrap()
+				.into(),
 		),
 		Obj(body) => Val::Obj(evaluate_object(context, body)?),
 		ObjExtend(s, t) => evaluate_add_op(
 			&evaluate(context.clone(), s)?,
 			&Val::Obj(evaluate_object(context, t)?),
 		)?,
-		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,
+		Apply(value, args, tailstrict) => {
+			evaluate_apply(context, value, args, loc.as_ref(), *tailstrict)?
+		}
 		Function(params, body) => {
 			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())
 		}
 		Intrinsic(name) => Val::Func(Rc::new(FuncVal::Intrinsic(name.clone()))),
 		AssertExpr(AssertStmt(value, msg), returned) => {
 			let assertion_result = push(
-				&value.1,
+				value.1.as_ref(),
 				|| "assertion condition".to_owned(),
 				|| {
 					evaluate(context.clone(), value)?
@@ -562,14 +557,22 @@
 			)?;
 			if assertion_result {
 				evaluate(context, returned)?
-			} else if let Some(msg) = msg {
-				throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));
 			} else {
-				throw!(AssertionFailed(Val::Null.to_string()?));
+				push(
+					value.1.as_ref(),
+					|| "assertion failure".to_owned(),
+					|| {
+						if let Some(msg) = msg {
+							throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));
+						} else {
+							throw!(AssertionFailed(Val::Null.to_string()?));
+						}
+					},
+				)?
 			}
 		}
 		ErrorStmt(e) => push(
-			loc,
+			loc.as_ref(),
 			|| "error statement".to_owned(),
 			|| {
 				throw!(RuntimeError(
@@ -583,7 +586,7 @@
 			cond_else,
 		} => {
 			if push(
-				loc,
+				loc.as_ref(),
 				|| "if condition".to_owned(),
 				|| evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"),
 			)? {
@@ -603,7 +606,7 @@
 			let import_location = Rc::make_mut(&mut tmp);
 			import_location.pop();
 			push(
-				loc,
+				loc.as_ref(),
 				|| 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
@@ -1,8 +1,9 @@
 use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val};
 use closure::closure;
+use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ArgsDesc, ParamsDesc};
 use rustc_hash::FxHashMap;
-use std::{collections::HashMap, hash::BuildHasherDefault, rc::Rc};
+use std::{collections::HashMap, hash::BuildHasherDefault};
 
 const NO_DEFAULT_CONTEXT: &str =
 	"no default context set for call with defined default parameter value";
@@ -66,7 +67,7 @@
 	ctx: Context,
 	body_ctx: Option<Context>,
 	params: &ParamsDesc,
-	args: &HashMap<Rc<str>, Val>,
+	args: &HashMap<IStr, Val>,
 	tailstrict: bool,
 ) -> Result<Context> {
 	let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());
@@ -143,9 +144,10 @@
 #[macro_export]
 macro_rules! parse_args {
 	($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [
-		$($id: expr, $name: ident $(: [$($p: path)|+] $(!! $a: path)?)?, $nt: expr);+ $(;)?
+		$($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)?
 	], $handler:block) => {{
-		use crate::{throw, error::Error::*};
+		use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType};
+
 		let args = $args;
 		if args.len() > $total_args {
 			throw!(TooManyArgsFunctionHas($total_args));
@@ -160,47 +162,19 @@
 					throw!(IntrinsicArgumentReorderingIsNotSupportedYet);
 				}
 			}
-			let $name = evaluate($ctx.clone(), &$name.1)?;
+			let $name = push_stack_frame(None, || format!("evaluating argument"), || {
+				let value = evaluate($ctx.clone(), &$name.1)?;
+				$ty.check(&value)?;
+				Ok(value)
+			})?;
 			$(
-				match $name {
-					$($p(_))|+ => {},
-					_ => throw!(TypeMismatch(
-						concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"),
-						$nt, $name.value_type()?
-					)),
+				let $name = if let $match(v) = $name {
+					v
+				} else {
+					unreachable!();
 				};
-				$(
-					let $name = match $name {
-						$a(v) => v,
-						_ =>throw!(TypeMismatch(concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"), $nt, $name.value_type()?)),
-					};
-				)*
-			)*
+			)?
 		)+
 		($handler as crate::Result<_>)
 	}};
-}
-
-#[test]
-fn test() -> Result<()> {
-	use crate::val::ValType;
-	use jrsonnet_parser::*;
-	let state = crate::EvaluationState::default();
-	let evaluator = state.with_stdlib();
-	let ctx = evaluator.create_default_context()?;
-	evaluator.run_in_state(|| {
-		parse_args!(ctx, "test", ArgsDesc(vec![
-			Arg(None, el!(Expr::Num(2.0))),
-			Arg(Some("b".into()), el!(Expr::Num(1.0))),
-		]), 2, [
-			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
-			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];
-		], {
-			assert!((a - 2.0).abs() <= f64::EPSILON);
-			assert!((b - 1.0).abs() <= f64::EPSILON);
-			Ok(())
-		})
-		.unwrap();
-		Ok(())
-	})
 }
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -3,6 +3,7 @@
 	throw,
 };
 use fs::File;
+use jrsonnet_interner::IStr;
 use std::fs;
 use std::io::Read;
 use std::{any::Any, cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc};
@@ -15,7 +16,7 @@
 	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>>;
 
 	/// Reads file from filesystem, should be used only with path received from `resolve_file`
-	fn load_file_contents(&self, resolved: &PathBuf) -> Result<Rc<str>>;
+	fn load_file_contents(&self, resolved: &PathBuf) -> Result<IStr>;
 
 	/// # Safety
 	///
@@ -32,7 +33,7 @@
 		throw!(ImportNotSupported(from.clone(), path.clone()))
 	}
 
-	fn load_file_contents(&self, _resolved: &PathBuf) -> Result<Rc<str>> {
+	fn load_file_contents(&self, _resolved: &PathBuf) -> Result<IStr> {
 		// Can be only caused by library direct consumer, not by supplied jsonnet
 		panic!("dummy resolver can't load any file")
 	}
@@ -72,7 +73,7 @@
 			throw!(ImportFileNotFound(from.clone(), path.clone()))
 		}
 	}
-	fn load_file_contents(&self, id: &PathBuf) -> Result<Rc<str>> {
+	fn load_file_contents(&self, id: &PathBuf) -> Result<IStr> {
 		let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.clone()))?;
 		let mut out = String::new();
 		file.read_to_string(&mut out)
@@ -89,7 +90,7 @@
 /// Caches results of the underlying resolver
 pub struct CachingImportResolver {
 	resolution_cache: RefCell<HashMap<ResolutionData, Result<Rc<PathBuf>>>>,
-	loading_cache: RefCell<HashMap<PathBuf, Result<Rc<str>>>>,
+	loading_cache: RefCell<HashMap<PathBuf, Result<IStr>>>,
 	inner: Box<dyn ImportResolver>,
 }
 impl ImportResolver for CachingImportResolver {
@@ -101,7 +102,7 @@
 			.clone()
 	}
 
-	fn load_file_contents(&self, resolved: &PathBuf) -> Result<Rc<str>> {
+	fn load_file_contents(&self, resolved: &PathBuf) -> Result<IStr> {
 		self.loading_cache
 			.borrow_mut()
 			.entry(resolved.clone())
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -22,11 +22,10 @@
 			} else {
 				Number::from_f64(*n).expect("to json number")
 			}),
-			Val::Lazy(v) => (&v.evaluate()?).try_into()?,
 			Val::Arr(a) => {
 				let mut out = Vec::with_capacity(a.len());
 				for item in a.iter() {
-					out.push(item.try_into()?);
+					out.push((&item?).try_into()?);
 				}
 				Self::Array(out)
 			}
@@ -55,9 +54,9 @@
 			Value::Array(a) => {
 				let mut out = Vec::with_capacity(a.len());
 				for v in a {
-					out.push(v.into());
+					out.push(LazyVal::new_resolved(v.into()));
 				}
-				Self::Arr(Rc::new(out))
+				Self::Arr(out.into())
 			}
 			Value::Object(o) => {
 				let mut entries = HashMap::with_capacity(o.len());
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/lib.rs
1#![cfg_attr(feature = "unstable", feature(stmt_expr_attributes))]2#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]3#![warn(clippy::all, clippy::nursery)]45mod builtin;6mod ctx;7mod dynamic;8pub mod error;9mod evaluate;10mod function;11mod import;12mod integrations;13mod map;14pub mod native;15mod obj;16pub mod trace;17mod val;1819pub use ctx::*;20pub use dynamic::*;21use error::{Error::*, LocError, Result, StackTraceElement};22pub use evaluate::*;23pub use function::parse_function_call;24pub use import::*;25use jrsonnet_parser::*;26use native::NativeCallback;27pub use obj::*;28use std::{29	cell::{Ref, RefCell, RefMut},30	collections::HashMap,31	fmt::Debug,32	path::PathBuf,33	rc::Rc,34};35use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat};36pub use val::*;3738type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;39#[derive(Clone)]40pub enum LazyBinding {41	Bindable(Rc<BindableFn>),42	Bound(LazyVal),43}4445impl Debug for LazyBinding {46	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {47		write!(f, "LazyBinding")48	}49}50impl LazyBinding {51	pub fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {52		match self {53			Self::Bindable(v) => v(this, super_obj),54			Self::Bound(v) => Ok(v.clone()),55		}56	}57}5859pub struct EvaluationSettings {60	/// Limits recursion by limiting the number of stack frames61	pub max_stack: usize,62	/// Limits amount of stack trace items preserved63	pub max_trace: usize,64	/// Used for s`td.extVar`65	pub ext_vars: HashMap<Rc<str>, Val>,66	/// Used for ext.native67	pub ext_natives: HashMap<Rc<str>, Rc<NativeCallback>>,68	/// TLA vars69	pub tla_vars: HashMap<Rc<str>, Val>,70	/// Global variables are inserted in default context71	pub globals: HashMap<Rc<str>, Val>,72	/// Used to resolve file locations/contents73	pub import_resolver: Box<dyn ImportResolver>,74	/// Used in manifestification functions75	pub manifest_format: ManifestFormat,76	/// Used for bindings77	pub trace_format: Box<dyn TraceFormat>,78}79impl Default for EvaluationSettings {80	fn default() -> Self {81		Self {82			max_stack: 200,83			max_trace: 20,84			globals: Default::default(),85			ext_vars: Default::default(),86			ext_natives: Default::default(),87			tla_vars: Default::default(),88			import_resolver: Box::new(DummyImportResolver),89			manifest_format: ManifestFormat::Json(4),90			trace_format: Box::new(CompactFormat {91				padding: 4,92				resolver: trace::PathResolver::Absolute,93			}),94		}95	}96}9798#[derive(Default)]99struct EvaluationData {100	/// Used for stack overflow detection, stacktrace is populated on unwind101	stack_depth: usize,102	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces103	files: HashMap<Rc<PathBuf>, FileData>,104	str_files: HashMap<Rc<PathBuf>, Rc<str>>,105}106107pub struct FileData {108	source_code: Rc<str>,109	parsed: LocExpr,110	evaluated: Option<Val>,111}112#[derive(Default)]113pub struct EvaluationStateInternals {114	/// Internal state115	data: RefCell<EvaluationData>,116	/// Settings, safe to change at runtime117	settings: RefCell<EvaluationSettings>,118}119120thread_local! {121	/// Contains the state for a currently executed file.122	/// Global state is fine here.123	pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)124}125pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {126	EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))127}128pub(crate) fn push<T>(129	e: &Option<ExprLocation>,130	frame_desc: impl FnOnce() -> String,131	f: impl FnOnce() -> Result<T>,132) -> Result<T> {133	if let Some(v) = e {134		with_state(|s| s.push(v, frame_desc, f))135	} else {136		f()137	}138}139140/// Maintains stack trace and import resolution141#[derive(Default, Clone)]142pub struct EvaluationState(Rc<EvaluationStateInternals>);143144impl EvaluationState {145	/// Parses and adds file as loaded146	pub fn add_file(&self, path: Rc<PathBuf>, source_code: Rc<str>) -> Result<()> {147		self.add_parsed_file(148			path.clone(),149			source_code.clone(),150			parse(151				&source_code,152				&ParserSettings {153					file_name: path.clone(),154					loc_data: true,155				},156			)157			.map_err(|error| ImportSyntaxError {158				error: Box::new(error),159				path,160				source_code,161			})?,162		)?;163164		Ok(())165	}166167	/// Adds file by source code and parsed expr168	pub fn add_parsed_file(169		&self,170		name: Rc<PathBuf>,171		source_code: Rc<str>,172		parsed: LocExpr,173	) -> Result<()> {174		self.data_mut().files.insert(175			name,176			FileData {177				source_code,178				parsed,179				evaluated: None,180			},181		);182183		Ok(())184	}185	pub fn get_source(&self, name: &PathBuf) -> Option<Rc<str>> {186		let ro_map = &self.data().files;187		ro_map.get(name).map(|value| value.source_code.clone())188	}189	pub fn map_source_locations(&self, file: &PathBuf, locs: &[usize]) -> Vec<CodeLocation> {190		offset_to_location(&self.get_source(file).unwrap(), locs)191	}192193	pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Val> {194		let file_path = self.resolve_file(from, path)?;195		{196			let data = self.data();197			let files = &data.files;198			if files.contains_key(&file_path) {199				drop(data);200				return self.evaluate_loaded_file_raw(&file_path);201			}202		}203		let contents = self.load_file_contents(&file_path)?;204		self.add_file(file_path.clone(), contents)?;205		self.evaluate_loaded_file_raw(&file_path)206	}207	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<str>> {208		let path = self.resolve_file(from, path)?;209		if !self.data().str_files.contains_key(&path) {210			let file_str = self.load_file_contents(&path)?;211			self.data_mut().str_files.insert(path.clone(), file_str);212		}213		Ok(self.data().str_files.get(&path).cloned().unwrap())214	}215216	fn evaluate_loaded_file_raw(&self, name: &PathBuf) -> Result<Val> {217		let expr: LocExpr = {218			let ro_map = &self.data().files;219			let value = ro_map220				.get(name)221				.unwrap_or_else(|| panic!("file not added: {:?}", name));222			if let Some(ref evaluated) = value.evaluated {223				return Ok(evaluated.clone());224			}225			value.parsed.clone()226		};227		let value = evaluate(self.create_default_context()?, &expr)?;228		{229			self.data_mut()230				.files231				.get_mut(name)232				.unwrap()233				.evaluated234				.replace(value.clone());235		}236		Ok(value)237	}238239	/// Adds standard library global variable (std) to this evaluator240	pub fn with_stdlib(&self) -> &Self {241		use jrsonnet_stdlib::STDLIB_STR;242		let std_path = Rc::new(PathBuf::from("std.jsonnet"));243		self.run_in_state(|| {244			self.add_parsed_file(245				std_path.clone(),246				STDLIB_STR.to_owned().into(),247				builtin::get_parsed_stdlib(),248			)249			.unwrap();250			let val = self.evaluate_loaded_file_raw(&std_path).unwrap();251			self.settings_mut().globals.insert("std".into(), val);252		});253		self254	}255256	/// Creates context with all passed global variables257	pub fn create_default_context(&self) -> Result<Context> {258		let globals = &self.settings().globals;259		let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();260		for (name, value) in globals.iter() {261			new_bindings.insert(262				name.clone(),263				LazyBinding::Bound(resolved_lazy_val!(value.clone())),264			);265		}266		Context::new().extend_unbound(new_bindings, None, None, None)267	}268269	/// Executes code creating a new stack frame270	pub fn push<T>(271		&self,272		e: &ExprLocation,273		frame_desc: impl FnOnce() -> String,274		f: impl FnOnce() -> Result<T>,275	) -> Result<T> {276		{277			let mut data = self.data_mut();278			let stack_depth = &mut data.stack_depth;279			if *stack_depth > self.max_stack() {280				// Error creation uses data, so i drop guard here281				drop(data);282				throw!(StackOverflow);283			} else {284				*stack_depth += 1;285			}286		}287		let result = f();288		self.data_mut().stack_depth -= 1;289		if let Err(mut err) = result {290			err.trace_mut().0.push(StackTraceElement {291				location: e.clone(),292				desc: frame_desc(),293			});294			return Err(err);295		}296		result297	}298299	/// Runs passed function in state (required if function needs to modify stack trace)300	pub fn run_in_state<T>(&self, f: impl FnOnce() -> T) -> T {301		EVAL_STATE.with(|v| {302			let has_state = v.borrow().is_some();303			if !has_state {304				v.borrow_mut().replace(self.clone());305			}306			let result = f();307			if !has_state {308				v.borrow_mut().take();309			}310			result311		})312	}313314	pub fn stringify_err(&self, e: &LocError) -> String {315		let mut out = String::new();316		self.settings()317			.trace_format318			.write_trace(&mut out, self, e)319			.unwrap();320		out321	}322323	pub fn manifest(&self, val: Val) -> Result<Rc<str>> {324		self.run_in_state(|| val.manifest(&self.manifest_format()))325	}326	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(Rc<str>, Rc<str>)>> {327		self.run_in_state(|| val.manifest_multi(&self.manifest_format()))328	}329	pub fn manifest_stream(&self, val: Val) -> Result<Vec<Rc<str>>> {330		self.run_in_state(|| val.manifest_stream(&self.manifest_format()))331	}332333	/// If passed value is function then call with set TLA334	pub fn with_tla(&self, val: Val) -> Result<Val> {335		self.run_in_state(|| {336			Ok(match val {337				Val::Func(func) => func.evaluate_map(338					self.create_default_context()?,339					&self.settings().tla_vars,340					true,341				)?,342				v => v,343			})344		})345	}346}347348/// Internals349impl EvaluationState {350	fn data(&self) -> Ref<EvaluationData> {351		self.0.data.borrow()352	}353	fn data_mut(&self) -> RefMut<EvaluationData> {354		self.0.data.borrow_mut()355	}356	pub fn settings(&self) -> Ref<EvaluationSettings> {357		self.0.settings.borrow()358	}359	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {360		self.0.settings.borrow_mut()361	}362}363364/// Raw methods evaluate passed values but don't perform TLA execution365impl EvaluationState {366	pub fn evaluate_file_raw(&self, name: &PathBuf) -> Result<Val> {367		self.run_in_state(|| self.import_file(&std::env::current_dir().expect("cwd"), name))368	}369	pub fn evaluate_file_raw_nocwd(&self, name: &PathBuf) -> Result<Val> {370		self.run_in_state(|| self.import_file(&PathBuf::from("."), name))371	}372	/// Parses and evaluates the given snippet373	pub fn evaluate_snippet_raw(&self, source: Rc<PathBuf>, code: Rc<str>) -> Result<Val> {374		let parsed = parse(375			&code,376			&ParserSettings {377				file_name: source.clone(),378				loc_data: true,379			},380		)381		.unwrap();382		self.add_parsed_file(source, code, parsed.clone())?;383		self.evaluate_expr_raw(parsed)384	}385	/// Evaluates the parsed expression386	pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {387		self.run_in_state(|| evaluate(self.create_default_context()?, &code))388	}389}390391/// Settings utilities392impl EvaluationState {393	pub fn add_ext_var(&self, name: Rc<str>, value: Val) {394		self.settings_mut().ext_vars.insert(name, value);395	}396	pub fn add_ext_str(&self, name: Rc<str>, value: Rc<str>) {397		self.add_ext_var(name, Val::Str(value));398	}399	pub fn add_ext_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {400		let value =401			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("ext_code {}", name))), code)?;402		self.add_ext_var(name, value);403		Ok(())404	}405406	pub fn add_tla(&self, name: Rc<str>, value: Val) {407		self.settings_mut().tla_vars.insert(name, value);408	}409	pub fn add_tla_str(&self, name: Rc<str>, value: Rc<str>) {410		self.add_tla(name, Val::Str(value));411	}412	pub fn add_tla_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {413		let value =414			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("tla_code {}", name))), code)?;415		self.add_tla(name, value);416		Ok(())417	}418419	pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {420		Ok(self.settings().import_resolver.resolve_file(from, path)?)421	}422	pub fn load_file_contents(&self, path: &PathBuf) -> Result<Rc<str>> {423		Ok(self.settings().import_resolver.load_file_contents(path)?)424	}425426	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {427		Ref::map(self.settings(), |s| &*s.import_resolver)428	}429	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {430		self.settings_mut().import_resolver = resolver;431	}432433	pub fn add_native(&self, name: Rc<str>, cb: Rc<NativeCallback>) {434		self.settings_mut().ext_natives.insert(name, cb);435	}436437	pub fn manifest_format(&self) -> ManifestFormat {438		self.settings().manifest_format.clone()439	}440	pub fn set_manifest_format(&self, format: ManifestFormat) {441		self.settings_mut().manifest_format = format;442	}443444	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {445		Ref::map(self.settings(), |s| &*s.trace_format)446	}447	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {448		self.settings_mut().trace_format = format;449	}450451	pub fn max_trace(&self) -> usize {452		self.settings().max_trace453	}454	pub fn set_max_trace(&self, trace: usize) {455		self.settings_mut().max_trace = trace;456	}457458	pub fn max_stack(&self) -> usize {459		self.settings().max_stack460	}461	pub fn set_max_stack(&self, trace: usize) {462		self.settings_mut().max_stack = trace;463	}464}465466#[cfg(test)]467pub mod tests {468	use super::Val;469	use crate::{error::Error::*, primitive_equals, EvaluationState};470	use jrsonnet_parser::*;471	use std::{path::PathBuf, rc::Rc};472473	#[test]474	#[should_panic]475	fn eval_state_stacktrace() {476		let state = EvaluationState::default();477		state.run_in_state(|| {478			state479				.push(480					&ExprLocation(Rc::new(PathBuf::from("test1.jsonnet")), 10, 20),481					|| "outer".to_owned(),482					|| {483						state.push(484							&ExprLocation(Rc::new(PathBuf::from("test2.jsonnet")), 30, 40),485							|| "inner".to_owned(),486							|| Err(RuntimeError("".into()).into()),487						)?;488						Ok(())489					},490				)491				.unwrap();492		});493	}494495	#[test]496	fn eval_state_standard() {497		let state = EvaluationState::default();498		state.with_stdlib();499		assert!(primitive_equals(500			&state501				.evaluate_snippet_raw(502					Rc::new(PathBuf::from("raw.jsonnet")),503					r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#.into()504				)505				.unwrap(),506			&Val::Bool(true),507		)508		.unwrap());509	}510511	macro_rules! eval {512		($str: expr) => {513			EvaluationState::default()514				.with_stdlib()515				.evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())516				.unwrap()517		};518	}519	macro_rules! eval_json {520		($str: expr) => {{521			let evaluator = EvaluationState::default();522			evaluator.with_stdlib();523			evaluator.run_in_state(|| {524				evaluator525					.evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())526					.unwrap()527					.to_json(0)528					.unwrap()529					.replace("\n", "")530				})531			}};532	}533534	/// Asserts given code returns `true`535	macro_rules! assert_eval {536		($str: expr) => {537			assert!(primitive_equals(&eval!($str), &Val::Bool(true)).unwrap())538		};539	}540541	/// Asserts given code returns `false`542	macro_rules! assert_eval_neg {543		($str: expr) => {544			assert!(primitive_equals(&eval!($str), &Val::Bool(false)).unwrap())545		};546	}547	macro_rules! assert_json {548		($str: expr, $out: expr) => {549			assert_eq!(eval_json!($str), $out.replace("\t", ""))550		};551	}552553	/// Sanity checking, before trusting to another tests554	#[test]555	fn equality_operator() {556		assert_eval!("2 == 2");557		assert_eval_neg!("2 != 2");558		assert_eval!("2 != 3");559		assert_eval_neg!("2 == 3");560		assert_eval!("'Hello' == 'Hello'");561		assert_eval_neg!("'Hello' != 'Hello'");562		assert_eval!("'Hello' != 'World'");563		assert_eval_neg!("'Hello' == 'World'");564	}565566	#[test]567	fn math_evaluation() {568		assert_eval!("2 + 2 * 2 == 6");569		assert_eval!("3 + (2 + 2 * 2) == 9");570	}571572	#[test]573	fn string_concat() {574		assert_eval!("'Hello' + 'World' == 'HelloWorld'");575		assert_eval!("'Hello' * 3 == 'HelloHelloHello'");576		assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");577	}578579	#[test]580	fn faster_join() {581		assert_eval!("std.join([0,0], [[1,2],[3,4],[5,6]]) == [1,2,0,0,3,4,0,0,5,6]");582		assert_eval!("std.join(',', ['1','2','3','4']) == '1,2,3,4'");583	}584585	#[test]586	fn function_contexts() {587		assert_eval!(588			r#"589				local k = {590					t(name = self.h): [self.h, name],591					h: 3,592				};593				local f = {594					t: k.t(),595					h: 4,596				};597				f.t[0] == f.t[1]598			"#599		);600	}601602	#[test]603	fn local() {604		assert_eval!("local a = 2; local b = 3; a + b == 5");605		assert_eval!("local a = 1, b = a + 1; a + b == 3");606		assert_eval!("local a = 1; local a = 2; a == 2");607	}608609	#[test]610	fn object_lazyness() {611		assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);612	}613614	#[test]615	fn object_inheritance() {616		assert_json!("{a: self.b} + {b:3}", r#"{"a": 3,"b": 3}"#);617	}618619	#[test]620	fn object_assertion_success() {621		eval!("{assert \"a\" in self} + {a:2}");622	}623624	#[test]625	fn object_assertion_error() {626		eval!("{assert \"a\" in self}");627	}628629	#[test]630	fn lazy_args() {631		eval!("local test(a) = 2; test(error '3')");632	}633634	#[test]635	#[should_panic]636	fn tailstrict_args() {637		eval!("local test(a) = 2; test(error '3') tailstrict");638	}639640	#[test]641	#[should_panic]642	fn no_binding_error() {643		eval!("a");644	}645646	#[test]647	fn test_object() {648		assert_json!("{a:2}", r#"{"a": 2}"#);649		assert_json!("{a:2+2}", r#"{"a": 4}"#);650		assert_json!("{a:2}+{b:2}", r#"{"a": 2,"b": 2}"#);651		assert_json!("{b:3}+{b:2}", r#"{"b": 2}"#);652		assert_json!("{b:3}+{b+:2}", r#"{"b": 5}"#);653		assert_json!("local test='a'; {[test]:2}", r#"{"a": 2}"#);654		assert_json!(655			r#"656				{657					name: "Alice",658					welcome: "Hello " + self.name + "!",659				}660			"#,661			r#"{"name": "Alice","welcome": "Hello Alice!"}"#662		);663		assert_json!(664			r#"665				{666					name: "Alice",667					welcome: "Hello " + self.name + "!",668				} + {669					name: "Bob"670				}671			"#,672			r#"{"name": "Bob","welcome": "Hello Bob!"}"#673		);674	}675676	#[test]677	fn functions() {678		assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");679		assert_json!(680			r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,681			r#""HelloDearWorld""#682		);683	}684685	#[test]686	fn local_methods() {687		assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");688		assert_json!(689			r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,690			r#""HelloDearWorld""#691		);692	}693694	#[test]695	fn object_locals() {696		assert_json!(r#"{local a = 3, b: a}"#, r#"{"b": 3}"#);697		assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b": 3}"#);698		assert_json!(699			r#"{local a = function (b) {[b]:4}, test: a("test")}"#,700			r#"{"test": {"test": 4}}"#701		);702	}703704	#[test]705	fn object_comp() {706		assert_json!(707			r#"{local t = "a", ["h"+i+"_"+z]: if "h"+(i-1)+"_"+z in self then t+1 else 0+t for i in [1,2,3] for z in [2,3,4] if z != i}"#,708			"{\"h1_2\": \"0a\",\"h1_3\": \"0a\",\"h1_4\": \"0a\",\"h2_3\": \"a1\",\"h2_4\": \"a1\",\"h3_2\": \"0a\",\"h3_4\": \"a1\"}"709		)710	}711712	#[test]713	fn direct_self() {714		println!(715			"{:#?}",716			eval!(717				r#"718					{719						local me = self,720						a: 3,721						b(): me.a,722					}723				"#724			)725		);726	}727728	#[test]729	fn indirect_self() {730		// `self` assigned to `me` was lost when being731		// referenced from field732		eval!(733			r#"{734				local me = self,735				a: 3,736				b: me.a,737			}.b"#738		);739	}740741	// We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly742	#[test]743	fn std_assert_ok() {744		eval!("std.assertEqual(4.5 << 2, 16)");745	}746747	#[test]748	#[should_panic]749	fn std_assert_failure() {750		eval!("std.assertEqual(4.5 << 2, 15)");751	}752753	#[test]754	fn string_is_string() {755		assert!(primitive_equals(756			&eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),757			&Val::Bool(false),758		)759		.unwrap());760	}761762	#[test]763	fn base64_works() {764		assert_json!(r#"std.base64("test")"#, r#""dGVzdA==""#);765	}766767	#[test]768	fn utf8_chars() {769		assert_json!(770			r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,771			r#"{"c": 128526,"l": 1}"#772		)773	}774775	#[test]776	fn json() {777		assert_json!(778			r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,779			r#""{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}""#780		);781	}782783	#[test]784	fn test() {785		assert_json!(786			r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,787			"[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"788		);789	}790791	#[test]792	fn sjsonnet() {793		eval!(794			r#"795			local x0 = {k: 1};796			local x1 = {k: x0.k + x0.k};797			local x2 = {k: x1.k + x1.k};798			local x3 = {k: x2.k + x2.k};799			local x4 = {k: x3.k + x3.k};800			local x5 = {k: x4.k + x4.k};801			local x6 = {k: x5.k + x5.k};802			local x7 = {k: x6.k + x6.k};803			local x8 = {k: x7.k + x7.k};804			local x9 = {k: x8.k + x8.k};805			local x10 = {k: x9.k + x9.k};806			local x11 = {k: x10.k + x10.k};807			local x12 = {k: x11.k + x11.k};808			local x13 = {k: x12.k + x12.k};809			local x14 = {k: x13.k + x13.k};810			local x15 = {k: x14.k + x14.k};811			local x16 = {k: x15.k + x15.k};812			local x17 = {k: x16.k + x16.k};813			local x18 = {k: x17.k + x17.k};814			local x19 = {k: x18.k + x18.k};815			local x20 = {k: x19.k + x19.k};816			local x21 = {k: x20.k + x20.k};817			x21.k818		"#819		);820	}821822	// This test is commented out by default, because of huge compilation slowdown823	// #[bench]824	// fn bench_codegen(b: &mut Bencher) {825	// 	b.iter(|| {826	// 		#[allow(clippy::all)]827	// 		let stdlib = {828	// 			use jrsonnet_parser::*;829	// 			include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))830	// 		};831	// 		stdlib832	// 	})833	// }834835	/*836	#[bench]837	fn bench_serialize(b: &mut Bencher) {838		b.iter(|| {839			bincode::deserialize::<jrsonnet_parser::LocExpr>(include_bytes!(concat!(840				env!("OUT_DIR"),841				"/stdlib.bincode"842			)))843			.expect("deserialize stdlib")844		})845	}846847	#[bench]848	fn bench_parse(b: &mut Bencher) {849		b.iter(|| {850			jrsonnet_parser::parse(851				jrsonnet_stdlib::STDLIB_STR,852				&jrsonnet_parser::ParserSettings {853					loc_data: true,854					file_name: Rc::new(PathBuf::from("std.jsonnet")),855				},856			)857		})858	}859	*/860861	#[test]862	fn equality() {863		println!(864			"{:?}",865			jrsonnet_parser::parse(866				"{ x: 1, y: 2 } == { x: 1, y: 2 }",867				&ParserSettings::default()868			)869		);870		assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")871	}872873	#[test]874	fn native_ext() -> crate::error::Result<()> {875		use super::native::NativeCallback;876		let evaluator = EvaluationState::default();877878		evaluator.with_stdlib();879		evaluator.settings_mut().ext_natives.insert(880			"native_add".into(),881			Rc::new(NativeCallback::new(882				ParamsDesc(Rc::new(vec![883					Param("a".into(), None),884					Param("b".into(), None),885				])),886				|args| match (&args[0], &args[1]) {887					(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),888					(_, _) => todo!(),889				},890			)),891		);892		evaluator.evaluate_snippet_raw(893			Rc::new(PathBuf::from("test.jsonnet")),894			"std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),895		)?;896		Ok(())897	}898899	#[test]900	fn constant_intrinsic() -> crate::error::Result<()> {901		assert_eval!(902			"local std2 = std; local std = std2 { primitiveEquals(a, b):: false }; 1 == 1"903		);904		Ok(())905	}906907	struct TestImportResolver(Rc<str>);908	impl crate::import::ImportResolver for TestImportResolver {909		fn resolve_file(&self, _: &PathBuf, _: &PathBuf) -> crate::error::Result<Rc<PathBuf>> {910			Ok(Rc::new(PathBuf::from("/test")))911		}912913		fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result<Rc<str>> {914			Ok(self.0.clone())915		}916917		unsafe fn as_any(&self) -> &dyn std::any::Any {918			panic!()919		}920	}921922	#[test]923	fn issue_23() {924		let state = EvaluationState::default();925		state.set_import_resolver(Box::new(TestImportResolver(r#"import "/test""#.into())));926		let _ = state.evaluate_file_raw(&PathBuf::from("/test"));927	}928}
after · crates/jrsonnet-evaluator/src/lib.rs
1#![cfg_attr(feature = "unstable", feature(stmt_expr_attributes))]2#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]3#![warn(clippy::all, clippy::nursery)]45mod builtin;6mod ctx;7mod dynamic;8pub mod error;9mod evaluate;10mod function;11mod import;12mod integrations;13mod map;14pub mod native;15mod obj;16pub mod trace;17pub mod typed;18mod val;1920pub use ctx::*;21pub use dynamic::*;22use error::{Error::*, LocError, Result, StackTraceElement};23pub use evaluate::*;24pub use function::parse_function_call;25pub use import::*;26use jrsonnet_interner::IStr;27use jrsonnet_parser::*;28use native::NativeCallback;29pub use obj::*;30use std::{31	cell::{Ref, RefCell, RefMut},32	collections::HashMap,33	fmt::Debug,34	path::PathBuf,35	rc::Rc,36};37use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat};38pub use val::*;3940type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;41#[derive(Clone)]42pub enum LazyBinding {43	Bindable(Rc<BindableFn>),44	Bound(LazyVal),45}4647impl Debug for LazyBinding {48	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {49		write!(f, "LazyBinding")50	}51}52impl LazyBinding {53	pub fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {54		match self {55			Self::Bindable(v) => v(this, super_obj),56			Self::Bound(v) => Ok(v.clone()),57		}58	}59}6061pub struct EvaluationSettings {62	/// Limits recursion by limiting the number of stack frames63	pub max_stack: usize,64	/// Limits amount of stack trace items preserved65	pub max_trace: usize,66	/// Used for s`td.extVar`67	pub ext_vars: HashMap<IStr, Val>,68	/// Used for ext.native69	pub ext_natives: HashMap<IStr, Rc<NativeCallback>>,70	/// TLA vars71	pub tla_vars: HashMap<IStr, Val>,72	/// Global variables are inserted in default context73	pub globals: HashMap<IStr, Val>,74	/// Used to resolve file locations/contents75	pub import_resolver: Box<dyn ImportResolver>,76	/// Used in manifestification functions77	pub manifest_format: ManifestFormat,78	/// Used for bindings79	pub trace_format: Box<dyn TraceFormat>,80}81impl Default for EvaluationSettings {82	fn default() -> Self {83		Self {84			max_stack: 200,85			max_trace: 20,86			globals: Default::default(),87			ext_vars: Default::default(),88			ext_natives: Default::default(),89			tla_vars: Default::default(),90			import_resolver: Box::new(DummyImportResolver),91			manifest_format: ManifestFormat::Json(4),92			trace_format: Box::new(CompactFormat {93				padding: 4,94				resolver: trace::PathResolver::Absolute,95			}),96		}97	}98}99100#[derive(Default)]101struct EvaluationData {102	/// Used for stack overflow detection, stacktrace is populated on unwind103	stack_depth: usize,104	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces105	files: HashMap<Rc<PathBuf>, FileData>,106	str_files: HashMap<Rc<PathBuf>, IStr>,107}108109pub struct FileData {110	source_code: IStr,111	parsed: LocExpr,112	evaluated: Option<Val>,113}114#[derive(Default)]115pub struct EvaluationStateInternals {116	/// Internal state117	data: RefCell<EvaluationData>,118	/// Settings, safe to change at runtime119	settings: RefCell<EvaluationSettings>,120}121122thread_local! {123	/// Contains the state for a currently executed file.124	/// Global state is fine here.125	pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)126}127pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {128	EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))129}130pub(crate) fn push<T>(131	e: Option<&ExprLocation>,132	frame_desc: impl FnOnce() -> String,133	f: impl FnOnce() -> Result<T>,134) -> Result<T> {135	with_state(|s| s.push(e, frame_desc, f))136}137138pub fn push_stack_frame<T>(139	e: Option<&ExprLocation>,140	frame_desc: impl FnOnce() -> String,141	f: impl FnOnce() -> Result<T>,142) -> Result<T> {143	push(e, frame_desc, f)144}145146/// Maintains stack trace and import resolution147#[derive(Default, Clone)]148pub struct EvaluationState(Rc<EvaluationStateInternals>);149150impl EvaluationState {151	/// Parses and adds file as loaded152	pub fn add_file(&self, path: Rc<PathBuf>, source_code: IStr) -> Result<()> {153		self.add_parsed_file(154			path.clone(),155			source_code.clone(),156			parse(157				&source_code,158				&ParserSettings {159					file_name: path.clone(),160					loc_data: true,161				},162			)163			.map_err(|error| ImportSyntaxError {164				error: Box::new(error),165				path,166				source_code,167			})?,168		)?;169170		Ok(())171	}172173	/// Adds file by source code and parsed expr174	pub fn add_parsed_file(175		&self,176		name: Rc<PathBuf>,177		source_code: IStr,178		parsed: LocExpr,179	) -> Result<()> {180		self.data_mut().files.insert(181			name,182			FileData {183				source_code,184				parsed,185				evaluated: None,186			},187		);188189		Ok(())190	}191	pub fn get_source(&self, name: &PathBuf) -> Option<IStr> {192		let ro_map = &self.data().files;193		ro_map.get(name).map(|value| value.source_code.clone())194	}195	pub fn map_source_locations(&self, file: &PathBuf, locs: &[usize]) -> Vec<CodeLocation> {196		offset_to_location(&self.get_source(file).unwrap(), locs)197	}198199	pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Val> {200		let file_path = self.resolve_file(from, path)?;201		{202			let data = self.data();203			let files = &data.files;204			if files.contains_key(&file_path) {205				drop(data);206				return self.evaluate_loaded_file_raw(&file_path);207			}208		}209		let contents = self.load_file_contents(&file_path)?;210		self.add_file(file_path.clone(), contents)?;211		self.evaluate_loaded_file_raw(&file_path)212	}213	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<IStr> {214		let path = self.resolve_file(from, path)?;215		if !self.data().str_files.contains_key(&path) {216			let file_str = self.load_file_contents(&path)?;217			self.data_mut().str_files.insert(path.clone(), file_str);218		}219		Ok(self.data().str_files.get(&path).cloned().unwrap())220	}221222	fn evaluate_loaded_file_raw(&self, name: &PathBuf) -> Result<Val> {223		let expr: LocExpr = {224			let ro_map = &self.data().files;225			let value = ro_map226				.get(name)227				.unwrap_or_else(|| panic!("file not added: {:?}", name));228			if let Some(ref evaluated) = value.evaluated {229				return Ok(evaluated.clone());230			}231			value.parsed.clone()232		};233		let value = evaluate(self.create_default_context()?, &expr)?;234		{235			self.data_mut()236				.files237				.get_mut(name)238				.unwrap()239				.evaluated240				.replace(value.clone());241		}242		Ok(value)243	}244245	/// Adds standard library global variable (std) to this evaluator246	pub fn with_stdlib(&self) -> &Self {247		use jrsonnet_stdlib::STDLIB_STR;248		let std_path = Rc::new(PathBuf::from("std.jsonnet"));249		self.run_in_state(|| {250			self.add_parsed_file(251				std_path.clone(),252				STDLIB_STR.to_owned().into(),253				builtin::get_parsed_stdlib(),254			)255			.unwrap();256			let val = self.evaluate_loaded_file_raw(&std_path).unwrap();257			self.settings_mut().globals.insert("std".into(), val);258		});259		self260	}261262	/// Creates context with all passed global variables263	pub fn create_default_context(&self) -> Result<Context> {264		let globals = &self.settings().globals;265		let mut new_bindings: HashMap<IStr, LazyBinding> = HashMap::new();266		for (name, value) in globals.iter() {267			new_bindings.insert(268				name.clone(),269				LazyBinding::Bound(resolved_lazy_val!(value.clone())),270			);271		}272		Context::new().extend_unbound(new_bindings, None, None, None)273	}274275	/// Executes code creating a new stack frame276	pub fn push<T>(277		&self,278		e: Option<&ExprLocation>,279		frame_desc: impl FnOnce() -> String,280		f: impl FnOnce() -> Result<T>,281	) -> Result<T> {282		{283			let mut data = self.data_mut();284			let stack_depth = &mut data.stack_depth;285			if *stack_depth > self.max_stack() {286				// Error creation uses data, so i drop guard here287				drop(data);288				throw!(StackOverflow);289			} else {290				*stack_depth += 1;291			}292		}293		let result = f();294		self.data_mut().stack_depth -= 1;295		if let Err(mut err) = result {296			err.trace_mut().0.push(StackTraceElement {297				location: e.cloned(),298				desc: frame_desc(),299			});300			return Err(err);301		}302		result303	}304305	/// Runs passed function in state (required if function needs to modify stack trace)306	pub fn run_in_state<T>(&self, f: impl FnOnce() -> T) -> T {307		EVAL_STATE.with(|v| {308			let has_state = v.borrow().is_some();309			if !has_state {310				v.borrow_mut().replace(self.clone());311			}312			let result = f();313			if !has_state {314				v.borrow_mut().take();315			}316			result317		})318	}319320	pub fn stringify_err(&self, e: &LocError) -> String {321		let mut out = String::new();322		self.settings()323			.trace_format324			.write_trace(&mut out, self, e)325			.unwrap();326		out327	}328329	pub fn manifest(&self, val: Val) -> Result<IStr> {330		self.run_in_state(|| val.manifest(&self.manifest_format()))331	}332	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {333		self.run_in_state(|| val.manifest_multi(&self.manifest_format()))334	}335	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {336		self.run_in_state(|| val.manifest_stream(&self.manifest_format()))337	}338339	/// If passed value is function then call with set TLA340	pub fn with_tla(&self, val: Val) -> Result<Val> {341		self.run_in_state(|| {342			Ok(match val {343				Val::Func(func) => push(344					None,345					|| "during TLA call".to_owned(),346					|| {347						Ok(func.evaluate_map(348							self.create_default_context()?,349							&self.settings().tla_vars,350							true,351						)?)352					},353				)?,354				v => v,355			})356		})357	}358}359360/// Internals361impl EvaluationState {362	fn data(&self) -> Ref<EvaluationData> {363		self.0.data.borrow()364	}365	fn data_mut(&self) -> RefMut<EvaluationData> {366		self.0.data.borrow_mut()367	}368	pub fn settings(&self) -> Ref<EvaluationSettings> {369		self.0.settings.borrow()370	}371	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {372		self.0.settings.borrow_mut()373	}374}375376/// Raw methods evaluate passed values but don't perform TLA execution377impl EvaluationState {378	pub fn evaluate_file_raw(&self, name: &PathBuf) -> Result<Val> {379		self.run_in_state(|| self.import_file(&std::env::current_dir().expect("cwd"), name))380	}381	pub fn evaluate_file_raw_nocwd(&self, name: &PathBuf) -> Result<Val> {382		self.run_in_state(|| self.import_file(&PathBuf::from("."), name))383	}384	/// Parses and evaluates the given snippet385	pub fn evaluate_snippet_raw(&self, source: Rc<PathBuf>, code: IStr) -> Result<Val> {386		let parsed = parse(387			&code,388			&ParserSettings {389				file_name: source.clone(),390				loc_data: true,391			},392		)393		.unwrap();394		self.add_parsed_file(source, code, parsed.clone())?;395		self.evaluate_expr_raw(parsed)396	}397	/// Evaluates the parsed expression398	pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {399		self.run_in_state(|| evaluate(self.create_default_context()?, &code))400	}401}402403/// Settings utilities404impl EvaluationState {405	pub fn add_ext_var(&self, name: IStr, value: Val) {406		self.settings_mut().ext_vars.insert(name, value);407	}408	pub fn add_ext_str(&self, name: IStr, value: IStr) {409		self.add_ext_var(name, Val::Str(value));410	}411	pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {412		let value =413			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("ext_code {}", name))), code)?;414		self.add_ext_var(name, value);415		Ok(())416	}417418	pub fn add_tla(&self, name: IStr, value: Val) {419		self.settings_mut().tla_vars.insert(name, value);420	}421	pub fn add_tla_str(&self, name: IStr, value: IStr) {422		self.add_tla(name, Val::Str(value));423	}424	pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {425		let value =426			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("tla_code {}", name))), code)?;427		self.add_tla(name, value);428		Ok(())429	}430431	pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {432		Ok(self.settings().import_resolver.resolve_file(from, path)?)433	}434	pub fn load_file_contents(&self, path: &PathBuf) -> Result<IStr> {435		Ok(self.settings().import_resolver.load_file_contents(path)?)436	}437438	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {439		Ref::map(self.settings(), |s| &*s.import_resolver)440	}441	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {442		self.settings_mut().import_resolver = resolver;443	}444445	pub fn add_native(&self, name: IStr, cb: Rc<NativeCallback>) {446		self.settings_mut().ext_natives.insert(name, cb);447	}448449	pub fn manifest_format(&self) -> ManifestFormat {450		self.settings().manifest_format.clone()451	}452	pub fn set_manifest_format(&self, format: ManifestFormat) {453		self.settings_mut().manifest_format = format;454	}455456	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {457		Ref::map(self.settings(), |s| &*s.trace_format)458	}459	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {460		self.settings_mut().trace_format = format;461	}462463	pub fn max_trace(&self) -> usize {464		self.settings().max_trace465	}466	pub fn set_max_trace(&self, trace: usize) {467		self.settings_mut().max_trace = trace;468	}469470	pub fn max_stack(&self) -> usize {471		self.settings().max_stack472	}473	pub fn set_max_stack(&self, trace: usize) {474		self.settings_mut().max_stack = trace;475	}476}477478#[cfg(test)]479pub mod tests {480	use super::Val;481	use crate::{error::Error::*, primitive_equals, EvaluationState};482	use jrsonnet_interner::IStr;483	use jrsonnet_parser::*;484	use std::{path::PathBuf, rc::Rc};485486	#[test]487	#[should_panic]488	fn eval_state_stacktrace() {489		let state = EvaluationState::default();490		state.run_in_state(|| {491			state492				.push(493					Some(&ExprLocation(494						Rc::new(PathBuf::from("test1.jsonnet")),495						10,496						20,497					)),498					|| "outer".to_owned(),499					|| {500						state.push(501							Some(&ExprLocation(502								Rc::new(PathBuf::from("test2.jsonnet")),503								30,504								40,505							)),506							|| "inner".to_owned(),507							|| Err(RuntimeError("".into()).into()),508						)?;509						Ok(())510					},511				)512				.unwrap();513		});514	}515516	#[test]517	fn eval_state_standard() {518		let state = EvaluationState::default();519		state.with_stdlib();520		assert!(primitive_equals(521			&state522				.evaluate_snippet_raw(523					Rc::new(PathBuf::from("raw.jsonnet")),524					r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#.into()525				)526				.unwrap(),527			&Val::Bool(true),528		)529		.unwrap());530	}531532	macro_rules! eval {533		($str: expr) => {534			EvaluationState::default()535				.with_stdlib()536				.evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())537				.unwrap()538		};539	}540	macro_rules! eval_json {541		($str: expr) => {{542			let evaluator = EvaluationState::default();543			evaluator.with_stdlib();544			evaluator.run_in_state(|| {545				evaluator546					.evaluate_snippet_raw(Rc::new(PathBuf::from("raw.jsonnet")), $str.into())547					.unwrap()548					.to_json(0)549					.unwrap()550					.replace("\n", "")551				})552			}};553	}554555	/// Asserts given code returns `true`556	macro_rules! assert_eval {557		($str: expr) => {558			assert!(primitive_equals(&eval!($str), &Val::Bool(true)).unwrap())559		};560	}561562	/// Asserts given code returns `false`563	macro_rules! assert_eval_neg {564		($str: expr) => {565			assert!(primitive_equals(&eval!($str), &Val::Bool(false)).unwrap())566		};567	}568	macro_rules! assert_json {569		($str: expr, $out: expr) => {570			assert_eq!(eval_json!($str), $out.replace("\t", ""))571		};572	}573574	/// Sanity checking, before trusting to another tests575	#[test]576	fn equality_operator() {577		assert_eval!("2 == 2");578		assert_eval_neg!("2 != 2");579		assert_eval!("2 != 3");580		assert_eval_neg!("2 == 3");581		assert_eval!("'Hello' == 'Hello'");582		assert_eval_neg!("'Hello' != 'Hello'");583		assert_eval!("'Hello' != 'World'");584		assert_eval_neg!("'Hello' == 'World'");585	}586587	#[test]588	fn math_evaluation() {589		assert_eval!("2 + 2 * 2 == 6");590		assert_eval!("3 + (2 + 2 * 2) == 9");591	}592593	#[test]594	fn string_concat() {595		assert_eval!("'Hello' + 'World' == 'HelloWorld'");596		assert_eval!("'Hello' * 3 == 'HelloHelloHello'");597		assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");598	}599600	#[test]601	fn faster_join() {602		assert_eval!("std.join([0,0], [[1,2],[3,4],[5,6]]) == [1,2,0,0,3,4,0,0,5,6]");603		assert_eval!("std.join(',', ['1','2','3','4']) == '1,2,3,4'");604	}605606	#[test]607	fn function_contexts() {608		assert_eval!(609			r#"610				local k = {611					t(name = self.h): [self.h, name],612					h: 3,613				};614				local f = {615					t: k.t(),616					h: 4,617				};618				f.t[0] == f.t[1]619			"#620		);621	}622623	#[test]624	fn local() {625		assert_eval!("local a = 2; local b = 3; a + b == 5");626		assert_eval!("local a = 1, b = a + 1; a + b == 3");627		assert_eval!("local a = 1; local a = 2; a == 2");628	}629630	#[test]631	fn object_lazyness() {632		assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);633	}634635	#[test]636	fn object_inheritance() {637		assert_json!("{a: self.b} + {b:3}", r#"{"a": 3,"b": 3}"#);638	}639640	#[test]641	fn object_assertion_success() {642		eval!("{assert \"a\" in self} + {a:2}");643	}644645	#[test]646	fn object_assertion_error() {647		eval!("{assert \"a\" in self}");648	}649650	#[test]651	fn lazy_args() {652		eval!("local test(a) = 2; test(error '3')");653	}654655	#[test]656	#[should_panic]657	fn tailstrict_args() {658		eval!("local test(a) = 2; test(error '3') tailstrict");659	}660661	#[test]662	#[should_panic]663	fn no_binding_error() {664		eval!("a");665	}666667	#[test]668	fn test_object() {669		assert_json!("{a:2}", r#"{"a": 2}"#);670		assert_json!("{a:2+2}", r#"{"a": 4}"#);671		assert_json!("{a:2}+{b:2}", r#"{"a": 2,"b": 2}"#);672		assert_json!("{b:3}+{b:2}", r#"{"b": 2}"#);673		assert_json!("{b:3}+{b+:2}", r#"{"b": 5}"#);674		assert_json!("local test='a'; {[test]:2}", r#"{"a": 2}"#);675		assert_json!(676			r#"677				{678					name: "Alice",679					welcome: "Hello " + self.name + "!",680				}681			"#,682			r#"{"name": "Alice","welcome": "Hello Alice!"}"#683		);684		assert_json!(685			r#"686				{687					name: "Alice",688					welcome: "Hello " + self.name + "!",689				} + {690					name: "Bob"691				}692			"#,693			r#"{"name": "Bob","welcome": "Hello Bob!"}"#694		);695	}696697	#[test]698	fn functions() {699		assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");700		assert_json!(701			r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,702			r#""HelloDearWorld""#703		);704	}705706	#[test]707	fn local_methods() {708		assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");709		assert_json!(710			r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,711			r#""HelloDearWorld""#712		);713	}714715	#[test]716	fn object_locals() {717		assert_json!(r#"{local a = 3, b: a}"#, r#"{"b": 3}"#);718		assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b": 3}"#);719		assert_json!(720			r#"{local a = function (b) {[b]:4}, test: a("test")}"#,721			r#"{"test": {"test": 4}}"#722		);723	}724725	#[test]726	fn object_comp() {727		assert_json!(728			r#"{local t = "a", ["h"+i+"_"+z]: if "h"+(i-1)+"_"+z in self then t+1 else 0+t for i in [1,2,3] for z in [2,3,4] if z != i}"#,729			"{\"h1_2\": \"0a\",\"h1_3\": \"0a\",\"h1_4\": \"0a\",\"h2_3\": \"a1\",\"h2_4\": \"a1\",\"h3_2\": \"0a\",\"h3_4\": \"a1\"}"730		)731	}732733	#[test]734	fn direct_self() {735		println!(736			"{:#?}",737			eval!(738				r#"739					{740						local me = self,741						a: 3,742						b(): me.a,743					}744				"#745			)746		);747	}748749	#[test]750	fn indirect_self() {751		// `self` assigned to `me` was lost when being752		// referenced from field753		eval!(754			r#"{755				local me = self,756				a: 3,757				b: me.a,758			}.b"#759		);760	}761762	// We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly763	#[test]764	fn std_assert_ok() {765		eval!("std.assertEqual(4.5 << 2, 16)");766	}767768	#[test]769	#[should_panic]770	fn std_assert_failure() {771		eval!("std.assertEqual(4.5 << 2, 15)");772	}773774	#[test]775	fn string_is_string() {776		assert!(primitive_equals(777			&eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),778			&Val::Bool(false),779		)780		.unwrap());781	}782783	#[test]784	fn base64_works() {785		assert_json!(r#"std.base64("test")"#, r#""dGVzdA==""#);786	}787788	#[test]789	fn utf8_chars() {790		assert_json!(791			r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,792			r#"{"c": 128526,"l": 1}"#793		)794	}795796	#[test]797	fn json() {798		assert_json!(799			r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,800			r#""{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}""#801		);802	}803804	#[test]805	fn test() {806		assert_json!(807			r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,808			"[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"809		);810	}811812	#[test]813	fn sjsonnet() {814		eval!(815			r#"816			local x0 = {k: 1};817			local x1 = {k: x0.k + x0.k};818			local x2 = {k: x1.k + x1.k};819			local x3 = {k: x2.k + x2.k};820			local x4 = {k: x3.k + x3.k};821			local x5 = {k: x4.k + x4.k};822			local x6 = {k: x5.k + x5.k};823			local x7 = {k: x6.k + x6.k};824			local x8 = {k: x7.k + x7.k};825			local x9 = {k: x8.k + x8.k};826			local x10 = {k: x9.k + x9.k};827			local x11 = {k: x10.k + x10.k};828			local x12 = {k: x11.k + x11.k};829			local x13 = {k: x12.k + x12.k};830			local x14 = {k: x13.k + x13.k};831			local x15 = {k: x14.k + x14.k};832			local x16 = {k: x15.k + x15.k};833			local x17 = {k: x16.k + x16.k};834			local x18 = {k: x17.k + x17.k};835			local x19 = {k: x18.k + x18.k};836			local x20 = {k: x19.k + x19.k};837			local x21 = {k: x20.k + x20.k};838			x21.k839		"#840		);841	}842843	// This test is commented out by default, because of huge compilation slowdown844	// #[bench]845	// fn bench_codegen(b: &mut Bencher) {846	// 	b.iter(|| {847	// 		#[allow(clippy::all)]848	// 		let stdlib = {849	// 			use jrsonnet_parser::*;850	// 			include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))851	// 		};852	// 		stdlib853	// 	})854	// }855856	/*857	#[bench]858	fn bench_serialize(b: &mut Bencher) {859		b.iter(|| {860			bincode::deserialize::<jrsonnet_parser::LocExpr>(include_bytes!(concat!(861				env!("OUT_DIR"),862				"/stdlib.bincode"863			)))864			.expect("deserialize stdlib")865		})866	}867868	#[bench]869	fn bench_parse(b: &mut Bencher) {870		b.iter(|| {871			jrsonnet_parser::parse(872				jrsonnet_stdlib::STDLIB_STR,873				&jrsonnet_parser::ParserSettings {874					loc_data: true,875					file_name: Rc::new(PathBuf::from("std.jsonnet")),876				},877			)878		})879	}880	*/881882	#[test]883	fn equality() {884		println!(885			"{:?}",886			jrsonnet_parser::parse(887				"{ x: 1, y: 2 } == { x: 1, y: 2 }",888				&ParserSettings::default()889			)890		);891		assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")892	}893894	#[test]895	fn native_ext() -> crate::error::Result<()> {896		use super::native::NativeCallback;897		let evaluator = EvaluationState::default();898899		evaluator.with_stdlib();900		evaluator.settings_mut().ext_natives.insert(901			"native_add".into(),902			Rc::new(NativeCallback::new(903				ParamsDesc(Rc::new(vec![904					Param("a".into(), None),905					Param("b".into(), None),906				])),907				|caller, args| {908					assert_eq!(909						caller.unwrap(),910						Rc::new(PathBuf::from("native_caller.jsonnet"))911					);912					match (&args[0], &args[1]) {913						(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),914						(_, _) => unreachable!(),915					}916				},917			)),918		);919		evaluator.evaluate_snippet_raw(920			Rc::new(PathBuf::from("native_caller.jsonnet")),921			"std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),922		)?;923		Ok(())924	}925926	#[test]927	fn constant_intrinsic() -> crate::error::Result<()> {928		assert_eval!(929			"local std2 = std; local std = std2 { primitiveEquals(a, b):: false }; 1 == 1"930		);931		Ok(())932	}933934	struct TestImportResolver(IStr);935	impl crate::import::ImportResolver for TestImportResolver {936		fn resolve_file(&self, _: &PathBuf, _: &PathBuf) -> crate::error::Result<Rc<PathBuf>> {937			Ok(Rc::new(PathBuf::from("/test")))938		}939940		fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result<IStr> {941			Ok(self.0.clone())942		}943944		unsafe fn as_any(&self) -> &dyn std::any::Any {945			panic!()946		}947	}948949	#[test]950	fn issue_23() {951		let state = EvaluationState::default();952		state.set_import_resolver(Box::new(TestImportResolver(r#"import "/test""#.into())));953		let _ = state.evaluate_file_raw(&PathBuf::from("/test"));954	}955}
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -1,17 +1,18 @@
+use jrsonnet_interner::IStr;
 use rustc_hash::FxHashMap;
-use std::{borrow::Borrow, hash::Hash, rc::Rc};
+use std::rc::Rc;
 
 #[derive(Default, Debug)]
-struct LayeredHashMapInternals<K: Hash, V> {
-	parent: Option<LayeredHashMap<K, V>>,
-	current: FxHashMap<K, V>,
+struct LayeredHashMapInternals<V> {
+	parent: Option<LayeredHashMap<V>>,
+	current: FxHashMap<IStr, V>,
 }
 
 #[derive(Debug)]
-pub struct LayeredHashMap<K: Hash, V>(Rc<LayeredHashMapInternals<K, V>>);
+pub struct LayeredHashMap<V>(Rc<LayeredHashMapInternals<V>>);
 
-impl<K: Hash + Eq, V> LayeredHashMap<K, V> {
-	pub fn extend(self, new_layer: FxHashMap<K, V>) -> Self {
+impl<V> LayeredHashMap<V> {
+	pub fn extend(self, new_layer: FxHashMap<IStr, V>) -> Self {
 		match Rc::try_unwrap(self.0) {
 			Ok(mut map) => {
 				map.current.extend(new_layer);
@@ -24,11 +25,7 @@
 		}
 	}
 
-	pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
-	where
-		K: Borrow<Q>,
-		Q: Hash + Eq,
-	{
+	pub fn get(&self, key: &IStr) -> Option<&V> {
 		(self.0)
 			.current
 			.get(key)
@@ -36,13 +33,13 @@
 	}
 }
 
-impl<K: Hash, V> Clone for LayeredHashMap<K, V> {
+impl<V> Clone for LayeredHashMap<V> {
 	fn clone(&self) -> Self {
 		Self(self.0.clone())
 	}
 }
 
-impl<K: Hash + Eq, V> Default for LayeredHashMap<K, V> {
+impl<V> Default for LayeredHashMap<V> {
 	fn default() -> Self {
 		Self(Rc::new(LayeredHashMapInternals {
 			parent: None,
modifiedcrates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/native.rs
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -1,20 +1,27 @@
+#![allow(clippy::type_complexity)]
+
 use crate::{error::Result, Val};
 use jrsonnet_parser::ParamsDesc;
 use std::fmt::Debug;
+use std::path::PathBuf;
+use std::rc::Rc;
 
 pub struct NativeCallback {
 	pub params: ParamsDesc,
-	handler: Box<dyn Fn(&[Val]) -> Result<Val>>,
+	handler: Box<dyn Fn(Option<Rc<PathBuf>>, &[Val]) -> Result<Val>>,
 }
 impl NativeCallback {
-	pub fn new(params: ParamsDesc, handler: impl Fn(&[Val]) -> Result<Val> + 'static) -> Self {
+	pub fn new(
+		params: ParamsDesc,
+		handler: impl Fn(Option<Rc<PathBuf>>, &[Val]) -> Result<Val> + 'static,
+	) -> Self {
 		Self {
 			params,
 			handler: Box::new(handler),
 		}
 	}
-	pub fn call(&self, args: &[Val]) -> Result<Val> {
-		(self.handler)(args)
+	pub fn call(&self, caller: Option<Rc<PathBuf>>, args: &[Val]) -> Result<Val> {
+		(self.handler)(caller, args)
 	}
 }
 impl Debug for NativeCallback {
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -1,5 +1,6 @@
 use crate::{evaluate_add_op, LazyBinding, Result, Val};
 use indexmap::IndexMap;
+use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ExprLocation, Visibility};
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
@@ -12,11 +13,11 @@
 }
 
 // Field => This
-type CacheKey = (Rc<str>, usize);
+type CacheKey = (IStr, usize);
 #[derive(Debug)]
 pub struct ObjValueInternals {
 	super_obj: Option<ObjValue>,
-	this_entries: Rc<HashMap<Rc<str>, ObjMember>>,
+	this_entries: Rc<HashMap<IStr, ObjMember>>,
 	value_cache: RefCell<HashMap<CacheKey, Option<Val>>>,
 }
 #[derive(Clone)]
@@ -47,7 +48,7 @@
 }
 
 impl ObjValue {
-	pub fn new(super_obj: Option<Self>, this_entries: Rc<HashMap<Rc<str>, ObjMember>>) -> Self {
+	pub fn new(super_obj: Option<Self>, this_entries: Rc<HashMap<IStr, ObjMember>>) -> Self {
 		Self(Rc::new(ObjValueInternals {
 			super_obj,
 			this_entries,
@@ -63,7 +64,7 @@
 			Some(v) => Self::new(Some(v.with_super(super_obj)), self.0.this_entries.clone()),
 		}
 	}
-	pub fn enum_fields(&self, handler: &impl Fn(&Rc<str>, &Visibility)) {
+	pub fn enum_fields(&self, handler: &impl Fn(&IStr, &Visibility)) {
 		if let Some(s) = &self.0.super_obj {
 			s.enum_fields(handler);
 		}
@@ -71,7 +72,7 @@
 			handler(name, &member.visibility);
 		}
 	}
-	pub fn fields_visibility(&self) -> IndexMap<Rc<str>, bool> {
+	pub fn fields_visibility(&self) -> IndexMap<IStr, bool> {
 		let out = Rc::new(RefCell::new(IndexMap::new()));
 		self.enum_fields(&|name, visibility| {
 			let mut out = out.borrow_mut();
@@ -91,7 +92,7 @@
 		});
 		Rc::try_unwrap(out).unwrap().into_inner()
 	}
-	pub fn visible_fields(&self) -> Vec<Rc<str>> {
+	pub fn visible_fields(&self) -> Vec<IStr> {
 		let mut visible_fields: Vec<_> = self
 			.fields_visibility()
 			.into_iter()
@@ -101,10 +102,11 @@
 		visible_fields.sort();
 		visible_fields
 	}
-	pub fn get(&self, key: Rc<str>) -> Result<Option<Val>> {
-		Ok(self.get_raw(key, self)?)
+	pub fn get(&self, key: IStr) -> Result<Option<Val>> {
+		Ok(self.get_raw(key, None)?)
 	}
-	pub(crate) fn get_raw(&self, key: Rc<str>, real_this: &Self) -> Result<Option<Val>> {
+	pub(crate) fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result<Option<Val>> {
+		let real_this = real_this.unwrap_or(self);
 		let cache_key = (key.clone(), Rc::as_ptr(&real_this.0) as usize);
 
 		if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
@@ -115,7 +117,7 @@
 			(Some(k), Some(s)) => {
 				let our = self.evaluate_this(k, real_this)?;
 				if k.add {
-					s.get_raw(key, real_this)?
+					s.get_raw(key, Some(real_this))?
 						.map_or(Ok(Some(our.clone())), |v| {
 							Ok(Some(evaluate_add_op(&v, &our)?))
 						})
@@ -123,7 +125,7 @@
 					Ok(Some(our))
 				}
 			}
-			(None, Some(s)) => s.get_raw(key, real_this),
+			(None, Some(s)) => s.get_raw(key, Some(real_this)),
 			(None, None) => Ok(None),
 		}?;
 		self.0
@@ -137,6 +139,10 @@
 			.evaluate(Some(real_this.clone()), self.0.super_obj.clone())?
 			.evaluate()?)
 	}
+
+	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
+		Rc::ptr_eq(&a.0, &b.0)
+	}
 }
 impl PartialEq for ObjValue {
 	fn eq(&self, other: &Self) -> bool {
modifiedcrates/jrsonnet-evaluator/src/trace/location.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/location.rs
+++ b/crates/jrsonnet-evaluator/src/trace/location.rs
@@ -37,7 +37,11 @@
 	];
 	let mut with_no_known_line_ending = vec![];
 	let mut this_line_offset = 0;
-	for (pos, ch) in file.chars().enumerate() {
+	for (pos, ch) in file
+		.chars()
+		.enumerate()
+		.chain(std::iter::once((file.len(), ' ')))
+	{
 		column += 1;
 		match offset_map.last() {
 			Some(x) if x.0 == pos => {
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -65,7 +65,7 @@
 			out,
 			"{}:{}-{}:{}",
 			start.line,
-			end.column - 1,
+			end.column.saturating_sub(1),
 			start.line,
 			end.column
 		)?;
@@ -119,20 +119,23 @@
 			.0
 			.iter()
 			.map(|el| {
-				let resolved_path = self.resolver.resolve(&el.location.0);
-				// TODO: Process all trace elements first
-				let location = evaluation_state
-					.map_source_locations(&el.location.0, &[el.location.1, el.location.2]);
-				(resolved_path, location)
-			})
-			.map(|(mut n, location)| {
-				use std::fmt::Write;
-				write!(n, ":").unwrap();
-				print_code_location(&mut n, &location[0], &location[1]).unwrap();
-				n
+				el.location.as_ref().map(|l| {
+					use std::fmt::Write;
+					let mut resolved_path = self.resolver.resolve(&l.0);
+					// TODO: Process all trace elements first
+					let location = evaluation_state.map_source_locations(&l.0, &[l.1, l.2]);
+					write!(resolved_path, ":").unwrap();
+					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();
+					resolved_path
+				})
 			})
 			.collect::<Vec<_>>();
-		let align = file_names.iter().map(|e| e.len()).max().unwrap_or(0);
+		let align = file_names
+			.iter()
+			.flatten()
+			.map(|e| e.len())
+			.max()
+			.unwrap_or(0);
 		for (i, (el, file)) in error.trace().0.iter().zip(file_names).enumerate() {
 			if i != 0 {
 				writeln!(out)?;
@@ -141,7 +144,7 @@
 				out,
 				"{:<p$}{:<w$}: {}",
 				"",
-				file,
+				file.unwrap_or_else(|| "".to_owned()),
 				el.desc,
 				p = self.padding,
 				w = align
@@ -165,17 +168,21 @@
 				writeln!(out)?;
 			}
 			let desc = &item.desc;
-			let source = item.location.clone();
-			let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
+			if let Some(source) = &item.location {
+				let start_end =
+					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
 
-			write!(
-				out,
-				"    at {} ({}:{}:{})",
-				desc,
-				source.0.to_str().unwrap(),
-				start_end[0].line,
-				start_end[0].column,
-			)?;
+				write!(
+					out,
+					"    at {} ({}:{}:{})",
+					desc,
+					source.0.to_str().unwrap(),
+					start_end[0].line,
+					start_end[0].column,
+				)?;
+			} else {
+				write!(out, "    at {}", desc,)?;
+			}
 		}
 		Ok(())
 	}
@@ -225,17 +232,20 @@
 		let trace = &error.trace();
 		for item in trace.0.iter() {
 			let desc = &item.desc;
-			let source = item.location.clone();
-			let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
-
-			self.print_snippet(
-				out,
-				&evaluation_state.get_source(&source.0).unwrap(),
-				&source.0,
-				&start_end[0],
-				&start_end[1],
-				desc,
-			)?;
+			if let Some(source) = &item.location {
+				let start_end =
+					evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
+				self.print_snippet(
+					out,
+					&evaluation_state.get_source(&source.0).unwrap(),
+					&source.0,
+					&start_end[0],
+					&start_end[1],
+					desc,
+				)?;
+			} else {
+				write!(out, "{}", desc)?;
+			}
 		}
 		Ok(())
 	}
addedcrates/jrsonnet-evaluator/src/typed.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/typed.rs
@@ -0,0 +1,265 @@
+use std::{fmt::Display, rc::Rc};
+
+use crate::{
+	error::{Error, LocError, Result},
+	push, Val,
+};
+use jrsonnet_parser::ExprLocation;
+use jrsonnet_types::{ComplexValType, ValType};
+use thiserror::Error;
+
+#[macro_export]
+macro_rules! unwrap_type {
+	($desc: expr, $value: expr, $typ: expr => $match: path) => {{
+		use $crate::{push_stack_frame, typed::CheckType};
+		push_stack_frame(None, $desc, || Ok($typ.check(&$value)?))?;
+		match $value {
+			$match(v) => v,
+			_ => unreachable!(),
+			}
+		}};
+}
+
+#[derive(Debug, Error, Clone)]
+pub enum TypeError {
+	#[error("expected {0}, got {1}")]
+	ExpectedGot(ComplexValType, ValType),
+	#[error("missing property {0} from {1:?}")]
+	MissingProperty(Rc<str>, ComplexValType),
+	#[error("every failed from {0}:\n{1}")]
+	UnionFailed(ComplexValType, TypeLocErrorList),
+	#[error("number out of bounds: {0} not in {1:?}..{2:?}")]
+	BoundsFailed(f64, Option<f64>, Option<f64>),
+}
+impl From<TypeError> for LocError {
+	fn from(e: TypeError) -> Self {
+		Error::TypeError(e.into()).into()
+	}
+}
+
+#[derive(Debug, Clone)]
+pub struct TypeLocError(Box<TypeError>, ValuePathStack);
+impl From<TypeError> for TypeLocError {
+	fn from(e: TypeError) -> Self {
+		Self(Box::new(e), ValuePathStack(Vec::new()))
+	}
+}
+impl From<TypeLocError> for LocError {
+	fn from(e: TypeLocError) -> Self {
+		Error::TypeError(e).into()
+	}
+}
+impl Display for TypeLocError {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		write!(f, "{}", self.0)?;
+		if !(self.1).0.is_empty() {
+			write!(f, "at {}", self.1)?;
+		}
+		Ok(())
+	}
+}
+
+#[derive(Debug, Clone)]
+pub struct TypeLocErrorList(Vec<TypeLocError>);
+impl Display for TypeLocErrorList {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		use std::fmt::Write;
+		let mut out = String::new();
+		for (i, err) in self.0.iter().enumerate() {
+			if i != 0 {
+				writeln!(f)?;
+			}
+			out.clear();
+			write!(out, "{}", err)?;
+
+			for (i, line) in out.lines().enumerate() {
+				if line.trim().is_empty() {
+					continue;
+				}
+				if i != 0 {
+					writeln!(f)?;
+					write!(f, "    ")?;
+				} else {
+					write!(f, "  - ")?;
+				}
+				write!(f, "{}", line)?;
+			}
+		}
+		Ok(())
+	}
+}
+
+fn push_type(
+	location: Option<&ExprLocation>,
+	error_reason: impl Fn() -> String,
+	path: impl Fn() -> ValuePathItem,
+	item: impl Fn() -> Result<()>,
+) -> Result<()> {
+	push(location, error_reason, || match item() {
+		Ok(_) => Ok(()),
+		Err(mut e) => {
+			if let Error::TypeError(e) = &mut e.error_mut() {
+				(e.1).0.push(path())
+			}
+			Err(e)
+		}
+	})
+}
+
+// TODO: check_fast for fast path of union type checking
+pub trait CheckType {
+	fn check(&self, value: &Val) -> Result<()>;
+}
+
+impl CheckType for ValType {
+	fn check(&self, value: &Val) -> Result<()> {
+		let got = value.value_type();
+		if got != *self {
+			let loc_error: TypeLocError = TypeError::ExpectedGot((*self).into(), got).into();
+			return Err(loc_error.into());
+		}
+		Ok(())
+	}
+}
+
+#[derive(Clone, Debug)]
+enum ValuePathItem {
+	Field(Rc<str>),
+	Index(u64),
+}
+impl Display for ValuePathItem {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		match self {
+			Self::Field(name) => write!(f, ".{}", name)?,
+			Self::Index(idx) => write!(f, "[{}]", idx)?,
+		}
+		Ok(())
+	}
+}
+
+#[derive(Clone, Debug)]
+struct ValuePathStack(Vec<ValuePathItem>);
+impl Display for ValuePathStack {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		write!(f, "self")?;
+		for elem in self.0.iter().rev() {
+			write!(f, "{}", elem)?;
+		}
+		Ok(())
+	}
+}
+
+impl CheckType for ComplexValType {
+	fn check(&self, value: &Val) -> Result<()> {
+		match self {
+			Self::Any => Ok(()),
+			Self::Simple(s) => s.check(value),
+			Self::Char => match value {
+				Val::Str(s) if s.len() == 1 || s.chars().count() == 1 => Ok(()),
+				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
+			},
+			Self::BoundedNumber(from, to) => {
+				if let Val::Num(n) = value {
+					if from.map(|from| from > *n).unwrap_or(false)
+						|| to.map(|to| to <= *n).unwrap_or(false)
+					{
+						return Err(TypeError::BoundsFailed(*n, *from, *to).into());
+					}
+					Ok(())
+				} else {
+					Err(TypeError::ExpectedGot(self.clone(), value.value_type()).into())
+				}
+			}
+			Self::Array(elem_type) => match value {
+				Val::Arr(a) => {
+					for (i, item) in a.iter().enumerate() {
+						push_type(
+							None,
+							|| format!("array index {}", i),
+							|| ValuePathItem::Index(i as u64),
+							|| Ok(elem_type.check(&item.clone()?)?),
+						)?;
+					}
+					Ok(())
+				}
+				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
+			},
+			Self::ArrayRef(elem_type) => match value {
+				Val::Arr(a) => {
+					for (i, item) in a.iter().enumerate() {
+						push_type(
+							None,
+							|| format!("array index {}", i),
+							|| ValuePathItem::Index(i as u64),
+							|| Ok(elem_type.check(&item.clone()?)?),
+						)?;
+					}
+					Ok(())
+				}
+				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
+			},
+			Self::ObjectRef(elems) => match value {
+				Val::Obj(obj) => {
+					for (k, v) in elems.iter() {
+						if let Some(got_v) = obj.get((*k).into())? {
+							push_type(
+								None,
+								|| format!("property {}", k),
+								|| ValuePathItem::Field((*k).into()),
+								|| v.check(&got_v),
+							)?
+						} else {
+							return Err(
+								TypeError::MissingProperty((*k).into(), self.clone()).into()
+							);
+						}
+					}
+					Ok(())
+				}
+				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
+			},
+			Self::Union(types) => {
+				let mut errors = Vec::new();
+				for ty in types.iter() {
+					match ty.check(value) {
+						Ok(()) => {
+							return Ok(());
+						}
+						Err(e) => match e.error() {
+							Error::TypeError(e) => errors.push(e.clone()),
+							_ => return Err(e),
+						},
+					}
+				}
+				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())
+			}
+			Self::UnionRef(types) => {
+				let mut errors = Vec::new();
+				for ty in types.iter() {
+					match ty.check(value) {
+						Ok(()) => {
+							return Ok(());
+						}
+						Err(e) => match e.error() {
+							Error::TypeError(e) => errors.push(e.clone()),
+							_ => return Err(e),
+						},
+					}
+				}
+				Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())
+			}
+			Self::Sum(types) => {
+				for ty in types.iter() {
+					ty.check(value)?
+				}
+				Ok(())
+			}
+			Self::SumRef(types) => {
+				for ty in types.iter() {
+					ty.check(value)?
+				}
+				Ok(())
+			}
+		}
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -9,13 +9,10 @@
 	native::NativeCallback,
 	throw, with_state, Context, ObjValue, Result,
 };
+use jrsonnet_interner::IStr;
 use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
-use std::{
-	cell::RefCell,
-	collections::HashMap,
-	fmt::{Debug, Display},
-	rc::Rc,
-};
+use jrsonnet_types::ValType;
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
 enum LazyValInternals {
 	Computed(Val),
@@ -65,7 +62,7 @@
 
 #[derive(Debug, PartialEq)]
 pub struct FuncDesc {
-	pub name: Rc<str>,
+	pub name: IStr,
 	pub ctx: Context,
 	pub params: ParamsDesc,
 	pub body: LocExpr,
@@ -76,9 +73,9 @@
 	/// Plain function implemented in jsonnet
 	Normal(FuncDesc),
 	/// Standard library function
-	Intrinsic(Rc<str>),
+	Intrinsic(IStr),
 	/// Library functions implemented in native
-	NativeExt(Rc<str>, Rc<NativeCallback>),
+	NativeExt(IStr, Rc<NativeCallback>),
 }
 
 impl PartialEq for FuncVal {
@@ -95,7 +92,7 @@
 	pub fn is_ident(&self) -> bool {
 		matches!(&self, Self::Intrinsic(n) if n as &str == "id")
 	}
-	pub fn name(&self) -> Rc<str> {
+	pub fn name(&self) -> IStr {
 		match self {
 			Self::Normal(normal) => normal.name.clone(),
 			Self::Intrinsic(name) => format!("std.{}", name).into(),
@@ -105,7 +102,7 @@
 	pub fn evaluate(
 		&self,
 		call_ctx: Context,
-		loc: &Option<ExprLocation>,
+		loc: Option<&ExprLocation>,
 		args: &ArgsDesc,
 		tailstrict: bool,
 	) -> Result<Val> {
@@ -127,7 +124,7 @@
 				for p in handler.params.0.iter() {
 					out_args.push(args.binding(p.0.clone())?.evaluate()?);
 				}
-				Ok(handler.call(&out_args)?)
+				Ok(handler.call(loc.clone().map(|l| l.0.clone()), &out_args)?)
 			}
 		}
 	}
@@ -135,7 +132,7 @@
 	pub fn evaluate_map(
 		&self,
 		call_ctx: Context,
-		args: &HashMap<Rc<str>, Val>,
+		args: &HashMap<IStr, Val>,
 		tailstrict: bool,
 	) -> Result<Val> {
 		match self {
@@ -166,53 +163,154 @@
 	}
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum ValType {
-	Bool,
-	Null,
-	Str,
-	Num,
-	Arr,
-	Obj,
-	Func,
+#[derive(Clone)]
+pub enum ManifestFormat {
+	YamlStream(Box<ManifestFormat>),
+	Yaml(usize),
+	Json(usize),
+	ToString,
+	String,
 }
-impl ValType {
-	pub const fn name(&self) -> &'static str {
-		use ValType::*;
+
+#[derive(Debug, Clone)]
+pub enum ArrValue {
+	Lazy(Rc<Vec<LazyVal>>),
+	Eager(Rc<Vec<Val>>),
+	Extended(Box<(Self, Self)>),
+}
+impl ArrValue {
+	pub fn new_eager() -> Self {
+		Self::Eager(Rc::new(Vec::new()))
+	}
+
+	pub fn len(&self) -> usize {
+		match self {
+			Self::Lazy(l) => l.len(),
+			Self::Eager(e) => e.len(),
+			Self::Extended(v) => v.0.len() + v.1.len(),
+		}
+	}
+
+	pub fn is_empty(&self) -> bool {
+		self.len() == 0
+	}
+
+	pub fn get(&self, index: usize) -> Result<Option<Val>> {
+		match self {
+			Self::Lazy(vec) => {
+				if let Some(v) = vec.get(index) {
+					Ok(Some(v.evaluate()?))
+				} else {
+					Ok(None)
+				}
+			}
+			Self::Eager(vec) => Ok(vec.get(index).cloned()),
+			Self::Extended(v) => {
+				let a_len = v.0.len();
+				if a_len > index {
+					v.0.get(index)
+				} else {
+					v.1.get(index - a_len)
+				}
+			}
+		}
+	}
+
+	pub fn get_lazy(&self, index: usize) -> Option<LazyVal> {
+		match self {
+			Self::Lazy(vec) => vec.get(index).cloned(),
+			Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved),
+			Self::Extended(v) => {
+				let a_len = v.0.len();
+				if a_len > index {
+					v.0.get_lazy(index)
+				} else {
+					v.1.get_lazy(index - a_len)
+				}
+			}
+		}
+	}
+
+	pub fn evaluated(&self) -> Result<Rc<Vec<Val>>> {
+		Ok(match self {
+			Self::Lazy(vec) => {
+				let mut out = Vec::with_capacity(vec.len());
+				for item in vec.iter() {
+					out.push(item.evaluate()?);
+				}
+				Rc::new(out)
+			}
+			Self::Eager(vec) => vec.clone(),
+			Self::Extended(_v) => {
+				let mut out = Vec::with_capacity(self.len());
+				for item in self.iter() {
+					out.push(item?);
+				}
+				Rc::new(out)
+			}
+		})
+	}
+
+	pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {
+		(0..self.len()).map(move |idx| match self {
+			Self::Lazy(l) => l[idx].evaluate(),
+			Self::Eager(e) => Ok(e[idx].clone()),
+			Self::Extended(_) => self.get(idx).map(|e| e.unwrap()),
+		})
+	}
+
+	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = LazyVal> + '_ {
+		(0..self.len()).map(move |idx| match self {
+			Self::Lazy(l) => l[idx].clone(),
+			Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()),
+			Self::Extended(_) => self.get_lazy(idx).unwrap(),
+		})
+	}
+
+	pub fn reversed(self) -> Self {
 		match self {
-			Bool => "boolean",
-			Null => "null",
-			Str => "string",
-			Num => "number",
-			Arr => "array",
-			Obj => "object",
-			Func => "function",
+			Self::Lazy(vec) => {
+				let mut out = (&vec as &Vec<_>).clone();
+				out.reverse();
+				Self::Lazy(Rc::new(out))
+			}
+			Self::Eager(vec) => {
+				let mut out = (&vec as &Vec<_>).clone();
+				out.reverse();
+				Self::Eager(Rc::new(out))
+			}
+			Self::Extended(b) => Self::Extended(Box::new((b.1.reversed(), b.0.reversed()))),
 		}
 	}
+
+	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
+		match (a, b) {
+			(Self::Lazy(a), Self::Lazy(b)) => Rc::ptr_eq(a, b),
+			(Self::Eager(a), Self::Eager(b)) => Rc::ptr_eq(a, b),
+			_ => false,
+		}
+	}
 }
-impl Display for ValType {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		write!(f, "{}", self.name())
+
+impl From<Vec<LazyVal>> for ArrValue {
+	fn from(v: Vec<LazyVal>) -> Self {
+		Self::Lazy(Rc::new(v))
 	}
 }
 
-#[derive(Clone)]
-pub enum ManifestFormat {
-	YamlStream(Box<ManifestFormat>),
-	Yaml(usize),
-	Json(usize),
-	ToString,
-	String,
+impl From<Vec<Val>> for ArrValue {
+	fn from(v: Vec<Val>) -> Self {
+		Self::Eager(Rc::new(v))
+	}
 }
 
 #[derive(Debug, Clone)]
 pub enum Val {
 	Bool(bool),
 	Null,
-	Str(Rc<str>),
+	Str(IStr),
 	Num(f64),
-	Lazy(LazyVal),
-	Arr(Rc<Vec<Val>>),
+	Arr(ArrValue),
 	Obj(ObjValue),
 	Func(Rc<FuncVal>),
 }
@@ -237,40 +335,33 @@
 	}
 
 	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {
-		let this_type = self.value_type()?;
+		let this_type = self.value_type();
 		if this_type != val_type {
 			throw!(TypeMismatch(context, vec![val_type], this_type))
 		} else {
 			Ok(())
 		}
 	}
+	pub fn unwrap_num(self) -> Result<f64> {
+		Ok(matches_unwrap!(self, Self::Num(v), v))
+	}
+	pub fn unwrap_func(self) -> Result<Rc<FuncVal>> {
+		Ok(matches_unwrap!(self, Self::Func(v), v))
+	}
 	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {
 		self.assert_type(context, ValType::Bool)?;
-		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Bool(v), v))
+		Ok(matches_unwrap!(self, Self::Bool(v), v))
 	}
-	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {
+	pub fn try_cast_str(self, context: &'static str) -> Result<IStr> {
 		self.assert_type(context, ValType::Str)?;
-		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Str(v), v))
+		Ok(matches_unwrap!(self, Self::Str(v), v))
 	}
 	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {
 		self.assert_type(context, ValType::Num)?;
-		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Self::Num(v), v))
-	}
-	pub fn inplace_unwrap(&mut self) -> Result<()> {
-		while let Self::Lazy(lazy) = self {
-			*self = lazy.evaluate()?;
-		}
-		Ok(())
-	}
-	pub fn unwrap_if_lazy(&self) -> Result<Self> {
-		Ok(if let Self::Lazy(v) = self {
-			v.evaluate()?.unwrap_if_lazy()?
-		} else {
-			self.clone()
-		})
+		self.unwrap_num()
 	}
-	pub fn value_type(&self) -> Result<ValType> {
-		Ok(match self {
+	pub const fn value_type(&self) -> ValType {
+		match self {
 			Self::Str(..) => ValType::Str,
 			Self::Num(..) => ValType::Num,
 			Self::Arr(..) => ValType::Arr,
@@ -278,18 +369,17 @@
 			Self::Bool(_) => ValType::Bool,
 			Self::Null => ValType::Null,
 			Self::Func(..) => ValType::Func,
-			Self::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,
-		})
+		}
 	}
 
-	pub fn to_string(&self) -> Result<Rc<str>> {
-		Ok(match self.unwrap_if_lazy()? {
+	pub fn to_string(&self) -> Result<IStr> {
+		Ok(match self {
 			Self::Bool(true) => "true".into(),
 			Self::Bool(false) => "false".into(),
 			Self::Null => "null".into(),
-			Self::Str(s) => s,
+			Self::Str(s) => s.clone(),
 			v => manifest_json_ex(
-				&v,
+				v,
 				&ManifestJsonOptions {
 					padding: "",
 					mtype: ManifestType::ToString,
@@ -300,7 +390,7 @@
 	}
 
 	/// Expects value to be object, outputs (key, manifested value) pairs
-	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(Rc<str>, Rc<str>)>> {
+	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {
 		let obj = match self {
 			Self::Obj(obj) => obj,
 			_ => throw!(MultiManifestOutputIsNotAObject),
@@ -318,19 +408,19 @@
 	}
 
 	/// Expects value to be array, outputs manifested values
-	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<Rc<str>>> {
+	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {
 		let arr = match self {
 			Self::Arr(a) => a,
 			_ => throw!(StreamManifestOutputIsNotAArray),
 		};
 		let mut out = Vec::with_capacity(arr.len());
 		for i in arr.iter() {
-			out.push(i.manifest(ty)?);
+			out.push(i?.manifest(ty)?);
 		}
 		Ok(out)
 	}
 
-	pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {
+	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {
 		Ok(match ty {
 			ManifestFormat::YamlStream(format) => {
 				let arr = match self {
@@ -348,7 +438,7 @@
 				if !arr.is_empty() {
 					for v in arr.iter() {
 						out.push_str("---\n");
-						out.push_str(&v.manifest(format)?);
+						out.push_str(&v?.manifest(format)?);
 						out.push('\n');
 					}
 					out.push_str("...");
@@ -367,7 +457,7 @@
 	}
 
 	/// For manifestification
-	pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {
+	pub fn to_json(&self, padding: usize) -> Result<IStr> {
 		manifest_json_ex(
 			self,
 			&ManifestJsonOptions {
@@ -419,7 +509,7 @@
 			.try_cast_str("to json")?)
 		})
 	}
-	pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {
+	pub fn to_yaml(&self, padding: usize) -> Result<IStr> {
 		with_state(|s| {
 			let ctx = s
 				.create_default_context()?
@@ -456,7 +546,7 @@
 
 /// Native implementation of `std.primitiveEquals`
 pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {
-	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {
+	Ok(match (val_a, val_b) {
 		(Val::Bool(a), Val::Bool(b)) => a == b,
 		(Val::Null, Val::Null) => true,
 		(Val::Str(a), Val::Str(b)) => a == b,
@@ -467,7 +557,7 @@
 		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(
 			"primitiveEquals operates on primitive types, got object".into(),
 		)),
-		(a, b) if is_function_like(&a) && is_function_like(&b) => {
+		(a, b) if is_function_like(a) && is_function_like(b) => {
 			throw!(RuntimeError("cannot test equality of functions".into()))
 		}
 		(_, _) => false,
@@ -476,26 +566,28 @@
 
 /// Native implementation of `std.equals`
 pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {
-	let val_a = val_a.unwrap_if_lazy()?;
-	let val_b = val_b.unwrap_if_lazy()?;
-
-	if val_a.value_type()? != val_b.value_type()? {
+	if val_a.value_type() != val_b.value_type() {
 		return Ok(false);
 	}
 	match (val_a, val_b) {
-		// Cant test for ptr equality, because all fields needs to be evaluated
 		(Val::Arr(a), Val::Arr(b)) => {
+			if ArrValue::ptr_eq(a, b) {
+				return Ok(true);
+			}
 			if a.len() != b.len() {
 				return Ok(false);
 			}
 			for (a, b) in a.iter().zip(b.iter()) {
-				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {
+				if !equals(&a?, &b?)? {
 					return Ok(false);
 				}
 			}
 			Ok(true)
 		}
 		(Val::Obj(a), Val::Obj(b)) => {
+			if ObjValue::ptr_eq(a, b) {
+				return Ok(true);
+			}
 			let fields = a.visible_fields();
 			if fields != b.visible_fields() {
 				return Ok(false);
@@ -507,6 +599,6 @@
 			}
 			Ok(true)
 		}
-		(a, b) => Ok(primitive_equals(&a, &b)?),
+		(a, b) => Ok(primitive_equals(a, b)?),
 	}
 }
addedcrates/jrsonnet-interner/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-interner/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
addedcrates/jrsonnet-interner/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-interner/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "jrsonnet-interner"
+version = "0.3.3"
+authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0" }
+rustc-hash = "1.1.0"
addedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -0,0 +1,104 @@
+use rustc_hash::FxHashMap;
+use serde::{Deserialize, Serialize};
+use std::{
+	cell::RefCell,
+	fmt::{self, Display},
+	hash::{BuildHasherDefault, Hash, Hasher},
+	ops::Deref,
+	rc::Rc,
+};
+
+#[derive(Clone, PartialOrd, Ord, Eq)]
+pub struct IStr(Rc<str>);
+
+impl Deref for IStr {
+	type Target = str;
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+
+impl PartialEq for IStr {
+	fn eq(&self, other: &Self) -> bool {
+		// It is ok, since all IStr should be inlined into same pool
+		Rc::ptr_eq(&self.0, &other.0)
+	}
+}
+
+impl PartialEq<str> for IStr {
+	fn eq(&self, other: &str) -> bool {
+		&self.0 as &str == other
+	}
+}
+
+impl Hash for IStr {
+	fn hash<H: Hasher>(&self, state: &mut H) {
+		state.write_usize(Rc::as_ptr(&self.0) as *const () as usize)
+	}
+}
+
+impl Drop for IStr {
+	fn drop(&mut self) {
+		// First reference - current object, second - POOL
+		if Rc::strong_count(&self.0) <= 2 {
+			STR_POOL.with(|pool| pool.borrow_mut().remove(&self.0));
+		}
+	}
+}
+
+impl fmt::Debug for IStr {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{:?}", &self.0)
+	}
+}
+
+impl Display for IStr {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		f.write_str(&self.0)
+	}
+}
+
+thread_local! {
+	static STR_POOL: RefCell<FxHashMap<Rc<str>, ()>> = RefCell::new(FxHashMap::with_capacity_and_hasher(200, BuildHasherDefault::default()));
+}
+
+impl From<&str> for IStr {
+	fn from(str: &str) -> Self {
+		IStr(STR_POOL.with(|pool| {
+			let mut pool = pool.borrow_mut();
+			if let Some((k, _)) = pool.get_key_value(str) {
+				k.clone()
+			} else {
+				let rc: Rc<str> = str.into();
+				pool.insert(rc.clone(), ());
+				rc
+			}
+		}))
+	}
+}
+
+impl From<String> for IStr {
+	fn from(str: String) -> Self {
+		(&str as &str).into()
+	}
+}
+
+impl Serialize for IStr {
+	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+	where
+		S: serde::Serializer,
+	{
+		(&self.0 as &str).serialize(serializer)
+	}
+}
+
+impl<'de> Deserialize<'de> for IStr {
+	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+	where
+		D: serde::Deserializer<'de>,
+	{
+		let s = <&str>::deserialize(deserializer)?;
+		Ok(s.into())
+	}
+}
modifiedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -10,16 +10,14 @@
 default = []
 serialize = ["serde"]
 deserialize = ["serde"]
-# Adds ability to dump AST as source code for easy embedding
-dump = ["structdump", "structdump-derive"]
 
 [dependencies]
+jrsonnet-interner = { path = "../jrsonnet-interner" }
+
 peg = "0.6.3"
 unescape = "0.1.0"
 
 serde = { version = "1.0", features = ["derive", "rc"], optional = true }
-structdump = { version = "0.1.2", optional = true }
-structdump-derive = { version = "0.1.2", optional = true }
 
 [dev-dependencies]
 jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -1,3 +1,4 @@
+use jrsonnet_interner::IStr;
 #[cfg(feature = "deserialize")]
 use serde::Deserialize;
 #[cfg(feature = "serialize")]
@@ -8,21 +9,17 @@
 	path::PathBuf,
 	rc::Rc,
 };
-#[cfg(feature = "dump")]
-use structdump_derive::Codegen;
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
 pub enum FieldName {
 	/// {fixed: 2}
-	Fixed(Rc<str>),
+	Fixed(IStr),
 	/// {["dyn"+"amic"]: 3}
 	Dyn(LocExpr),
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq)]
@@ -35,13 +32,11 @@
 	Unhide,
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
 pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -53,7 +48,6 @@
 	pub value: LocExpr,
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -63,7 +57,6 @@
 	AssertStmt(AssertStmt),
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq)]
@@ -89,7 +82,6 @@
 	}
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq)]
@@ -97,6 +89,7 @@
 	Mul,
 	Div,
 
+	/// Implemented as intrinsic, put here for completeness
 	Mod,
 
 	Add,
@@ -146,14 +139,12 @@
 }
 
 /// name, default value
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
-pub struct Param(pub Rc<str>, pub Option<LocExpr>);
+pub struct Param(pub IStr, pub Option<LocExpr>);
 
 /// Defined function parameters
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
@@ -165,13 +156,11 @@
 	}
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
 pub struct Arg(pub Option<String>, pub LocExpr);
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -183,29 +172,25 @@
 	}
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
 pub struct BindSpec {
-	pub name: Rc<str>,
+	pub name: IStr,
 	pub params: Option<ParamsDesc>,
 	pub value: LocExpr,
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
 pub struct IfSpecData(pub LocExpr);
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
-pub struct ForSpecData(pub Rc<str>, pub LocExpr);
+pub struct ForSpecData(pub IStr, pub LocExpr);
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -214,7 +199,6 @@
 	ForSpec(ForSpecData),
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -226,7 +210,6 @@
 	pub compspecs: Vec<CompSpec>,
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -235,7 +218,6 @@
 	ObjComp(ObjComp),
 }
 
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq, Clone, Copy)]
@@ -256,7 +238,6 @@
 }
 
 /// Syntax base
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, PartialEq)]
@@ -264,11 +245,11 @@
 	Literal(LiteralType),
 
 	/// String value: "hello"
-	Str(Rc<str>),
+	Str(IStr),
 	/// Number: 1, 2.0, 2e+20
 	Num(f64),
 	/// Variable name: test
-	Var(Rc<str>),
+	Var(IStr),
 
 	/// Array of expressions: [1, 2, "Hello"]
 	Arr(Vec<LocExpr>),
@@ -315,7 +296,7 @@
 	/// function(x) x
 	Function(ParamsDesc, LocExpr),
 	/// std.primitiveEquals
-	Intrinsic(Rc<str>),
+	Intrinsic(IStr),
 	/// if true == false then 1 else 2
 	IfElse {
 		cond: IfSpecData,
@@ -325,7 +306,6 @@
 }
 
 /// file, begin offset, end offset
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Clone, PartialEq)]
@@ -337,7 +317,6 @@
 }
 
 /// Holds AST expression and its location in source file
-#[cfg_attr(feature = "dump", derive(Codegen))]
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Clone, PartialEq)]
addedcrates/jrsonnet-types/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-types/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "jrsonnet-types"
+version = "0.3.3"
+authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
+edition = "2018"
+
+[dependencies]
+peg = "0.6.3"
\ No newline at end of file
addedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -0,0 +1,285 @@
+use std::fmt::Display;
+
+#[macro_export]
+macro_rules! ty {
+	((Array<number>)) => {{
+		$crate::ComplexValType::ArrayRef(&$crate::ComplexValType::Simple($crate::ValType::Num))
+	}};
+	(array) => {
+		$crate::ComplexValType::Simple($crate::ValType::Arr)
+	};
+	(boolean) => {
+		$crate::ComplexValType::Simple($crate::ValType::Bool)
+	};
+	(null) => {
+		$crate::ComplexValType::Simple($crate::ValType::Null)
+	};
+	(string) => {
+		$crate::ComplexValType::Simple($crate::ValType::Str)
+	};
+	(char) => {
+		$crate::ComplexValType::Char
+	};
+	(number) => {
+		$crate::ComplexValType::Simple($crate::ValType::Num)
+	};
+	(BoundedNumber<($min:expr), ($max:expr)>) => {{
+		$crate::ComplexValType::BoundedNumber($min, $max)
+	}};
+	(object) => {
+		$crate::ComplexValType::Simple($crate::ValType::Obj)
+	};
+	(any) => {
+		$crate::ComplexValType::Any
+	};
+	(function) => {
+		$crate::ComplexValType::Simple($crate::ValType::Func)
+	};
+	(($($a:tt) |+)) => {{
+		static CONTENTS: &'static [$crate::ComplexValType] = &[
+			$(ty!($a)),+
+		];
+		$crate::ComplexValType::UnionRef(CONTENTS)
+	}};
+	(($($a:tt) &+)) => {{
+		static CONTENTS: &'static [$crate::ComplexValType] = &[
+			$(ty!($a)),+
+		];
+		$crate::ComplexValType::SumRef(CONTENTS)
+	}};
+}
+
+#[test]
+fn test() {
+	assert_eq!(
+		ty!((Array<number>)),
+		ComplexValType::ArrayRef(&ComplexValType::Simple(ValType::Num))
+	);
+	assert_eq!(ty!(array), ComplexValType::Simple(ValType::Arr));
+	assert_eq!(ty!(any), ComplexValType::Any);
+	assert_eq!(
+		ty!((string | number)),
+		ComplexValType::UnionRef(&[
+			ComplexValType::Simple(ValType::Str),
+			ComplexValType::Simple(ValType::Num)
+		])
+	);
+	assert_eq!(
+		format!("{}", ty!(((string & number) | (object & null)))),
+		"string & number | object & null"
+	);
+	assert_eq!(format!("{}", ty!((string | array))), "string | array");
+	assert_eq!(
+		format!("{}", ty!(((string & number) | array))),
+		"string & number | array"
+	);
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ValType {
+	Bool,
+	Null,
+	Str,
+	Num,
+	Arr,
+	Obj,
+	Func,
+}
+
+impl ValType {
+	pub const fn name(&self) -> &'static str {
+		use ValType::*;
+		match self {
+			Bool => "boolean",
+			Null => "null",
+			Str => "string",
+			Num => "number",
+			Arr => "array",
+			Obj => "object",
+			Func => "function",
+		}
+	}
+}
+
+impl Display for ValType {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		write!(f, "{}", self.name())
+	}
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum ComplexValType {
+	Any,
+	Char,
+	Simple(ValType),
+	BoundedNumber(Option<f64>, Option<f64>),
+	Array(Box<ComplexValType>),
+	ArrayRef(&'static ComplexValType),
+	ObjectRef(&'static [(&'static str, ComplexValType)]),
+	Union(Vec<ComplexValType>),
+	UnionRef(&'static [ComplexValType]),
+	Sum(Vec<ComplexValType>),
+	SumRef(&'static [ComplexValType]),
+}
+impl From<ValType> for ComplexValType {
+	fn from(s: ValType) -> Self {
+		Self::Simple(s)
+	}
+}
+
+fn write_union(
+	f: &mut std::fmt::Formatter<'_>,
+	is_union: bool,
+	union: &[ComplexValType],
+) -> std::fmt::Result {
+	for (i, v) in union.iter().enumerate() {
+		let should_add_braces =
+			matches!(v, ComplexValType::UnionRef(_) | ComplexValType::Union(_) if !is_union);
+		if i != 0 {
+			write!(f, " {} ", if is_union { '|' } else { '&' })?;
+		}
+		if should_add_braces {
+			write!(f, "(")?;
+		}
+		write!(f, "{}", v)?;
+		if should_add_braces {
+			write!(f, ")")?;
+		}
+	}
+	Ok(())
+}
+
+fn print_array(a: &ComplexValType, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+	if *a == ComplexValType::Any {
+		write!(f, "array")?
+	} else {
+		write!(f, "Array<{}>", a)?
+	}
+	Ok(())
+}
+
+impl Display for ComplexValType {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		match self {
+			ComplexValType::Any => write!(f, "any")?,
+			ComplexValType::Simple(s) => write!(f, "{}", s)?,
+			ComplexValType::Char => write!(f, "char")?,
+			ComplexValType::BoundedNumber(a, b) => write!(
+				f,
+				"BoundedNumber<{}, {}>",
+				a.map(|e| e.to_string()).unwrap_or_else(|| "".into()),
+				b.map(|e| e.to_string()).unwrap_or_else(|| "".into())
+			)?,
+			ComplexValType::ArrayRef(a) => print_array(a, f)?,
+			ComplexValType::Array(a) => print_array(a, f)?,
+			ComplexValType::ObjectRef(fields) => {
+				write!(f, "{{")?;
+				for (i, (k, v)) in fields.iter().enumerate() {
+					if i != 0 {
+						write!(f, ", ")?;
+					}
+					write!(f, "{}: {}", k, v)?;
+				}
+				write!(f, "}}")?;
+			}
+			ComplexValType::Union(v) => write_union(f, true, v)?,
+			ComplexValType::UnionRef(v) => write_union(f, true, v)?,
+			ComplexValType::Sum(v) => write_union(f, false, v)?,
+			ComplexValType::SumRef(v) => write_union(f, false, v)?,
+		};
+		Ok(())
+	}
+}
+
+peg::parser! {
+pub grammar parser() for str {
+	rule number() -> f64
+		= n:$(['0'..='9']+) { n.parse().unwrap() }
+
+	rule any_ty() -> ComplexValType = "any" { ComplexValType::Any }
+	rule char_ty() -> ComplexValType = "character" { ComplexValType::Char }
+	rule bool_ty() -> ComplexValType = "boolean" { ComplexValType::Simple(ValType::Bool) }
+	rule null_ty() -> ComplexValType = "null" { ComplexValType::Simple(ValType::Null) }
+	rule str_ty() -> ComplexValType = "string" { ComplexValType::Simple(ValType::Str) }
+	rule num_ty() -> ComplexValType = "number" { ComplexValType::Simple(ValType::Num) }
+	rule simple_array_ty() -> ComplexValType = "array" { ComplexValType::Simple(ValType::Arr) }
+	rule simple_object_ty() -> ComplexValType = "object" { ComplexValType::Simple(ValType::Obj) }
+	rule simple_function_ty() -> ComplexValType = "function" { ComplexValType::Simple(ValType::Func) }
+
+	rule array_ty() -> ComplexValType
+		= "Array<" t:ty() ">" { ComplexValType::Array(Box::new(t)) }
+
+	rule bounded_number_ty() -> ComplexValType
+		= "BoundedNumber<" a:number() ", " b:number() ">" { ComplexValType::BoundedNumber(Some(a), Some(b)) }
+
+	rule ty_basic() -> ComplexValType
+		= any_ty()
+		/ char_ty()
+		/ bool_ty()
+		/ null_ty()
+		/ str_ty()
+		/ num_ty()
+		/ simple_array_ty()
+		/ simple_object_ty()
+		/ simple_function_ty()
+		/ array_ty()
+		/ bounded_number_ty()
+
+	pub rule ty() -> ComplexValType
+		= precedence! {
+			a:(@) " | " b:@ {
+				match a {
+					ComplexValType::Union(mut a) => {
+						a.push(b);
+						ComplexValType::Union(a)
+					}
+					_ => ComplexValType::Union(vec![a, b]),
+				}
+			}
+			--
+			a:(@) " & " b:@ {
+				match a {
+					ComplexValType::Sum(mut a) => {
+						a.push(b);
+						ComplexValType::Sum(a)
+					}
+					_ => ComplexValType::Sum(vec![a, b]),
+				}
+			}
+			--
+			"(" t:ty() ")" { t }
+			t:ty_basic() { t }
+		}
+}
+}
+
+#[cfg(test)]
+pub mod tests {
+	use super::parser;
+
+	#[test]
+	fn precedence() {
+		assert_eq!(
+			parser::ty("(any & any) | (any | any) & any")
+				.unwrap()
+				.to_string(),
+			"any & any | (any | any) & any"
+		);
+	}
+
+	#[test]
+	fn array() {
+		assert_eq!(parser::ty("Array<any>").unwrap().to_string(), "array");
+		assert_eq!(
+			parser::ty("Array<number>").unwrap().to_string(),
+			"Array<number>"
+		);
+	}
+	#[test]
+	fn bounded_number() {
+		assert_eq!(
+			parser::ty("BoundedNumber<1, 2>").unwrap().to_string(),
+			"BoundedNumber<1, 2>"
+		);
+	}
+}