git.delta.rocks / jrsonnet / refs/commits / 78d82dfdf2a1

difftreelog

feat derive(Typed) for struct

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

9 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,8 +70,9 @@
 
 [[package]]
 name = "clap"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
+version = "3.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
 dependencies = [
  "atty",
  "bitflags",
@@ -82,27 +83,28 @@
  "strsim",
  "termcolor",
  "textwrap",
- "vec_map",
 ]
 
 [[package]]
+name = "clap_complete"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25"
+dependencies = [
+ "clap",
+]
+
+[[package]]
 name = "clap_derive"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
+version = "3.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
 dependencies = [
  "heck",
  "proc-macro-error",
  "proc-macro2",
  "quote",
  "syn",
-]
-
-[[package]]
-name = "clap_generate"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
-dependencies = [
- "clap",
 ]
 
 [[package]]
@@ -148,12 +150,9 @@
 
 [[package]]
 name = "heck"
-version = "0.3.3"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
-dependencies = [
- "unicode-segmentation",
-]
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
 
 [[package]]
 name = "hermit-abi"
@@ -185,7 +184,7 @@
 version = "0.4.2"
 dependencies = [
  "clap",
- "clap_generate",
+ "clap_complete",
  "gcmodule",
  "jrsonnet-cli",
  "jrsonnet-evaluator",
@@ -214,6 +213,7 @@
  "bincode",
  "gcmodule",
  "jrsonnet-interner",
+ "jrsonnet-macros",
  "jrsonnet-parser",
  "jrsonnet-stdlib",
  "jrsonnet-types",
@@ -236,6 +236,15 @@
 ]
 
 [[package]]
