git.delta.rocks / jrsonnet / refs/commits / 953b3d0f77fd

difftreelog

doc: clarify string pooling error

lnlslqrmYaroslav Bolyukin2026-04-04parent: #69c0811.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/typed/conversions.rs
1use std::{any::TypeId, collections::BTreeMap, marker::PhantomData, mem::transmute, ops::Deref};23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_types::{ComplexValType, ValType};67use crate::{8	arr::{ArrValue, BytesArray},9	bail,10	function::FuncVal,11	typed::CheckType,12	val::{IndexableVal, NumValue, StrValue, ThunkMapper},13	ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,14};1516#[doc(hidden)]17pub mod __typed_macro_prelude {18	pub use ::jrsonnet_evaluator::{19		error::{ErrorKind, Result as JrResult},20		typed::{21			CheckType, ComplexValType, FromUntyped, IntoUntyped, ParseTypedObj, SerializeTypedObj,22			Typed,23		},24		IStr, ObjValue, ObjValueBuilder, State, Val,25	};26}27pub use jrsonnet_macros::{FromUntyped, IntoUntyped, Typed};2829#[derive(Trace)]30struct ThunkFromUntyped<K: Trace>(PhantomData<fn() -> K>);31impl<K> ThunkMapper<Val> for ThunkFromUntyped<K>32where33	K: Typed + FromUntyped + Trace,34{35	type Output = K;3637	fn map(self, from: Val) -> Result<Self::Output> {38		K::from_untyped(from)39	}40}41impl<K: Trace> Default for ThunkFromUntyped<K> {42	fn default() -> Self {43		Self(PhantomData)44	}45}46#[derive(Trace)]47struct ThunkIntoUntyped<K: Trace>(PhantomData<fn() -> K>);48impl<K> ThunkMapper<K> for ThunkIntoUntyped<K>49where50	K: Typed + Trace + IntoUntyped,51{52	type Output = Val;5354	fn map(self, from: K) -> Result<Self::Output> {55		K::into_untyped(from)56	}57}58impl<K: Trace> Default for ThunkIntoUntyped<K> {59	fn default() -> Self {60		Self(PhantomData)61	}62}6364#[diagnostic::on_unimplemented(65	note = "don't implement `ParseTypedObj` directly, it is automatically provided by `FromUntyped` derive"66)]67pub trait ParseTypedObj: Typed {68	fn parse(obj: &ObjValue) -> Result<Self>;69}7071#[diagnostic::on_unimplemented(72	note = "don't implement `SerializeTypedObj` directly, it is automatically provided by `IntoUntyped` derive"73)]74pub trait SerializeTypedObj: Typed {75	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;76	fn into_object(self) -> Result<ObjValue> {77		let mut builder = ObjValueBuilder::new();78		self.serialize(&mut builder)?;79		Ok(builder.build())80	}81}8283pub trait Typed: Sized {84	const TYPE: &'static ComplexValType;85}86pub trait IntoUntyped: Typed {87	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`88	fn provides_lazy() -> bool {89		false90	}91	fn into_untyped(typed: Self) -> Result<Val>;92	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {93		Thunk::from(Self::into_untyped(typed))94	}95}96pub trait IntoUntypedResult: Typed {97	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result98	/// This method returns identity in impl Typed for Result, and should not be overriden99	#[doc(hidden)]100	fn into_untyped_result(typed: Self) -> Result<Val>;101}102impl<T> IntoUntypedResult for T103where104	T: IntoUntyped,105{106	fn into_untyped_result(typed: Self) -> Result<Val> {107		T::into_untyped(typed)108	}109}110111pub trait FromUntyped: Typed {112	fn from_untyped(untyped: Val) -> Result<Self>;113	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {114		Self::from_untyped(lazy.evaluate()?)115	}116117	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible118	fn wants_lazy() -> bool {119		false120	}121}122123impl<T> Typed for Thunk<T>124where125	T: Typed + Trace + Clone,126{127	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);128}129130fn try_cast_thunk_val<T: 'static>(typed: Thunk<T>) -> Result<Thunk<Val>, Thunk<T>> {131	if TypeId::of::<T>() == TypeId::of::<Val>() {132		// SAFETY: We know that it is exactly the same type, and we discard the original after that133		// to avoid double-free.134		let transmuted = unsafe { transmute::<Thunk<T>, Thunk<Val>>(typed) };135		Ok(transmuted)136	} else {137		Err(typed)138	}139}140impl<T> IntoUntyped for Thunk<T>141where142	T: IntoUntyped + Trace + Clone,143{144	fn into_untyped(typed: Self) -> Result<Val> {145		T::into_untyped(typed.evaluate()?)146	}147	fn provides_lazy() -> bool {148		true149	}150151	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {152		// Avoid lazy mapping153		let inner = match try_cast_thunk_val(inner) {154			Ok(v) => return v,155			Err(e) => e,156		};157		inner.map(<ThunkIntoUntyped<T>>::default())158	}159}160161fn try_cast_thunk_t<T: 'static>(typed: Thunk<Val>) -> Result<Thunk<T>, Thunk<Val>> {162	if TypeId::of::<T>() == TypeId::of::<Val>() {163		// SAFETY: We know that it is exactly the same type, and we discard the original after that164		// to avoid double-free.165		let transmuted = unsafe { transmute::<Thunk<Val>, Thunk<T>>(typed) };166		Ok(transmuted)167	} else {168		Err(typed)169	}170}171impl<T> FromUntyped for Thunk<T>172where173	T: Typed + FromUntyped + Trace + Clone,174{175	fn from_untyped(untyped: Val) -> Result<Self> {176		Self::from_lazy_untyped(Thunk::evaluated(untyped))177	}178179	fn wants_lazy() -> bool {180		true181	}182183	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {184		// Avoid lazy mapping185		let inner = match try_cast_thunk_t(inner) {186			Ok(v) => return Ok(v),187			Err(e) => e,188		};189		Ok(inner.map(<ThunkFromUntyped<T>>::default()))190	}191}192193#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]194pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;195#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]196pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;197198macro_rules! impl_int {199	($($ty:ty)*) => {$(200		impl Typed for $ty {201			const TYPE: &'static ComplexValType =202				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));203		}204		impl FromUntyped for $ty {205			fn from_untyped(value: Val) -> Result<Self> {206				<Self as Typed>::TYPE.check(&value)?;207				match value {208					Val::Num(n) => {209						let n = n.get();210						#[allow(clippy::float_cmp)]211						if n.trunc() != n {212							bail!(213								"cannot convert number with fractional part to {}",214								stringify!($ty)215							)216						}217						#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation, reason = "checked by TYPE")]218						Ok(n as Self)219					}220					_ => unreachable!(),221				}222			}223		}224		impl IntoUntyped for $ty {225			fn into_untyped(value: Self) -> Result<Val> {226				Ok(Val::Num(value.into()))227			}228		}229	)*};230}231232impl_int!(i8 u8 i16 u16 i32 u32);233234macro_rules! impl_bounded_int {235	($($name:ident = $ty:ty)*) => {$(236		#[derive(Clone, Copy)]237		#[allow(clippy::cast_possible_truncation, reason = "overflow is api misuse")]238		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);239		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {240			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {241				if value >= MIN && value <= MAX {242					Some(Self(value))243				} else {244					None245				}246			}247			pub const fn value(self) -> $ty {248				self.0249			}250		}251		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {252			type Target = $ty;253			fn deref(&self) -> &Self::Target {254				&self.0255			}256		}257258		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {259			#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, reason = "overflow is api misuse")]260			const TYPE: &'static ComplexValType =261				&ComplexValType::BoundedNumber(262					Some(MIN as f64),263					Some(MAX as f64),264				);265		}266267		impl<const MIN: $ty, const MAX: $ty> FromUntyped for $name<MIN, MAX> {268			fn from_untyped(value: Val) -> Result<Self> {269				<Self as Typed>::TYPE.check(&value)?;270				match value {271					Val::Num(n) => {272						let n = n.get();273						#[allow(clippy::float_cmp)]274						if n.trunc() != n {275							bail!(276								"cannot convert number with fractional part to {}",277								stringify!($ty)278							)279						}280						#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "overflow is api misuse, the range is checked by TYPE")]281						Ok(Self(n as $ty))282					}283					_ => unreachable!(),284				}285			}286		}287288		impl<const MIN: $ty, const MAX: $ty> IntoUntyped for $name<MIN, MAX> {289			#[allow(clippy::cast_lossless)]290			fn into_untyped(value: Self) -> Result<Val> {291				Ok(Val::try_num(value.0)?)292			}293		}294	)*};295}296297impl_bounded_int!(298	BoundedI8 = i8299	BoundedI16 = i16300	BoundedI32 = i32301	BoundedI64 = i64302	BoundedUsize = usize303);304305impl Typed for f64 {306	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);307}308impl IntoUntyped for f64 {309	fn into_untyped(value: Self) -> Result<Val> {310		Ok(Val::try_num(value)?)311	}312}313impl FromUntyped for f64 {314	fn from_untyped(value: Val) -> Result<Self> {315		<Self as Typed>::TYPE.check(&value)?;316		match value {317			Val::Num(n) => Ok(n.get()),318			_ => unreachable!(),319		}320	}321}322323pub struct PositiveF64(pub f64);324impl Typed for PositiveF64 {325	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);326}327impl IntoUntyped for PositiveF64 {328	fn into_untyped(value: Self) -> Result<Val> {329		Ok(Val::try_num(value.0)?)330	}331}332impl FromUntyped for PositiveF64 {333	fn from_untyped(value: Val) -> Result<Self> {334		<Self as Typed>::TYPE.check(&value)?;335		match value {336			Val::Num(n) => Ok(Self(n.get())),337			_ => unreachable!(),338		}339	}340}341impl Typed for usize {342	const TYPE: &'static ComplexValType =343		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));344}345impl IntoUntyped for usize {346	fn into_untyped(value: Self) -> Result<Val> {347		Ok(Val::try_num(value)?)348	}349}350impl FromUntyped for usize {351	fn from_untyped(value: Val) -> Result<Self> {352		<Self as Typed>::TYPE.check(&value)?;353		match value {354			Val::Num(n) => {355				let n = n.get();356				#[allow(clippy::float_cmp)]357				if n.trunc() != n {358					bail!("cannot convert number with fractional part to usize")359				}360				#[allow(361					clippy::cast_possible_truncation,362					clippy::cast_sign_loss,363					reason = "the range is checked by TYPE"364				)]365				Ok(n as Self)366			}367			_ => unreachable!(),368		}369	}370}371372impl Typed for IStr {373	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);374}375impl IntoUntyped for IStr {376	fn into_untyped(value: Self) -> Result<Val> {377		Ok(Val::string(value))378	}379}380impl FromUntyped for IStr {381	fn from_untyped(value: Val) -> Result<Self> {382		<Self as Typed>::TYPE.check(&value)?;383		match value {384			Val::Str(s) => Ok(s.into_flat()),385			_ => unreachable!(),386		}387	}388}389390impl Typed for String {391	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);392}393impl IntoUntyped for String {394	fn into_untyped(value: Self) -> Result<Val> {395		Ok(Val::string(value))396	}397}398impl FromUntyped for String {399	fn from_untyped(value: Val) -> Result<Self> {400		<Self as Typed>::TYPE.check(&value)?;401		match value {402			Val::Str(s) => Ok(s.to_string()),403			_ => unreachable!(),404		}405	}406}407408impl Typed for StrValue {409	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);410}411impl IntoUntyped for StrValue {412	fn into_untyped(value: Self) -> Result<Val> {413		Ok(Val::Str(value))414	}415}416impl FromUntyped for StrValue {417	fn from_untyped(value: Val) -> Result<Self> {418		<Self as Typed>::TYPE.check(&value)?;419		match value {420			Val::Str(s) => Ok(s),421			_ => unreachable!(),422		}423	}424}425426impl Typed for char {427	const TYPE: &'static ComplexValType = &ComplexValType::Char;428}429impl IntoUntyped for char {430	fn into_untyped(value: Self) -> Result<Val> {431		Ok(Val::string(value))432	}433}434impl FromUntyped for char {435	fn from_untyped(value: Val) -> Result<Self> {436		<Self as Typed>::TYPE.check(&value)?;437		match value {438			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),439			_ => unreachable!(),440		}441	}442}443444// TODO: View into vec using ArrayLike?445impl<T> Typed for Vec<T>446where447	T: Typed,448{449	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);450}451impl<T: Typed + IntoUntyped> IntoUntyped for Vec<T> {452	fn into_untyped(value: Self) -> Result<Val> {453		Ok(Val::Arr(454			value455				.into_iter()456				.map(T::into_untyped)457				.collect::<Result<ArrValue>>()?,458		))459	}460}461impl<T: Typed + FromUntyped> FromUntyped for Vec<T> {462	fn from_untyped(value: Val) -> Result<Self> {463		let Val::Arr(a) = value else {464			<Self as Typed>::TYPE.check(&value)?;465			unreachable!("typecheck should fail")466		};467		a.iter()468			.enumerate()469			.map(|(i, r)| {470				r.and_then(|t| {471					T::from_untyped(t).with_description(|| format!("parsing elem <{i}>"))472				})473			})474			.collect::<Result<Self>>()475	}476}477478// TODO: View into BTreeMap using ObjectCore?479impl<K, V> Typed for BTreeMap<K, V>480where481	K: Typed + Ord,482	V: Typed,483{484	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);485}486impl<K, V> IntoUntyped for BTreeMap<K, V>487where488	K: Typed + Ord + IntoUntyped,489	V: Typed + IntoUntyped,490{491	fn into_untyped(typed: Self) -> Result<Val> {492		let mut out = ObjValueBuilder::with_capacity(typed.len());493		for (k, v) in typed {494			let Some(key) = K::into_untyped(k)?.as_str() else {495				bail!("map key should serialize to string");496			};497			let value = V::into_untyped(v)?;498			out.field(key).value(value);499		}500		Ok(Val::Obj(out.build()))501	}502}503impl<K, V> FromUntyped for BTreeMap<K, V>504where505	K: FromUntyped + Ord,506	V: FromUntyped,507{508	fn from_untyped(value: Val) -> Result<Self> {509		Self::TYPE.check(&value)?;510		let obj = value.as_obj().expect("typecheck should fail");511512		let mut out = Self::new();513		if V::wants_lazy() {514			for key in obj.fields_ex(515				false,516				#[cfg(feature = "exp-preserve-order")]517				false,518			) {519				let value = obj.get_lazy(key.clone()).expect("field exists");520				let value = V::from_lazy_untyped(value)?;521				let key = K::from_untyped(Val::Str(key.into()))?;522				let _ = out.insert(key, value);523			}524		} else {525			for (key, value) in obj.iter(526				#[cfg(feature = "exp-preserve-order")]527				false,528			) {529				let key = K::from_untyped(Val::Str(key.into()))?;530				let value = V::from_untyped(value?)?;531				let _ = out.insert(key, value);532			}533		}534		Ok(out)535	}536}537538impl Typed for Val {539	const TYPE: &'static ComplexValType = &ComplexValType::Any;540}541impl IntoUntyped for Val {542	fn into_untyped(typed: Self) -> Result<Val> {543		Ok(typed)544	}545}546impl FromUntyped for Val {547	fn from_untyped(untyped: Val) -> Result<Self> {548		Ok(untyped)549	}550}551552#[doc(hidden)]553impl<T> Typed for Result<T>554where555	T: Typed,556{557	const TYPE: &'static ComplexValType = &ComplexValType::Any;558}559impl<T: IntoUntyped> IntoUntypedResult for Result<T> {560	fn into_untyped_result(typed: Self) -> Result<Val> {561		typed.map(T::into_untyped)?562	}563}564565/// Specialization566impl Typed for IBytes {567	const TYPE: &'static ComplexValType =568		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));569}570impl IntoUntyped for IBytes {571	fn into_untyped(value: Self) -> Result<Val> {572		Ok(Val::Arr(ArrValue::bytes(value)))573	}574}575impl FromUntyped for IBytes {576	fn from_untyped(value: Val) -> Result<Self> {577		let Val::Arr(a) = &value else {578			<Self as Typed>::TYPE.check(&value)?;579			unreachable!()580		};581		if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {582			return Ok(bytes.0.as_slice().into());583		}584		<Self as Typed>::TYPE.check(&value)?;585		// Any::downcast_ref::<ByteArray>(&a);586		let mut out = Vec::with_capacity(a.len());587		for e in a.iter() {588			let r = e?;589			out.push(u8::from_untyped(r)?);590		}591		Ok(out.as_slice().into())592	}593}594595pub struct M1;596impl Typed for M1 {597	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));598}599impl IntoUntyped for M1 {600	fn into_untyped(_: Self) -> Result<Val> {601		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))602	}603}604impl FromUntyped for M1 {605	fn from_untyped(value: Val) -> Result<Self> {606		<Self as Typed>::TYPE.check(&value)?;607		Ok(Self)608	}609}610611macro_rules! decl_either {612	($($name: ident, $($id: ident)*);*) => {$(613		#[derive(Clone)]614		pub enum $name<$($id),*> {615			$($id($id)),*616		}617		impl<$($id),*> Typed for $name<$($id),*>618		where619			$($id: Typed,)*620		{621			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);622		}623		impl<$($id),*> IntoUntyped for $name<$($id),*>624		where625			$($id: Typed + IntoUntyped,)*626		{627			fn into_untyped(value: Self) -> Result<Val> {628				match value {$(629					$name::$id(v) => $id::into_untyped(v)630				),*}631			}632		}633634		impl<$($id),*> FromUntyped for $name<$($id),*>635		where636			$($id: Typed + FromUntyped,)*637		{638			fn from_untyped(value: Val) -> Result<Self> {639				$(640					if $id::TYPE.check(&value).is_ok() {641						$id::from_untyped(value).map(Self::$id)642					} else643				)* {644					<Self as Typed>::TYPE.check(&value)?;645					unreachable!()646				}647			}648		}649	)*}650}651decl_either!(652	Either1, A;653	Either2, A B;654	Either3, A B C;655	Either4, A B C D;656	Either5, A B C D E;657	Either6, A B C D E F;658	Either7, A B C D E F G659);660#[macro_export]661macro_rules! Either {662	($a:ty) => {$crate::typed::Either1<$a>};663	($a:ty, $b:ty) => {$crate::typed::Either2<$a, $b>};664	($a:ty, $b:ty, $c:ty) => {$crate::typed::Either3<$a, $b, $c>};665	($a:ty, $b:ty, $c:ty, $d:ty) => {$crate::typed::Either4<$a, $b, $c, $d>};666	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {$crate::typed::Either5<$a, $b, $c, $d, $e>};667	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {$crate::typed::Either6<$a, $b, $c, $d, $e, $f>};668	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {$crate::typed::Either7<$a, $b, $c, $d, $e, $f, $g>};669}670pub use Either;671672pub type MyType = Either![u32, f64, String];673674impl Typed for ArrValue {675	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);676}677impl IntoUntyped for ArrValue {678	fn into_untyped(value: Self) -> Result<Val> {679		Ok(Val::Arr(value))680	}681}682impl FromUntyped for ArrValue {683	fn from_untyped(value: Val) -> Result<Self> {684		<Self as Typed>::TYPE.check(&value)?;685		match value {686			Val::Arr(a) => Ok(a),687			_ => unreachable!(),688		}689	}690}691692impl Typed for FuncVal {693	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);694}695impl IntoUntyped for FuncVal {696	fn into_untyped(value: Self) -> Result<Val> {697		Ok(Val::Func(value))698	}699}700impl FromUntyped for FuncVal {701	fn from_untyped(value: Val) -> Result<Self> {702		<Self as Typed>::TYPE.check(&value)?;703		match value {704			Val::Func(a) => Ok(a),705			_ => unreachable!(),706		}707	}708}709710impl Typed for ObjValue {711	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);712}713impl IntoUntyped for ObjValue {714	fn into_untyped(value: Self) -> Result<Val> {715		Ok(Val::Obj(value))716	}717}718impl FromUntyped for ObjValue {719	fn from_untyped(value: Val) -> Result<Self> {720		<Self as Typed>::TYPE.check(&value)?;721		match value {722			Val::Obj(a) => Ok(a),723			_ => unreachable!(),724		}725	}726}727728impl Typed for bool {729	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);730}731impl IntoUntyped for bool {732	fn into_untyped(value: Self) -> Result<Val> {733		Ok(Val::Bool(value))734	}735}736impl FromUntyped for bool {737	fn from_untyped(value: Val) -> Result<Self> {738		<Self as Typed>::TYPE.check(&value)?;739		match value {740			Val::Bool(a) => Ok(a),741			_ => unreachable!(),742		}743	}744}745746impl Typed for IndexableVal {747	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[748		&ComplexValType::Simple(ValType::Arr),749		&ComplexValType::Simple(ValType::Str),750	]);751}752impl IntoUntyped for IndexableVal {753	fn into_untyped(value: Self) -> Result<Val> {754		match value {755			Self::Str(s) => Ok(Val::string(s)),756			Self::Arr(a) => Ok(Val::Arr(a)),757		}758	}759}760impl FromUntyped for IndexableVal {761	fn from_untyped(value: Val) -> Result<Self> {762		<Self as Typed>::TYPE.check(&value)?;763		value.into_indexable()764	}765}766767pub struct Null;768impl Typed for Null {769	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);770}771impl IntoUntyped for Null {772	fn into_untyped(_: Self) -> Result<Val> {773		Ok(Val::Null)774	}775}776impl FromUntyped for Null {777	fn from_untyped(value: Val) -> Result<Self> {778		<Self as Typed>::TYPE.check(&value)?;779		Ok(Self)780	}781}782783impl<T> Typed for Option<T>784where785	T: Typed,786{787	const TYPE: &'static ComplexValType =788		&ComplexValType::UnionRef(&[&ComplexValType::Simple(ValType::Null), T::TYPE]);789}790impl<T> IntoUntyped for Option<T>791where792	T: Typed + IntoUntyped,793{794	fn into_untyped(typed: Self) -> Result<Val> {795		typed.map_or_else(|| Ok(Val::Null), |v| T::into_untyped(v))796	}797}798impl<T> FromUntyped for Option<T>799where800	T: Typed + FromUntyped,801{802	fn from_untyped(untyped: Val) -> Result<Self> {803		if matches!(untyped, Val::Null) {804			Ok(None)805		} else {806			T::from_untyped(untyped).map(Some)807		}808	}809}810811impl Typed for NumValue {812	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);813}814impl IntoUntyped for NumValue {815	fn into_untyped(typed: Self) -> Result<Val> {816		Ok(Val::Num(typed))817	}818}819impl FromUntyped for NumValue {820	fn from_untyped(untyped: Val) -> Result<Self> {821		Self::TYPE.check(&untyped)?;822		match untyped {823			Val::Num(v) => Ok(v),824			_ => unreachable!(),825		}826	}827}
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -175,10 +175,9 @@
 				// destructor is called, but instead re-initialize the TLS with the empty pool.
 				// Allow non-pooled Drop in this case.
 				// https://github.com/CertainLach/jrsonnet/issues/98#issuecomment-1591624016
-				//
-				// However, if pool is not empty, most likely this is issue #113, and then I don't
-				// have any explainations for now.
-				assert!(pool.is_empty(), "received an unpooled string not during the program termination, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");
+				// Another cause might be that you have improperly used jrsonnet in multi-threaded environment:
+				// https://github.com/CertainLach/jrsonnet/issues/113
+				debug_assert!(pool.is_empty(), "if you have landed here - you most likely did something naughty with multi-threading. jrsonnet string pooling uses thread_local pool");
 			}
 		});
 	}
modifiedcrates/jrsonnet-rowan-parser/src/generated/nodes.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
+++ b/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
@@ -3,9 +3,9 @@
 
 #![allow(non_snake_case, clippy::match_like_matches_macro)]
 use crate::{
-	ast::{support, AstChildren, AstNode, AstToken},
 	SyntaxKind::{self, *},
 	SyntaxNode, SyntaxToken, T,
+	ast::{AstChildren, AstNode, AstToken, support},
 };
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]