git.delta.rocks / jrsonnet / refs/commits / 407517ff4770

difftreelog

refactor rewrite builtins to new type system

Yaroslav Bolyukin2020-12-01parent: #028057c.patch.diff
in: master

8 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -169,6 +169,7 @@
  "indexmap",
  "jrsonnet-parser",
  "jrsonnet-stdlib",
+ "jrsonnet-types",
  "md5",
  "pathdiff",
  "rustc-hash",
@@ -195,6 +196,10 @@
 version = "0.3.3"
 
 [[package]]
+name = "jrsonnet-types"
+version = "0.3.2"
+
+[[package]]
 name = "jsonnet"
 version = "0.3.3"
 dependencies = [
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,8 +4,9 @@
 	"crates/jrsonnet-evaluator",
 	"crates/jrsonnet-stdlib",
 	"crates/jrsonnet-cli",
+	"crates/jrsonnet-types",
 	"bindings/jsonnet",
-	"cmds/jrsonnet"
+	"cmds/jrsonnet",
 ]
 
 [profile.test]
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
before · crates/jrsonnet-evaluator/Cargo.toml
1[package]2name = "jrsonnet-evaluator"3description = "jsonnet interpreter"4version = "0.3.3"5authors = ["Yaroslav Bolyukin <iam@lach.pw>"]6license = "MIT"7edition = "2018"89[features]10default = ["serialized-stdlib", "faster", "explaining-traces", "serde-json"]11# Serializes standard library AST instead of parsing them every run12serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]13# Allow to convert Val into serde_json::Value and backwards14serde-json = ["serde", "serde_json"]15# Same as above, but with generated code instead of serde. Reduces memory usage, but increases binary size and compilation time16codegenerated-stdlib = []17# Replace some standard library functions with faster implementations (I.e manifestJsonEx)18# Library works fine without this feature, but requires more memory and time for std function calls19faster = []20# Rustc-like trace visualization21explaining-traces = ["annotate-snippets"]2223# Unlocks extra features, but works only on unstable24unstable = []2526[dependencies]27jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.3.3" }28jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }29pathdiff = "0.2.0"3031closure = "0.3.0"32indexmap = "1.6"3334md5 = "0.7.0"35base64 = "0.13.0"36rustc-hash = "1.1.0"3738thiserror = "1.0"3940# Serialized stdlib41[dependencies.serde]42version = "1.0"43optional = true44[dependencies.bincode]45version = "1.3.1"46optional = true4748# Serde json49[dependencies.serde_json]50version = "1.0"51optional = true5253# Explaining traces54[dependencies.annotate-snippets]55version = "0.9.0"56features = ["color"]57optional = true5859[build-dependencies]60jrsonnet-parser = { path = "../jrsonnet-parser", features = ["dump", "serialize", "deserialize"], version = "0.3.3" }61jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }62structdump = "0.1.2"63serde = "1.0"64bincode = "1.3.1"
after · crates/jrsonnet-evaluator/Cargo.toml
1[package]2name = "jrsonnet-evaluator"3description = "jsonnet interpreter"4version = "0.3.3"5authors = ["Yaroslav Bolyukin <iam@lach.pw>"]6license = "MIT"7edition = "2018"89[features]10default = ["serialized-stdlib", "faster", "explaining-traces", "serde-json"]11# Serializes standard library AST instead of parsing them every run12serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]13# Allow to convert Val into serde_json::Value and backwards14serde-json = ["serde", "serde_json"]15# Same as above, but with generated code instead of serde. Reduces memory usage, but increases binary size and compilation time16codegenerated-stdlib = []17# Replace some standard library functions with faster implementations (I.e manifestJsonEx)18# Library works fine without this feature, but requires more memory and time for std function calls19faster = []20# Rustc-like trace visualization21explaining-traces = ["annotate-snippets"]2223# Unlocks extra features, but works only on unstable24unstable = []2526[dependencies]27jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.3.3" }28jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }29jrsonnet-types = { path = "../jrsonnet-types", version = "0.3.3" }30pathdiff = "0.2.0"3132closure = "0.3.0"33indexmap = "1.6"3435md5 = "0.7.0"36base64 = "0.13.0"37rustc-hash = "1.1.0"3839thiserror = "1.0"4041# Serialized stdlib42[dependencies.serde]43version = "1.0"44optional = true45[dependencies.bincode]46version = "1.3.1"47optional = true4849# Serde json50[dependencies.serde_json]51version = "1.0"52optional = true5354# Explaining traces55[dependencies.annotate-snippets]56version = "0.9.0"57features = ["color"]58optional = true5960[build-dependencies]61jrsonnet-parser = { path = "../jrsonnet-parser", features = ["dump", "serialize", "deserialize"], version = "0.3.3" }62jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.3" }63structdump = "0.1.2"64serde = "1.0"65bincode = "1.3.1"
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -1,17 +1,20 @@
 use crate::{
 	equals,
 	error::{Error::*, Result},
-	evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val,
-	ValType,
+	evaluate, parse_args, primitive_equals, push, throw,
+	typed::CheckType,
+	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_parser::{ArgsDesc, BinaryOpType, ExprLocation};
+use jrsonnet_types::{ty, ComplexValType, ValType};
+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;
@@ -22,7 +25,7 @@
 		|| 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,6 +33,32 @@
 	)
 }
 
