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

difftreelog

refactor split Typed into FromUntyped and IntoUntyped

ysonnywlYaroslav Bolyukin2026-03-22parent: #8667194.patch.diff
in: master

20 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -6,7 +6,7 @@
 use jrsonnet_evaluator::{
 	error::{Error, ErrorKind},
 	function::builtin::{NativeCallback, NativeCallbackHandler},
-	typed::Typed,
+	typed::FromUntyped as _,
 	IStr, Val,
 };
 
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -11,10 +11,6 @@
 workspace = true
 
 [features]
-default = [
-    "exp-regex",
-]
-
 experimental = [
     "exp-preserve-order",
     "exp-destruct",
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -5,11 +5,11 @@
 	rc::Rc,
 };
 
-use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};
+use jrsonnet_gcmodule::{cc_dyn, Cc};
 use jrsonnet_interner::IBytes;
 use jrsonnet_parser::{Expr, Spanned};
 
-use crate::{function::NativeFn, typed::Typed, Context, Result, Thunk, Val};
+use crate::{function::NativeFn, Context, Result, Thunk, Val};
 
 mod spec;
 pub use spec::{ArrayLike, *};
@@ -241,4 +241,3 @@
 		self.0.is_cheap()
 	}
 }
-
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::rc::Rc;2use std::{any::Any, cell::RefCell, fmt::Debug, mem::replace};34use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::{IBytes, IStr};6use jrsonnet_parser::{Expr, Spanned};78use super::ArrValue;9use crate::function::NativeFn;10use crate::{11	error::ErrorKind::InfiniteRecursionDetected, evaluate, typed::Typed, val::ThunkValue, Context,12	Error, ObjValue, Result, Thunk, Val,13};1415pub trait ArrayLike: Any + Trace + Debug {16	fn len(&self) -> usize;17	fn is_empty(&self) -> bool {18		self.len() == 019	}20	fn get(&self, index: usize) -> Result<Option<Val>>;21	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;22	fn get_cheap(&self, index: usize) -> Option<Val>;2324	fn is_cheap(&self) -> bool;25}2627#[derive(Debug, Trace)]28pub struct SliceArray {29	pub(crate) inner: ArrValue,30	pub(crate) from: u32,31	pub(crate) to: u32,32	pub(crate) step: u32,33}3435impl SliceArray {36	fn map_idx(&self, index: usize) -> usize {37		self.from as usize + self.step as usize * index38	}39}40impl ArrayLike for SliceArray {41	fn len(&self) -> usize {42		(self.to - self.from).div_ceil(self.step) as usize43	}4445	fn get(&self, index: usize) -> Result<Option<Val>> {46		self.inner.get(self.map_idx(index))47	}4849	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {50		self.inner.get_lazy(self.map_idx(index))51	}5253	fn get_cheap(&self, index: usize) -> Option<Val> {54		self.inner.get_cheap(self.map_idx(index))55	}56	fn is_cheap(&self) -> bool {57		self.inner.is_cheap()58	}59}6061#[derive(Trace, Debug)]62pub struct CharArray(pub Vec<char>);63impl ArrayLike for CharArray {64	fn len(&self) -> usize {65		self.0.len()66	}6768	fn get(&self, index: usize) -> Result<Option<Val>> {69		Ok(self.get_cheap(index))70	}7172	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {73		self.get_cheap(index).map(Thunk::evaluated)74	}7576	fn get_cheap(&self, index: usize) -> Option<Val> {77		self.0.get(index).map(|v| Val::string(*v))78	}79	fn is_cheap(&self) -> bool {80		true81	}82}8384#[derive(Trace, Debug)]85pub struct BytesArray(pub IBytes);86impl ArrayLike for BytesArray {87	fn len(&self) -> usize {88		self.0.len()89	}9091	fn get(&self, index: usize) -> Result<Option<Val>> {92		Ok(self.get_cheap(index))93	}9495	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {96		self.get_cheap(index).map(Thunk::evaluated)97	}9899	fn get_cheap(&self, index: usize) -> Option<Val> {100		self.0.get(index).map(|v| Val::Num((*v).into()))101	}102	fn is_cheap(&self) -> bool {103		true104	}105}106107#[derive(Debug, Trace, Clone)]108enum ArrayThunk {109	Computed(Val),110	Errored(Error),111	Waiting,112	Pending,113}114115#[derive(Debug, Trace, Clone)]116pub struct ExprArray {117	ctx: Context,118	src: Rc<Vec<Spanned<Expr>>>,119	cached: Cc<RefCell<Vec<ArrayThunk>>>,120}121impl ExprArray {122	pub fn new(ctx: Context, src: Rc<Vec<Spanned<Expr>>>) -> Self {123		Self {124			ctx,125			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),126			src,127		}128	}129}130impl ArrayLike for ExprArray {131	fn len(&self) -> usize {132		self.cached.borrow().len()133	}134	fn get(&self, index: usize) -> Result<Option<Val>> {135		if index >= self.len() {136			return Ok(None);137		}138		match &self.cached.borrow()[index] {139			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),140			ArrayThunk::Errored(e) => return Err(e.clone()),141			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),142			ArrayThunk::Waiting => {}143		}144145		let ArrayThunk::Waiting =146			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)147		else {148			unreachable!()149		};150151		let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {152			Ok(v) => v,153			Err(e) => {154				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());155				return Err(e);156			}157		};158		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());159		Ok(Some(new_value))160	}161	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {162		#[derive(Trace)]163		struct ExprArrThunk {164			expr: ExprArray,165			index: usize,166		}167		impl ThunkValue for ExprArrThunk {168			type Output = Val;169170			fn get(&self) -> Result<Self::Output> {171				self.expr172					.get(self.index)173					.transpose()174					.expect("index checked")175			}176		}177178		if index >= self.len() {179			return None;180		}181		match &self.cached.borrow()[index] {182			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),183			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),184			ArrayThunk::Waiting | ArrayThunk::Pending => {}185		}186187		Some(Thunk::new(ExprArrThunk {188			expr: self.clone(),189			index,190		}))191	}192	fn get_cheap(&self, _index: usize) -> Option<Val> {193		None194	}195	fn is_cheap(&self) -> bool {196		false197	}198}199200#[derive(Trace, Debug)]201pub struct ExtendedArray {202	pub a: ArrValue,203	pub b: ArrValue,204	split: usize,205	len: usize,206}207impl ExtendedArray {208	pub fn new(a: ArrValue, b: ArrValue) -> Self {209		let a_len = a.len();210		let b_len = b.len();211		Self {212			a,213			b,214			split: a_len,215			len: a_len.checked_add(b_len).expect("too large array value"),216		}217	}218}219220struct WithExactSize<I>(I, usize);221impl<I, T> Iterator for WithExactSize<I>222where223	I: Iterator<Item = T>,224{225	type Item = T;226227	fn next(&mut self) -> Option<Self::Item> {228		self.0.next()229	}230	fn nth(&mut self, n: usize) -> Option<Self::Item> {231		self.0.nth(n)232	}233	fn size_hint(&self) -> (usize, Option<usize>) {234		(self.1, Some(self.1))235	}236}237impl<I> DoubleEndedIterator for WithExactSize<I>238where239	I: DoubleEndedIterator,240{241	fn next_back(&mut self) -> Option<Self::Item> {242		self.0.next_back()243	}244	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {245		self.0.nth_back(n)246	}247}248impl<I> ExactSizeIterator for WithExactSize<I>249where250	I: Iterator,251{252	fn len(&self) -> usize {253		self.1254	}255}256impl ArrayLike for ExtendedArray {257	fn get(&self, index: usize) -> Result<Option<Val>> {258		if self.split > index {259			self.a.get(index)260		} else {261			self.b.get(index - self.split)262		}263	}264	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {265		if self.split > index {266			self.a.get_lazy(index)267		} else {268			self.b.get_lazy(index - self.split)269		}270	}271272	fn len(&self) -> usize {273		self.len274	}275276	fn get_cheap(&self, index: usize) -> Option<Val> {277		if self.split > index {278			self.a.get_cheap(index)279		} else {280			self.b.get_cheap(index - self.split)281		}282	}283	fn is_cheap(&self) -> bool {284		self.a.is_cheap() && self.b.is_cheap()285	}286}287288#[derive(Trace, Debug)]289pub struct LazyArray(pub Vec<Thunk<Val>>);290impl ArrayLike for LazyArray {291	fn len(&self) -> usize {292		self.0.len()293	}294	fn get(&self, index: usize) -> Result<Option<Val>> {295		let Some(v) = self.0.get(index) else {296			return Ok(None);297		};298		v.evaluate().map(Some)299	}300	fn get_cheap(&self, _index: usize) -> Option<Val> {301		None302	}303	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {304		self.0.get(index).cloned()305	}306	fn is_cheap(&self) -> bool {307		false308	}309}310311#[derive(Trace, Debug)]312pub struct EagerArray(pub Vec<Val>);313impl ArrayLike for EagerArray {314	fn len(&self) -> usize {315		self.0.len()316	}317318	fn get(&self, index: usize) -> Result<Option<Val>> {319		Ok(self.0.get(index).cloned())320	}321322	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {323		self.0.get(index).cloned().map(Thunk::evaluated)324	}325326	fn get_cheap(&self, index: usize) -> Option<Val> {327		self.0.get(index).cloned()328	}329	fn is_cheap(&self) -> bool {330		true331	}332}333334/// Inclusive range type335#[derive(Debug, Trace, PartialEq, Eq)]336pub struct RangeArray {337	start: i32,338	end: i32,339}340impl RangeArray {341	pub fn empty() -> Self {342		Self::new_exclusive(0, 0)343	}344	pub fn new_exclusive(start: i32, end: i32) -> Self {345		end.checked_sub(1)346			.map_or_else(Self::empty, |end| Self { start, end })347	}348	pub fn new_inclusive(start: i32, end: i32) -> Self {349		Self { start, end }350	}351	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {352		WithExactSize(353			self.start..=self.end,354			(self.end as usize)355				.wrapping_sub(self.start as usize)356				.wrapping_add(1),357		)358	}359}360361impl ArrayLike for RangeArray {362	fn len(&self) -> usize {363		self.range().len()364	}365	fn is_empty(&self) -> bool {366		self.range().len() == 0367	}368369	fn get(&self, index: usize) -> Result<Option<Val>> {370		Ok(self.get_cheap(index))371	}372373	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {374		self.get_cheap(index).map(Thunk::evaluated)375	}376377	fn get_cheap(&self, index: usize) -> Option<Val> {378		self.range().nth(index).map(|i| Val::Num(i.into()))379	}380	fn is_cheap(&self) -> bool {381		true382	}383}384385#[derive(Debug, Trace)]386pub struct ReverseArray(pub ArrValue);387impl ArrayLike for ReverseArray {388	fn len(&self) -> usize {389		self.0.len()390	}391392	fn get(&self, index: usize) -> Result<Option<Val>> {393		self.0.get(self.0.len() - index - 1)394	}395396	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {397		self.0.get_lazy(self.0.len() - index - 1)398	}399400	fn get_cheap(&self, index: usize) -> Option<Val> {401		self.0.get_cheap(self.0.len() - index - 1)402	}403	fn is_cheap(&self) -> bool {404		self.0.is_cheap()405	}406}407408#[derive(Trace, Clone, Debug)]409pub enum ArrayMapper {410	Plain(NativeFn!((Val) -> Val)),411	WithIndex(NativeFn!((u32, Val) -> Val)),412}413414#[derive(Trace, Debug, Clone)]415pub struct MappedArray {416	inner: ArrValue,417	cached: Cc<RefCell<Vec<ArrayThunk>>>,418	mapper: ArrayMapper,419}420impl MappedArray {421	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {422		let len = inner.len();423		Self {424			inner,425			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),426			mapper,427		}428	}429	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {430		match &self.mapper {431			ArrayMapper::Plain(f) => f.call(value),432			ArrayMapper::WithIndex(f) => f.call(index as u32, value),433		}434	}435}436impl ArrayLike for MappedArray {437	fn len(&self) -> usize {438		self.cached.borrow().len()439	}440441	fn get(&self, index: usize) -> Result<Option<Val>> {442		if index >= self.len() {443			return Ok(None);444		}445		match &self.cached.borrow()[index] {446			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),447			ArrayThunk::Errored(e) => return Err(e.clone()),448			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),449			ArrayThunk::Waiting => {}450		}451452		let ArrayThunk::Waiting =453			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)454		else {455			unreachable!()456		};457458		let val = self459			.inner460			.get(index)461			.transpose()462			.expect("index checked")463			.and_then(|r| self.evaluate(index, r));464465		let new_value = match val {466			Ok(v) => v,467			Err(e) => {468				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());469				return Err(e);470			}471		};472		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());473		Ok(Some(new_value))474	}475	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {476		#[derive(Trace)]477		struct MappedArrayThunk {478			arr: MappedArray,479			index: usize,480		}481		impl ThunkValue for MappedArrayThunk {482			type Output = Val;483484			fn get(&self) -> Result<Self::Output> {485				self.arr.get(self.index).transpose().expect("index checked")486			}487		}488489		if index >= self.len() {490			return None;491		}492		match &self.cached.borrow()[index] {493			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),494			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),495			ArrayThunk::Waiting | ArrayThunk::Pending => {}496		}497498		Some(Thunk::new(MappedArrayThunk {499			arr: self.clone(),500			index,501		}))502	}503504	fn get_cheap(&self, _index: usize) -> Option<Val> {505		None506	}507	fn is_cheap(&self) -> bool {508		false509	}510}511512#[derive(Trace, Debug)]513pub struct RepeatedArray {514	data: ArrValue,515	repeats: usize,516	total_len: usize,517}518impl RepeatedArray {519	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {520		let total_len = data.len().checked_mul(repeats)?;521		Some(Self {522			data,523			repeats,524			total_len,525		})526	}527}528529impl ArrayLike for RepeatedArray {530	fn len(&self) -> usize {531		self.total_len532	}533534	fn get(&self, index: usize) -> Result<Option<Val>> {535		if index > self.total_len {536			return Ok(None);537		}538		self.data.get(index % self.data.len())539	}540541	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {542		if index > self.total_len {543			return None;544		}545		self.data.get_lazy(index % self.data.len())546	}547548	fn get_cheap(&self, index: usize) -> Option<Val> {549		if index > self.total_len {550			return None;551		}552		self.data.get_cheap(index % self.data.len())553	}554	fn is_cheap(&self) -> bool {555		self.data.is_cheap()556	}557}558559#[derive(Trace, Debug)]560pub struct PickObjectValues {561	obj: ObjValue,562	keys: Vec<IStr>,563}564565impl PickObjectValues {566	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {567		Self { obj, keys }568	}569}570571impl ArrayLike for PickObjectValues {572	fn len(&self) -> usize {573		self.keys.len()574	}575576	fn get(&self, index: usize) -> Result<Option<Val>> {577		let Some(key) = self.keys.get(index) else {578			return Ok(None);579		};580		Ok(Some(self.obj.get_or_bail(key.clone())?))581	}582583	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {584		let key = self.keys.get(index)?;585		Some(self.obj.get_lazy_or_bail(key.clone()))586	}587588	fn get_cheap(&self, _index: usize) -> Option<Val> {589		None590	}591592	fn is_cheap(&self) -> bool {593		false594	}595}596597#[derive(Trace, Debug)]598pub struct PickObjectKeyValues {599	obj: ObjValue,600	keys: Vec<IStr>,601}602603impl PickObjectKeyValues {604	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {605		Self { obj, keys }606	}607}608609#[derive(Typed)]610pub struct KeyValue {611	key: IStr,612	value: Thunk<Val>,613}614615impl ArrayLike for PickObjectKeyValues {616	fn len(&self) -> usize {617		self.keys.len()618	}619620	fn get(&self, index: usize) -> Result<Option<Val>> {621		let Some(key) = self.keys.get(index) else {622			return Ok(None);623		};624		Ok(Some(625			KeyValue::into_untyped(KeyValue {626				key: key.clone(),627				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),628			})629			.expect("convertible"),630		))631	}632633	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {634		let key = self.keys.get(index)?;635		// Nothing can fail in the key part, yet value is still636		// lazy-evaluated637		Some(Thunk::evaluated(638			KeyValue::into_untyped(KeyValue {639				key: key.clone(),640				value: self.obj.get_lazy_or_bail(key.clone()),641			})642			.expect("convertible"),643		))644	}645646	fn get_cheap(&self, _index: usize) -> Option<Val> {647		None648	}649650	fn is_cheap(&self) -> bool {651		false652	}653}
after · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::rc::Rc;2use std::{any::Any, cell::RefCell, fmt::Debug, mem::replace};34use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::{IBytes, IStr};6use jrsonnet_parser::{Expr, Spanned};78use super::ArrValue;9use crate::function::NativeFn;10use crate::{11	error::ErrorKind::InfiniteRecursionDetected,12	evaluate,13	typed::{IntoUntyped, Typed},14	val::ThunkValue,15	Context, Error, ObjValue, Result, Thunk, Val,16};1718pub trait ArrayLike: Any + Trace + Debug {19	fn len(&self) -> usize;20	fn is_empty(&self) -> bool {21		self.len() == 022	}23	fn get(&self, index: usize) -> Result<Option<Val>>;24	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;25	fn get_cheap(&self, index: usize) -> Option<Val>;2627	fn is_cheap(&self) -> bool;28}2930#[derive(Debug, Trace)]31pub struct SliceArray {32	pub(crate) inner: ArrValue,33	pub(crate) from: u32,34	pub(crate) to: u32,35	pub(crate) step: u32,36}3738impl SliceArray {39	fn map_idx(&self, index: usize) -> usize {40		self.from as usize + self.step as usize * index41	}42}43impl ArrayLike for SliceArray {44	fn len(&self) -> usize {45		(self.to - self.from).div_ceil(self.step) as usize46	}4748	fn get(&self, index: usize) -> Result<Option<Val>> {49		self.inner.get(self.map_idx(index))50	}5152	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {53		self.inner.get_lazy(self.map_idx(index))54	}5556	fn get_cheap(&self, index: usize) -> Option<Val> {57		self.inner.get_cheap(self.map_idx(index))58	}59	fn is_cheap(&self) -> bool {60		self.inner.is_cheap()61	}62}6364#[derive(Trace, Debug)]65pub struct CharArray(pub Vec<char>);66impl ArrayLike for CharArray {67	fn len(&self) -> usize {68		self.0.len()69	}7071	fn get(&self, index: usize) -> Result<Option<Val>> {72		Ok(self.get_cheap(index))73	}7475	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {76		self.get_cheap(index).map(Thunk::evaluated)77	}7879	fn get_cheap(&self, index: usize) -> Option<Val> {80		self.0.get(index).map(|v| Val::string(*v))81	}82	fn is_cheap(&self) -> bool {83		true84	}85}8687#[derive(Trace, Debug)]88pub struct BytesArray(pub IBytes);89impl ArrayLike for BytesArray {90	fn len(&self) -> usize {91		self.0.len()92	}9394	fn get(&self, index: usize) -> Result<Option<Val>> {95		Ok(self.get_cheap(index))96	}9798	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {99		self.get_cheap(index).map(Thunk::evaluated)100	}101102	fn get_cheap(&self, index: usize) -> Option<Val> {103		self.0.get(index).map(|v| Val::Num((*v).into()))104	}105	fn is_cheap(&self) -> bool {106		true107	}108}109110#[derive(Debug, Trace, Clone)]111enum ArrayThunk {112	Computed(Val),113	Errored(Error),114	Waiting,115	Pending,116}117118#[derive(Debug, Trace, Clone)]119pub struct ExprArray {120	ctx: Context,121	src: Rc<Vec<Spanned<Expr>>>,122	cached: Cc<RefCell<Vec<ArrayThunk>>>,123}124impl ExprArray {125	pub fn new(ctx: Context, src: Rc<Vec<Spanned<Expr>>>) -> Self {126		Self {127			ctx,128			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),129			src,130		}131	}132}133impl ArrayLike for ExprArray {134	fn len(&self) -> usize {135		self.cached.borrow().len()136	}137	fn get(&self, index: usize) -> Result<Option<Val>> {138		if index >= self.len() {139			return Ok(None);140		}141		match &self.cached.borrow()[index] {142			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),143			ArrayThunk::Errored(e) => return Err(e.clone()),144			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),145			ArrayThunk::Waiting => {}146		}147148		let ArrayThunk::Waiting =149			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)150		else {151			unreachable!()152		};153154		let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {155			Ok(v) => v,156			Err(e) => {157				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());158				return Err(e);159			}160		};161		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());162		Ok(Some(new_value))163	}164	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {165		#[derive(Trace)]166		struct ExprArrThunk {167			expr: ExprArray,168			index: usize,169		}170		impl ThunkValue for ExprArrThunk {171			type Output = Val;172173			fn get(&self) -> Result<Self::Output> {174				self.expr175					.get(self.index)176					.transpose()177					.expect("index checked")178			}179		}180181		if index >= self.len() {182			return None;183		}184		match &self.cached.borrow()[index] {185			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),186			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),187			ArrayThunk::Waiting | ArrayThunk::Pending => {}188		}189190		Some(Thunk::new(ExprArrThunk {191			expr: self.clone(),192			index,193		}))194	}195	fn get_cheap(&self, _index: usize) -> Option<Val> {196		None197	}198	fn is_cheap(&self) -> bool {199		false200	}201}202203#[derive(Trace, Debug)]204pub struct ExtendedArray {205	pub a: ArrValue,206	pub b: ArrValue,207	split: usize,208	len: usize,209}210impl ExtendedArray {211	pub fn new(a: ArrValue, b: ArrValue) -> Self {212		let a_len = a.len();213		let b_len = b.len();214		Self {215			a,216			b,217			split: a_len,218			len: a_len.checked_add(b_len).expect("too large array value"),219		}220	}221}222223struct WithExactSize<I>(I, usize);224impl<I, T> Iterator for WithExactSize<I>225where226	I: Iterator<Item = T>,227{228	type Item = T;229230	fn next(&mut self) -> Option<Self::Item> {231		self.0.next()232	}233	fn nth(&mut self, n: usize) -> Option<Self::Item> {234		self.0.nth(n)235	}236	fn size_hint(&self) -> (usize, Option<usize>) {237		(self.1, Some(self.1))238	}239}240impl<I> DoubleEndedIterator for WithExactSize<I>241where242	I: DoubleEndedIterator,243{244	fn next_back(&mut self) -> Option<Self::Item> {245		self.0.next_back()246	}247	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {248		self.0.nth_back(n)249	}250}251impl<I> ExactSizeIterator for WithExactSize<I>252where253	I: Iterator,254{255	fn len(&self) -> usize {256		self.1257	}258}259impl ArrayLike for ExtendedArray {260	fn get(&self, index: usize) -> Result<Option<Val>> {261		if self.split > index {262			self.a.get(index)263		} else {264			self.b.get(index - self.split)265		}266	}267	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {268		if self.split > index {269			self.a.get_lazy(index)270		} else {271			self.b.get_lazy(index - self.split)272		}273	}274275	fn len(&self) -> usize {276		self.len277	}278279	fn get_cheap(&self, index: usize) -> Option<Val> {280		if self.split > index {281			self.a.get_cheap(index)282		} else {283			self.b.get_cheap(index - self.split)284		}285	}286	fn is_cheap(&self) -> bool {287		self.a.is_cheap() && self.b.is_cheap()288	}289}290291#[derive(Trace, Debug)]292pub struct LazyArray(pub Vec<Thunk<Val>>);293impl ArrayLike for LazyArray {294	fn len(&self) -> usize {295		self.0.len()296	}297	fn get(&self, index: usize) -> Result<Option<Val>> {298		let Some(v) = self.0.get(index) else {299			return Ok(None);300		};301		v.evaluate().map(Some)302	}303	fn get_cheap(&self, _index: usize) -> Option<Val> {304		None305	}306	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {307		self.0.get(index).cloned()308	}309	fn is_cheap(&self) -> bool {310		false311	}312}313314#[derive(Trace, Debug)]315pub struct EagerArray(pub Vec<Val>);316impl ArrayLike for EagerArray {317	fn len(&self) -> usize {318		self.0.len()319	}320321	fn get(&self, index: usize) -> Result<Option<Val>> {322		Ok(self.0.get(index).cloned())323	}324325	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {326		self.0.get(index).cloned().map(Thunk::evaluated)327	}328329	fn get_cheap(&self, index: usize) -> Option<Val> {330		self.0.get(index).cloned()331	}332	fn is_cheap(&self) -> bool {333		true334	}335}336337/// Inclusive range type338#[derive(Debug, Trace, PartialEq, Eq)]339pub struct RangeArray {340	start: i32,341	end: i32,342}343impl RangeArray {344	pub fn empty() -> Self {345		Self::new_exclusive(0, 0)346	}347	pub fn new_exclusive(start: i32, end: i32) -> Self {348		end.checked_sub(1)349			.map_or_else(Self::empty, |end| Self { start, end })350	}351	pub fn new_inclusive(start: i32, end: i32) -> Self {352		Self { start, end }353	}354	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {355		WithExactSize(356			self.start..=self.end,357			(self.end as usize)358				.wrapping_sub(self.start as usize)359				.wrapping_add(1),360		)361	}362}363364impl ArrayLike for RangeArray {365	fn len(&self) -> usize {366		self.range().len()367	}368	fn is_empty(&self) -> bool {369		self.range().len() == 0370	}371372	fn get(&self, index: usize) -> Result<Option<Val>> {373		Ok(self.get_cheap(index))374	}375376	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {377		self.get_cheap(index).map(Thunk::evaluated)378	}379380	fn get_cheap(&self, index: usize) -> Option<Val> {381		self.range().nth(index).map(|i| Val::Num(i.into()))382	}383	fn is_cheap(&self) -> bool {384		true385	}386}387388#[derive(Debug, Trace)]389pub struct ReverseArray(pub ArrValue);390impl ArrayLike for ReverseArray {391	fn len(&self) -> usize {392		self.0.len()393	}394395	fn get(&self, index: usize) -> Result<Option<Val>> {396		self.0.get(self.0.len() - index - 1)397	}398399	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {400		self.0.get_lazy(self.0.len() - index - 1)401	}402403	fn get_cheap(&self, index: usize) -> Option<Val> {404		self.0.get_cheap(self.0.len() - index - 1)405	}406	fn is_cheap(&self) -> bool {407		self.0.is_cheap()408	}409}410411#[derive(Trace, Clone, Debug)]412pub enum ArrayMapper {413	Plain(NativeFn!((Val) -> Val)),414	WithIndex(NativeFn!((u32, Val) -> Val)),415}416417#[derive(Trace, Debug, Clone)]418pub struct MappedArray {419	inner: ArrValue,420	cached: Cc<RefCell<Vec<ArrayThunk>>>,421	mapper: ArrayMapper,422}423impl MappedArray {424	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {425		let len = inner.len();426		Self {427			inner,428			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),429			mapper,430		}431	}432	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {433		match &self.mapper {434			ArrayMapper::Plain(f) => f.call(value),435			ArrayMapper::WithIndex(f) => f.call(index as u32, value),436		}437	}438}439impl ArrayLike for MappedArray {440	fn len(&self) -> usize {441		self.cached.borrow().len()442	}443444	fn get(&self, index: usize) -> Result<Option<Val>> {445		if index >= self.len() {446			return Ok(None);447		}448		match &self.cached.borrow()[index] {449			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),450			ArrayThunk::Errored(e) => return Err(e.clone()),451			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),452			ArrayThunk::Waiting => {}453		}454455		let ArrayThunk::Waiting =456			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)457		else {458			unreachable!()459		};460461		let val = self462			.inner463			.get(index)464			.transpose()465			.expect("index checked")466			.and_then(|r| self.evaluate(index, r));467468		let new_value = match val {469			Ok(v) => v,470			Err(e) => {471				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());472				return Err(e);473			}474		};475		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());476		Ok(Some(new_value))477	}478	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {479		#[derive(Trace)]480		struct MappedArrayThunk {481			arr: MappedArray,482			index: usize,483		}484		impl ThunkValue for MappedArrayThunk {485			type Output = Val;486487			fn get(&self) -> Result<Self::Output> {488				self.arr.get(self.index).transpose().expect("index checked")489			}490		}491492		if index >= self.len() {493			return None;494		}495		match &self.cached.borrow()[index] {496			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),497			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),498			ArrayThunk::Waiting | ArrayThunk::Pending => {}499		}500501		Some(Thunk::new(MappedArrayThunk {502			arr: self.clone(),503			index,504		}))505	}506507	fn get_cheap(&self, _index: usize) -> Option<Val> {508		None509	}510	fn is_cheap(&self) -> bool {511		false512	}513}514515#[derive(Trace, Debug)]516pub struct RepeatedArray {517	data: ArrValue,518	repeats: usize,519	total_len: usize,520}521impl RepeatedArray {522	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {523		let total_len = data.len().checked_mul(repeats)?;524		Some(Self {525			data,526			repeats,527			total_len,528		})529	}530}531532impl ArrayLike for RepeatedArray {533	fn len(&self) -> usize {534		self.total_len535	}536537	fn get(&self, index: usize) -> Result<Option<Val>> {538		if index > self.total_len {539			return Ok(None);540		}541		self.data.get(index % self.data.len())542	}543544	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {545		if index > self.total_len {546			return None;547		}548		self.data.get_lazy(index % self.data.len())549	}550551	fn get_cheap(&self, index: usize) -> Option<Val> {552		if index > self.total_len {553			return None;554		}555		self.data.get_cheap(index % self.data.len())556	}557	fn is_cheap(&self) -> bool {558		self.data.is_cheap()559	}560}561562#[derive(Trace, Debug)]563pub struct PickObjectValues {564	obj: ObjValue,565	keys: Vec<IStr>,566}567568impl PickObjectValues {569	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {570		Self { obj, keys }571	}572}573574impl ArrayLike for PickObjectValues {575	fn len(&self) -> usize {576		self.keys.len()577	}578579	fn get(&self, index: usize) -> Result<Option<Val>> {580		let Some(key) = self.keys.get(index) else {581			return Ok(None);582		};583		Ok(Some(self.obj.get_or_bail(key.clone())?))584	}585586	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {587		let key = self.keys.get(index)?;588		Some(self.obj.get_lazy_or_bail(key.clone()))589	}590591	fn get_cheap(&self, _index: usize) -> Option<Val> {592		None593	}594595	fn is_cheap(&self) -> bool {596		false597	}598}599600#[derive(Trace, Debug)]601pub struct PickObjectKeyValues {602	obj: ObjValue,603	keys: Vec<IStr>,604}605606impl PickObjectKeyValues {607	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {608		Self { obj, keys }609	}610}611612#[derive(Typed)]613pub struct KeyValue {614	key: IStr,615	value: Thunk<Val>,616}617618impl ArrayLike for PickObjectKeyValues {619	fn len(&self) -> usize {620		self.keys.len()621	}622623	fn get(&self, index: usize) -> Result<Option<Val>> {624		let Some(key) = self.keys.get(index) else {625			return Ok(None);626		};627		Ok(Some(628			KeyValue::into_untyped(KeyValue {629				key: key.clone(),630				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),631			})632			.expect("convertible"),633		))634	}635636	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {637		let key = self.keys.get(index)?;638		// Nothing can fail in the key part, yet value is still639		// lazy-evaluated640		Some(Thunk::evaluated(641			KeyValue::into_untyped(KeyValue {642				key: key.clone(),643				value: self.obj.get_lazy_or_bail(key.clone()),644			})645			.expect("convertible"),646		))647	}648649	fn get_cheap(&self, _index: usize) -> Option<Val> {650		None651	}652653	fn is_cheap(&self) -> bool {654		false655	}656}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -20,7 +20,7 @@
 	function::{CallLocation, FuncDesc, FuncVal},
 	gc::WithCapacityExt as _,
 	in_frame,
