git.delta.rocks / jrsonnet / refs/commits / 590966465ed7

difftreelog

test basic interop checks

Yaroslav Bolyukin2022-04-22parent: #321e7ee.patch.diff
in: master

7 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,4 +1,5 @@
 use std::{
+	fmt::Debug,
 	path::{Path, PathBuf},
 	rc::Rc,
 };
@@ -166,7 +167,7 @@
 #[derive(Debug, Clone, Trace)]
 pub struct StackTrace(pub Vec<StackTraceElement>);
 
-#[derive(Debug, Clone, Trace)]
+#[derive(Clone, Trace)]
 pub struct LocError(Box<(Error, StackTrace)>);
 impl LocError {
 	pub fn new(e: Error) -> Self {
@@ -186,6 +187,15 @@
 		&mut (self.0).1
 	}
 }
+impl Debug for LocError {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		writeln!(f, "{}", self.0 .0)?;
+		for el in self.0 .1 .0.iter() {
+			writeln!(f, "\t{:?}", el)?;
+		}
+		Ok(())
+	}
+}
 
 pub type Result<V, E = LocError> = std::result::Result<V, E>;
 
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function.rs
1use std::{borrow::Cow, collections::HashMap};23use gcmodule::Trace;4use jrsonnet_interner::IStr;5pub use jrsonnet_macros::builtin;6use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};78use crate::{9	error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, typed::Typed,10	val::LazyValValue, Context, FutureWrapper, GcHashMap, LazyVal, Result, State, Val,11};1213#[derive(Clone, Copy)]14pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);15impl<'l> CallLocation<'l> {16	pub const fn new(loc: &'l ExprLocation) -> Self {17		Self(Some(loc))18	}19}20impl CallLocation<'static> {21	pub const fn native() -> Self {22		Self(None)23	}24}2526#[derive(Trace)]27struct EvaluateLazyVal {28	ctx: Context,29	expr: LocExpr,30}31impl LazyValValue for EvaluateLazyVal {32	fn get(self: Box<Self>, s: State) -> Result<Val> {33		evaluate(s, self.ctx, &self.expr)34	}35}3637#[derive(Trace)]38struct EvaluateNamedLazyVal {39	ctx: FutureWrapper<Context>,40	name: IStr,41	value: LocExpr,42}43impl LazyValValue for EvaluateNamedLazyVal {44	fn get(self: Box<Self>, s: State) -> Result<Val> {45		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)46	}47}4849pub trait ArgLike {50	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;51}52impl ArgLike for &LocExpr {53	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {54		Ok(if tailstrict {55			LazyVal::new_resolved(evaluate(s, ctx, self)?)56		} else {57			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {58				ctx,59				expr: (*self).clone(),60			})))61		})62	}63}64impl<T> ArgLike for T65where66	T: Typed + Clone,67{68	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {69		let val = T::into_untyped(self.clone(), s)?;70		Ok(LazyVal::new_resolved(val))71	}72}73pub enum TlaArg {74	String(IStr),75	Code(LocExpr),76	Val(Val),77}78impl ArgLike for TlaArg {79	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {80		match self {81			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),82			TlaArg::Code(code) => Ok(if tailstrict {83				LazyVal::new_resolved(evaluate(s, ctx, code)?)84			} else {85				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {86					ctx,87					expr: code.clone(),88				})))89			}),90			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),91		}92	}93}9495pub trait ArgsLike {96	fn unnamed_len(&self) -> usize;97	fn unnamed_iter(98		&self,99		s: State,100		ctx: Context,101		tailstrict: bool,102		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,103	) -> Result<()>;104	fn named_iter(105		&self,106		s: State,107		ctx: Context,108		tailstrict: bool,109		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,110	) -> Result<()>;111	fn named_names(&self, handler: &mut dyn FnMut(&IStr));112}113114impl ArgsLike for ArgsDesc {115	fn unnamed_len(&self) -> usize {116		self.unnamed.len()117	}118119	fn unnamed_iter(120		&self,121		s: State,122		ctx: Context,123		tailstrict: bool,124		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,125	) -> Result<()> {126		for (id, arg) in self.unnamed.iter().enumerate() {127			handler(128				id,129				if tailstrict {130					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)131				} else {132					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {133						ctx: ctx.clone(),134						expr: arg.clone(),135					})))136				},137			)?;138		}139		Ok(())140	}141142	fn named_iter(143		&self,144		s: State,145		ctx: Context,146		tailstrict: bool,147		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,148	) -> Result<()> {149		for (name, arg) in self.named.iter() {150			handler(151				name,152				if tailstrict {153					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)154				} else {155					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {156						ctx: ctx.clone(),157						expr: arg.clone(),158					})))159				},160			)?;161		}162		Ok(())163	}164165	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {166		for (name, _) in self.named.iter() {167			handler(name)168		}169	}170}171172impl<A: ArgLike> ArgsLike for [(IStr, A)] {173	fn unnamed_len(&self) -> usize {174		0175	}176177	fn unnamed_iter(178		&self,179		_s: State,180		_ctx: Context,181		_tailstrict: bool,182		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,183	) -> Result<()> {184		Ok(())185	}186187	fn named_iter(188		&self,189		s: State,190		ctx: Context,191		tailstrict: bool,192		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,193	) -> Result<()> {194		for (name, val) in self.iter() {195			handler(name, val.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;196		}197		Ok(())198	}199200	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {201		for (name, _) in self.iter() {202			handler(name);203		}204	}205}206207impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {208	fn unnamed_len(&self) -> usize {209		0210	}211212	fn unnamed_iter(213		&self,214		_s: State,215		_ctx: Context,216		_tailstrict: bool,217		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,218	) -> Result<()> {219		Ok(())220	}221222	fn named_iter(223		&self,224		s: State,225		ctx: Context,226		tailstrict: bool,227		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,228	) -> Result<()> {229		for (name, value) in self.iter() {230			handler(231				name,232				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,233			)?;234		}235		Ok(())236	}237238	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {239		for (name, _) in self.iter() {240			handler(name);241		}242	}243}244245impl<A: ArgLike> ArgsLike for [A] {246	fn unnamed_len(&self) -> usize {247		self.len()248	}249250	fn unnamed_iter(251		&self,252		s: State,253		ctx: Context,254		tailstrict: bool,255		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,256	) -> Result<()> {257		for (i, arg) in self.iter().enumerate() {258			handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;259		}260		Ok(())261	}262263	fn named_iter(264		&self,265		_s: State,266		_ctx: Context,267		_tailstrict: bool,268		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,269	) -> Result<()> {270		Ok(())271	}272273	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}274}275impl<A: ArgLike> ArgsLike for &[A] {276	fn unnamed_len(&self) -> usize {277		(*self).unnamed_len()278	}279280	fn unnamed_iter(281		&self,282		s: State,283		ctx: Context,284		tailstrict: bool,285		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,286	) -> Result<()> {287		(*self).unnamed_iter(s, ctx, tailstrict, handler)288	}289290	fn named_iter(291		&self,292		s: State,293		ctx: Context,294		tailstrict: bool,295		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,296	) -> Result<()> {297		(*self).named_iter(s, ctx, tailstrict, handler)298	}299300	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {301		(*self).named_names(handler)302	}303}304305/// Creates correct [context](Context) for function body evaluation returning error on invalid call.306///307/// ## Parameters308/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)309/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)310/// * `params`: function parameters' definition311/// * `args`: passed function arguments312/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily313pub fn parse_function_call(314	s: State,315	ctx: Context,316	body_ctx: Context,317	params: &ParamsDesc,318	args: &dyn ArgsLike,319	tailstrict: bool,320) -> Result<Context> {321	let mut passed_args = GcHashMap::with_capacity(params.len());322	if args.unnamed_len() > params.len() {323		throw!(TooManyArgsFunctionHas(params.len()))324	}325326	let mut filled_args = 0;327328	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {329		let name = params[id].0.clone();330		passed_args.insert(name, arg);331		filled_args += 1;332		Ok(())333	})?;334335	args.named_iter(s, ctx, tailstrict, &mut |name, value| {336		// FIXME: O(n) for arg existence check337		if !params.iter().any(|p| &p.0 == name) {338			throw!(UnknownFunctionParameter((name as &str).to_owned()));339		}340		if passed_args.insert(name.clone(), value).is_some() {341			throw!(BindingParameterASecondTime(name.clone()));342		}343		filled_args += 1;344		Ok(())345	})?;346347	if filled_args < params.len() {348		// Some args are unset, but maybe we have defaults for them349		// Default values should be created in newly created context350		let fctx = Context::new_future();351		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);352353		for param in params.iter().filter(|p| p.1.is_some()) {354			if passed_args.contains_key(&param.0.clone()) {355				continue;356			}357			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {358				ctx: fctx.clone(),359				name: param.0.clone(),360				value: param.1.clone().unwrap(),361			})));362363			defaults.insert(364				param.0.clone(),365				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {366					ctx: fctx.clone(),367					name: param.0.clone(),368					value: param.1.clone().unwrap(),369				}))),370			);371			filled_args += 1;372		}373374		// Some args still wasn't filled375		if filled_args != params.len() {376			for param in params.iter().skip(args.unnamed_len()) {377				let mut found = false;378				args.named_names(&mut |name| {379					if name == &param.0 {380						found = true;381					}382				});383				if !found {384					throw!(FunctionParameterNotBoundInCall(param.0.clone()));385				}386			}387			unreachable!();388		}389390		Ok(body_ctx391			.extend(passed_args, None, None, None)392			.extend_bound(defaults)393			.into_future(fctx))394	} else {395		let body_ctx = body_ctx.extend(passed_args, None, None, None);396		Ok(body_ctx)397	}398}399400type BuiltinParamName = Cow<'static, str>;401402#[derive(Clone, Trace)]403pub struct BuiltinParam {404	pub name: BuiltinParamName,405	pub has_default: bool,406}407408/// Do not implement it directly, instead use #[builtin] macro409pub trait Builtin: Trace {410	fn name(&self) -> &str;411	fn params(&self) -> &[BuiltinParam];412	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;413}414415pub trait StaticBuiltin: Builtin + Send + Sync416where417	Self: 'static,418{419	// In impl, to make it object safe:420	// const INST: &'static Self;421}422423/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead424///425/// ## Parameters426/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)427/// * `params`: function parameters' definition428/// * `args`: passed function arguments429/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily430pub fn parse_builtin_call(431	s: State,432	ctx: Context,433	params: &[BuiltinParam],434	args: &dyn ArgsLike,435	tailstrict: bool,436) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {437	let mut passed_args = GcHashMap::with_capacity(params.len());438	if args.unnamed_len() > params.len() {439		throw!(TooManyArgsFunctionHas(params.len()))440	}441442	let mut filled_args = 0;443444	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {445		let name = params[id].name.clone();446		passed_args.insert(name, arg);447		filled_args += 1;448		Ok(())449	})?;450451	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {452		// FIXME: O(n) for arg existence check453		let p = params454			.iter()455			.find(|p| p.name == name as &str)456			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;457		if passed_args.insert(p.name.clone(), arg).is_some() {458			throw!(BindingParameterASecondTime(name.clone()));459		}460		filled_args += 1;461		Ok(())462	})?;463464	if filled_args < params.len() {465		for param in params.iter().filter(|p| p.has_default) {466			if passed_args.contains_key(&param.name) {467				continue;468			}469			filled_args += 1;470		}471472		// Some args still wasn't filled473		if filled_args != params.len() {474			for param in params.iter().skip(args.unnamed_len()) {475				let mut found = false;476				args.named_names(&mut |name| {477					if name as &str == &param.name as &str {478						found = true;479					}480				});481				if !found {482					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));483				}484			}485			unreachable!();486		}487	}488	Ok(passed_args)489}490491/// Creates Context, which has all argument default values applied492/// and with unbound values causing error to be returned493pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {494	let fctx = Context::new_future();495496	let mut bindings = GcHashMap::new();497498	#[derive(Trace)]499	struct DependsOnUnbound(IStr);500	impl LazyValValue for DependsOnUnbound {501		fn get(self: Box<Self>, _: State) -> Result<Val> {502			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())503		}504	}505506	for param in params.iter() {507		if let Some(v) = &param.1 {508			bindings.insert(509				param.0.clone(),510				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {511					ctx: fctx.clone(),512					name: param.0.clone(),513					value: v.clone(),514				}))),515			);516		} else {517			bindings.insert(518				param.0.clone(),519				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),520			);521		}522	}523524	body_ctx525		.extend(bindings, None, None, None)526		.into_future(fctx)527}
after · crates/jrsonnet-evaluator/src/function.rs
1use std::{borrow::Cow, collections::HashMap};23use gcmodule::Trace;4use jrsonnet_interner::IStr;5pub use jrsonnet_macros::builtin;6use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};78use crate::{9	error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, typed::Typed,10	val::LazyValValue, Context, FutureWrapper, GcHashMap, LazyVal, Result, State, Val,11};1213#[derive(Clone, Copy)]14pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);15impl<'l> CallLocation<'l> {16	pub const fn new(loc: &'l ExprLocation) -> Self {17		Self(Some(loc))18	}19}20impl CallLocation<'static> {21	pub const fn native() -> Self {22		Self(None)23	}24}2526#[derive(Trace)]27struct EvaluateLazyVal {28	ctx: Context,29	expr: LocExpr,30}31impl LazyValValue for EvaluateLazyVal {32	fn get(self: Box<Self>, s: State) -> Result<Val> {33		evaluate(s, self.ctx, &self.expr)34	}35}3637#[derive(Trace)]38struct EvaluateNamedLazyVal {39	ctx: FutureWrapper<Context>,40	name: IStr,41	value: LocExpr,42}43impl LazyValValue for EvaluateNamedLazyVal {44	fn get(self: Box<Self>, s: State) -> Result<Val> {45		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)46	}47}48pub trait ArgLike {49	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;50}51impl ArgLike for &LocExpr {52	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {53		Ok(if tailstrict {54			LazyVal::new_resolved(evaluate(s, ctx, self)?)55		} else {56			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {57				ctx,58				expr: (*self).clone(),59			})))60		})61	}62}63impl<T> ArgLike for T64where65	T: Typed + Clone,66{67	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {68		let val = T::into_untyped(self.clone(), s)?;69		Ok(LazyVal::new_resolved(val))70	}71}72pub enum TlaArg {73	String(IStr),74	Code(LocExpr),75	Val(Val),76}77impl ArgLike for TlaArg {78	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {79		match self {80			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),81			TlaArg::Code(code) => Ok(if tailstrict {82				LazyVal::new_resolved(evaluate(s, ctx, code)?)83			} else {84				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {85					ctx,86					expr: code.clone(),87				})))88			}),89			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),90		}91	}92}9394pub trait ArgsLike {95	fn unnamed_len(&self) -> usize;96	fn unnamed_iter(97		&self,98		s: State,99		ctx: Context,100		tailstrict: bool,101		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,102	) -> Result<()>;103	fn named_iter(104		&self,105		s: State,106		ctx: Context,107		tailstrict: bool,108		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,109	) -> Result<()>;110	fn named_names(&self, handler: &mut dyn FnMut(&IStr));111}112113impl ArgsLike for ArgsDesc {114	fn unnamed_len(&self) -> usize {115		self.unnamed.len()116	}117118	fn unnamed_iter(119		&self,120		s: State,121		ctx: Context,122		tailstrict: bool,123		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,124	) -> Result<()> {125		for (id, arg) in self.unnamed.iter().enumerate() {126			handler(127				id,128				if tailstrict {129					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)130				} else {131					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {132						ctx: ctx.clone(),133						expr: arg.clone(),134					})))135				},136			)?;137		}138		Ok(())139	}140141	fn named_iter(142		&self,143		s: State,144		ctx: Context,145		tailstrict: bool,146		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,147	) -> Result<()> {148		for (name, arg) in self.named.iter() {149			handler(150				name,151				if tailstrict {152					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)153				} else {154					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {155						ctx: ctx.clone(),156						expr: arg.clone(),157					})))158				},159			)?;160		}161		Ok(())162	}163164	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {165		for (name, _) in self.named.iter() {166			handler(name)167		}168	}169}170171impl ArgsLike for [(); 0] {172	fn unnamed_len(&self) -> usize {173		0174	}175176	fn unnamed_iter(177		&self,178		_s: State,179		_ctx: Context,180		_tailstrict: bool,181		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,182	) -> Result<()> {183		Ok(())184	}185186	fn named_iter(187		&self,188		_s: State,189		_ctx: Context,190		_tailstrict: bool,191		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,192	) -> Result<()> {193		Ok(())194	}195196	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}197}198199impl<A: ArgLike> ArgsLike for [(IStr, A)] {200	fn unnamed_len(&self) -> usize {201		0202	}203204	fn unnamed_iter(205		&self,206		_s: State,207		_ctx: Context,208		_tailstrict: bool,209		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,210	) -> Result<()> {211		Ok(())212	}213214	fn named_iter(215		&self,216		s: State,217		ctx: Context,218		tailstrict: bool,219		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,220	) -> Result<()> {221		for (name, val) in self.iter() {222			handler(name, val.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;223		}224		Ok(())225	}226227	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {228		for (name, _) in self.iter() {229			handler(name);230		}231	}232}233234impl<A: ArgLike> ArgsLike for HashMap<IStr, A> {235	fn unnamed_len(&self) -> usize {236		0237	}238239	fn unnamed_iter(240		&self,241		_s: State,242		_ctx: Context,243		_tailstrict: bool,244		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,245	) -> Result<()> {246		Ok(())247	}248249	fn named_iter(250		&self,251		s: State,252		ctx: Context,253		tailstrict: bool,254		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,255	) -> Result<()> {256		for (name, value) in self.iter() {257			handler(258				name,259				value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,260			)?;261		}262		Ok(())263	}264265	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {266		for (name, _) in self.iter() {267			handler(name);268		}269	}270}271272impl<A: ArgLike> ArgsLike for [A] {273	fn unnamed_len(&self) -> usize {274		self.len()275	}276277	fn unnamed_iter(278		&self,279		s: State,280		ctx: Context,281		tailstrict: bool,282		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,283	) -> Result<()> {284		for (i, arg) in self.iter().enumerate() {285			handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;286		}287		Ok(())288	}289290	fn named_iter(291		&self,292		_s: State,293		_ctx: Context,294		_tailstrict: bool,295		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,296	) -> Result<()> {297		Ok(())298	}299300	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}301}302impl<A: ArgLike> ArgsLike for &[A] {303	fn unnamed_len(&self) -> usize {304		(*self).unnamed_len()305	}306307	fn unnamed_iter(308		&self,309		s: State,310		ctx: Context,311		tailstrict: bool,312		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,313	) -> Result<()> {314		(*self).unnamed_iter(s, ctx, tailstrict, handler)315	}316317	fn named_iter(318		&self,319		s: State,320		ctx: Context,321		tailstrict: bool,322		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,323	) -> Result<()> {324		(*self).named_iter(s, ctx, tailstrict, handler)325	}326327	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {328		(*self).named_names(handler)329	}330}331332/// Creates correct [context](Context) for function body evaluation returning error on invalid call.333///334/// ## Parameters335/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)336/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)337/// * `params`: function parameters' definition338/// * `args`: passed function arguments339/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily340pub fn parse_function_call(341	s: State,342	ctx: Context,343	body_ctx: Context,344	params: &ParamsDesc,345	args: &dyn ArgsLike,346	tailstrict: bool,347) -> Result<Context> {348	let mut passed_args = GcHashMap::with_capacity(params.len());349	if args.unnamed_len() > params.len() {350		throw!(TooManyArgsFunctionHas(params.len()))351	}352353	let mut filled_args = 0;354355	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {356		let name = params[id].0.clone();357		passed_args.insert(name, arg);358		filled_args += 1;359		Ok(())360	})?;361362	args.named_iter(s, ctx, tailstrict, &mut |name, value| {363		// FIXME: O(n) for arg existence check364		if !params.iter().any(|p| &p.0 == name) {365			throw!(UnknownFunctionParameter((name as &str).to_owned()));366		}367		if passed_args.insert(name.clone(), value).is_some() {368			throw!(BindingParameterASecondTime(name.clone()));369		}370		filled_args += 1;371		Ok(())372	})?;373374	if filled_args < params.len() {375		// Some args are unset, but maybe we have defaults for them376		// Default values should be created in newly created context377		let fctx = Context::new_future();378		let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);379380		for param in params.iter().filter(|p| p.1.is_some()) {381			if passed_args.contains_key(&param.0.clone()) {382				continue;383			}384			LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {385				ctx: fctx.clone(),386				name: param.0.clone(),387				value: param.1.clone().unwrap(),388			})));389390			defaults.insert(391				param.0.clone(),392				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {393					ctx: fctx.clone(),394					name: param.0.clone(),395					value: param.1.clone().unwrap(),396				}))),397			);398			filled_args += 1;399		}400401		// Some args still wasn't filled402		if filled_args != params.len() {403			for param in params.iter().skip(args.unnamed_len()) {404				let mut found = false;405				args.named_names(&mut |name| {406					if name == &param.0 {407						found = true;408					}409				});410				if !found {411					throw!(FunctionParameterNotBoundInCall(param.0.clone()));412				}413			}414			unreachable!();415		}416417		Ok(body_ctx418			.extend(passed_args, None, None, None)419			.extend_bound(defaults)420			.into_future(fctx))421	} else {422		let body_ctx = body_ctx.extend(passed_args, None, None, None);423		Ok(body_ctx)424	}425}426427type BuiltinParamName = Cow<'static, str>;428429#[derive(Clone, Trace)]430pub struct BuiltinParam {431	pub name: BuiltinParamName,432	pub has_default: bool,433}434435/// Do not implement it directly, instead use #[builtin] macro436pub trait Builtin: Trace {437	fn name(&self) -> &str;438	fn params(&self) -> &[BuiltinParam];439	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;440}441442pub trait StaticBuiltin: Builtin + Send + Sync443where444	Self: 'static,445{446	// In impl, to make it object safe:447	// const INST: &'static Self;448}449450/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead451///452/// ## Parameters453/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)454/// * `params`: function parameters' definition455/// * `args`: passed function arguments456/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily457pub fn parse_builtin_call(458	s: State,459	ctx: Context,460	params: &[BuiltinParam],461	args: &dyn ArgsLike,462	tailstrict: bool,463) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {464	let mut passed_args = GcHashMap::with_capacity(params.len());465	if args.unnamed_len() > params.len() {466		throw!(TooManyArgsFunctionHas(params.len()))467	}468469	let mut filled_args = 0;470471	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {472		let name = params[id].name.clone();473		passed_args.insert(name, arg);474		filled_args += 1;475		Ok(())476	})?;477478	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {479		// FIXME: O(n) for arg existence check480		let p = params481			.iter()482			.find(|p| p.name == name as &str)483			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;484		if passed_args.insert(p.name.clone(), arg).is_some() {485			throw!(BindingParameterASecondTime(name.clone()));486		}487		filled_args += 1;488		Ok(())489	})?;490491	if filled_args < params.len() {492		for param in params.iter().filter(|p| p.has_default) {493			if passed_args.contains_key(&param.name) {494				continue;495			}496			filled_args += 1;497		}498499		// Some args still wasn't filled500		if filled_args != params.len() {501			for param in params.iter().skip(args.unnamed_len()) {502				let mut found = false;503				args.named_names(&mut |name| {504					if name as &str == &param.name as &str {505						found = true;506					}507				});508				if !found {509					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));510				}511			}512			unreachable!();513		}514	}515	Ok(passed_args)516}517518/// Creates Context, which has all argument default values applied519/// and with unbound values causing error to be returned520pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {521	let fctx = Context::new_future();522523	let mut bindings = GcHashMap::new();524525	#[derive(Trace)]526	struct DependsOnUnbound(IStr);527	impl LazyValValue for DependsOnUnbound {528		fn get(self: Box<Self>, _: State) -> Result<Val> {529			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())530		}531	}532533	for param in params.iter() {534		if let Some(v) = &param.1 {535			bindings.insert(536				param.0.clone(),537				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {538					ctx: fctx.clone(),539					name: param.0.clone(),540					value: v.clone(),541				}))),542			);543		} else {544			bindings.insert(545				param.0.clone(),546				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),547			);548		}549	}550551	body_ctx552		.extend(bindings, None, None, None)553		.into_future(fctx)554}
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -14,11 +14,11 @@
 };
 
 pub trait TypedObj: Typed {
-	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
-	fn parse(obj: &ObjValue) -> Result<Self>;
-	fn into_object(self) -> Result<ObjValue> {
+	fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<()>;
+	fn parse(obj: &ObjValue, s: State) -> Result<Self>;
+	fn into_object(self, s: State) -> Result<ObjValue> {
 		let mut builder = ObjValueBuilder::new();
-		self.serialize(&mut builder)?;
+		self.serialize(s, &mut builder)?;
 		Ok(builder.build())
 	}
 }
addedcrates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -0,0 +1,105 @@
+mod common;
+
+use std::path::PathBuf;
+
+use gcmodule::Cc;
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, Builtin, CallLocation},
+	gc::TraceBox,
+	typed::Typed,
+	val::FuncVal,
+	State, Val,
+};
+
+#[builtin]
+fn a() -> Result<u32> {
+	Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+	let s = State::default();
+	let a: a = a {};
+	let v = u32::from_untyped(
+		a.call(
+			s.clone(),
+			s.create_default_context(),
+			CallLocation::native(),
+			&[],
+		)?,
+		s.clone(),
+	)?;
+
+	ensure_eq!(v, 1);
+	Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+	Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.settings_mut().globals.insert(
+		"nativeAdd".into(),
+		Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
+	);
+
+	let v = s.evaluate_snippet_raw(
+		PathBuf::new().into(),
+		"
+            assert nativeAdd(1, 2) == 3;
+            assert nativeAdd(100, 200) == 300;
+            null
+        "
+		.into(),
+	)?;
+	ensure_val_eq!(s.clone(), v, Val::Null);
+	Ok(())
+}
+
+#[builtin(fields(
+    a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+	Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+	Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add {
+		a,
+	})))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.settings_mut().globals.insert(
+		"curryAdd".into(),
+		Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
+	);
+
+	let v = s.evaluate_snippet_raw(
+		PathBuf::new().into(),
+		"
+            local a = curryAdd(1);
+            local b = curryAdd(4);
+
+            assert a(2) == 3;
+            assert a(200) == 201;
+
+            assert b(2) == 6;
+            assert b(200) == 204;
+            null
+        "
+		.into(),
+	)?;
+	ensure_val_eq!(s.clone(), v, Val::Null);
+	Ok(())
+}
addedcrates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! ensure_eq {
+	($a:expr, $b:expr $(,)?) => {{
+		if $a != $b {
+			::jrsonnet_evaluator::throw_runtime!(
+				"assertion failed: a != b\na={:#?}\nb={:#?}",
+				$a,
+				$b,
+			)
+		}
+	}};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+	($s:expr, $a:expr, $b:expr) => {{
+		if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+			::jrsonnet_evaluator::throw_runtime!(
+				"assertion failed: a != b\na={:#?}\nb={:#?}",
+				$a.to_json($s.clone(), 2)?,
+				$b.to_json($s.clone(), 2)?,
+			)
+		}
+	}};
+}
addedcrates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/typed_obj.rs
@@ -0,0 +1,194 @@
+mod common;
+
+use std::{fmt::Debug, path::PathBuf};
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+	a: u32,
+	b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+	let untyped = T::into_untyped(value.clone(), s.clone())?;
+	let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+	ensure_eq!(value, value2);
+	let untyped2 = T::into_untyped(value2, s.clone())?;
+	ensure_val_eq!(s, untyped, untyped2);
+
+	Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let a = A::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(a, A { a: 1, b: 2 });
+	test_roundtrip(a.clone(), s.clone())?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+	a: u32,
+	#[typed(rename = "c")]
+	b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let b = B::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, c: 2}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(b, B { a: 1, b: 2 });
+	ensure_eq!(
+		&B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+		"{a: 1, c: 2}",
+	);
+	test_roundtrip(b.clone(), s.clone())?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+	#[typed(rename = "apiVersion")]
+	api_version: String,
+	#[typed(rename = "kind")]
+	kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+	#[typed(flatten)]
+	kind: ObjectKind,
+	b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let obj = Object::from_untyped(
+		s.evaluate_snippet_raw(
+			PathBuf::new().into(),
+			"{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
+		)?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		obj,
+		Object {
+			kind: ObjectKind {
+				api_version: "ver".into(),
+				kind: "kind".into(),
+			},
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+	);
+	test_roundtrip(obj.clone(), s.clone())?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+	a: Option<u32>,
+	b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(c, C { a: Some(1), b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"a": 1, "b": 2}"#,
+	);
+	test_roundtrip(c.clone(), s.clone())?;
+	Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(c, C { a: None, b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(c.clone(), s.clone())?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+	#[typed(flatten(ok))]
+	e: Option<E>,
+	b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+	v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v:1}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		d,
+		D {
+			e: Some(E { v: 1 }),
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2, "v": 1}"#,
+	);
+	test_roundtrip(d.clone(), s.clone())?;
+	Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v: '1'}".into())?,
+		s.clone(),
+	)?;
+	ensure_eq!(d, D { e: None, b: 2 });
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(d.clone(), s.clone())?;
+	Ok(())
+}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -6,7 +6,7 @@
 	parse_macro_input,
 	punctuated::Punctuated,
 	spanned::Spanned,