+thread_local! {
+	pub static INTRINSICS: HashMap<&'static str, fn(Context, &Option<ExprLocation>, &ArgsDesc) -> Result<Val>> = {
+		let mut out: HashMap<&'static str, _> = HashMap::new();
+		out.insert("length", intrinsic_length);
+		out
+	};
+}
+
+fn intrinsic_length(context: Context, _loc: &Option<ExprLocation>, args: &ArgsDesc) -> Result<Val> {
+	Ok(parse_args!(context, "length", args, 1, [
+		0, x: ty!((str | obj | [any]));
+	], {
+		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!(),
+		})
+	})?)
+}
+
 #[allow(clippy::cognitive_complexity)]
 pub fn call_builtin(
 	context: Context,
@@ -38,13 +67,12 @@
 	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];
+		"length" => parse_args!(context, "length", args, 1, [
+			0, x: ty!((str | obj | [any]));
 		], {
 			Ok(match x {
 				Val::Str(n) => Val::Num(n.chars().count() as f64),
-				Val::Arr(i) => Val::Num(i.len() as f64),
+				Val::Arr(a) => Val::Num(a.len() as f64),
 				Val::Obj(o) => Val::Num(
 					o.fields_visibility()
 						.into_iter()
@@ -54,43 +82,32 @@
 				_ => unreachable!(),
 			})
 		})?,
-		// any
-		"type" => parse_args!(context, "std.type", args, 1, [
-			0, x, vec![];
+		"type" => parse_args!(context, "type", args, 1, [
+			0, x: ty!(any);
 		], {
-			Ok(Val::Str(x.value_type()?.name().into()))
+			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];
+		"makeArray" => parse_args!(context, "makeArray", args, 2, [
+			0, sz: ty!(number((Some(0.0))..(None))) => Val::Num;
+			1, func: ty!(fn.any) => Val::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(
+				out.push(LazyVal::new_resolved(func.evaluate_values(
 					Context::new(),
 					&[Val::Num(i as f64)]
-				)?)
+				)?))
 			}
-			Ok(Val::Arr(Rc::new(out)))
+			Ok(Val::Arr(out.into()))
 		})?,
-		// string
-		"codepoint" => parse_args!(context, "std.codepoint", args, 1, [
-			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		"codepoint" => parse_args!(context, "codepoint", args, 1, [
+			0, str: ty!(char) => Val::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];
+		"objectFieldsEx" => parse_args!(context, "objectFieldsEx", args, 2, [
+			0, obj: ty!(obj) => Val::Obj;
+			1, inc_hidden: ty!(bool) => Val::Bool;
 		], {
 			let mut out = obj.fields_visibility()
 				.into_iter()
@@ -98,13 +115,12 @@
 				.map(|(k, _v)|k)
 				.collect::<Vec<_>>();
 			out.sort();
-			Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))
+			Ok(Val::Arr(out.into_iter().map(Val::Str).collect::<Vec<_>>().into()))
 		})?,
-		// 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];
+		"objectHasEx" => parse_args!(context, "objectHasEx", args, 3, [
+			0, obj: ty!(obj) => Val::Obj;
+			1, f: ty!(str) => Val::Str;
+			2, inc_hidden: ty!(bool) => Val::Bool;
 		], {
 			Ok(Val::Bool(
 				obj.fields_visibility()
@@ -113,13 +129,12 @@
 					.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];
+			0, indexable: ty!((str | [any]));
+			1, index: ty!((num | null));
+			2, end: ty!((num | null));
+			3, step: ty!((num | null));
 		], {
 			let index = match index {
 				Val::Num(v) => v as usize,
@@ -145,53 +160,53 @@
 					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()))
+					Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).collect::<Result<Vec<Val>>>()?).into()))
 				}
 				_ => unreachable!()
 			}
 		})?,