+name = "jrsonnet-macros"
+version = "0.4.2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "jrsonnet-parser"
 version = "0.4.2"
 dependencies = [
@@ -301,6 +310,12 @@
 checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
 
 [[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
 name = "mimalloc-sys"
 version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -321,9 +336,12 @@
 
 [[package]]
 name = "os_str_bytes"
-version = "3.1.0"
+version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
 
 [[package]]
 name = "parking_lot"
@@ -357,9 +375,9 @@
 
 [[package]]
 name = "peg"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a"
+checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c"
 dependencies = [
  "peg-macros",
  "peg-runtime",
@@ -367,9 +385,9 @@
 
 [[package]]
 name = "peg-macros"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c"
+checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16"
 dependencies = [
  "peg-runtime",
  "proc-macro2",
@@ -378,9 +396,9 @@
 
 [[package]]
 name = "peg-runtime"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088"
+checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f"
 
 [[package]]
 name = "proc-macro-error"
@@ -536,12 +554,9 @@
 
 [[package]]
 name = "textwrap"
-version = "0.14.2"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
-dependencies = [
- "unicode-width",
-]
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
 
 [[package]]
 name = "thiserror"
@@ -562,12 +577,6 @@
  "quote",
  "syn",
 ]
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
 
 [[package]]
 name = "unicode-width"
@@ -580,12 +589,6 @@
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
-
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
 
 [[package]]
 name = "version_check"
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -179,7 +179,7 @@
 	}
 }
 
-pub type Result<V> = std::result::Result<V, LocError>;
+pub type Result<V, E = LocError> = std::result::Result<V, E>;
 
 #[macro_export]
 macro_rules! throw {
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function.rs
1use crate::{2	error::{Error::*, LocError},3	evaluate, evaluate_named,4	gc::TraceBox,5	throw,6	typed::Typed,7	Context, FutureWrapper, GcHashMap, LazyVal, LazyValValue, Result, Val,8};9use gcmodule::Trace;10use jrsonnet_interner::IStr;11use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};12use std::{borrow::Cow, collections::HashMap, convert::TryFrom};1314#[derive(Trace)]15struct EvaluateLazyVal {16	context: Context,17	expr: LocExpr,18}19impl LazyValValue for EvaluateLazyVal {20	fn get(self: Box<Self>) -> Result<Val> {21		evaluate(self.context, &self.expr)22	}23}2425pub trait ArgLike {26	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal>;27}28impl ArgLike for &LocExpr {29	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {30		Ok(if tailstrict {31			LazyVal::new_resolved(evaluate(ctx, self)?)32		} else {33			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {34				context: ctx,35				expr: (*self).clone(),36			})))37		})38	}39}40impl<T> ArgLike for T41where42	T: Typed + Clone,43	Val: TryFrom<T, Error = LocError>,44{45	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {46		let val: Val = Val::try_from(self.clone())?;47		Ok(LazyVal::new_resolved(val))48	}49}50pub enum TlaArg {51	String(IStr),52	Code(LocExpr),53	Val(Val),54}55impl ArgLike for TlaArg {56	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {57		match self {58			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),59			TlaArg::Code(code) => Ok(if tailstrict {60				LazyVal::new_resolved(evaluate(ctx, code)?)61			} else {62				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {63					context: ctx,64					expr: code.clone(),65				})))66			}),67			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),68		}69	}70}7172pub trait ArgsLike {73	fn unnamed_len(&self) -> usize;74	fn unnamed_iter(75		&self,76		ctx: Context,77		tailstrict: bool,78		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,79	) -> Result<()>;80	fn named_iter(81		&self,82		ctx: Context,83		tailstrict: bool,84		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,85	) -> Result<()>;86	fn named_names(&self, handler: &mut dyn FnMut(&IStr));87}8889impl ArgsLike for ArgsDesc {90	fn unnamed_len(&self) -> usize {91		self.unnamed.len()92	}9394	fn unnamed_iter(95		&self,96		ctx: Context,97		tailstrict: bool,98		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,99	) -> Result<()> {100		for (id, arg) in self.unnamed.iter().enumerate() {101			handler(102				id,103				if tailstrict {104					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)105				} else {106					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {107						context: ctx.clone(),108						expr: arg.clone(),109					})))110				},111			)?;112		}113		Ok(())114	}115116	fn named_iter(117		&self,118		ctx: Context,119		tailstrict: bool,120		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,121	) -> Result<()> {122		for (name, arg) in self.named.iter() {123			handler(124				name,125				if tailstrict {126					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)127				} else {128					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {129						context: ctx.clone(),130						expr: arg.clone(),131					})))132				},133			)?;134		}135		Ok(())136	}137138	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {139		for (name, _) in self.named.iter() {140			handler(name)141		}142	}143}144145impl<A: ArgLike> ArgsLike for [(IStr, A)] {146	fn unnamed_len(&self) -> usize {147		0148	}149150	fn unnamed_iter(151		&self,152		_ctx: Context,153		_tailstrict: bool,154		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,155	) -> Result<()> {156		Ok(())157	}158159	fn named_iter(160		&self,161		ctx: Context,162		tailstrict: bool,163		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,164	) -> Result<()> {165		for (name, val) in self.iter() {166			handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?;167		}168		Ok(())169	}170171	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {172		for (name, _) in self.iter() {173			handler(name);174		}175	}176}177178impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {179	fn unnamed_len(&self) -> usize {180		0181	}182183	fn unnamed_iter(184		&self,185		_ctx: Context,186		_tailstrict: bool,187		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,188	) -> Result<()> {189		Ok(())190	}191192	fn named_iter(193		&self,194		ctx: Context,195		tailstrict: bool,196		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,197	) -> Result<()> {198		for (name, value) in self.iter() {199			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;200		}201		Ok(())202	}203204	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {205		for (name, _) in self.iter() {206			handler(name);207		}208	}209}210211impl<A: ArgLike> ArgsLike for [A] {212	fn unnamed_len(&self) -> usize {213		self.len()214	}215216	fn unnamed_iter(217		&self,218		ctx: Context,219		tailstrict: bool,220		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,221	) -> Result<()> {222		for (i, arg) in self.iter().enumerate() {223			handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?;224		}225		Ok(())226	}227228	fn named_iter(229		&self,230		_ctx: Context,231		_tailstrict: bool,232		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,233	) -> Result<()> {234		Ok(())235	}236237	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}238}239impl<A: ArgLike> ArgsLike for &[A] {240	fn unnamed_len(&self) -> usize {241		(*self).unnamed_len()242	}243244	fn unnamed_iter(245		&self,246		ctx: Context,247		tailstrict: bool,248		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,249	) -> Result<()> {250		(*self).unnamed_iter(ctx, tailstrict, handler)251	}252253	fn named_iter(254		&self,255		ctx: Context,256		tailstrict: bool,257		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,258	) -> Result<()> {259		(*self).named_iter(ctx, tailstrict, handler)260	}261262	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {263		(*self).named_names(handler)264	}265}266267/// Creates correct [context](Context) for function body evaluation returning error on invalid call.268///269/// ## Parameters270/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)271/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)272/// * `params`: function parameters' definition273/// * `args`: passed function arguments274/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily275pub fn parse_function_call(276	ctx: Context,277	body_ctx: Context,278	params: &ParamsDesc,279	args: &dyn ArgsLike,280	tailstrict: bool,281) -> Result<Context> {282	let mut passed_args = GcHashMap::with_capacity(params.len());283	if args.unnamed_len() > params.len() {284		throw!(TooManyArgsFunctionHas(params.len()))285	}286287	let mut filled_args = 0;288289	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {290		let name = params[id].0.clone();291		passed_args.insert(name, arg);292		filled_args += 1;293		Ok(())294	})?;295296	args.named_iter(ctx, tailstrict, &mut |name, value| {297		// FIXME: O(n) for arg existence check298		if !params.iter().any(|p| &p.0 == name) {299			throw!(UnknownFunctionParameter((name as &str).to_owned()));300		}301		if passed_args.insert(name.clone(), value).is_some() {302			throw!(BindingParameterASecondTime(name.clone()));303		}304		filled_args += 1;305		Ok(())306	})?;307308	if filled_args < params.len() {309		// Some args are unset, but maybe we have defaults for them310		// Default values should be created in newly created context311		let future_context = FutureWrapper::<Context>::new();312		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);313314		for param in params.iter().filter(|p| p.1.is_some()) {315			if passed_args.contains_key(&param.0.clone()) {316				continue;317			}318			#[derive(Trace)]319			struct LazyNamedBinding {320				future_context: FutureWrapper<Context>,321				name: IStr,322				value: LocExpr,323			}324			impl LazyValValue for LazyNamedBinding {325				fn get(self: Box<Self>) -> Result<Val> {326					evaluate_named(self.future_context.unwrap(), &self.value, self.name)327				}328			}329			LazyVal::new(TraceBox(Box::new(LazyNamedBinding {330				future_context: future_context.clone(),331				name: param.0.clone(),332				value: param.1.clone().unwrap(),333			})));334335			defaults.insert(336				param.0.clone(),337				LazyVal::new(TraceBox(Box::new(LazyNamedBinding {338					future_context: future_context.clone(),339					name: param.0.clone(),340					value: param.1.clone().unwrap(),341				}))),342			);343			filled_args += 1;344		}345346		// Some args still wasn't filled347		if filled_args != params.len() {348			for param in params.iter().skip(args.unnamed_len()) {349				let mut found = false;350				args.named_names(&mut |name| {351					if name == &param.0 {352						found = true;353					}354				});355				if !found {356					throw!(FunctionParameterNotBoundInCall(param.0.clone()));357				}358			}359			unreachable!();360		}361362		Ok(body_ctx363			.extend(passed_args, None, None, None)364			.extend_bound(defaults)365			.into_future(future_context))366	} else {367		let body_ctx = body_ctx.extend(passed_args, None, None, None);368		Ok(body_ctx)369	}370}371372type BuiltinParamName = Cow<'static, str>;373374#[derive(Clone, Trace)]375pub struct BuiltinParam {376	pub name: BuiltinParamName,377	pub has_default: bool,378}379380pub trait Builtin: Trace {381	fn name(&self) -> &str;382	fn params(&self) -> &[BuiltinParam];383	fn call(384		&self,385		context: Context,386		loc: Option<&ExprLocation>,387		args: &dyn ArgsLike,388	) -> Result<Val>;389}390391pub trait StaticBuiltin: Builtin + Send + Sync392where393	Self: 'static,394{395	// In impl, to make it object safe:396	// const INST: &'static Self;397}398399/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead400///401/// ## Parameters402/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)403/// * `params`: function parameters' definition404/// * `args`: passed function arguments405/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily406pub fn parse_builtin_call(407	ctx: Context,408	params: &[BuiltinParam],409	args: &dyn ArgsLike,410	tailstrict: bool,411) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {412	let mut passed_args = GcHashMap::with_capacity(params.len());413	if args.unnamed_len() > params.len() {414		throw!(TooManyArgsFunctionHas(params.len()))415	}416417	let mut filled_args = 0;418419	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {420		let name = params[id].name.clone();421		passed_args.insert(name, arg);422		filled_args += 1;423		Ok(())424	})?;425426	args.named_iter(ctx, tailstrict, &mut |name, arg| {427		// FIXME: O(n) for arg existence check428		let p = params429			.iter()430			.find(|p| p.name == name as &str)431			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;432		if passed_args.insert(p.name.clone(), arg).is_some() {433			throw!(BindingParameterASecondTime(name.clone()));434		}435		filled_args += 1;436		Ok(())437	})?;438439	if filled_args < params.len() {440		for param in params.iter().filter(|p| p.has_default) {441			if passed_args.contains_key(&param.name) {442				continue;443			}444			filled_args += 1;445		}446447		// Some args still wasn't filled448		if filled_args != params.len() {449			for param in params.iter().skip(args.unnamed_len()) {450				let mut found = false;451				args.named_names(&mut |name| {452					if name as &str == &param.name as &str {453						found = true;454					}455				});456				if !found {457					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));458				}459			}460			unreachable!();461		}462	}463	Ok(passed_args)464}
after · crates/jrsonnet-evaluator/src/function.rs
1use crate::{2	error::{Error::*, LocError},3	evaluate, evaluate_named,4	gc::TraceBox,5	throw,6	typed::Typed,7	Context, FutureWrapper, GcHashMap, LazyVal, LazyValValue, Result, Val,8};9use gcmodule::Trace;10use jrsonnet_interner::IStr;11pub use jrsonnet_macros::builtin;12use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};13use std::{borrow::Cow, collections::HashMap, convert::TryFrom};1415#[derive(Trace)]16struct EvaluateLazyVal {17	context: Context,18	expr: LocExpr,19}20impl LazyValValue for EvaluateLazyVal {21	fn get(self: Box<Self>) -> Result<Val> {22		evaluate(self.context, &self.expr)23	}24}2526pub trait ArgLike {27	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal>;28}29impl ArgLike for &LocExpr {30	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {31		Ok(if tailstrict {32			LazyVal::new_resolved(evaluate(ctx, self)?)33		} else {34			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {35				context: ctx,36				expr: (*self).clone(),37			})))38		})39	}40}41impl<T> ArgLike for T42where43	T: Typed + Clone,44	Val: TryFrom<T, Error = LocError>,45{46	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {47		let val: Val = Val::try_from(self.clone())?;48		Ok(LazyVal::new_resolved(val))49	}50}51pub enum TlaArg {52	String(IStr),53	Code(LocExpr),54	Val(Val),55}56impl ArgLike for TlaArg {57	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<LazyVal> {58		match self {59			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),60			TlaArg::Code(code) => Ok(if tailstrict {61				LazyVal::new_resolved(evaluate(ctx, code)?)62			} else {63				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {64					context: ctx,65					expr: code.clone(),66				})))67			}),68			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),69		}70	}71}7273pub trait ArgsLike {74	fn unnamed_len(&self) -> usize;75	fn unnamed_iter(76		&self,77		ctx: Context,78		tailstrict: bool,79		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,80	) -> Result<()>;81	fn named_iter(82		&self,83		ctx: Context,84		tailstrict: bool,85		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,86	) -> Result<()>;87	fn named_names(&self, handler: &mut dyn FnMut(&IStr));88}8990impl ArgsLike for ArgsDesc {91	fn unnamed_len(&self) -> usize {92		self.unnamed.len()93	}9495	fn unnamed_iter(96		&self,97		ctx: Context,98		tailstrict: bool,99		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,100	) -> Result<()> {101		for (id, arg) in self.unnamed.iter().enumerate() {102			handler(103				id,104				if tailstrict {105					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)106				} else {107					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {108						context: ctx.clone(),109						expr: arg.clone(),110					})))111				},112			)?;113		}114		Ok(())115	}116117	fn named_iter(118		&self,119		ctx: Context,120		tailstrict: bool,121		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,122	) -> Result<()> {123		for (name, arg) in self.named.iter() {124			handler(125				name,126				if tailstrict {127					LazyVal::new_resolved(evaluate(ctx.clone(), arg)?)128				} else {129					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {130						context: ctx.clone(),131						expr: arg.clone(),132					})))133				},134			)?;135		}136		Ok(())137	}138139	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {140		for (name, _) in self.named.iter() {141			handler(name)142		}143	}144}145146impl<A: ArgLike> ArgsLike for [(IStr, A)] {147	fn unnamed_len(&self) -> usize {148		0149	}150151	fn unnamed_iter(152		&self,153		_ctx: Context,154		_tailstrict: bool,155		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,156	) -> Result<()> {157		Ok(())158	}159160	fn named_iter(161		&self,162		ctx: Context,163		tailstrict: bool,164		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,165	) -> Result<()> {166		for (name, val) in self.iter() {167			handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?;168		}169		Ok(())170	}171172	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {173		for (name, _) in self.iter() {174			handler(name);175		}176	}177}178179impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {180	fn unnamed_len(&self) -> usize {181		0182	}183184	fn unnamed_iter(185		&self,186		_ctx: Context,187		_tailstrict: bool,188		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,189	) -> Result<()> {190		Ok(())191	}192193	fn named_iter(194		&self,195		ctx: Context,196		tailstrict: bool,197		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,198	) -> Result<()> {199		for (name, value) in self.iter() {200			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;201		}202		Ok(())203	}204205	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {206		for (name, _) in self.iter() {207			handler(name);208		}209	}210}211212impl<A: ArgLike> ArgsLike for [A] {213	fn unnamed_len(&self) -> usize {214		self.len()215	}216217	fn unnamed_iter(218		&self,219		ctx: Context,220		tailstrict: bool,221		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,222	) -> Result<()> {223		for (i, arg) in self.iter().enumerate() {224			handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?;225		}226		Ok(())227	}228229	fn named_iter(230		&self,231		_ctx: Context,232		_tailstrict: bool,233		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,234	) -> Result<()> {235		Ok(())236	}237238	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}239}240impl<A: ArgLike> ArgsLike for &[A] {241	fn unnamed_len(&self) -> usize {242		(*self).unnamed_len()243	}244245	fn unnamed_iter(246		&self,247		ctx: Context,248		tailstrict: bool,249		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,250	) -> Result<()> {251		(*self).unnamed_iter(ctx, tailstrict, handler)252	}253254	fn named_iter(255		&self,256		ctx: Context,257		tailstrict: bool,258		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,259	) -> Result<()> {260		(*self).named_iter(ctx, tailstrict, handler)261	}262263	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {264		(*self).named_names(handler)265	}266}267268/// Creates correct [context](Context) for function body evaluation returning error on invalid call.269///270/// ## Parameters271/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)272/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)273/// * `params`: function parameters' definition274/// * `args`: passed function arguments275/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily276pub fn parse_function_call(277	ctx: Context,278	body_ctx: Context,279	params: &ParamsDesc,280	args: &dyn ArgsLike,281	tailstrict: bool,282) -> Result<Context> {283	let mut passed_args = GcHashMap::with_capacity(params.len());284	if args.unnamed_len() > params.len() {285		throw!(TooManyArgsFunctionHas(params.len()))286	}287288	let mut filled_args = 0;289290	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {291		let name = params[id].0.clone();292		passed_args.insert(name, arg);293		filled_args += 1;294		Ok(())295	})?;296297	args.named_iter(ctx, tailstrict, &mut |name, value| {298		// FIXME: O(n) for arg existence check299		if !params.iter().any(|p| &p.0 == name) {300			throw!(UnknownFunctionParameter((name as &str).to_owned()));301		}302		if passed_args.insert(name.clone(), value).is_some() {303			throw!(BindingParameterASecondTime(name.clone()));304		}305		filled_args += 1;306		Ok(())307	})?;308309	if filled_args < params.len() {310		// Some args are unset, but maybe we have defaults for them311		// Default values should be created in newly created context312		let future_context = FutureWrapper::<Context>::new();313		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);314315		for param in params.iter().filter(|p| p.1.is_some()) {316			if passed_args.contains_key(&param.0.clone()) {317				continue;318			}319			#[derive(Trace)]320			struct LazyNamedBinding {321				future_context: FutureWrapper<Context>,322				name: IStr,323				value: LocExpr,324			}325			impl LazyValValue for LazyNamedBinding {326				fn get(self: Box<Self>) -> Result<Val> {327					evaluate_named(self.future_context.unwrap(), &self.value, self.name)328				}329			}330			LazyVal::new(TraceBox(Box::new(LazyNamedBinding {331				future_context: future_context.clone(),332				name: param.0.clone(),333				value: param.1.clone().unwrap(),334			})));335336			defaults.insert(337				param.0.clone(),338				LazyVal::new(TraceBox(Box::new(LazyNamedBinding {339					future_context: future_context.clone(),340					name: param.0.clone(),341					value: param.1.clone().unwrap(),342				}))),343			);344			filled_args += 1;345		}346347		// Some args still wasn't filled348		if filled_args != params.len() {349			for param in params.iter().skip(args.unnamed_len()) {350				let mut found = false;351				args.named_names(&mut |name| {352					if name == &param.0 {353						found = true;354					}355				});356				if !found {357					throw!(FunctionParameterNotBoundInCall(param.0.clone()));358				}359			}360			unreachable!();361		}362363		Ok(body_ctx364			.extend(passed_args, None, None, None)365			.extend_bound(defaults)366			.into_future(future_context))367	} else {368		let body_ctx = body_ctx.extend(passed_args, None, None, None);369		Ok(body_ctx)370	}371}372373type BuiltinParamName = Cow<'static, str>;374375#[derive(Clone, Trace)]376pub struct BuiltinParam {377	pub name: BuiltinParamName,378	pub has_default: bool,379}380381/// Do not implement it directly, instead use #[builtin] macro382pub trait Builtin: Trace {383	fn name(&self) -> &str;384	fn params(&self) -> &[BuiltinParam];385	fn call(386		&self,387		context: Context,388		loc: Option<&ExprLocation>,389		args: &dyn ArgsLike,390	) -> Result<Val>;391}392393pub trait StaticBuiltin: Builtin + Send + Sync394where395	Self: 'static,396{397	// In impl, to make it object safe:398	// const INST: &'static Self;399}400401/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead402///403/// ## Parameters404/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)405/// * `params`: function parameters' definition406/// * `args`: passed function arguments407/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily408pub fn parse_builtin_call(409	ctx: Context,410	params: &[BuiltinParam],411	args: &dyn ArgsLike,412	tailstrict: bool,413) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {414	let mut passed_args = GcHashMap::with_capacity(params.len());415	if args.unnamed_len() > params.len() {416		throw!(TooManyArgsFunctionHas(params.len()))417	}418419	let mut filled_args = 0;420421	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {422		let name = params[id].name.clone();423		passed_args.insert(name, arg);424		filled_args += 1;425		Ok(())426	})?;427428	args.named_iter(ctx, tailstrict, &mut |name, arg| {429		// FIXME: O(n) for arg existence check430		let p = params431			.iter()432			.find(|p| p.name == name as &str)433			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;434		if passed_args.insert(p.name.clone(), arg).is_some() {435			throw!(BindingParameterASecondTime(name.clone()));436		}437		filled_args += 1;438		Ok(())439	})?;440441	if filled_args < params.len() {442		for param in params.iter().filter(|p| p.has_default) {443			if passed_args.contains_key(&param.name) {444				continue;445			}446			filled_args += 1;447		}448449		// Some args still wasn't filled450		if filled_args != params.len() {451			for param in params.iter().skip(args.unnamed_len()) {452				let mut found = false;453				args.named_names(&mut |name| {454					if name as &str == &param.name as &str {455						found = true;456					}457				});458				if !found {459					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));460				}461			}462			unreachable!();463		}464	}465	Ok(passed_args)466}
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -174,7 +174,11 @@
 	pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
 }
 pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
