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
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -14,6 +14,7 @@
 pub mod native;
 mod obj;
 pub mod trace;
+pub mod typed;
 mod val;
 
 pub use ctx::*;
@@ -22,6 +23,7 @@
 pub use evaluate::*;
 pub use function::parse_function_call;
 pub use import::*;
+use jrsonnet_interner::IStr;
 use jrsonnet_parser::*;
 use native::NativeCallback;
 pub use obj::*;
@@ -62,13 +64,13 @@
 	/// Limits amount of stack trace items preserved
 	pub max_trace: usize,
 	/// Used for s`td.extVar`
-	pub ext_vars: HashMap<Rc<str>, Val>,
+	pub ext_vars: HashMap<IStr, Val>,
 	/// Used for ext.native
-	pub ext_natives: HashMap<Rc<str>, Rc<NativeCallback>>,
+	pub ext_natives: HashMap<IStr, Rc<NativeCallback>>,
 	/// TLA vars
-	pub tla_vars: HashMap<Rc<str>, Val>,
+	pub tla_vars: HashMap<IStr, Val>,
 	/// Global variables are inserted in default context
-	pub globals: HashMap<Rc<str>, Val>,
+	pub globals: HashMap<IStr, Val>,
 	/// Used to resolve file locations/contents
 	pub import_resolver: Box<dyn ImportResolver>,
 	/// Used in manifestification functions
@@ -101,11 +103,11 @@
 	stack_depth: usize,
 	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces
 	files: HashMap<Rc<PathBuf>, FileData>,
-	str_files: HashMap<Rc<PathBuf>, Rc<str>>,
+	str_files: HashMap<Rc<PathBuf>, IStr>,
 }
 
 pub struct FileData {
-	source_code: Rc<str>,
+	source_code: IStr,
 	parsed: LocExpr,
 	evaluated: Option<Val>,
 }
@@ -126,24 +128,28 @@
 	EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))
 }
 pub(crate) fn push<T>(
-	e: &Option<ExprLocation>,
+	e: Option<&ExprLocation>,
 	frame_desc: impl FnOnce() -> String,
 	f: impl FnOnce() -> Result<T>,
 ) -> Result<T> {
-	if let Some(v) = e {
-		with_state(|s| s.push(v, frame_desc, f))
-	} else {
-		f()
-	}
+	with_state(|s| s.push(e, frame_desc, f))
 }
 