-	typed::Typed,
+	typed::{FromUntyped, IntoUntyped as _, Typed},
 	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
 	with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
 	ResultExt, SupThis, Unbound, Val,
@@ -620,7 +620,7 @@
 			}
 		}
 		Slice(slice) => {
-			fn parse_idx<T: Typed>(
+			fn parse_idx<T: Typed + FromUntyped>(
 				loc: CallLocation<'_>,
 				ctx: Context,
 				expr: Option<&Spanned<Expr>>,
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -8,7 +8,7 @@
 	error::ErrorKind::*,
 	evaluate,
 	stdlib::std_format,
-	typed::Typed,
+	typed::IntoUntyped as _,
 	val::{equals, StrValue},
 	Context, Result, Val,
 };
modifiedcrates/jrsonnet-evaluator/src/function/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/native.rs
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -3,7 +3,11 @@
 use jrsonnet_gcmodule::Trace;
 
 use super::PreparedFuncVal;
-use crate::{bail, function::FuncVal, typed::Typed, CallLocation, Result, Val};
+use crate::{
+	function::FuncVal,
+	typed::{FromUntyped, IntoUntyped, Typed},
+	CallLocation, Result, Val,
+};
 use jrsonnet_types::{ComplexValType, ValType};
 
 #[derive(Debug, Trace, Clone)]
@@ -12,8 +16,8 @@
 	($i:expr; $($gen:ident)*) => {
 		impl<$($gen,)* O> NativeFn<($($gen,)* O,)>
 		where
-			$($gen: Typed,)*
-			O: Typed,
+			$($gen: Typed + IntoUntyped,)*
+			O: Typed + FromUntyped,
 		{
 			#[allow(non_snake_case, clippy::too_many_arguments)]
 			pub fn call(
@@ -22,7 +26,7 @@
 			) -> Result<O> {
 				let val = self.0.call(
 					CallLocation::native(),
-					&[$(Typed::into_lazy_untyped($gen),)*],
+					&[$(IntoUntyped::into_lazy_untyped($gen),)*],
 					&[],
 				)?;
 				O::from_untyped(val)
@@ -30,11 +34,9 @@
 		}
 		impl<$($gen,)* O> Typed for NativeFn<($($gen,)* O,)> {
 			const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
-
-			fn into_untyped(_typed: Self) -> Result<Val> {
-				bail!("can only convert functions from jsonnet to native")
-			}
+		}
 
+		impl<$($gen,)* O> FromUntyped for NativeFn<($($gen,)* O,)> {
 			fn from_untyped(untyped: Val) -> Result<Self> {
 				let func = FuncVal::from_untyped(untyped)?;
 				Ok(Self(
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -9,7 +9,7 @@
 use crate::{
 	bail,
 	error::{format_found, suggest_object_fields, ErrorKind::*},
-	typed::Typed,
+	typed::FromUntyped,
 	Error, ObjValue, Result, Val,
 };
 
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,6 @@
 use std::{collections::BTreeMap, marker::PhantomData, ops::Deref};
 
-use jrsonnet_gcmodule::{Cc, Trace};
+use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::{IBytes, IStr};
 pub use jrsonnet_macros::Typed;
 use jrsonnet_types::{ComplexValType, ValType};
@@ -8,17 +8,17 @@
 use crate::{
 	arr::{ArrValue, BytesArray},
 	bail,
-	function::{FuncDesc, FuncVal},
+	function::FuncVal,
 	typed::CheckType,
 	val::{IndexableVal, NumValue, StrValue, ThunkMapper},
 	ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,
 };
 
 #[derive(Trace)]
-struct FromUntyped<K: Trace>(PhantomData<fn() -> K>);
-impl<K> ThunkMapper<Val> for FromUntyped<K>
+struct ThunkFromUntyped<K: Trace>(PhantomData<fn() -> K>);
+impl<K> ThunkMapper<Val> for ThunkFromUntyped<K>
 where
-	K: Typed + Trace,
+	K: Typed + FromUntyped + Trace,
 {
 	type Output = K;
 
@@ -26,12 +26,29 @@
 		K::from_untyped(from)
 	}
 }
-impl<K: Trace> Default for FromUntyped<K> {
+impl<K: Trace> Default for ThunkFromUntyped<K> {
 	fn default() -> Self {
 		Self(PhantomData)
 	}
 }
+#[derive(Trace)]
+struct ThunkIntoUntyped<K: Trace>(PhantomData<fn() -> K>);
+impl<K> ThunkMapper<K> for ThunkIntoUntyped<K>
+where
+	K: Typed + Trace + IntoUntyped,
+{
+	type Output = Val;
 
+	fn map(self, from: K) -> Result<Self::Output> {
+		K::into_untyped(from)
+	}
+}
+impl<K: Trace> Default for ThunkIntoUntyped<K> {
+	fn default() -> Self {
+		Self(PhantomData)
+	}
+}
+
 pub trait TypedObj: Typed {
 	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
 	fn parse(obj: &ObjValue) -> Result<Self>;
@@ -44,31 +61,41 @@
 
 pub trait Typed: Sized {
 	const TYPE: &'static ComplexValType;
+}
+pub trait IntoUntyped: Typed {
+	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`
+	fn provides_lazy() -> bool {
+		false
+	}
 	fn into_untyped(typed: Self) -> Result<Val>;
 	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {
 		Thunk::from(Self::into_untyped(typed))
 	}
+}
+pub trait IntoUntypedResult: Typed {
+	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result
+	/// This method returns identity in impl Typed for Result, and should not be overriden
+	#[doc(hidden)]
+	fn into_untyped_result(typed: Self) -> Result<Val>;
+}
+impl<T> IntoUntypedResult for T
+where
+	T: IntoUntyped,
+{
+	fn into_untyped_result(typed: Self) -> Result<Val> {
+		T::into_untyped(typed)
+	}
+}
+
+pub trait FromUntyped: Typed {
 	fn from_untyped(untyped: Val) -> Result<Self>;
 	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {
 		Self::from_untyped(lazy.evaluate()?)
 	}
 
-	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`
-	fn provides_lazy() -> bool {
-		false
-	}
-
 	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible
 	fn wants_lazy() -> bool {
 		false
-	}
-
-	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result
-	/// This method returns identity in impl Typed for Result, and should not be overriden
-	#[doc(hidden)]
-	fn into_result(typed: Self) -> Result<Val> {
-		let value = Self::into_untyped(typed)?;
-		Ok(value)
 	}
 }
 
@@ -77,38 +104,30 @@
 	T: Typed + Trace + Clone,
 {
 	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);
+}
 
+impl<T> IntoUntyped for Thunk<T>
+where
+	T: Typed + IntoUntyped + Trace + Clone,
+{
 	fn into_untyped(typed: Self) -> Result<Val> {
 		T::into_untyped(typed.evaluate()?)
-	}
-
-	fn from_untyped(untyped: Val) -> Result<Self> {
-		Self::from_lazy_untyped(Thunk::evaluated(untyped))
 	}
-
 	fn provides_lazy() -> bool {
 		true
 	}
 
 	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {
-		#[derive(Trace)]
-		struct IntoUntyped<K: Trace>(PhantomData<fn() -> K>);
-		impl<K> ThunkMapper<K> for IntoUntyped<K>
-		where
-			K: Typed + Trace,
-		{
-			type Output = Val;
+		inner.map(<ThunkIntoUntyped<T>>::default())
+	}
+}
 
-			fn map(self, from: K) -> Result<Self::Output> {
-				K::into_untyped(from)
-			}
-		}
-		impl<K: Trace> Default for IntoUntyped<K> {
-			fn default() -> Self {
-				Self(PhantomData)
-			}
-		}
-		inner.map(<IntoUntyped<T>>::default())
+impl<T> FromUntyped for Thunk<T>
+where
+	T: Typed + FromUntyped + Trace + Clone,
+{
+	fn from_untyped(untyped: Val) -> Result<Self> {
+		Self::from_lazy_untyped(Thunk::evaluated(untyped))
 	}
 
 	fn wants_lazy() -> bool {
@@ -116,7 +135,7 @@
 	}
 
 	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {
-		Ok(inner.map(<FromUntyped<T>>::default()))
+		Ok(inner.map(<ThunkFromUntyped<T>>::default()))
 	}
 }
 
@@ -128,6 +147,8 @@
 		impl Typed for $ty {
 			const TYPE: &'static ComplexValType =
 				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));
+		}
+		impl FromUntyped for $ty {
 			fn from_untyped(value: Val) -> Result<Self> {
 				<Self as Typed>::TYPE.check(&value)?;
 				match value {
@@ -145,6 +166,8 @@
 					_ => unreachable!(),
 				}
 			}
+		}
+		impl IntoUntyped for $ty {
 			fn into_untyped(value: Self) -> Result<Val> {
 				Ok(Val::Num(value.into()))
 			}
@@ -183,7 +206,9 @@
 					Some(MIN as f64),
 					Some(MAX as f64),
 				);
+		}
 
+		impl<const MIN: $ty, const MAX: $ty> FromUntyped for $name<MIN, MAX> {
 			fn from_untyped(value: Val) -> Result<Self> {
 				<Self as Typed>::TYPE.check(&value)?;
 				match value {
@@ -201,7 +226,9 @@
 					_ => unreachable!(),
 				}
 			}
+		}
 
+		impl<const MIN: $ty, const MAX: $ty> IntoUntyped for $name<MIN, MAX> {
 			#[allow(clippy::cast_lossless)]
 			fn into_untyped(value: Self) -> Result<Val> {
 				Ok(Val::try_num(value.0)?)
@@ -220,11 +247,13 @@
 
 impl Typed for f64 {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);
-
+}
+impl IntoUntyped for f64 {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::try_num(value)?)
 	}
-
+}
+impl FromUntyped for f64 {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -237,11 +266,13 @@
 pub struct PositiveF64(pub f64);
 impl Typed for PositiveF64 {
 	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);
-
+}
+impl IntoUntyped for PositiveF64 {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::try_num(value.0)?)
 	}
-
+}
+impl FromUntyped for PositiveF64 {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -253,11 +284,13 @@
 impl Typed for usize {
 	const TYPE: &'static ComplexValType =
 		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));
-
+}
+impl IntoUntyped for usize {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::try_num(value)?)
 	}