-	EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))
+	EVAL_STATE.with(|s| {
+		f(s.borrow().as_ref().expect(
+			"missing evaluation state, some functions should be called inside of run_in_state call",
+		))
+	})
 }
 pub fn push_frame<T>(
 	e: Option<&ExprLocation>,
@@ -728,12 +732,15 @@
 	}
 
 	macro_rules! eval {
-		($str: expr) => {
-			EvaluationState::default()
-				.with_stdlib()
-				.evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())
-				.unwrap()
-		};
+		($str: expr) => {{
+			let evaluator = EvaluationState::default();
+			evaluator.with_stdlib();
+			evaluator.run_in_state(|| {
+				evaluator
+					.evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())
+					.unwrap()
+			})
+		}};
 	}
 	macro_rules! eval_json {
 		($str: expr) => {{
@@ -1265,4 +1272,47 @@
 		assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "d"), 0)"#);
 		assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "a"), 2)"#);
 	}
+
+	mod derive_typed {
+		use crate::{typed::Typed, EvaluationState};
+		use std::path::PathBuf;
+
+		#[derive(Typed, PartialEq, Debug)]
+		struct MyTyped {
+			a: u32,
+			b: String,
+		}
+
+		#[test]
+		fn test() {
+			let es = EvaluationState::default();
+			let val = eval!("{a: 14, b: 'Hello, world!'}");
+			let typed = es.run_in_state(|| MyTyped::try_from(val).unwrap());
+
+			assert_eq!(
+				typed,
+				MyTyped {
+					a: 14,
+					b: "Hello, world!".to_string()
+				}
+			);
+			es.settings_mut().globals.insert(
+				"mytyped".into(),
+				es.run_in_state(|| typed.try_into()).unwrap(),
+			);
+
+			let v = es
+				.evaluate_snippet_raw(
+					PathBuf::from("raw.jsonnet").into(),
+					"
+				mytyped == {a: 14, b: 'Hello, world!'}
+			"
+					.into(),
+				)
+				.unwrap()
+				.as_bool()
+				.unwrap();
+			assert!(v)
+		}
+	}
 }
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -1,6 +1,7 @@
 use std::convert::{TryFrom, TryInto};
 
 use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::Typed;
 use jrsonnet_types::{ComplexValType, ValType};
 
 use crate::{
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -8,20 +8,8 @@
 	push_description_frame, Val,
 };
 use gcmodule::Trace;
