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

difftreelog

feat thunk conversion avoiding lazy

tozttkqwYaroslav Bolyukin2026-04-04parent: #3791235.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/typed/conversions.rs
1use std::{collections::BTreeMap, marker::PhantomData, ops::Deref};23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_types::{ComplexValType, ValType};67use crate::{8	ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,9	arr::{ArrValue, BytesArray},10	bail,11	function::FuncVal,12	typed::CheckType,13	val::{IndexableVal, NumValue, StrValue, ThunkMapper},14};1516#[doc(hidden)]17pub mod __typed_macro_prelude {18	pub use ::jrsonnet_evaluator::{19		IStr, ObjValue, ObjValueBuilder, State, Val,20		error::{ErrorKind, Result as JrResult},21		typed::{22			CheckType, ComplexValType, FromUntyped, IntoUntyped, ParseTypedObj, SerializeTypedObj,23			Typed,24		},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}129130impl IntoUntyped for Thunk<Val> {131	fn into_untyped(typed: Self) -> Result<Val> {132		typed.evaluate()133	}134	fn provides_lazy() -> bool {135		true136	}137138	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {139		inner140	}141}142143impl<T> FromUntyped for Thunk<T>144where145	T: Typed + FromUntyped + Trace + Clone,146{147	fn from_untyped(untyped: Val) -> Result<Self> {148		Self::from_lazy_untyped(Thunk::evaluated(untyped))149	}150151	fn wants_lazy() -> bool {152		true153	}154155	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {156		Ok(inner.map(<ThunkFromUntyped<T>>::default()))157	}158}159160#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]161pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;162#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]163pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;164165macro_rules! impl_int {166	($($ty:ty)*) => {$(167		impl Typed for $ty {168			const TYPE: &'static ComplexValType =169				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));170		}171		impl FromUntyped for $ty {172			fn from_untyped(value: Val) -> Result<Self> {173				<Self as Typed>::TYPE.check(&value)?;174				match value {175					Val::Num(n) => {176						let n = n.get();177						#[allow(clippy::float_cmp)]178						if n.trunc() != n {179							bail!(180								"cannot convert number with fractional part to {}",181								stringify!($ty)182							)183						}184						#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation, reason = "checked by TYPE")]185						Ok(n as Self)186					}187					_ => unreachable!(),188				}189			}190		}191		impl IntoUntyped for $ty {192			fn into_untyped(value: Self) -> Result<Val> {193				Ok(Val::Num(value.into()))194			}195		}196	)*};197}198199impl_int!(i8 u8 i16 u16 i32 u32);200201macro_rules! impl_bounded_int {202	($($name:ident = $ty:ty)*) => {$(203		#[derive(Clone, Copy)]204		#[allow(clippy::cast_possible_truncation, reason = "overflow is api misuse")]205		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);206		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {207			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {208				if value >= MIN && value <= MAX {209					Some(Self(value))210				} else {211					None212				}213			}214			pub const fn value(self) -> $ty {215				self.0216			}217		}218		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {219			type Target = $ty;220			fn deref(&self) -> &Self::Target {221				&self.0222			}223		}224225		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {226			#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, reason = "overflow is api misuse")]227			const TYPE: &'static ComplexValType =228				&ComplexValType::BoundedNumber(229					Some(MIN as f64),230					Some(MAX as f64),231				);232		}233234		impl<const MIN: $ty, const MAX: $ty> FromUntyped for $name<MIN, MAX> {235			fn from_untyped(value: Val) -> Result<Self> {236				<Self as Typed>::TYPE.check(&value)?;237				match value {238					Val::Num(n) => {239						let n = n.get();240						#[allow(clippy::float_cmp)]241						if n.trunc() != n {242							bail!(243								"cannot convert number with fractional part to {}",244								stringify!($ty)245							)246						}247						#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "overflow is api misuse, the range is checked by TYPE")]248						Ok(Self(n as $ty))249					}250					_ => unreachable!(),251				}252			}253		}254255		impl<const MIN: $ty, const MAX: $ty> IntoUntyped for $name<MIN, MAX> {256			#[allow(clippy::cast_lossless)]257			fn into_untyped(value: Self) -> Result<Val> {258				Ok(Val::try_num(value.0)?)259			}260		}261	)*};262}263264impl_bounded_int!(265	BoundedI8 = i8266	BoundedI16 = i16267	BoundedI32 = i32268	BoundedI64 = i64269	BoundedUsize = usize270);271272impl Typed for f64 {273	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);274}275impl IntoUntyped for f64 {276	fn into_untyped(value: Self) -> Result<Val> {277		Ok(Val::try_num(value)?)278	}279}280impl FromUntyped for f64 {281	fn from_untyped(value: Val) -> Result<Self> {282		<Self as Typed>::TYPE.check(&value)?;283		match value {284			Val::Num(n) => Ok(n.get()),285			_ => unreachable!(),286		}287	}288}289290pub struct PositiveF64(pub f64);291impl Typed for PositiveF64 {292	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);293}294impl IntoUntyped for PositiveF64 {295	fn into_untyped(value: Self) -> Result<Val> {296		Ok(Val::try_num(value.0)?)297	}298}299impl FromUntyped for PositiveF64 {300	fn from_untyped(value: Val) -> Result<Self> {301		<Self as Typed>::TYPE.check(&value)?;302		match value {303			Val::Num(n) => Ok(Self(n.get())),304			_ => unreachable!(),305		}306	}307}308impl Typed for usize {309	const TYPE: &'static ComplexValType =310		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));311}312impl IntoUntyped for usize {313	fn into_untyped(value: Self) -> Result<Val> {314		Ok(Val::try_num(value)?)315	}316}317impl FromUntyped for usize {318	fn from_untyped(value: Val) -> Result<Self> {319		<Self as Typed>::TYPE.check(&value)?;320		match value {321			Val::Num(n) => {322				let n = n.get();323				#[allow(clippy::float_cmp)]324				if n.trunc() != n {325					bail!("cannot convert number with fractional part to usize")326				}327				#[allow(328					clippy::cast_possible_truncation,329					clippy::cast_sign_loss,330					reason = "the range is checked by TYPE"331				)]332				Ok(n as Self)333			}334			_ => unreachable!(),335		}336	}337}338339impl Typed for IStr {340	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);341}342impl IntoUntyped for IStr {343	fn into_untyped(value: Self) -> Result<Val> {344		Ok(Val::string(value))345	}346}347impl FromUntyped for IStr {348	fn from_untyped(value: Val) -> Result<Self> {349		<Self as Typed>::TYPE.check(&value)?;350		match value {351			Val::Str(s) => Ok(s.into_flat()),352			_ => unreachable!(),353		}354	}355}356357impl Typed for String {358	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);359}360impl IntoUntyped for String {361	fn into_untyped(value: Self) -> Result<Val> {362		Ok(Val::string(value))363	}364}365impl FromUntyped for String {366	fn from_untyped(value: Val) -> Result<Self> {367		<Self as Typed>::TYPE.check(&value)?;368		match value {369			Val::Str(s) => Ok(s.to_string()),370			_ => unreachable!(),371		}372	}373}374375impl Typed for StrValue {376	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);377}378impl IntoUntyped for StrValue {379	fn into_untyped(value: Self) -> Result<Val> {380		Ok(Val::Str(value))381	}382}383impl FromUntyped for StrValue {384	fn from_untyped(value: Val) -> Result<Self> {385		<Self as Typed>::TYPE.check(&value)?;386		match value {387			Val::Str(s) => Ok(s),388			_ => unreachable!(),389		}390	}391}392393impl Typed for char {394	const TYPE: &'static ComplexValType = &ComplexValType::Char;395}396impl IntoUntyped for char {397	fn into_untyped(value: Self) -> Result<Val> {398		Ok(Val::string(value))399	}400}401impl FromUntyped for char {402	fn from_untyped(value: Val) -> Result<Self> {403		<Self as Typed>::TYPE.check(&value)?;404		match value {405			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),406			_ => unreachable!(),407		}408	}409}410411// TODO: View into vec using ArrayLike?412impl<T> Typed for Vec<T>413where414	T: Typed,415{416	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);417}418impl<T: Typed + IntoUntyped> IntoUntyped for Vec<T> {419	fn into_untyped(value: Self) -> Result<Val> {420		Ok(Val::Arr(421			value422				.into_iter()423				.map(T::into_untyped)424				.collect::<Result<ArrValue>>()?,425		))426	}427}428impl<T: Typed + FromUntyped> FromUntyped for Vec<T> {429	fn from_untyped(value: Val) -> Result<Self> {430		let Val::Arr(a) = value else {431			<Self as Typed>::TYPE.check(&value)?;432			unreachable!("typecheck should fail")433		};434		a.iter()435			.enumerate()436			.map(|(i, r)| {437				r.and_then(|t| {438					T::from_untyped(t).with_description(|| format!("parsing elem <{i}>"))439				})440			})441			.collect::<Result<Self>>()442	}443}444445// TODO: View into BTreeMap using ObjectCore?446impl<K, V> Typed for BTreeMap<K, V>447where448	K: Typed + Ord,449	V: Typed,450{451	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);452}453impl<K, V> IntoUntyped for BTreeMap<K, V>454where455	K: Typed + Ord + IntoUntyped,456	V: Typed + IntoUntyped,457{458	fn into_untyped(typed: Self) -> Result<Val> {459		let mut out = ObjValueBuilder::with_capacity(typed.len());460		for (k, v) in typed {461			let Some(key) = K::into_untyped(k)?.as_str() else {462				bail!("map key should serialize to string");463			};464			let value = V::into_untyped(v)?;465			out.field(key).value(value);466		}467		Ok(Val::Obj(out.build()))468	}469}470impl<K, V> FromUntyped for BTreeMap<K, V>471where472	K: FromUntyped + Ord,473	V: FromUntyped,474{475	fn from_untyped(value: Val) -> Result<Self> {476		Self::TYPE.check(&value)?;477		let obj = value.as_obj().expect("typecheck should fail");478479		let mut out = Self::new();480		if V::wants_lazy() {481			for key in obj.fields_ex(482				false,483				#[cfg(feature = "exp-preserve-order")]484				false,485			) {486				let value = obj.get_lazy(key.clone()).expect("field exists");487				let value = V::from_lazy_untyped(value)?;488				let key = K::from_untyped(Val::Str(key.into()))?;489				let _ = out.insert(key, value);490			}491		} else {492			for (key, value) in obj.iter(493				#[cfg(feature = "exp-preserve-order")]494				false,495			) {496				let key = K::from_untyped(Val::Str(key.into()))?;497				let value = V::from_untyped(value?)?;498				let _ = out.insert(key, value);499			}500		}501		Ok(out)502	}503}504505impl Typed for Val {506	const TYPE: &'static ComplexValType = &ComplexValType::Any;507}508impl IntoUntyped for Val {509	fn into_untyped(typed: Self) -> Result<Val> {510		Ok(typed)511	}512}513impl FromUntyped for Val {514	fn from_untyped(untyped: Val) -> Result<Self> {515		Ok(untyped)516	}517}518519#[doc(hidden)]520impl<T> Typed for Result<T>521where522	T: Typed,523{524	const TYPE: &'static ComplexValType = &ComplexValType::Any;525}526impl<T: IntoUntyped> IntoUntypedResult for Result<T> {527	fn into_untyped_result(typed: Self) -> Result<Val> {528		typed.map(T::into_untyped)?529	}530}531532/// Specialization533impl Typed for IBytes {534	const TYPE: &'static ComplexValType =535		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));536}537impl IntoUntyped for IBytes {538	fn into_untyped(value: Self) -> Result<Val> {539		Ok(Val::Arr(ArrValue::bytes(value)))540	}541}542impl FromUntyped for IBytes {543	fn from_untyped(value: Val) -> Result<Self> {544		let Val::Arr(a) = &value else {545			<Self as Typed>::TYPE.check(&value)?;546			unreachable!()547		};548		if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {549			return Ok(bytes.0.as_slice().into());550		}551		<Self as Typed>::TYPE.check(&value)?;552		// Any::downcast_ref::<ByteArray>(&a);553		let mut out = Vec::with_capacity(a.len());554		for e in a.iter() {555			let r = e?;556			out.push(u8::from_untyped(r)?);557		}558		Ok(out.as_slice().into())559	}560}561562pub struct M1;563impl Typed for M1 {564	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));565}566impl IntoUntyped for M1 {567	fn into_untyped(_: Self) -> Result<Val> {568		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))569	}570}571impl FromUntyped for M1 {572	fn from_untyped(value: Val) -> Result<Self> {573		<Self as Typed>::TYPE.check(&value)?;574		Ok(Self)575	}576}577578macro_rules! decl_either {579	($($name: ident, $($id: ident)*);*) => {$(580		#[derive(Clone)]581		pub enum $name<$($id),*> {582			$($id($id)),*583		}584		impl<$($id),*> Typed for $name<$($id),*>585		where586			$($id: Typed,)*587		{588			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);589		}590		impl<$($id),*> IntoUntyped for $name<$($id),*>591		where592			$($id: Typed + IntoUntyped,)*593		{594			fn into_untyped(value: Self) -> Result<Val> {595				match value {$(596					$name::$id(v) => $id::into_untyped(v)597				),*}598			}599		}600601		impl<$($id),*> FromUntyped for $name<$($id),*>602		where603			$($id: Typed + FromUntyped,)*604		{605			fn from_untyped(value: Val) -> Result<Self> {606				$(607					if $id::TYPE.check(&value).is_ok() {608						$id::from_untyped(value).map(Self::$id)609					} else610				)* {611					<Self as Typed>::TYPE.check(&value)?;612					unreachable!()613				}614			}615		}616	)*}617}618decl_either!(619	Either1, A;620	Either2, A B;621	Either3, A B C;622	Either4, A B C D;623	Either5, A B C D E;624	Either6, A B C D E F;625	Either7, A B C D E F G626);627#[macro_export]628macro_rules! Either {629	($a:ty) => {$crate::typed::Either1<$a>};630	($a:ty, $b:ty) => {$crate::typed::Either2<$a, $b>};631	($a:ty, $b:ty, $c:ty) => {$crate::typed::Either3<$a, $b, $c>};632	($a:ty, $b:ty, $c:ty, $d:ty) => {$crate::typed::Either4<$a, $b, $c, $d>};633	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {$crate::typed::Either5<$a, $b, $c, $d, $e>};634	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {$crate::typed::Either6<$a, $b, $c, $d, $e, $f>};635	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {$crate::typed::Either7<$a, $b, $c, $d, $e, $f, $g>};636}637pub use Either;638639pub type MyType = Either![u32, f64, String];640641impl Typed for ArrValue {642	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);643}644impl IntoUntyped for ArrValue {645	fn into_untyped(value: Self) -> Result<Val> {646		Ok(Val::Arr(value))647	}648}649impl FromUntyped for ArrValue {650	fn from_untyped(value: Val) -> Result<Self> {651		<Self as Typed>::TYPE.check(&value)?;652		match value {653			Val::Arr(a) => Ok(a),654			_ => unreachable!(),655		}656	}657}658659impl Typed for FuncVal {660	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);661}662impl IntoUntyped for FuncVal {663	fn into_untyped(value: Self) -> Result<Val> {664		Ok(Val::Func(value))665	}666}667impl FromUntyped for FuncVal {668	fn from_untyped(value: Val) -> Result<Self> {669		<Self as Typed>::TYPE.check(&value)?;670		match value {671			Val::Func(a) => Ok(a),672			_ => unreachable!(),673		}674	}675}676677impl Typed for ObjValue {678	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);679}680impl IntoUntyped for ObjValue {681	fn into_untyped(value: Self) -> Result<Val> {682		Ok(Val::Obj(value))683	}684}685impl FromUntyped for ObjValue {686	fn from_untyped(value: Val) -> Result<Self> {687		<Self as Typed>::TYPE.check(&value)?;688		match value {689			Val::Obj(a) => Ok(a),690			_ => unreachable!(),691		}692	}693}694695impl Typed for bool {696	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);697}698impl IntoUntyped for bool {699	fn into_untyped(value: Self) -> Result<Val> {700		Ok(Val::Bool(value))701	}702}703impl FromUntyped for bool {704	fn from_untyped(value: Val) -> Result<Self> {705		<Self as Typed>::TYPE.check(&value)?;706		match value {707			Val::Bool(a) => Ok(a),708			_ => unreachable!(),709		}710	}711}712713impl Typed for IndexableVal {714	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[715		&ComplexValType::Simple(ValType::Arr),716		&ComplexValType::Simple(ValType::Str),717	]);718}719impl IntoUntyped for IndexableVal {720	fn into_untyped(value: Self) -> Result<Val> {721		match value {722			Self::Str(s) => Ok(Val::string(s)),723			Self::Arr(a) => Ok(Val::Arr(a)),724		}725	}726}727impl FromUntyped for IndexableVal {728	fn from_untyped(value: Val) -> Result<Self> {729		<Self as Typed>::TYPE.check(&value)?;730		value.into_indexable()731	}732}733734pub struct Null;735impl Typed for Null {736	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);737}738impl IntoUntyped for Null {739	fn into_untyped(_: Self) -> Result<Val> {740		Ok(Val::Null)741	}742}743impl FromUntyped for Null {744	fn from_untyped(value: Val) -> Result<Self> {745		<Self as Typed>::TYPE.check(&value)?;746		Ok(Self)747	}748}749750impl<T> Typed for Option<T>751where752	T: Typed,753{754	const TYPE: &'static ComplexValType =755		&ComplexValType::UnionRef(&[&ComplexValType::Simple(ValType::Null), T::TYPE]);756}757impl<T> IntoUntyped for Option<T>758where759	T: Typed + IntoUntyped,760{761	fn into_untyped(typed: Self) -> Result<Val> {762		typed.map_or_else(|| Ok(Val::Null), |v| T::into_untyped(v))763	}764}765impl<T> FromUntyped for Option<T>766where767	T: Typed + FromUntyped,768{769	fn from_untyped(untyped: Val) -> Result<Self> {770		if matches!(untyped, Val::Null) {771			Ok(None)772		} else {773			T::from_untyped(untyped).map(Some)774		}775	}776}777778impl Typed for NumValue {779	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);780}781impl IntoUntyped for NumValue {782	fn into_untyped(typed: Self) -> Result<Val> {783		Ok(Val::Num(typed))784	}785}786impl FromUntyped for NumValue {787	fn from_untyped(untyped: Val) -> Result<Self> {788		Self::TYPE.check(&untyped)?;789		match untyped {790			Val::Num(v) => Ok(v),791			_ => unreachable!(),792		}793	}794}
after · 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}