-
+}
+impl FromUntyped for usize {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -276,11 +309,13 @@
 
 impl Typed for IStr {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
-
+}
+impl IntoUntyped for IStr {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::string(value))
 	}
-
+}
+impl FromUntyped for IStr {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -292,11 +327,13 @@
 
 impl Typed for String {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
-
+}
+impl IntoUntyped for String {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::string(value))
 	}
-
+}
+impl FromUntyped for String {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -308,11 +345,13 @@
 
 impl Typed for StrValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
-
+}
+impl IntoUntyped for StrValue {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Str(value))
 	}
-
+}
+impl FromUntyped for StrValue {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -324,11 +363,13 @@
 
 impl Typed for char {
 	const TYPE: &'static ComplexValType = &ComplexValType::Char;
-
+}
+impl IntoUntyped for char {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::string(value))
 	}
-
+}
+impl FromUntyped for char {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -338,12 +379,14 @@
 	}
 }
 
+// TODO: View into vec using ArrayLike?
 impl<T> Typed for Vec<T>
 where
 	T: Typed,
 {
 	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);
-
+}
+impl<T: Typed + IntoUntyped> IntoUntyped for Vec<T> {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Arr(
 			value
@@ -352,7 +395,8 @@
 				.collect::<Result<ArrValue>>()?,
 		))
 	}