-	token::Comma,
+	token::{self, Comma},
 	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
 	PathArguments, Result, ReturnType, Token, Type,
 };
@@ -90,6 +90,7 @@
 	syn::custom_keyword!(fields);
 	syn::custom_keyword!(rename);
 	syn::custom_keyword!(flatten);
+	syn::custom_keyword!(ok);
 }
 
 struct EmptyAttr;
@@ -135,7 +136,7 @@
 }
 
 impl ArgInfo {
-	fn parse(arg: &FnArg) -> Result<Self> {
+	fn parse(name: &str, arg: &FnArg) -> Result<Self> {
 		let arg = match arg {
 			FnArg::Receiver(_) => unreachable!(),
 			FnArg::Typed(a) => a,
@@ -149,8 +150,6 @@
 			return Ok(Self::State);
 		} else if type_is_path(ty, "CallLocation").is_some() {
 			return Ok(Self::Location);
-		} else if type_is_path(ty, "Self").is_some() {
-			return Ok(Self::This);
 		} else if type_is_path(ty, "LazyVal").is_some() {
 			return Ok(Self::Lazy {
 				is_option: false,
@@ -158,6 +157,11 @@
 			});
 		}
 
+		match &ty as &Type {
+			Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This),
+			_ => {}
+		}
+
 		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
 			if type_is_path(ty, "LazyVal").is_some() {
 				return Ok(Self::Lazy {
@@ -230,11 +234,12 @@
 		return Err(Error::new(result.span(), "return value should be result"));
 	};
 
+	let name = fun.sig.ident.to_string();
 	let args = fun
 		.sig
 		.inputs
 		.iter()
-		.map(ArgInfo::parse)
+		.map(|arg| ArgInfo::parse(&name, arg))
 		.collect::<Result<Vec<_>>>()?;
 
 	let params_desc = args.iter().flat_map(|a| match a {
@@ -343,9 +348,9 @@
 		}
 		const _: () = {
 			use ::jrsonnet_evaluator::{
-				State,
+				State, Val,
 				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
-				error::Result, Context,
+				error::Result, Context, typed::Typed,
 				parser::ExprLocation,
 			};
 			const PARAMS: &'static [BuiltinParam] = &[
@@ -379,6 +384,9 @@
 struct TypedAttr {
 	rename: Option<String>,
 	flatten: bool,
+	/// flatten(ok) strategy for flattened optionals
+	/// field would be None in case of any parsing error (as in serde)
+	flatten_ok: bool,
 }
 impl Parse for TypedAttr {
 	fn parse(input: ParseStream) -> syn::Result<Self> {
@@ -399,6 +407,17 @@
 			} else if lookahead.peek(kw::flatten) {
 				input.parse::<kw::flatten>()?;
 				out.flatten = true;
+				if input.peek(token::Paren) {
+					let content;
+					parenthesized!(content in input);
+					let lookahead = content.lookahead1();
+					if lookahead.peek(kw::ok) {
+						content.parse::<kw::ok>()?;
+						out.flatten_ok = true;
+					} else {
+						return Err(lookahead.error());
+					}
+				}
 			} else if input.is_empty() {
 				break;
 			} else {
@@ -417,75 +436,101 @@
 	}
 }
 
-struct TypedField<'f>(&'f syn::Field, TypedAttr);
-impl<'f> TypedField<'f> {
-	fn try_new(field: &'f syn::Field) -> Result<Self> {
+struct TypedField {
+	attr: TypedAttr,
+	ident: Ident,
+	ty: Type,
+	is_option: bool,
+}
+impl TypedField {
+	fn parse(field: &syn::Field) -> Result<Self> {
 		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();
-		if field.ident.is_none() {
+		let ident = if let Some(ident) = field.ident.clone() {
+			ident
+		} else {
 			return Err(Error::new(
 				field.span(),
 				"this field should appear in output object, but it has no visible name",
 			));
+		};
+		let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {
+			(true, ty.clone())
+		} else {
+			(false, field.ty.clone())
+		};
+		if is_option && attr.flatten {
+			if !attr.flatten_ok {
+				return Err(Error::new(
+					field.span(),
+					"strategy should be set when flattening Option",
+				));
+			}
+		} else {
+			if attr.flatten_ok {
+				return Err(Error::new(
+					field.span(),
+					"flatten(ok) is only useable on optional fields",
+				));
+			}
 		}
-		Ok(Self(field, attr))
-	}
-	fn ident(&self) -> Ident {
-		self.0
-			.ident
-			.clone()
-			.expect("constructor disallows fields without name")
+		Ok(Self {
+			attr,
+			ident,
+			ty,
+			is_option,
+		})
 	}
 	/// None if this field is flattened in jsonnet output
 	fn name(&self) -> Option<String> {
-		if self.1.flatten {
+		if self.attr.flatten {
 			return None;
 		}
 		Some(
-			self.1
+			self.attr
 				.rename
 				.clone()
-				.unwrap_or_else(|| self.ident().to_string()),
+				.unwrap_or_else(|| self.ident.to_string()),
 		)
 	}
 
 	fn expand_field(&self) -> Option<TokenStream> {
-		if self.is_option() {
+		if self.is_option {
 			return None;
 		}
 		let name = self.name()?;
-		let ty = &self.0.ty;
+		let ty = &self.ty;
 		Some(quote! {
 			(#name, <#ty>::TYPE)
 		})
 	}
 	fn expand_parse(&self) -> TokenStream {
-		let ident = self.ident();
-		let ty = &self.0.ty;
-		if self.1.flatten {
+		let ident = &self.ident;
+		let ty = &self.ty;
+		if self.attr.flatten {
 			// optional flatten is handled in same way as serde
-			return if self.is_option() {
+			return if self.is_option {
 				quote! {
-					#ident: <#ty>::parse(&obj).ok(),
+					#ident: <#ty>::parse(&obj, s.clone()).ok(),
 				}
 			} else {
 				quote! {
-					#ident: <#ty>::parse(&obj)?,
+					#ident: <#ty>::parse(&obj, s.clone())?,
 				}
 			};
 		};
 
 		let name = self.name().unwrap();
-		let value = if let Some(ty) = self.as_option() {
+		let value = if self.is_option {
 			quote! {
-				if let Some(value) = obj.get(#name.into())? {
-					Some(<#ty>::try_from(vakue)?)
+				if let Some(value) = obj.get(s.clone(), #name.into())? {
+					Some(<#ty>::from_untyped(value, s.clone())?)
 				} else {
 					None
 				}
 			}
 		} else {
 			quote! {
-				<#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?
+				<#ty>::from_untyped(obj.get(s.clone(), #name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?, s.clone())?
 			}
 		};
 
@@ -493,39 +538,33 @@
 			#ident: #value,
 		}
 	}
-	fn expand_serialize(&self) -> TokenStream {
-		let ident = self.ident();
-		if let Some(name) = self.name() {
-			if self.is_option() {
+	fn expand_serialize(&self) -> Result<TokenStream> {
+		let ident = &self.ident;
+		let ty = &self.ty;
+		Ok(if let Some(name) = self.name() {
+			if self.is_option {
 				quote! {
 					if let Some(value) = self.#ident {
-						out.member(#name.into()).value(value.try_into()?)?;
+						out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(value, s.clone())?)?;
 					}
 				}
 			} else {
 				quote! {
-					out.member(#name.into()).value(self.#ident.try_into()?)?;
+					out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(self.#ident, s.clone())?)?;
 				}
 			}
-		} else if self.is_option() {
+		} else if self.is_option {
 			quote! {
 				if let Some(value) = self.#ident {
-					value.serialize(out)?;
+					value.serialize(s.clone(), out)?;
 				}
 			}
 		} else {
 			quote! {
-				self.#ident.serialize(out)?;
+				self.#ident.serialize(s.clone(), out)?;
 			}
-		}
-	}
-
-	fn as_option(&self) -> Option<&Type> {
-		extract_type_from_option(&self.0.ty).unwrap()
+		})
 	}
-	fn is_option(&self) -> bool {
-		self.as_option().is_some()
-	}
 }
 
 #[proc_macro_derive(Typed, attributes(typed))]
@@ -548,7 +587,7 @@
 	let fields = data
 		.fields
 		.iter()
-		.map(TypedField::try_new)
+		.map(TypedField::parse)
 		.collect::<Result<Vec<_>>>()?;
 
 	let typed = {
@@ -566,12 +605,12 @@
 
 				fn from_untyped(value: Val, s: State) -> Result<Self> {
 					let obj = value.as_obj().expect("shape is correct");
-					Self::parse(&obj)
+					Self::parse(&obj, s)
 				}
 
 				fn into_untyped(value: Self, s: State) -> Result<Val> {
 					let mut out = ObjValueBuilder::new();
-					value.serialize(&mut out)?;
+					value.serialize(s, &mut out)?;
 					Ok(Val::Obj(out.build()))
 				}
 
@@ -580,26 +619,29 @@
 	};
 
 	let fields_parse = fields.iter().map(TypedField::expand_parse);
-	let fields_serialize = fields.iter().map(TypedField::expand_serialize);
+	let fields_serialize = fields
+		.iter()
+		.map(TypedField::expand_serialize)
+		.collect::<Result<Vec<_>>>()?;
 
 	Ok(quote! {
 		const _: () = {
 			use ::jrsonnet_evaluator::{
 				typed::{ComplexValType, Typed, TypedObj, CheckType},
-				Val,
-				error::{LocError, Error},
+				Val, State,
+				error::{LocError, Error, Result},
 				ObjValueBuilder, ObjValue,
 			};
 
 			#typed
 
-			impl #ident {
-				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {
+			impl TypedObj for #ident {
+				fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<(), LocError> {
 					#(#fields_serialize)*
 
 					Ok(())
 				}
-				fn parse(obj: &ObjValue) -> Result<Self, LocError> {
+				fn parse(obj: &ObjValue, s: State) -> Result<Self, LocError> {
 					Ok(Self {
 						#(#fields_parse)*
 					})