-		"primitiveEquals" => parse_args!(context, "std.primitiveEquals", args, 2, [
-			0, a, vec![];
-			1, b, vec![];
+		"primitiveEquals" => parse_args!(context, "primitiveEquals", args, 2, [
+			0, a: ty!(any);
+			1, b: ty!(any);
 		], {
 			Ok(Val::Bool(primitive_equals(&a, &b)?))
 		})?,
 		// faster
-		"equals" => parse_args!(context, "std.equals", args, 2, [
-			0, a, vec![];
-			1, b, vec![];
+		"equals" => parse_args!(context, "equals", args, 2, [
+			0, a: ty!(any);
+			1, b: ty!(any);
 		], {
 			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![];
+		"modulo" => parse_args!(context, "modulo", args, 2, [
+			0, a: ty!(num) => Val::Num;
+			1, b: ty!(num) => Val::Num;
+		], {
+			Ok(Val::Num(a % b))
+		})?,
+		"mod" => parse_args!(context, "mod", args, 2, [
+			0, a: ty!((num | str));
+			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(jrsonnet_parser::BinaryOpType::Mod, a.value_type()?, b.value_type()?))
+				(a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(BinaryOpType::Mod, a.value_type(), b.value_type()))
 			}
 		})?,
-		"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];
+		"floor" => parse_args!(context, "floor", args, 1, [
+			0, x: ty!(num) => Val::Num;
 		], {
 			Ok(Val::Num(x.floor()))
 		})?,
-		"log" => parse_args!(context, "std.log", args, 2, [
-			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+		"log" => parse_args!(context, "log", args, 1, [
+			0, n: ty!(num) => Val::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![];
+		"trace" => parse_args!(context, "trace", args, 2, [
+			0, str: ty!(str) => Val::Str;
+			1, rest: ty!(any);
 		], {
 			eprint!("TRACE:");
 			if let Some(loc) = loc {
@@ -203,94 +218,88 @@
 			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];
+		"pow" => parse_args!(context, "pow", args, 2, [
+			0, x: ty!(num) => Val::Num;
+			1, n: ty!(num) => Val::Num;
 		], {
 			Ok(Val::Num(x.powf(n)))
 		})?,
-		"extVar" => parse_args!(context, "std.extVar", args, 1, [
-			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+		"extVar" => parse_args!(context, "extVar", args, 1, [
+			0, x: ty!(str) => Val::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];
+		"native" => parse_args!(context, "native", args, 1, [
+			0, x: ty!(str) => 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))?)
 		})?,
-		"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];
+		"filter" => parse_args!(context, "filter", args, 2, [
+			0, func: ty!(fn.any) => Val::Func;
+			1, arr: ty!([any]) => Val::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(),
-			)))
+			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()))
 		})?,
-		// 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![];
+		"foldl" => parse_args!(context, "foldl", args, 3, [
+			0, func: ty!(fn.any) => Val::Func;
+			1, arr: ty!([any]) => Val::Arr;
+			2, init: ty!(any);
 		], {
 			let mut acc = init;
-			for i in arr.iter().cloned() {
-				acc = func.evaluate_values(context.clone(), &[acc, i])?;
+			for i in arr.iter() {
+				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![];
+		"foldr" => parse_args!(context, "foldr", args, 3, [
+			0, func: ty!(fn.any) => Val::Func;
+			1, arr: ty!([any]) => Val::Arr;
+			2, init: ty!(any);
 		], {
 			let mut acc = init;
-			for i in arr.iter().rev().cloned() {
-				acc = func.evaluate_values(context.clone(), &[acc, i])?;
+			for i in arr.iter().rev() {
+				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];
+		"sortImpl" => parse_args!(context, "sort", args, 2, [
+			0, arr: ty!([any]) => Val::Arr;
+			1, keyF: ty!(fn.any) => Val::Func;
 		], {
 			if arr.len() <= 1 {
 				return Ok(Val::Arr(arr))
 			}
-			Ok(Val::Arr(sort::sort(context, arr, &keyF)?))
+			Ok(Val::Arr(ArrValue::Eager(sort::sort(context, arr.evaluated()?, &keyF)?)))
 		})?,
 		// faster
-		"format" => parse_args!(context, "std.format", args, 2, [
-			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
-			1, vals, vec![]
+		"format" => parse_args!(context, "format", args, 2, [
+			0, str: ty!(str) => Val::Str;
+			1, vals: ty!(any)
 		], {
 			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];
+		"range" => parse_args!(context, "range", args, 2, [
+			0, from: ty!(num) => Val::Num;
+			1, to: ty!(num) => Val::Num;
 		], {
 			let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));
 			for i in from as usize..=to as usize {
 				out.push(Val::Num(i as f64));
 			}
-			Ok(Val::Arr(Rc::new(out)))
+			Ok(Val::Arr(out.into()))
 		})?,
-		"char" => parse_args!(context, "std.char", args, 1, [
-			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+		"char" => parse_args!(context, "char", args, 1, [
+			0, n: ty!(num) => Val::Num;
 		], {
 			let mut out = String::new();
 			out.push(std::char::from_u32(n as u32).ok_or_else(||
@@ -298,19 +307,18 @@
 			)?);
 			Ok(Val::Str(out.into()))
 		})?,
-		"encodeUTF8" => parse_args!(context, "std.encodeUtf8", args, 1, [
-			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		"encodeUTF8" => parse_args!(context, "encodeUTF8", args, 1, [
+			0, str: ty!(str) => Val::Str;
 		], {
-			Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))
+			Ok(Val::Arr((str.bytes().map(|b| Val::Num(b as f64)).collect::<Vec<Val>>()).into()))
 		})?,
-		"md5" => parse_args!(context, "std.md5", args, 1, [
-			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+		"md5" => parse_args!(context, "md5", args, 1, [
+			0, str: ty!(str) => Val::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];
+		"base64" => parse_args!(context, "base64", args, 1, [
+			0, input: ty!((str | [num]));
 		], {
 			Ok(Val::Str(match input {
 				Val::Str(s) => {
@@ -318,44 +326,51 @@
 				},
 				Val::Arr(a) => {
 					base64::encode(a.iter().map(|v| {
-						Ok(v.clone().try_cast_num("base64 array")? as u8)
+						Ok(v?.clone().unwrap_num()? 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];
+		"join" => parse_args!(context, "join", args, 2, [
+			0, sep: ty!((str | [any]));
+			1, arr: ty!([any]) => Val::Arr;
 		], {
 			Ok(match sep {
 				Val::Arr(joiner_items) => {
 					let mut out = Vec::new();
 
 					let mut first = true;
-					for item in arr.iter().cloned() {
-						if let Val::Arr(items) = item.unwrap_if_lazy()? {
+					for item in arr.iter() {
+						let item = item?.clone();
+						if let Val::Arr(items) = item {
 							if !first {
 								out.reserve(joiner_items.len());
-								out.extend(joiner_items.iter().cloned());
+								// TODO: extend
+								for item in joiner_items.iter() {
+									out.push(item?);
+								}
 							}
 							first = false;
 							out.reserve(items.len());
-							out.extend(items.iter().cloned());
+							// 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::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()? {
+					for item in arr.iter() {
+						let item = item?.clone();
+						if let Val::Str(item) = item {
 							if !first {
 								out += &sep;
 							}
@@ -371,32 +386,30 @@
 				_ => unreachable!()
 			})
 		})?,
-		// Faster
-		"escapeStringJson" => parse_args!(context, "std.escapeStringJson", args, 1, [
-			0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];
+		// faster
+		"escapeStringJson" => parse_args!(context, "escapeStringJson", args, 1, [
+			0, str_: ty!(str) => Val::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];
+		// faster
+		"manifestJsonEx" => parse_args!(context, "manifestJsonEx", args, 2, [
+			0, value: ty!(any);
+			1, indent: ty!(str) => Val::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];
+		// faster
+		"reverse" => parse_args!(context, "reverse", args, 1, [
+			0, value: ty!([any]) => Val::Arr;
 		], {
-			let mut marr = arr;
-			Rc::make_mut(&mut marr).reverse();
-			Ok(Val::Arr(marr))
+			Ok(Val::Arr(value.reversed()))
 		})?,
-		"id" => parse_args!(context, "std.id", args, 1, [
-			0, v, vec![];
+		"id" => parse_args!(context, "id", args, 1, [
+			0, v: ty!(any);
 		], {
 			Ok(v)
 		})?,
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,8 +1,9 @@
 use crate::{
 	builtin::{format::FormatError, sort::SortError},
-	ValType,
+	typed::TypeLocError,
 };
 use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType};
+use jrsonnet_types::ValType;
 use std::{path::PathBuf, rc::Rc};
 use thiserror::Error;
 
@@ -117,6 +118,8 @@
 
 	#[error("format error: {0}")]
 	Format(#[from] FormatError),
+	#[error("type error: {0}")]
+	TypeError(TypeLocError),
 	#[error("sort error: {0}")]
 	Sort(#[from] SortError),
 }
@@ -144,6 +147,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/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -143,9 +143,8 @@
 #[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::*};
 		let args = $args;
 		if args.len() > $total_args {
 			throw!(TooManyArgsFunctionHas($total_args));
@@ -160,47 +159,19 @@
 					throw!(IntrinsicArgumentReorderingIsNotSupportedYet);
 				}
 			}
-			let $name = evaluate($ctx.clone(), &$name.1)?;
+			let $name = push(&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/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;
+mod typed;
 mod val;
 
 pub use ctx::*;
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
 		)?;