-
+}
+impl<T: Typed + FromUntyped> FromUntyped for Vec<T> {
 	fn from_untyped(value: Val) -> Result<Self> {
 		let Val::Arr(a) = value else {
 			<Self as Typed>::TYPE.check(&value)?;
@@ -369,9 +413,19 @@
 	}
 }
 
-impl<K: Typed + Ord, V: Typed> Typed for BTreeMap<K, V> {
+// TODO: View into BTreeMap using ObjectCore?
+impl<K, V> Typed for BTreeMap<K, V>
+where
+	K: Typed + Ord,
+	V: Typed,
+{
 	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);
-
+}
+impl<K, V> IntoUntyped for BTreeMap<K, V>
+where
+	K: Typed + Ord + IntoUntyped,
+	V: Typed + IntoUntyped,
+{
 	fn into_untyped(typed: Self) -> Result<Val> {
 		let mut out = ObjValueBuilder::with_capacity(typed.len());
 		for (k, v) in typed {
@@ -383,7 +437,12 @@
 		}
 		Ok(Val::Obj(out.build()))
 	}
-
+}
+impl<K, V> FromUntyped for BTreeMap<K, V>
+where
+	K: FromUntyped + Ord,
+	V: FromUntyped,
+{
 	fn from_untyped(value: Val) -> Result<Self> {
 		Self::TYPE.check(&value)?;
 		let obj = value.as_obj().expect("typecheck should fail");
@@ -416,32 +475,27 @@
 
 impl Typed for Val {
 	const TYPE: &'static ComplexValType = &ComplexValType::Any;
-
+}
+impl IntoUntyped for Val {
 	fn into_untyped(typed: Self) -> Result<Val> {
 		Ok(typed)
 	}
+}
+impl FromUntyped for Val {
 	fn from_untyped(untyped: Val) -> Result<Self> {
 		Ok(untyped)
 	}
 }
 
-// Hack
 #[doc(hidden)]
 impl<T> Typed for Result<T>
 where
 	T: Typed,
 {
 	const TYPE: &'static ComplexValType = &ComplexValType::Any;
-
-	fn into_untyped(_typed: Self) -> Result<Val> {
-		panic!("do not use this conversion")
-	}
-
-	fn from_untyped(_untyped: Val) -> Result<Self> {
-		panic!("do not use this conversion")
-	}
-
-	fn into_result(typed: Self) -> Result<Val> {
+}
+impl<T: IntoUntyped> IntoUntypedResult for Result<T> {
+	fn into_untyped_result(typed: Self) -> Result<Val> {
 		typed.map(T::into_untyped)?
 	}
 }
@@ -450,11 +504,13 @@
 impl Typed for IBytes {
 	const TYPE: &'static ComplexValType =
 		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));
-
+}
+impl IntoUntyped for IBytes {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Arr(ArrValue::bytes(value)))
 	}
-
+}
+impl FromUntyped for IBytes {
 	fn from_untyped(value: Val) -> Result<Self> {
 		let Val::Arr(a) = &value else {
 			<Self as Typed>::TYPE.check(&value)?;
@@ -477,11 +533,13 @@
 pub struct M1;
 impl Typed for M1 {
 	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));
-
+}
+impl IntoUntyped for M1 {
 	fn into_untyped(_: Self) -> Result<Val> {
 		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))
 	}