+pub fn push_stack_frame<T>(
+	e: Option<&ExprLocation>,
+	frame_desc: impl FnOnce() -> String,
+	f: impl FnOnce() -> Result<T>,
+) -> Result<T> {
+	push(e, frame_desc, f)
+}
+
 /// Maintains stack trace and import resolution
 #[derive(Default, Clone)]
 pub struct EvaluationState(Rc<EvaluationStateInternals>);
 
 impl EvaluationState {
 	/// Parses and adds file as loaded
-	pub fn add_file(&self, path: Rc<PathBuf>, source_code: Rc<str>) -> Result<()> {
+	pub fn add_file(&self, path: Rc<PathBuf>, source_code: IStr) -> Result<()> {
 		self.add_parsed_file(
 			path.clone(),
 			source_code.clone(),
@@ -168,7 +174,7 @@
 	pub fn add_parsed_file(
 		&self,
 		name: Rc<PathBuf>,
-		source_code: Rc<str>,
+		source_code: IStr,
 		parsed: LocExpr,
 	) -> Result<()> {
 		self.data_mut().files.insert(
@@ -182,7 +188,7 @@
 
 		Ok(())
 	}
-	pub fn get_source(&self, name: &PathBuf) -> Option<Rc<str>> {
+	pub fn get_source(&self, name: &PathBuf) -> Option<IStr> {
 		let ro_map = &self.data().files;
 		ro_map.get(name).map(|value| value.source_code.clone())
 	}
@@ -204,7 +210,7 @@
 		self.add_file(file_path.clone(), contents)?;
 		self.evaluate_loaded_file_raw(&file_path)
 	}
-	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<str>> {
+	pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<IStr> {
 		let path = self.resolve_file(from, path)?;
 		if !self.data().str_files.contains_key(&path) {
 			let file_str = self.load_file_contents(&path)?;
@@ -256,7 +262,7 @@
 	/// Creates context with all passed global variables
 	pub fn create_default_context(&self) -> Result<Context> {
 		let globals = &self.settings().globals;
-		let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();
+		let mut new_bindings: HashMap<IStr, LazyBinding> = HashMap::new();
 		for (name, value) in globals.iter() {
 			new_bindings.insert(
 				name.clone(),
@@ -269,7 +275,7 @@
 	/// Executes code creating a new stack frame
 	pub fn push<T>(
 		&self,
-		e: &ExprLocation,
+		e: Option<&ExprLocation>,
 		frame_desc: impl FnOnce() -> String,
 		f: impl FnOnce() -> Result<T>,
 	) -> Result<T> {
@@ -288,7 +294,7 @@
 		self.data_mut().stack_depth -= 1;
 		if let Err(mut err) = result {
 			err.trace_mut().0.push(StackTraceElement {
-				location: e.clone(),
+				location: e.cloned(),
 				desc: frame_desc(),
 			});
 			return Err(err);
@@ -320,13 +326,13 @@
 		out
 	}
 
-	pub fn manifest(&self, val: Val) -> Result<Rc<str>> {
+	pub fn manifest(&self, val: Val) -> Result<IStr> {
 		self.run_in_state(|| val.manifest(&self.manifest_format()))
 	}
-	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(Rc<str>, Rc<str>)>> {
+	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {
 		self.run_in_state(|| val.manifest_multi(&self.manifest_format()))
 	}
-	pub fn manifest_stream(&self, val: Val) -> Result<Vec<Rc<str>>> {
+	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {
 		self.run_in_state(|| val.manifest_stream(&self.manifest_format()))
 	}
 
@@ -334,10 +340,16 @@
 	pub fn with_tla(&self, val: Val) -> Result<Val> {
 		self.run_in_state(|| {
 			Ok(match val {
-				Val::Func(func) => func.evaluate_map(
-					self.create_default_context()?,
-					&self.settings().tla_vars,
-					true,
+				Val::Func(func) => push(
+					None,
+					|| "during TLA call".to_owned(),
+					|| {
+						Ok(func.evaluate_map(
+							self.create_default_context()?,
+							&self.settings().tla_vars,
+							true,
+						)?)
+					},
 				)?,
 				v => v,
 			})
@@ -370,7 +382,7 @@
 		self.run_in_state(|| self.import_file(&PathBuf::from("."), name))
 	}
 	/// Parses and evaluates the given snippet
-	pub fn evaluate_snippet_raw(&self, source: Rc<PathBuf>, code: Rc<str>) -> Result<Val> {
+	pub fn evaluate_snippet_raw(&self, source: Rc<PathBuf>, code: IStr) -> Result<Val> {
 		let parsed = parse(
 			&code,
 			&ParserSettings {
@@ -390,26 +402,26 @@
 
 /// Settings utilities
 impl EvaluationState {
-	pub fn add_ext_var(&self, name: Rc<str>, value: Val) {
+	pub fn add_ext_var(&self, name: IStr, value: Val) {
 		self.settings_mut().ext_vars.insert(name, value);
 	}
-	pub fn add_ext_str(&self, name: Rc<str>, value: Rc<str>) {
+	pub fn add_ext_str(&self, name: IStr, value: IStr) {
 		self.add_ext_var(name, Val::Str(value));
 	}
-	pub fn add_ext_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {
+	pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {
 		let value =
 			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("ext_code {}", name))), code)?;
 		self.add_ext_var(name, value);
 		Ok(())
 	}
 
-	pub fn add_tla(&self, name: Rc<str>, value: Val) {
+	pub fn add_tla(&self, name: IStr, value: Val) {
 		self.settings_mut().tla_vars.insert(name, value);
 	}
-	pub fn add_tla_str(&self, name: Rc<str>, value: Rc<str>) {
+	pub fn add_tla_str(&self, name: IStr, value: IStr) {
 		self.add_tla(name, Val::Str(value));
 	}
-	pub fn add_tla_code(&self, name: Rc<str>, code: Rc<str>) -> Result<()> {
+	pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {
 		let value =
 			self.evaluate_snippet_raw(Rc::new(PathBuf::from(format!("tla_code {}", name))), code)?;
 		self.add_tla(name, value);
@@ -419,7 +431,7 @@
 	pub fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {
 		Ok(self.settings().import_resolver.resolve_file(from, path)?)
 	}
-	pub fn load_file_contents(&self, path: &PathBuf) -> Result<Rc<str>> {
+	pub fn load_file_contents(&self, path: &PathBuf) -> Result<IStr> {
 		Ok(self.settings().import_resolver.load_file_contents(path)?)
 	}
 
@@ -430,7 +442,7 @@
 		self.settings_mut().import_resolver = resolver;
 	}
 
-	pub fn add_native(&self, name: Rc<str>, cb: Rc<NativeCallback>) {
+	pub fn add_native(&self, name: IStr, cb: Rc<NativeCallback>) {
 		self.settings_mut().ext_natives.insert(name, cb);
 	}
 
@@ -467,6 +479,7 @@
 pub mod tests {
 	use super::Val;
 	use crate::{error::Error::*, primitive_equals, EvaluationState};
+	use jrsonnet_interner::IStr;
 	use jrsonnet_parser::*;
 	use std::{path::PathBuf, rc::Rc};
 
@@ -477,11 +490,19 @@
 		state.run_in_state(|| {
 			state
 				.push(
-					&ExprLocation(Rc::new(PathBuf::from("test1.jsonnet")), 10, 20),
+					Some(&ExprLocation(
+						Rc::new(PathBuf::from("test1.jsonnet")),
+						10,
+						20,
+					)),
 					|| "outer".to_owned(),
 					|| {
 						state.push(
-							&ExprLocation(Rc::new(PathBuf::from("test2.jsonnet")), 30, 40),
+							Some(&ExprLocation(
+								Rc::new(PathBuf::from("test2.jsonnet")),
+								30,
+								40,
+							)),
 							|| "inner".to_owned(),
 							|| Err(RuntimeError("".into()).into()),
 						)?;
@@ -883,14 +904,20 @@
 					Param("a".into(), None),
 					Param("b".into(), None),
 				])),
-				|args| match (&args[0], &args[1]) {
-					(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),
-					(_, _) => todo!(),
+				|caller, args| {
+					assert_eq!(
+						caller.unwrap(),
+						Rc::new(PathBuf::from("native_caller.jsonnet"))
+					);
+					match (&args[0], &args[1]) {
+						(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),
+						(_, _) => unreachable!(),
+					}
 				},
 			)),
 		);
 		evaluator.evaluate_snippet_raw(
-			Rc::new(PathBuf::from("test.jsonnet")),
+			Rc::new(PathBuf::from("native_caller.jsonnet")),
 			"std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),
 		)?;
 		Ok(())
@@ -904,13 +931,13 @@
 		Ok(())
 	}
 
-	struct TestImportResolver(Rc<str>);
+	struct TestImportResolver(IStr);
 	impl crate::import::ImportResolver for TestImportResolver {
 		fn resolve_file(&self, _: &PathBuf, _: &PathBuf) -> crate::error::Result<Rc<PathBuf>> {
 			Ok(Rc::new(PathBuf::from("/test")))
 		}
 
-		fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result<Rc<str>> {
+		fn load_file_contents(&self, _: &PathBuf) -> crate::error::Result<IStr> {
 			Ok(self.0.clone())
 		}
 
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

no changes

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>"
+		);
+	}
+}