-use jrsonnet_types::{ComplexValType, ValType};
+pub 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_frame, typed::CheckType};
-		push_frame(None, $desc, || Ok($typ.check(&$value)?))?;
-		match $value {
-			$match(v) => v,
-			_ => unreachable!(),
-		}
-	}};
-}
 
 #[derive(Debug, Error, Clone, Trace)]
 pub enum TypeError {
@@ -136,7 +124,7 @@
 impl Display for ValuePathItem {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		match self {
-			Self::Field(name) => write!(f, ".{}", name)?,
+			Self::Field(name) => write!(f, ".{:?}", name)?,
 			Self::Index(idx) => write!(f, "[{}]", idx)?,
 		}
 		Ok(())
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -91,7 +91,7 @@
 	Normal(Cc<FuncDesc>),
 	/// Standard library function
 	StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
-
+	/// User-provided function
 	Builtin(Cc<TraceBox<dyn Builtin>>),
 }
 
@@ -99,8 +99,10 @@
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		match self {
 			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
-			Self::StaticBuiltin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(),
-			Self::Builtin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(),
+			Self::StaticBuiltin(arg0) => {
+				f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
+			}
+			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
 		}
 	}
 }
@@ -338,6 +340,49 @@
 }
 
 impl Val {
+	pub fn as_bool(&self) -> Option<bool> {
+		match self {
+			Val::Bool(v) => Some(*v),
+			_ => None,
+		}
+	}
+	pub fn as_null(&self) -> Option<()> {
+		match self {
+			Val::Null => Some(()),
+			_ => None,
+		}
+	}
+	pub fn as_str(&self) -> Option<IStr> {
+		match self {
+			Val::Str(s) => Some(s.clone()),
+			_ => None,
+		}
+	}
+	pub fn as_num(&self) -> Option<f64> {
+		match self {
+			Val::Num(n) => Some(*n),
+			_ => None,
+		}
+	}
+	pub fn as_arr(&self) -> Option<ArrValue> {
+		match self {
+			Val::Arr(a) => Some(a.clone()),
+			_ => None,
+		}
+	}
+	pub fn as_obj(&self) -> Option<ObjValue> {
+		match self {
+			Val::Obj(o) => Some(o.clone()),
+			_ => None,
+		}
+	}
+	pub fn as_func(&self) -> Option<FuncVal> {
+		match self {
+			Val::Func(f) => Some(f.clone()),
+			_ => None,
+		}
+	}
+
 	/// Creates `Val::Num` after checking for numeric overflow.
 	/// As numbers are `f64`, we can just check for their finity.
 	pub fn new_checked_num(num: f64) -> Result<Self> {
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,8 +1,8 @@
 use quote::{quote, quote_spanned};
 use syn::{
 	parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned,
-	token::Comma, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments, Token,
-	Type,
+	token::Comma, DeriveInput, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path,
+	PathArguments, Token, Type,
 };
 
 fn is_location_arg(t: &PatType) -> bool {
@@ -254,3 +254,86 @@
 	})
 	.into()
 }