-
+}
+impl FromUntyped for M1 {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		Ok(Self)
@@ -499,13 +557,22 @@
 			$($id: Typed,)*
 		{
 			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);
-
+		}
+		impl<$($id),*> IntoUntyped for $name<$($id),*>
+		where
+			$($id: Typed + IntoUntyped,)*
+		{
 			fn into_untyped(value: Self) -> Result<Val> {
 				match value {$(
 					$name::$id(v) => $id::into_untyped(v)
 				),*}
 			}
+		}
 
+		impl<$($id),*> FromUntyped for $name<$($id),*>
+		where
+			$($id: Typed + FromUntyped,)*
+		{
 			fn from_untyped(value: Val) -> Result<Self> {
 				$(
 					if $id::TYPE.check(&value).is_ok() {
@@ -544,11 +611,13 @@
 
 impl Typed for ArrValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);
-
+}
+impl IntoUntyped for ArrValue {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Arr(value))
 	}
-
+}
+impl FromUntyped for ArrValue {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -560,32 +629,17 @@
 
 impl Typed for FuncVal {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
-
+}
+impl IntoUntyped for FuncVal {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Func(value))
 	}
-
-	fn from_untyped(value: Val) -> Result<Self> {
-		<Self as Typed>::TYPE.check(&value)?;
-		match value {
-			Val::Func(a) => Ok(a),
-			_ => unreachable!(),
-		}
-	}
 }
