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

difftreelog

feat impl Typed for BTreeMap

Yaroslav Bolyukin2023-07-27parent: #9567d36.patch.diff
in: master

2 files 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::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5pub use jrsonnet_macros::Typed;6use jrsonnet_types::{ComplexValType, ValType};78use crate::{9	arr::ArrValue,10	error::Result,11	function::{native::NativeDesc, FuncDesc, FuncVal},12	throw,13	typed::CheckType,14	val::{IndexableVal, StrValue, ThunkMapper},15	ObjValue, ObjValueBuilder, Thunk, Val,16};1718#[derive(Trace)]19struct FromUntyped<K: Trace>(PhantomData<fn() -> K>);20impl<K> ThunkMapper<Val> for FromUntyped<K>21where22	K: Typed + Trace,23{24	type Output = K;2526	fn map(self, from: Val) -> Result<Self::Output> {27		K::from_untyped(from)28	}29}30impl<K: Trace> Default for FromUntyped<K> {31	fn default() -> Self {32		Self(PhantomData)33	}34}3536pub trait TypedObj: Typed {37	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;38	fn parse(obj: &ObjValue) -> Result<Self>;39	fn into_object(self) -> Result<ObjValue> {40		let mut builder = ObjValueBuilder::new();41		self.serialize(&mut builder)?;42		Ok(builder.build())43	}44}4546pub trait Typed: Sized {47	const TYPE: &'static ComplexValType;48	fn into_untyped(typed: Self) -> Result<Val>;49	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {50		Thunk::from(Self::into_untyped(typed))51	}52	fn from_untyped(untyped: Val) -> Result<Self>;53	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {54		Self::from_untyped(lazy.evaluate()?)55	}5657	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`58	fn provides_lazy() -> bool {59		false60	}6162	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible63	fn wants_lazy() -> bool {64		false65	}6667	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result68	/// This method returns identity in impl Typed for Result, and should not be overriden69	#[doc(hidden)]70	fn into_result(typed: Self) -> Result<Val> {71		let value = Self::into_untyped(typed)?;72		Ok(value)73	}74}7576impl<T> Typed for Thunk<T>77where78	T: Typed + Trace + Clone,79{80	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);8182	fn into_untyped(typed: Self) -> Result<Val> {83		T::into_untyped(typed.evaluate()?)84	}8586	fn from_untyped(untyped: Val) -> Result<Self> {87		Self::from_lazy_untyped(Thunk::evaluated(untyped))88	}8990	fn provides_lazy() -> bool {91		true92	}9394	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {95		#[derive(Trace)]96		struct IntoUntyped<K: Trace>(PhantomData<fn() -> K>);97		impl<K> ThunkMapper<K> for IntoUntyped<K>98		where99			K: Typed + Trace,100		{101			type Output = Val;102103			fn map(self, from: K) -> Result<Self::Output> {104				K::into_untyped(from)105			}106		}107		impl<K: Trace> Default for IntoUntyped<K> {108			fn default() -> Self {109				Self(PhantomData)110			}111		}112		inner.map(<IntoUntyped<T>>::default())113	}114115	fn wants_lazy() -> bool {116		true117	}118119	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {120		Ok(inner.map(<FromUntyped<T>>::default()))121	}122}123124const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;125126macro_rules! impl_int {127	($($ty:ty)*) => {$(128		impl Typed for $ty {129			const TYPE: &'static ComplexValType =130				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));131			fn from_untyped(value: Val) -> Result<Self> {132				<Self as Typed>::TYPE.check(&value)?;133				match value {134					Val::Num(n) => {135						#[allow(clippy::float_cmp)]136						if n.trunc() != n {137							throw!(138								"cannot convert number with fractional part to {}",139								stringify!($ty)140							)141						}142						Ok(n as Self)143					}144					_ => unreachable!(),145				}146			}147			fn into_untyped(value: Self) -> Result<Val> {148				Ok(Val::Num(value as f64))149			}150		}151	)*};152}153154impl_int!(i8 u8 i16 u16 i32 u32);155156macro_rules! impl_bounded_int {157	($($name:ident = $ty:ty)*) => {$(158		#[derive(Clone, Copy)]159		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);160		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {161			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {162				if value >= MIN && value <= MAX {163					Some(Self(value))164				} else {165					None166				}167			}168			pub const fn value(self) -> $ty {169				self.0170			}171		}172		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {173			type Target = $ty;174			fn deref(&self) -> &Self::Target {175				&self.0176			}177		}178179		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {180			const TYPE: &'static ComplexValType =181				&ComplexValType::BoundedNumber(182					Some(MIN as f64),183					Some(MAX as f64),184				);185186			fn from_untyped(value: Val) -> Result<Self> {187				<Self as Typed>::TYPE.check(&value)?;188				match value {189					Val::Num(n) => {190						#[allow(clippy::float_cmp)]191						if n.trunc() != n {192							throw!(193								"cannot convert number with fractional part to {}",194								stringify!($ty)195							)196						}197						Ok(Self(n as $ty))198					}199					_ => unreachable!(),200				}201			}202203			fn into_untyped(value: Self) -> Result<Val> {204				Ok(Val::Num(value.0 as f64))205			}206		}207	)*};208}209210impl_bounded_int!(211	BoundedI8 = i8212	BoundedI16 = i16213	BoundedI32 = i32214	BoundedI64 = i64215	BoundedUsize = usize216);217218impl Typed for f64 {219	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);220221	fn into_untyped(value: Self) -> Result<Val> {222		Ok(Val::Num(value))223	}224225	fn from_untyped(value: Val) -> Result<Self> {226		<Self as Typed>::TYPE.check(&value)?;227		match value {228			Val::Num(n) => Ok(n),229			_ => unreachable!(),230		}231	}232}233234pub struct PositiveF64(pub f64);235impl Typed for PositiveF64 {236	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);237238	fn into_untyped(value: Self) -> Result<Val> {239		Ok(Val::Num(value.0))240	}241242	fn from_untyped(value: Val) -> Result<Self> {243		<Self as Typed>::TYPE.check(&value)?;244		match value {245			Val::Num(n) => Ok(Self(n)),246			_ => unreachable!(),247		}248	}249}250impl Typed for usize {251	const TYPE: &'static ComplexValType =252		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));253254	fn into_untyped(value: Self) -> Result<Val> {255		if value > MAX_SAFE_INTEGER as Self {256			throw!("number is too large")257		}258		Ok(Val::Num(value as f64))259	}260261	fn from_untyped(value: Val) -> Result<Self> {262		<Self as Typed>::TYPE.check(&value)?;263		match value {264			Val::Num(n) => {265				#[allow(clippy::float_cmp)]266				if n.trunc() != n {267					throw!("cannot convert number with fractional part to usize")268				}269				Ok(n as Self)270			}271			_ => unreachable!(),272		}273	}274}275276impl Typed for IStr {277	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);278279	fn into_untyped(value: Self) -> Result<Val> {280		Ok(Val::Str(StrValue::Flat(value)))281	}282283	fn from_untyped(value: Val) -> Result<Self> {284		<Self as Typed>::TYPE.check(&value)?;285		match value {286			Val::Str(s) => Ok(s.into_flat()),287			_ => unreachable!(),288		}289	}290}291292impl Typed for String {293	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);294295	fn into_untyped(value: Self) -> Result<Val> {296		Ok(Val::Str(StrValue::Flat(value.into())))297	}298299	fn from_untyped(value: Val) -> Result<Self> {300		<Self as Typed>::TYPE.check(&value)?;301		match value {302			Val::Str(s) => Ok(s.to_string()),303			_ => unreachable!(),304		}305	}306}307308impl Typed for char {309	const TYPE: &'static ComplexValType = &ComplexValType::Char;310311	fn into_untyped(value: Self) -> Result<Val> {312		Ok(Val::Str(StrValue::Flat(value.to_string().into())))313	}314315	fn from_untyped(value: Val) -> Result<Self> {316		<Self as Typed>::TYPE.check(&value)?;317		match value {318			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),319			_ => unreachable!(),320		}321	}322}323324impl<T> Typed for Vec<T>325where326	T: Typed,327{328	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);329330	fn into_untyped(value: Self) -> Result<Val> {331		Ok(Val::Arr(332			value333				.into_iter()334				.map(T::into_untyped)335				.collect::<Result<ArrValue>>()?,336		))337	}338339	fn from_untyped(value: Val) -> Result<Self> {340		let Val::Arr(a) = value else {341			<Self as Typed>::TYPE.check(&value)?;342			unreachable!("typecheck should fail")343		};344		a.iter()345			.map(|r| r.and_then(T::from_untyped))346			.collect::<Result<Vec<T>>>()347	}348}349350impl Typed for Val {351	const TYPE: &'static ComplexValType = &ComplexValType::Any;352353	fn into_untyped(typed: Self) -> Result<Val> {354		Ok(typed)355	}356	fn from_untyped(untyped: Val) -> Result<Self> {357		Ok(untyped)358	}359}360361// Hack362#[doc(hidden)]363impl<T> Typed for Result<T>364where365	T: Typed,366{367	const TYPE: &'static ComplexValType = &ComplexValType::Any;368369	fn into_untyped(_typed: Self) -> Result<Val> {370		panic!("do not use this conversion")371	}372373	fn from_untyped(_untyped: Val) -> Result<Self> {374		panic!("do not use this conversion")375	}376377	fn into_result(typed: Self) -> Result<Val> {378		typed.map(T::into_untyped)?379	}380}381382/// Specialization383impl Typed for IBytes {384	const TYPE: &'static ComplexValType =385		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));386387	fn into_untyped(value: Self) -> Result<Val> {388		Ok(Val::Arr(ArrValue::bytes(value)))389	}390391	fn from_untyped(value: Val) -> Result<Self> {392		if let Val::Arr(ArrValue::Bytes(bytes)) = value {393			return Ok(bytes.0);394		}395		<Self as Typed>::TYPE.check(&value)?;396		match value {397			Val::Arr(a) => {398				let mut out = Vec::with_capacity(a.len());399				for e in a.iter() {400					let r = e?;401					out.push(u8::from_untyped(r)?);402				}403				Ok(out.as_slice().into())404			}405			_ => unreachable!(),406		}407	}408}409410pub struct M1;411impl Typed for M1 {412	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));413414	fn into_untyped(_: Self) -> Result<Val> {415		Ok(Val::Num(-1.0))416	}417418	fn from_untyped(value: Val) -> Result<Self> {419		<Self as Typed>::TYPE.check(&value)?;420		Ok(Self)421	}422}423424macro_rules! decl_either {425	($($name: ident, $($id: ident)*);*) => {$(426		#[derive(Clone)]427		pub enum $name<$($id),*> {428			$($id($id)),*429		}430		impl<$($id),*> Typed for $name<$($id),*>431		where432			$($id: Typed,)*433		{434			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);435436			fn into_untyped(value: Self) -> Result<Val> {437				match value {$(438					$name::$id(v) => $id::into_untyped(v)439				),*}440			}441442			fn from_untyped(value: Val) -> Result<Self> {443				$(444					if $id::TYPE.check(&value).is_ok() {445						$id::from_untyped(value).map(Self::$id)446					} else447				)* {448					<Self as Typed>::TYPE.check(&value)?;449					unreachable!()450				}451			}452		}453	)*}454}455decl_either!(456	Either1, A;457	Either2, A B;458	Either3, A B C;459	Either4, A B C D;460	Either5, A B C D E;461	Either6, A B C D E F;462	Either7, A B C D E F G463);464#[macro_export]465macro_rules! Either {466	($a:ty) => {Either1<$a>};467	($a:ty, $b:ty) => {Either2<$a, $b>};468	($a:ty, $b:ty, $c:ty) => {Either3<$a, $b, $c>};469	($a:ty, $b:ty, $c:ty, $d:ty) => {Either4<$a, $b, $c, $d>};470	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {Either5<$a, $b, $c, $d, $e>};471	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {Either6<$a, $b, $c, $d, $e, $f>};472	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {Either7<$a, $b, $c, $d, $e, $f, $g>};473}474pub use Either;475476pub type MyType = Either![u32, f64, String];477478impl Typed for ArrValue {479	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);480481	fn into_untyped(value: Self) -> Result<Val> {482		Ok(Val::Arr(value))483	}484485	fn from_untyped(value: Val) -> Result<Self> {486		<Self as Typed>::TYPE.check(&value)?;487		match value {488			Val::Arr(a) => Ok(a),489			_ => unreachable!(),490		}491	}492}493494impl Typed for FuncVal {495	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);496497	fn into_untyped(value: Self) -> Result<Val> {498		Ok(Val::Func(value))499	}500501	fn from_untyped(value: Val) -> Result<Self> {502		<Self as Typed>::TYPE.check(&value)?;503		match value {504			Val::Func(a) => Ok(a),505			_ => unreachable!(),506		}507	}508}509510impl Typed for Cc<FuncDesc> {511	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);512513	fn into_untyped(value: Self) -> Result<Val> {514		Ok(Val::Func(FuncVal::Normal(value)))515	}516517	fn from_untyped(value: Val) -> Result<Self> {518		<Self as Typed>::TYPE.check(&value)?;519		match value {520			Val::Func(FuncVal::Normal(desc)) => Ok(desc),521			Val::Func(_) => throw!("expected normal function, not builtin"),522			_ => unreachable!(),523		}524	}525}526527impl Typed for ObjValue {528	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);529530	fn into_untyped(value: Self) -> Result<Val> {531		Ok(Val::Obj(value))532	}533534	fn from_untyped(value: Val) -> Result<Self> {535		<Self as Typed>::TYPE.check(&value)?;536		match value {537			Val::Obj(a) => Ok(a),538			_ => unreachable!(),539		}540	}541}542543impl Typed for bool {544	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);545546	fn into_untyped(value: Self) -> Result<Val> {547		Ok(Val::Bool(value))548	}549550	fn from_untyped(value: Val) -> Result<Self> {551		<Self as Typed>::TYPE.check(&value)?;552		match value {553			Val::Bool(a) => Ok(a),554			_ => unreachable!(),555		}556	}557}558impl Typed for IndexableVal {559	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[560		&ComplexValType::Simple(ValType::Arr),561		&ComplexValType::Simple(ValType::Str),562	]);563564	fn into_untyped(value: Self) -> Result<Val> {565		match value {566			IndexableVal::Str(s) => Ok(Val::Str(StrValue::Flat(s))),567			IndexableVal::Arr(a) => Ok(Val::Arr(a)),568		}569	}570571	fn from_untyped(value: Val) -> Result<Self> {572		<Self as Typed>::TYPE.check(&value)?;573		value.into_indexable()574	}575}576577pub struct Null;578impl Typed for Null {579	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);580581	fn into_untyped(_: Self) -> Result<Val> {582		Ok(Val::Null)583	}584585	fn from_untyped(value: Val) -> Result<Self> {586		<Self as Typed>::TYPE.check(&value)?;587		Ok(Self)588	}589}590591pub struct NativeFn<D: NativeDesc>(D::Value);592impl<D: NativeDesc> Deref for NativeFn<D> {593	type Target = D::Value;594595	fn deref(&self) -> &Self::Target {596		&self.0597	}598}599impl<D: NativeDesc> Typed for NativeFn<D> {600	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);601602	fn into_untyped(_typed: Self) -> Result<Val> {603		throw!("can only convert functions from jsonnet to native")604	}605606	fn from_untyped(untyped: Val) -> Result<Self> {607		Ok(Self(608			untyped609				.as_func()610				.expect("shape is checked")611				.into_native::<D>(),612		))613	}614}
after · crates/jrsonnet-evaluator/src/typed/conversions.rs
1use std::{collections::BTreeMap, marker::PhantomData, ops::Deref};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5pub use jrsonnet_macros::Typed;6use jrsonnet_types::{ComplexValType, ValType};78use crate::{9	arr::ArrValue,10	error::Result,11	function::{native::NativeDesc, FuncDesc, FuncVal},12	throw,13	typed::CheckType,14	val::{IndexableVal, StrValue, ThunkMapper},15	ObjValue, ObjValueBuilder, Thunk, Val,16};1718#[derive(Trace)]19struct FromUntyped<K: Trace>(PhantomData<fn() -> K>);20impl<K> ThunkMapper<Val> for FromUntyped<K>21where22	K: Typed + Trace,23{24	type Output = K;2526	fn map(self, from: Val) -> Result<Self::Output> {27		K::from_untyped(from)28	}29}30impl<K: Trace> Default for FromUntyped<K> {31	fn default() -> Self {32		Self(PhantomData)33	}34}3536pub trait TypedObj: Typed {37	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;38	fn parse(obj: &ObjValue) -> Result<Self>;39	fn into_object(self) -> Result<ObjValue> {40		let mut builder = ObjValueBuilder::new();41		self.serialize(&mut builder)?;42		Ok(builder.build())43	}44}4546pub trait Typed: Sized {47	const TYPE: &'static ComplexValType;48	fn into_untyped(typed: Self) -> Result<Val>;49	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {50		Thunk::from(Self::into_untyped(typed))51	}52	fn from_untyped(untyped: Val) -> Result<Self>;53	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {54		Self::from_untyped(lazy.evaluate()?)55	}5657	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`58	fn provides_lazy() -> bool {59		false60	}6162	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible63	fn wants_lazy() -> bool {64		false65	}6667	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result68	/// This method returns identity in impl Typed for Result, and should not be overriden69	#[doc(hidden)]70	fn into_result(typed: Self) -> Result<Val> {71		let value = Self::into_untyped(typed)?;72		Ok(value)73	}74}7576impl<T> Typed for Thunk<T>77where78	T: Typed + Trace + Clone,79{80	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);8182	fn into_untyped(typed: Self) -> Result<Val> {83		T::into_untyped(typed.evaluate()?)84	}8586	fn from_untyped(untyped: Val) -> Result<Self> {87		Self::from_lazy_untyped(Thunk::evaluated(untyped))88	}8990	fn provides_lazy() -> bool {91		true92	}9394	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {95		#[derive(Trace)]96		struct IntoUntyped<K: Trace>(PhantomData<fn() -> K>);97		impl<K> ThunkMapper<K> for IntoUntyped<K>98		where99			K: Typed + Trace,100		{101			type Output = Val;102103			fn map(self, from: K) -> Result<Self::Output> {104				K::into_untyped(from)105			}106		}107		impl<K: Trace> Default for IntoUntyped<K> {108			fn default() -> Self {109				Self(PhantomData)110			}111		}112		inner.map(<IntoUntyped<T>>::default())113	}114115	fn wants_lazy() -> bool {116		true117	}118119	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {120		Ok(inner.map(<FromUntyped<T>>::default()))121	}122}123124const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;125126macro_rules! impl_int {127	($($ty:ty)*) => {$(128		impl Typed for $ty {129			const TYPE: &'static ComplexValType =130				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));131			fn from_untyped(value: Val) -> Result<Self> {132				<Self as Typed>::TYPE.check(&value)?;133				match value {134					Val::Num(n) => {135						#[allow(clippy::float_cmp)]136						if n.trunc() != n {137							throw!(138								"cannot convert number with fractional part to {}",139								stringify!($ty)140							)141						}142						Ok(n as Self)143					}144					_ => unreachable!(),145				}146			}147			fn into_untyped(value: Self) -> Result<Val> {148				Ok(Val::Num(value as f64))149			}150		}151	)*};152}153154impl_int!(i8 u8 i16 u16 i32 u32);155156macro_rules! impl_bounded_int {157	($($name:ident = $ty:ty)*) => {$(158		#[derive(Clone, Copy)]159		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);160		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {161			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {162				if value >= MIN && value <= MAX {163					Some(Self(value))164				} else {165					None166				}167			}168			pub const fn value(self) -> $ty {169				self.0170			}171		}172		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {173			type Target = $ty;174			fn deref(&self) -> &Self::Target {175				&self.0176			}177		}178179		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {180			const TYPE: &'static ComplexValType =181				&ComplexValType::BoundedNumber(182					Some(MIN as f64),183					Some(MAX as f64),184				);185186			fn from_untyped(value: Val) -> Result<Self> {187				<Self as Typed>::TYPE.check(&value)?;188				match value {189					Val::Num(n) => {190						#[allow(clippy::float_cmp)]191						if n.trunc() != n {192							throw!(193								"cannot convert number with fractional part to {}",194								stringify!($ty)195							)196						}197						Ok(Self(n as $ty))198					}199					_ => unreachable!(),200				}201			}202203			fn into_untyped(value: Self) -> Result<Val> {204				Ok(Val::Num(value.0 as f64))205			}206		}207	)*};208}209210impl_bounded_int!(211	BoundedI8 = i8212	BoundedI16 = i16213	BoundedI32 = i32214	BoundedI64 = i64215	BoundedUsize = usize216);217218impl Typed for f64 {219	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);220221	fn into_untyped(value: Self) -> Result<Val> {222		Ok(Val::Num(value))223	}224225	fn from_untyped(value: Val) -> Result<Self> {226		<Self as Typed>::TYPE.check(&value)?;227		match value {228			Val::Num(n) => Ok(n),229			_ => unreachable!(),230		}231	}232}233234pub struct PositiveF64(pub f64);235impl Typed for PositiveF64 {236	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);237238	fn into_untyped(value: Self) -> Result<Val> {239		Ok(Val::Num(value.0))240	}241242	fn from_untyped(value: Val) -> Result<Self> {243		<Self as Typed>::TYPE.check(&value)?;244		match value {245			Val::Num(n) => Ok(Self(n)),246			_ => unreachable!(),247		}248	}249}250impl Typed for usize {251	const TYPE: &'static ComplexValType =252		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));253254	fn into_untyped(value: Self) -> Result<Val> {255		if value > MAX_SAFE_INTEGER as Self {256			throw!("number is too large")257		}258		Ok(Val::Num(value as f64))259	}260261	fn from_untyped(value: Val) -> Result<Self> {262		<Self as Typed>::TYPE.check(&value)?;263		match value {264			Val::Num(n) => {265				#[allow(clippy::float_cmp)]266				if n.trunc() != n {267					throw!("cannot convert number with fractional part to usize")268				}269				Ok(n as Self)270			}271			_ => unreachable!(),272		}273	}274}275276impl Typed for IStr {277	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);278279	fn into_untyped(value: Self) -> Result<Val> {280		Ok(Val::Str(StrValue::Flat(value)))281	}282283	fn from_untyped(value: Val) -> Result<Self> {284		<Self as Typed>::TYPE.check(&value)?;285		match value {286			Val::Str(s) => Ok(s.into_flat()),287			_ => unreachable!(),288		}289	}290}291292impl Typed for String {293	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);294295	fn into_untyped(value: Self) -> Result<Val> {296		Ok(Val::Str(StrValue::Flat(value.into())))297	}298299	fn from_untyped(value: Val) -> Result<Self> {300		<Self as Typed>::TYPE.check(&value)?;301		match value {302			Val::Str(s) => Ok(s.to_string()),303			_ => unreachable!(),304		}305	}306}307308impl Typed for char {309	const TYPE: &'static ComplexValType = &ComplexValType::Char;310311	fn into_untyped(value: Self) -> Result<Val> {312		Ok(Val::Str(StrValue::Flat(value.to_string().into())))313	}314315	fn from_untyped(value: Val) -> Result<Self> {316		<Self as Typed>::TYPE.check(&value)?;317		match value {318			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),319			_ => unreachable!(),320		}321	}322}323324impl<T> Typed for Vec<T>325where326	T: Typed,327{328	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);329330	fn into_untyped(value: Self) -> Result<Val> {331		Ok(Val::Arr(332			value333				.into_iter()334				.map(T::into_untyped)335				.collect::<Result<ArrValue>>()?,336		))337	}338339	fn from_untyped(value: Val) -> Result<Self> {340		let Val::Arr(a) = value else {341			<Self as Typed>::TYPE.check(&value)?;342			unreachable!("typecheck should fail")343		};344		a.iter()345			.map(|r| r.and_then(T::from_untyped))346			.collect::<Result<Vec<T>>>()347	}348}349350impl<K: Typed + Ord, V: Typed> Typed for BTreeMap<K, V> {351	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);352353	fn into_untyped(typed: Self) -> Result<Val> {354		let mut out = ObjValueBuilder::with_capacity(typed.len());355		for (k, v) in typed {356			let Some(key) = K::into_untyped(k)?.as_str() else {357				throw!("map key should serialize to string");358			};359			let value = V::into_untyped(v)?;360			out.member(key).value_unchecked(value);361		}362		Ok(Val::Obj(out.build()))363	}364365	fn from_untyped(value: Val) -> Result<Self> {366		Self::TYPE.check(&value)?;367		let obj = value.as_obj().expect("typecheck should fail");368369		let mut out = BTreeMap::new();370		if V::wants_lazy() {371			for key in obj.fields_ex(372				false,373				#[cfg(feature = "exp-preserve-order")]374				false,375			) {376				let value = obj.get_lazy(key.clone()).expect("field exists");377				let value = V::from_lazy_untyped(value)?;378				let key = K::from_untyped(Val::Str(key.into()))?;379				let _ = out.insert(key, value);380			}381		} else {382			for (key, value) in obj.iter(383				#[cfg(feature = "exp-preserve-order")]384				false,385			) {386				let key = K::from_untyped(Val::Str(key.into()))?;387				let value = V::from_untyped(value?)?;388				let _ = out.insert(key, value);389			}390		}391		Ok(out)392	}393}394395impl Typed for Val {396	const TYPE: &'static ComplexValType = &ComplexValType::Any;397398	fn into_untyped(typed: Self) -> Result<Val> {399		Ok(typed)400	}401	fn from_untyped(untyped: Val) -> Result<Self> {402		Ok(untyped)403	}404}405406// Hack407#[doc(hidden)]408impl<T> Typed for Result<T>409where410	T: Typed,411{412	const TYPE: &'static ComplexValType = &ComplexValType::Any;413414	fn into_untyped(_typed: Self) -> Result<Val> {415		panic!("do not use this conversion")416	}417418	fn from_untyped(_untyped: Val) -> Result<Self> {419		panic!("do not use this conversion")420	}421422	fn into_result(typed: Self) -> Result<Val> {423		typed.map(T::into_untyped)?424	}425}426427/// Specialization428impl Typed for IBytes {429	const TYPE: &'static ComplexValType =430		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));431432	fn into_untyped(value: Self) -> Result<Val> {433		Ok(Val::Arr(ArrValue::bytes(value)))434	}435436	fn from_untyped(value: Val) -> Result<Self> {437		if let Val::Arr(ArrValue::Bytes(bytes)) = value {438			return Ok(bytes.0);439		}440		<Self as Typed>::TYPE.check(&value)?;441		match value {442			Val::Arr(a) => {443				let mut out = Vec::with_capacity(a.len());444				for e in a.iter() {445					let r = e?;446					out.push(u8::from_untyped(r)?);447				}448				Ok(out.as_slice().into())449			}450			_ => unreachable!(),451		}452	}453}454455pub struct M1;456impl Typed for M1 {457	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));458459	fn into_untyped(_: Self) -> Result<Val> {460		Ok(Val::Num(-1.0))461	}462463	fn from_untyped(value: Val) -> Result<Self> {464		<Self as Typed>::TYPE.check(&value)?;465		Ok(Self)466	}467}468469macro_rules! decl_either {470	($($name: ident, $($id: ident)*);*) => {$(471		#[derive(Clone)]472		pub enum $name<$($id),*> {473			$($id($id)),*474		}475		impl<$($id),*> Typed for $name<$($id),*>476		where477			$($id: Typed,)*478		{479			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);480481			fn into_untyped(value: Self) -> Result<Val> {482				match value {$(483					$name::$id(v) => $id::into_untyped(v)484				),*}485			}486487			fn from_untyped(value: Val) -> Result<Self> {488				$(489					if $id::TYPE.check(&value).is_ok() {490						$id::from_untyped(value).map(Self::$id)491					} else492				)* {493					<Self as Typed>::TYPE.check(&value)?;494					unreachable!()495				}496			}497		}498	)*}499}500decl_either!(501	Either1, A;502	Either2, A B;503	Either3, A B C;504	Either4, A B C D;505	Either5, A B C D E;506	Either6, A B C D E F;507	Either7, A B C D E F G508);509#[macro_export]510macro_rules! Either {511	($a:ty) => {Either1<$a>};512	($a:ty, $b:ty) => {Either2<$a, $b>};513	($a:ty, $b:ty, $c:ty) => {Either3<$a, $b, $c>};514	($a:ty, $b:ty, $c:ty, $d:ty) => {Either4<$a, $b, $c, $d>};515	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {Either5<$a, $b, $c, $d, $e>};516	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {Either6<$a, $b, $c, $d, $e, $f>};517	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {Either7<$a, $b, $c, $d, $e, $f, $g>};518}519pub use Either;520521pub type MyType = Either![u32, f64, String];522523impl Typed for ArrValue {524	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);525526	fn into_untyped(value: Self) -> Result<Val> {527		Ok(Val::Arr(value))528	}529530	fn from_untyped(value: Val) -> Result<Self> {531		<Self as Typed>::TYPE.check(&value)?;532		match value {533			Val::Arr(a) => Ok(a),534			_ => unreachable!(),535		}536	}537}538539impl Typed for FuncVal {540	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);541542	fn into_untyped(value: Self) -> Result<Val> {543		Ok(Val::Func(value))544	}545546	fn from_untyped(value: Val) -> Result<Self> {547		<Self as Typed>::TYPE.check(&value)?;548		match value {549			Val::Func(a) => Ok(a),550			_ => unreachable!(),551		}552	}553}554555impl Typed for Cc<FuncDesc> {556	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);557558	fn into_untyped(value: Self) -> Result<Val> {559		Ok(Val::Func(FuncVal::Normal(value)))560	}561562	fn from_untyped(value: Val) -> Result<Self> {563		<Self as Typed>::TYPE.check(&value)?;564		match value {565			Val::Func(FuncVal::Normal(desc)) => Ok(desc),566			Val::Func(_) => throw!("expected normal function, not builtin"),567			_ => unreachable!(),568		}569	}570}571572impl Typed for ObjValue {573	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);574575	fn into_untyped(value: Self) -> Result<Val> {576		Ok(Val::Obj(value))577	}578579	fn from_untyped(value: Val) -> Result<Self> {580		<Self as Typed>::TYPE.check(&value)?;581		match value {582			Val::Obj(a) => Ok(a),583			_ => unreachable!(),584		}585	}586}587588impl Typed for bool {589	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);590591	fn into_untyped(value: Self) -> Result<Val> {592		Ok(Val::Bool(value))593	}594595	fn from_untyped(value: Val) -> Result<Self> {596		<Self as Typed>::TYPE.check(&value)?;597		match value {598			Val::Bool(a) => Ok(a),599			_ => unreachable!(),600		}601	}602}603impl Typed for IndexableVal {604	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[605		&ComplexValType::Simple(ValType::Arr),606		&ComplexValType::Simple(ValType::Str),607	]);608609	fn into_untyped(value: Self) -> Result<Val> {610		match value {611			IndexableVal::Str(s) => Ok(Val::Str(StrValue::Flat(s))),612			IndexableVal::Arr(a) => Ok(Val::Arr(a)),613		}614	}615616	fn from_untyped(value: Val) -> Result<Self> {617		<Self as Typed>::TYPE.check(&value)?;618		value.into_indexable()619	}620}621622pub struct Null;623impl Typed for Null {624	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);625626	fn into_untyped(_: Self) -> Result<Val> {627		Ok(Val::Null)628	}629630	fn from_untyped(value: Val) -> Result<Self> {631		<Self as Typed>::TYPE.check(&value)?;632		Ok(Self)633	}634}635636pub struct NativeFn<D: NativeDesc>(D::Value);637impl<D: NativeDesc> Deref for NativeFn<D> {638	type Target = D::Value;639640	fn deref(&self) -> &Self::Target {641		&self.0642	}643}644impl<D: NativeDesc> Typed for NativeFn<D> {645	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);646647	fn into_untyped(_typed: Self) -> Result<Val> {648		throw!("can only convert functions from jsonnet to native")649	}650651	fn from_untyped(untyped: Val) -> Result<Self> {652		Ok(Self(653			untyped654				.as_func()655				.expect("shape is checked")656				.into_native::<D>(),657		))658	}659}
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -191,6 +191,19 @@
 				}
 				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
 			},
+			Self::AttrsOf(a) => match value {
+				Val::Obj(o) => {
+					for (_key, value) in o.iter(
+						#[cfg(feature = "exp-preserve-order")]
+						false,
+					) {
+						let value = value?;
+						a.check(&value)?;
+					}
+					Ok(())
+				}
+				v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
+			},
 			Self::ObjectRef(elems) => match value {
 				Val::Obj(obj) => {
 					for (k, v) in elems.iter() {