+
+#[proc_macro_derive(Typed)]
+pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let input = parse_macro_input!(item as DeriveInput);
+	let data = match &input.data {
+		syn::Data::Struct(s) => s,
+		_ => {
+			return syn::Error::new(input.span(), "only structs supported")
+				.to_compile_error()
+				.into()
+		}
+	};
+
+	let ident = &input.ident;
+
+	let fields_def = data.fields.iter().map(|f| {
+		let name = f
+			.ident
+			.as_ref()
+			.expect("only named fields supported")
+			.to_string();
+		let ty = &f.ty;
+		quote! {
+			(#name, #ty::TYPE),
+		}
+	});
+	let fields_parse = data.fields.iter().map(|f| {
+		let ident = f.ident.as_ref().unwrap();
+		let name = ident.to_string();
+		let ty = &f.ty;
+		quote! {
+			#ident: #ty::try_from(obj.get(#name.into())?.expect("shape is correct"))?,
+		}
+	});
+	let fields_serialize = data.fields.iter().map(|f| {
+		let ident = f.ident.as_ref().unwrap();
+		let name = ident.to_string();
+		quote! {
+			out.member(#name.into()).value(self.#ident.try_into()?);
+		}
+	});
+	let field_count = data.fields.len();
+
+	quote! {
+		const _: () = {
+			use ::jrsonnet_evaluator::{
+				typed::{ComplexValType, Typed, CheckType},
+				Val,
+				error::LocError,
+				obj::ObjValueBuilder,
+			};
+
+			const ITEMS: [(&'static str, &'static ComplexValType); #field_count] = [
+				#(#fields_def)*
+			];
+			impl Typed for #ident {
+				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);
+			}
+
+			impl TryFrom<Val> for #ident {
+				type Error = LocError;
+				fn try_from(value: Val) -> Result<Self, Self::Error> {
+					<Self as Typed>::TYPE.check(&value)?;
+					let obj = value.as_obj().expect("shape is correct");
+
+					Ok(Self {
+						#(#fields_parse)*
+					})
+				}
+			}
+			impl TryInto<Val> for #ident {
+				type Error = LocError;
+				fn try_into(self) -> Result<Val, Self::Error> {
+					let mut out = ObjValueBuilder::new();
+					#(#fields_serialize)*
+					Ok(Val::Obj(out.build()))
+				}
+			}
+			()
+		};
+	}
+	.into()
+}
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -122,7 +122,7 @@
 	BoundedNumber(Option<f64>, Option<f64>),
 	Array(Box<ComplexValType>),
 	ArrayRef(&'static ComplexValType),
-	ObjectRef(&'static [(&'static str, ComplexValType)]),
+	ObjectRef(&'static [(&'static str, &'static ComplexValType)]),
 	Union(Vec<ComplexValType>),
 	UnionRef(&'static [&'static ComplexValType]),
 	Sum(Vec<ComplexValType>),