-
-impl Typed for Cc<FuncDesc> {
-	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
-
-	fn into_untyped(value: Self) -> Result<Val> {
-		Ok(Val::Func(FuncVal::Normal(value)))
-	}
-
+impl FromUntyped for FuncVal {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
-			Val::Func(FuncVal::Normal(desc)) => Ok(desc),
-			Val::Func(_) => bail!("expected normal function, not builtin"),
+			Val::Func(a) => Ok(a),
 			_ => unreachable!(),
 		}
 	}
@@ -593,11 +647,13 @@
 
 impl Typed for ObjValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);
-
+}
+impl IntoUntyped for ObjValue {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Obj(value))
 	}
-
+}
+impl FromUntyped for ObjValue {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -609,11 +665,13 @@
 
 impl Typed for bool {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);
-
+}
+impl IntoUntyped for bool {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::Bool(value))
 	}
-
+}
+impl FromUntyped for bool {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
@@ -622,19 +680,22 @@
 		}
 	}
 }
+
 impl Typed for IndexableVal {
 	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[
 		&ComplexValType::Simple(ValType::Arr),
 		&ComplexValType::Simple(ValType::Str),
 	]);
-
+}
+impl IntoUntyped for IndexableVal {
 	fn into_untyped(value: Self) -> Result<Val> {
 		match value {
 			Self::Str(s) => Ok(Val::string(s)),
 			Self::Arr(a) => Ok(Val::Arr(a)),
 		}
 	}
-
+}
+impl FromUntyped for IndexableVal {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		value.into_indexable()
@@ -644,11 +705,13 @@
 pub struct Null;
 impl Typed for Null {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);
-
+}
+impl IntoUntyped for Null {
 	fn into_untyped(_: Self) -> Result<Val> {
 		Ok(Val::Null)
 	}
-
+}
+impl FromUntyped for Null {
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		Ok(Self)
@@ -661,11 +724,19 @@
 {
 	const TYPE: &'static ComplexValType =
 		&ComplexValType::UnionRef(&[&ComplexValType::Simple(ValType::Null), T::TYPE]);
-
+}
+impl<T> IntoUntyped for Option<T>
+where
+	T: Typed + IntoUntyped,
+{
 	fn into_untyped(typed: Self) -> Result<Val> {
 		typed.map_or_else(|| Ok(Val::Null), |v| T::into_untyped(v))
 	}
-
+}
+impl<T> FromUntyped for Option<T>
+where
+	T: Typed + FromUntyped,
+{
 	fn from_untyped(untyped: Val) -> Result<Self> {
 		if matches!(untyped, Val::Null) {
 			Ok(None)
@@ -677,11 +748,13 @@
 
 impl Typed for NumValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);
-
+}
+impl IntoUntyped for NumValue {
 	fn into_untyped(typed: Self) -> Result<Val> {
 		Ok(Val::Num(typed))
 	}
-
+}
+impl FromUntyped for NumValue {
 	fn from_untyped(untyped: Val) -> Result<Self> {
 		Self::TYPE.check(&untyped)?;
 		match untyped {
modifiedcrates/jrsonnet-interner/src/names.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/names.rs
+++ b/crates/jrsonnet-interner/src/names.rs
@@ -0,0 +1 @@
+
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -8,15 +8,15 @@
 	parse_macro_input,
 	punctuated::Punctuated,
 	spanned::Spanned,
-	token::{self, Comma},
+	token::Comma,
 	Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
 	LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
 };
 
 use self::typed::derive_typed_inner;
 
+mod names;
 mod typed;
-mod names;
 
 fn try_parse_attr_noargs<I>(attrs: &[Attribute], ident: I) -> Result<bool>
 where
@@ -202,7 +202,7 @@
 			_ => {}
 		}
 
-		let (optionality, ty) = if try_parse_attr_noargs(&mut arg.attrs, "default")? {
+		let (optionality, ty) = if try_parse_attr_noargs(&arg.attrs, "default")? {
 			remove_attr(&mut arg.attrs, "default");
 			(Optionality::TypeDefault, ty.clone())
 		} else if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {
@@ -322,7 +322,7 @@
 				let name = name.as_ref().map_or("<unnamed>", String::as_str);
 				let eval = quote! {jrsonnet_evaluator::in_description_frame(
 					|| format!("argument <{}> evaluation", #name),
-					|| <#ty>::from_untyped(value.evaluate()?),
+					|| <#ty as FromUntyped>::from_untyped(value.evaluate()?),
 				)?};
 				let value = match optionality {
 					Optionality::Required => quote! {{
@@ -411,7 +411,7 @@
 			use ::jrsonnet_evaluator::{
 				State, Val,
 				function::{builtin::{Builtin, StaticBuiltin}, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},
-				Result, Context, typed::Typed,
+				Result, Context, typed::{Typed, FromUntyped, IntoUntypedResult},
 				parser::Span, params, Thunk,
 			};
 			params!(
@@ -432,7 +432,7 @@
 				#[allow(unused_variables)]
 				fn call(&self, location: CallLocation<'_>, parsed: &[Option<Thunk<Val>>]) -> Result<Val> {
 					let result: #result = #name(#(#pass)*);
-					<_ as Typed>::into_result(result)
+					<_ as IntoUntypedResult>::into_untyped_result(result)
 				}
 				fn as_any(&self) -> &dyn ::std::any::Any {
 					self
modifiedcrates/jrsonnet-macros/src/names.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/names.rs
+++ b/crates/jrsonnet-macros/src/names.rs
@@ -1,6 +1,5 @@
 use proc_macro2::TokenStream;
 use quote::quote;
-use std::cell::RefCell;
 
 #[derive(Default)]
 pub struct Names {
modifiedcrates/jrsonnet-macros/src/typed.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/typed.rs
+++ b/crates/jrsonnet-macros/src/typed.rs
@@ -178,7 +178,7 @@
 					None
 				};
 
-				__value.map(<#ty as Typed>::from_untyped).transpose()?
+				__value.map(<#ty as FromUntyped>::from_untyped).transpose()?
 			},
 		}
 	}
@@ -219,7 +219,7 @@
 					return Err(ErrorKind::NoSuchField(__names[#error_text].clone(), vec![]).into());
 				};
 
-				<#ty as Typed>::from_untyped(__value)?
+				<#ty as FromUntyped>::from_untyped(__value)?
 			},
 		}
 	}
@@ -258,14 +258,14 @@
 						out.field(__names[#name].clone())
 							#hide
 							#add
-							.try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;
+							.try_thunk(<#ty as IntoUntyped>::into_lazy_untyped(value))?;
 					}
 				} else {
 					quote! {
 						out.field(__names[#name].clone())
 							#hide
 							#add
-							.try_value(<#ty as Typed>::into_untyped(value)?)?;
+							.try_value(<#ty as IntoUntyped>::into_untyped(value)?)?;
 					}
 				};
 				if self.is_option {
@@ -313,18 +313,21 @@
 				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[
 					#(#fields,)*
 				]);
+			}
 
+			impl #impl_generics FromUntyped for #ident #ty_generics #where_clause {
 				fn from_untyped(value: Val) -> JrResult<Self> {
 					let obj = value.as_obj().expect("shape is correct");
 					Self::parse(&obj)
 				}
+			}
 
+			impl #impl_generics IntoUntyped for #ident #ty_generics #where_clause {
 				fn into_untyped(value: Self) -> JrResult<Val> {
 					let mut out = ObjValueBuilder::with_capacity(#capacity);
 					value.serialize(&mut out)?;
 					Ok(Val::Obj(out.build()))
 				}
-
 			}
 		}
 	};
@@ -344,7 +347,7 @@
 	Ok(quote! {
 		const _: () = {
 			use ::jrsonnet_evaluator::{
-				typed::{ComplexValType, Typed, TypedObj, CheckType},
+				typed::{ComplexValType, Typed, IntoUntyped, FromUntyped, TypedObj, CheckType},
 				Val, State,
 				error::{ErrorKind, Result as JrResult},
 				ObjValueBuilder, ObjValue, IStr,
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -4,7 +4,7 @@
 	bail,
 	function::{builtin, FuncVal, NativeFn},
 	runtime_error,
-	typed::{BoundedI32, BoundedUsize, Either2, Typed},
+	typed::{BoundedI32, BoundedUsize, Either2, FromUntyped},
 	val::{equals, ArrValue, IndexableVal},
 	Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,
 };
@@ -24,7 +24,7 @@
 	}
 	func.evaluate_trivial().map_or_else(
 		// TODO: Different mapped array impl avoiding allocating unnecessary vals
-		|| Ok(ArrValue::range_exclusive(0, *sz).map(Typed::from_untyped(Val::Func(func))?)),
+		|| Ok(ArrValue::range_exclusive(0, *sz).map(FromUntyped::from_untyped(Val::Func(func))?)),
 		|trivial| {
 			let mut out = Vec::with_capacity(*sz as usize);
 			for _ in 0..*sz {
modifiedcrates/jrsonnet-stdlib/src/keyf.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/keyf.rs
+++ b/crates/jrsonnet-stdlib/src/keyf.rs
@@ -1,5 +1,5 @@
 use jrsonnet_evaluator::function::{CallLocation, FuncVal, PreparedFuncVal};
-use jrsonnet_evaluator::typed::{ComplexValType, Typed, ValType};
+use jrsonnet_evaluator::typed::{ComplexValType, FromUntyped, Typed, ValType};
 use jrsonnet_evaluator::{Error, Result, Thunk, Val};
 
 #[derive(Default, Clone)]
@@ -31,11 +31,9 @@
 
 impl Typed for KeyF {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
+}
+impl FromUntyped for KeyF {
 	fn from_untyped(untyped: Val) -> Result<Self> {
 		FuncVal::from_untyped(untyped).map(Self::new)
-	}
-
-	fn into_untyped(_typed: Self) -> Result<Val> {
-		unreachable!("unused, todo: port split of Typed trait from #193")
 	}
 }
modifiedcrates/jrsonnet-stdlib/src/manifest/ini.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/ini.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/ini.rs
@@ -2,7 +2,7 @@
 
 use jrsonnet_evaluator::{
 	manifest::{ManifestFormat, ToStringFormat},
-	typed::Typed,
+	typed::{FromUntyped, Typed},
 	ObjValue, Result, ResultExt, Val,
 };
 use jrsonnet_parser::IStr;
modifiedcrates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/xml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -1,7 +1,7 @@
 use jrsonnet_evaluator::{
 	bail, in_description_frame,
 	manifest::{ManifestFormat, ToStringFormat},
-	typed::{ComplexValType, Either2, Typed, ValType},
+	typed::{ComplexValType, Either2, FromUntyped, Typed, ValType},
 	val::ArrValue,
 	Either, ObjValue, Result, ResultExt, Val,
 };
@@ -32,11 +32,8 @@
 }
 impl Typed for JSONMLValue {
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);
-
-	fn into_untyped(_typed: Self) -> Result<Val> {
-		unreachable!("not used, reserved for parseXML?")
-	}
-
+}
+impl FromUntyped for JSONMLValue {
 	fn from_untyped(untyped: Val) -> Result<Self> {
 		let val = <Either![ArrValue, String]>::from_untyped(untyped)
 			.description("parsing JSONML value (an array or string)")?;
@@ -73,7 +70,7 @@
 			children: in_description_frame(
 				|| "parsing children".to_owned(),
 				|| {
-					Typed::from_untyped(Val::Arr(arr.slice(
+					FromUntyped::from_untyped(Val::Arr(arr.slice(
 						Some(if has_attrs { 2 } else { 1 }),
 						None,
 						None,
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -4,7 +4,7 @@
 	bail,
 	error::{ErrorKind::*, Result},
 	function::builtin,
-	typed::{Either2, Typed, M1},
+	typed::{Either2, FromUntyped, M1},
 	val::{ArrValue, IndexableVal},
 	Either, IStr, Val,
 };
modifiedtests/tests/builtin.rsdiffbeforeafterboth
--- a/tests/tests/builtin.rs
+++ b/tests/tests/builtin.rs
@@ -5,7 +5,7 @@
 	function::{CallLocation, FuncVal, builtin, builtin::Builtin},
 	parser::Source,
 	trace::PathResolver,
-	typed::Typed,
+	typed::FromUntyped,
 };
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_stdlib::ContextInitializer as StdContextInitializer;
modifiedtests/tests/typed_obj.rsdiffbeforeafterboth
--- a/tests/tests/typed_obj.rs
+++ b/tests/tests/typed_obj.rs
@@ -2,7 +2,11 @@
 
 use std::fmt::Debug;
 
-use jrsonnet_evaluator::{Result, State, trace::PathResolver, typed::Typed};
+use jrsonnet_evaluator::{
+	Result, State,
+	trace::PathResolver,
+	typed::{FromUntyped, IntoUntyped, Typed},
+};
 use jrsonnet_stdlib::ContextInitializer;
 
 #[derive(Clone, Typed, PartialEq, Debug)]
@@ -11,7 +15,9 @@
 	b: u16,
 }
 
-fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T) -> Result<()> {
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone + FromUntyped + IntoUntyped>(
+	value: T,
+) -> Result<()> {
 	let untyped = T::into_untyped(value.clone())?;
 	let value2 = T::from_untyped(untyped.clone())?;
 	ensure_eq!(value, value2);