git.delta.rocks / jrsonnet / refs/commits / 1ddb92c00d0f

difftreelog

style fix clippy warnings

ntlrsvsuYaroslav Bolyukin2026-03-22parent: #f0c8338.patch.diff
in: master

24 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -676,18 +676,18 @@
 
 [[package]]
 name = "jrsonnet-gcmodule"
-version = "0.4.1"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c33f4f6cdc60f5ae94ebae3dfe7f484ae79b364225d9b19601b24c804cfd8751"
+checksum = "f95b976a79e4000bb9e07ff0709dca0ea27bcf1952d4c17d91fb7364d6145683"
 dependencies = [
  "jrsonnet-gcmodule-derive",
 ]
 
 [[package]]
 name = "jrsonnet-gcmodule-derive"
-version = "0.4.1"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b30c95b285f9bb6709f1b3e6fc69b3a25e39b32ff987587fd108f0f22be5fa3"
+checksum = "51d928626220a310ff0cec815e80cf7fe104697184352ca21c40534e0b0d72d9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1602,7 +1602,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
 dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,7 +20,7 @@
 jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre97" }
 jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre97" }
 jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre97" }
-jrsonnet-gcmodule = { version = "0.4.1" }
+jrsonnet-gcmodule = { version = "0.4.2" }
 # Diagnostics.
 # hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
 # Previous implementation was based on annotate-snippets, which I don't like for many reasons.
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -66,8 +66,8 @@
 				base.as_ptr(),
 				rel.as_ptr(),
 				&mut found_here.cast_const(),
-				&mut buf,
-				&mut buf_len,
+				&raw mut buf,
+				&raw mut buf_len,
 			)
 		};
 		let buf_slice: &[u8] = unsafe { std::slice::from_raw_parts(buf.cast(), buf_len) };
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -9,6 +9,9 @@
 	evaluate_method, evaluate_named_param, Context, Pending, Thunk, Val,
 };
 
+#[cfg(feature = "exp-preserve-order")]
+use crate::evaluate;
+
 #[allow(clippy::too_many_lines)]
 #[allow(unused_variables)]
 pub fn destruct<H: BuildHasher>(
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -143,7 +143,7 @@
 					false,
 				) {
 					let fctx = Pending::new();
-					let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());
+					let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
 					let obj = obj.clone();
 					let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
 						Thunk::evaluated(Val::string(field.clone())),
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -98,9 +98,9 @@
 
 	fn call(&self, _loc: CallLocation<'_>, args: &[Option<Thunk<Val>>]) -> Result<Val> {
 		let args = args
-			.into_iter()
+			.iter()
 			.map(|a| a.as_ref().expect("legacy natives have no default params"))
-			.map(|a| a.evaluate())
+			.map(Thunk::evaluate)
 			.collect::<Result<Vec<Val>>>()?;
 		self.handler.call(&args)
 	}
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -1,12 +1,5 @@
 use std::{
-	any::Any,
-	cell::{Cell, RefCell},
-	clone::Clone,
-	collections::hash_map::Entry,
-	fmt::{self, Debug},
-	hash::{Hash, Hasher},
-	num::Saturating,
-	ops::ControlFlow,
+	any::Any, cell::{Cell, RefCell}, clone::Clone, cmp::Reverse, collections::hash_map::Entry, fmt::{self, Debug}, hash::{Hash, Hasher}, num::Saturating, ops::ControlFlow
 };
 
 use educe::Educe;
@@ -39,18 +32,19 @@
 
 	use jrsonnet_gcmodule::Trace;
 
-	#[derive(Clone, Copy, Default, Debug, Trace)]
+	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]
 	pub struct FieldIndex(());
 	impl FieldIndex {
 		pub fn absolute(_v: u32) -> Self {
 			Self(())
 		}
+		#[must_use]
 		pub const fn next(self) -> Self {
 			Self(())
 		}
 	}
 
-	#[derive(Clone, Copy, Default, Debug, Trace)]
+	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]
 	pub struct SuperDepth(());
 	impl SuperDepth {
 		pub(super) fn deepen(self) {}
@@ -59,8 +53,6 @@
 
 #[cfg(feature = "exp-preserve-order")]
 pub mod ordering {
-	use std::cmp::Reverse;
-
 	use jrsonnet_gcmodule::Trace;
 
 	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]
@@ -69,6 +61,7 @@
 		pub fn absolute(v: u32) -> Self {
 			Self(v)
 		}
+		#[must_use]
 		pub fn next(self) -> Self {
 			Self(self.0 + 1)
 		}
@@ -78,22 +71,20 @@
 	pub struct SuperDepth(u32);
 	impl SuperDepth {
 		pub(super) fn deepen(&mut self) {
-			self.0 += 1
+			self.0 += 1;
 		}
 	}
+}
+
+use ordering::{FieldIndex, SuperDepth};
 
-	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
-	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);
-	impl FieldSortKey {
-		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {
-			Self(Reverse(depth), index)
-		}
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
+pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);
+impl FieldSortKey {
+	pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {
+		Self(Reverse(depth), index)
 	}
 }
-
-#[cfg(feature = "exp-preserve-order")]
-use ordering::FieldSortKey;
-use ordering::{FieldIndex, SuperDepth};
 
 // 0 - add
 //  12 - visibility
@@ -795,7 +786,7 @@
 struct FieldVisibilityData {
 	omitted_until: Saturating<usize>,
 	exists_visible: Option<Visibility>,
-	#[cfg(feature = "exp-preserve-order")]
+	#[allow(dead_code, reason = "used for exp-object-ordering, ZST otherwise")]
 	key: FieldSortKey,
 }
 impl FieldVisibilityData {
@@ -804,7 +795,7 @@
 			.expect("non-existing fields shall be dropped at the end of fn fields_visibility()")
 			.is_visible()
 	}
-	#[cfg(feature = "exp-preserve-order")]
+	#[allow(dead_code, reason = "used for exp-object-ordering, ZST otherwise")]
 	fn sort_key(&self) -> FieldSortKey {
 		self.key
 	}
@@ -818,12 +809,11 @@
 		let mut omit_index = Saturating(0);
 		for core in self.0.cores.iter().rev() {
 			core.0
-				.enum_fields_core(&mut super_depth, &mut |_depth, _index, name, visibility| {
+				.enum_fields_core(&mut super_depth, &mut |depth, index, name, visibility| {
 					let entry = out.entry(name);
-					let data = entry.or_insert(FieldVisibilityData {
+					let data = entry.or_insert_with(|| FieldVisibilityData {
 						exists_visible: None,
-						#[cfg(feature = "exp-preserve-order")]
-						key: FieldSortKey::new(_depth, _index),
+						key: FieldSortKey::new(depth, index),
 						omitted_until: omit_index,
 					});
 					match visibility {
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	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}6364pub trait ParseTypedObj: Typed {65	fn parse(obj: &ObjValue) -> Result<Self>;66}67pub trait SerializeTypedObj: Typed {68	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;69	fn into_object(self) -> Result<ObjValue> {70		let mut builder = ObjValueBuilder::new();71		self.serialize(&mut builder)?;72		Ok(builder.build())73	}74}7576pub trait Typed: Sized {77	const TYPE: &'static ComplexValType;78}79pub trait IntoUntyped: Typed {80	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`81	fn provides_lazy() -> bool {82		false83	}84	fn into_untyped(typed: Self) -> Result<Val>;85	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {86		Thunk::from(Self::into_untyped(typed))87	}88}89pub trait IntoUntypedResult: Typed {90	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result91	/// This method returns identity in impl Typed for Result, and should not be overriden92	#[doc(hidden)]93	fn into_untyped_result(typed: Self) -> Result<Val>;94}95impl<T> IntoUntypedResult for T96where97	T: IntoUntyped,98{99	fn into_untyped_result(typed: Self) -> Result<Val> {100		T::into_untyped(typed)101	}102}103104pub trait FromUntyped: Typed {105	fn from_untyped(untyped: Val) -> Result<Self>;106	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {107		Self::from_untyped(lazy.evaluate()?)108	}109110	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible111	fn wants_lazy() -> bool {112		false113	}114}115116impl<T> Typed for Thunk<T>117where118	T: Typed + Trace + Clone,119{120	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);121}122123impl<T> IntoUntyped for Thunk<T>124where125	T: Typed + IntoUntyped + Trace + Clone,126{127	fn into_untyped(typed: Self) -> Result<Val> {128		T::into_untyped(typed.evaluate()?)129	}130	fn provides_lazy() -> bool {131		true132	}133134	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {135		inner.map(<ThunkIntoUntyped<T>>::default())136	}137}138139impl<T> FromUntyped for Thunk<T>140where141	T: Typed + FromUntyped + Trace + Clone,142{143	fn from_untyped(untyped: Val) -> Result<Self> {144		Self::from_lazy_untyped(Thunk::evaluated(untyped))145	}146147	fn wants_lazy() -> bool {148		true149	}150151	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {152		Ok(inner.map(<ThunkFromUntyped<T>>::default()))153	}154}155156pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;157pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;158159macro_rules! impl_int {160	($($ty:ty)*) => {$(161		impl Typed for $ty {162			const TYPE: &'static ComplexValType =163				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));164		}165		impl FromUntyped for $ty {166			fn from_untyped(value: Val) -> Result<Self> {167				<Self as Typed>::TYPE.check(&value)?;168				match value {169					Val::Num(n) => {170						let n = n.get();171						#[allow(clippy::float_cmp)]172						if n.trunc() != n {173							bail!(174								"cannot convert number with fractional part to {}",175								stringify!($ty)176							)177						}178						Ok(n as Self)179					}180					_ => unreachable!(),181				}182			}183		}184		impl IntoUntyped for $ty {185			fn into_untyped(value: Self) -> Result<Val> {186				Ok(Val::Num(value.into()))187			}188		}189	)*};190}191192impl_int!(i8 u8 i16 u16 i32 u32);193194macro_rules! impl_bounded_int {195	($($name:ident = $ty:ty)*) => {$(196		#[derive(Clone, Copy)]197		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);198		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {199			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {200				if value >= MIN && value <= MAX {201					Some(Self(value))202				} else {203					None204				}205			}206			pub const fn value(self) -> $ty {207				self.0208			}209		}210		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {211			type Target = $ty;212			fn deref(&self) -> &Self::Target {213				&self.0214			}215		}216217		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {218			const TYPE: &'static ComplexValType =219				&ComplexValType::BoundedNumber(220					Some(MIN as f64),221					Some(MAX as f64),222				);223		}224225		impl<const MIN: $ty, const MAX: $ty> FromUntyped for $name<MIN, MAX> {226			fn from_untyped(value: Val) -> Result<Self> {227				<Self as Typed>::TYPE.check(&value)?;228				match value {229					Val::Num(n) => {230						let n = n.get();231						#[allow(clippy::float_cmp)]232						if n.trunc() != n {233							bail!(234								"cannot convert number with fractional part to {}",235								stringify!($ty)236							)237						}238						Ok(Self(n as $ty))239					}240					_ => unreachable!(),241				}242			}243		}244245		impl<const MIN: $ty, const MAX: $ty> IntoUntyped for $name<MIN, MAX> {246			#[allow(clippy::cast_lossless)]247			fn into_untyped(value: Self) -> Result<Val> {248				Ok(Val::try_num(value.0)?)249			}250		}251	)*};252}253254impl_bounded_int!(255	BoundedI8 = i8256	BoundedI16 = i16257	BoundedI32 = i32258	BoundedI64 = i64259	BoundedUsize = usize260);261262impl Typed for f64 {263	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);264}265impl IntoUntyped for f64 {266	fn into_untyped(value: Self) -> Result<Val> {267		Ok(Val::try_num(value)?)268	}269}270impl FromUntyped for f64 {271	fn from_untyped(value: Val) -> Result<Self> {272		<Self as Typed>::TYPE.check(&value)?;273		match value {274			Val::Num(n) => Ok(n.get()),275			_ => unreachable!(),276		}277	}278}279280pub struct PositiveF64(pub f64);281impl Typed for PositiveF64 {282	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);283}284impl IntoUntyped for PositiveF64 {285	fn into_untyped(value: Self) -> Result<Val> {286		Ok(Val::try_num(value.0)?)287	}288}289impl FromUntyped for PositiveF64 {290	fn from_untyped(value: Val) -> Result<Self> {291		<Self as Typed>::TYPE.check(&value)?;292		match value {293			Val::Num(n) => Ok(Self(n.get())),294			_ => unreachable!(),295		}296	}297}298impl Typed for usize {299	const TYPE: &'static ComplexValType =300		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));301}302impl IntoUntyped for usize {303	fn into_untyped(value: Self) -> Result<Val> {304		Ok(Val::try_num(value)?)305	}306}307impl FromUntyped for usize {308	fn from_untyped(value: Val) -> Result<Self> {309		<Self as Typed>::TYPE.check(&value)?;310		match value {311			Val::Num(n) => {312				let n = n.get();313				#[allow(clippy::float_cmp)]314				if n.trunc() != n {315					bail!("cannot convert number with fractional part to usize")316				}317				Ok(n as Self)318			}319			_ => unreachable!(),320		}321	}322}323324impl Typed for IStr {325	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);326}327impl IntoUntyped for IStr {328	fn into_untyped(value: Self) -> Result<Val> {329		Ok(Val::string(value))330	}331}332impl FromUntyped for IStr {333	fn from_untyped(value: Val) -> Result<Self> {334		<Self as Typed>::TYPE.check(&value)?;335		match value {336			Val::Str(s) => Ok(s.into_flat()),337			_ => unreachable!(),338		}339	}340}341342impl Typed for String {343	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);344}345impl IntoUntyped for String {346	fn into_untyped(value: Self) -> Result<Val> {347		Ok(Val::string(value))348	}349}350impl FromUntyped for String {351	fn from_untyped(value: Val) -> Result<Self> {352		<Self as Typed>::TYPE.check(&value)?;353		match value {354			Val::Str(s) => Ok(s.to_string()),355			_ => unreachable!(),356		}357	}358}359360impl Typed for StrValue {361	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);362}363impl IntoUntyped for StrValue {364	fn into_untyped(value: Self) -> Result<Val> {365		Ok(Val::Str(value))366	}367}368impl FromUntyped for StrValue {369	fn from_untyped(value: Val) -> Result<Self> {370		<Self as Typed>::TYPE.check(&value)?;371		match value {372			Val::Str(s) => Ok(s),373			_ => unreachable!(),374		}375	}376}377378impl Typed for char {379	const TYPE: &'static ComplexValType = &ComplexValType::Char;380}381impl IntoUntyped for char {382	fn into_untyped(value: Self) -> Result<Val> {383		Ok(Val::string(value))384	}385}386impl FromUntyped for char {387	fn from_untyped(value: Val) -> Result<Self> {388		<Self as Typed>::TYPE.check(&value)?;389		match value {390			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),391			_ => unreachable!(),392		}393	}394}395396// TODO: View into vec using ArrayLike?397impl<T> Typed for Vec<T>398where399	T: Typed,400{401	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);402}403impl<T: Typed + IntoUntyped> IntoUntyped for Vec<T> {404	fn into_untyped(value: Self) -> Result<Val> {405		Ok(Val::Arr(406			value407				.into_iter()408				.map(T::into_untyped)409				.collect::<Result<ArrValue>>()?,410		))411	}412}413impl<T: Typed + FromUntyped> FromUntyped for Vec<T> {414	fn from_untyped(value: Val) -> Result<Self> {415		let Val::Arr(a) = value else {416			<Self as Typed>::TYPE.check(&value)?;417			unreachable!("typecheck should fail")418		};419		a.iter()420			.enumerate()421			.map(|(i, r)| {422				r.and_then(|t| {423					T::from_untyped(t).with_description(|| format!("parsing elem <{i}>"))424				})425			})426			.collect::<Result<Self>>()427	}428}429430// TODO: View into BTreeMap using ObjectCore?431impl<K, V> Typed for BTreeMap<K, V>432where433	K: Typed + Ord,434	V: Typed,435{436	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);437}438impl<K, V> IntoUntyped for BTreeMap<K, V>439where440	K: Typed + Ord + IntoUntyped,441	V: Typed + IntoUntyped,442{443	fn into_untyped(typed: Self) -> Result<Val> {444		let mut out = ObjValueBuilder::with_capacity(typed.len());445		for (k, v) in typed {446			let Some(key) = K::into_untyped(k)?.as_str() else {447				bail!("map key should serialize to string");448			};449			let value = V::into_untyped(v)?;450			out.field(key).value(value);451		}452		Ok(Val::Obj(out.build()))453	}454}455impl<K, V> FromUntyped for BTreeMap<K, V>456where457	K: FromUntyped + Ord,458	V: FromUntyped,459{460	fn from_untyped(value: Val) -> Result<Self> {461		Self::TYPE.check(&value)?;462		let obj = value.as_obj().expect("typecheck should fail");463464		let mut out = Self::new();465		if V::wants_lazy() {466			for key in obj.fields_ex(467				false,468				#[cfg(feature = "exp-preserve-order")]469				false,470			) {471				let value = obj.get_lazy(key.clone()).expect("field exists");472				let value = V::from_lazy_untyped(value)?;473				let key = K::from_untyped(Val::Str(key.into()))?;474				let _ = out.insert(key, value);475			}476		} else {477			for (key, value) in obj.iter(478				#[cfg(feature = "exp-preserve-order")]479				false,480			) {481				let key = K::from_untyped(Val::Str(key.into()))?;482				let value = V::from_untyped(value?)?;483				let _ = out.insert(key, value);484			}485		}486		Ok(out)487	}488}489490impl Typed for Val {491	const TYPE: &'static ComplexValType = &ComplexValType::Any;492}493impl IntoUntyped for Val {494	fn into_untyped(typed: Self) -> Result<Val> {495		Ok(typed)496	}497}498impl FromUntyped for Val {499	fn from_untyped(untyped: Val) -> Result<Self> {500		Ok(untyped)501	}502}503504#[doc(hidden)]505impl<T> Typed for Result<T>506where507	T: Typed,508{509	const TYPE: &'static ComplexValType = &ComplexValType::Any;510}511impl<T: IntoUntyped> IntoUntypedResult for Result<T> {512	fn into_untyped_result(typed: Self) -> Result<Val> {513		typed.map(T::into_untyped)?514	}515}516517/// Specialization518impl Typed for IBytes {519	const TYPE: &'static ComplexValType =520		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));521}522impl IntoUntyped for IBytes {523	fn into_untyped(value: Self) -> Result<Val> {524		Ok(Val::Arr(ArrValue::bytes(value)))525	}526}527impl FromUntyped for IBytes {528	fn from_untyped(value: Val) -> Result<Self> {529		let Val::Arr(a) = &value else {530			<Self as Typed>::TYPE.check(&value)?;531			unreachable!()532		};533		if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {534			return Ok(bytes.0.as_slice().into());535		}536		<Self as Typed>::TYPE.check(&value)?;537		// Any::downcast_ref::<ByteArray>(&a);538		let mut out = Vec::with_capacity(a.len());539		for e in a.iter() {540			let r = e?;541			out.push(u8::from_untyped(r)?);542		}543		Ok(out.as_slice().into())544	}545}546547pub struct M1;548impl Typed for M1 {549	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));550}551impl IntoUntyped for M1 {552	fn into_untyped(_: Self) -> Result<Val> {553		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))554	}555}556impl FromUntyped for M1 {557	fn from_untyped(value: Val) -> Result<Self> {558		<Self as Typed>::TYPE.check(&value)?;559		Ok(Self)560	}561}562563macro_rules! decl_either {564	($($name: ident, $($id: ident)*);*) => {$(565		#[derive(Clone)]566		pub enum $name<$($id),*> {567			$($id($id)),*568		}569		impl<$($id),*> Typed for $name<$($id),*>570		where571			$($id: Typed,)*572		{573			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);574		}575		impl<$($id),*> IntoUntyped for $name<$($id),*>576		where577			$($id: Typed + IntoUntyped,)*578		{579			fn into_untyped(value: Self) -> Result<Val> {580				match value {$(581					$name::$id(v) => $id::into_untyped(v)582				),*}583			}584		}585586		impl<$($id),*> FromUntyped for $name<$($id),*>587		where588			$($id: Typed + FromUntyped,)*589		{590			fn from_untyped(value: Val) -> Result<Self> {591				$(592					if $id::TYPE.check(&value).is_ok() {593						$id::from_untyped(value).map(Self::$id)594					} else595				)* {596					<Self as Typed>::TYPE.check(&value)?;597					unreachable!()598				}599			}600		}601	)*}602}603decl_either!(604	Either1, A;605	Either2, A B;606	Either3, A B C;607	Either4, A B C D;608	Either5, A B C D E;609	Either6, A B C D E F;610	Either7, A B C D E F G611);612#[macro_export]613macro_rules! Either {614	($a:ty) => {$crate::typed::Either1<$a>};615	($a:ty, $b:ty) => {$crate::typed::Either2<$a, $b>};616	($a:ty, $b:ty, $c:ty) => {$crate::typed::Either3<$a, $b, $c>};617	($a:ty, $b:ty, $c:ty, $d:ty) => {$crate::typed::Either4<$a, $b, $c, $d>};618	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {$crate::typed::Either5<$a, $b, $c, $d, $e>};619	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {$crate::typed::Either6<$a, $b, $c, $d, $e, $f>};620	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {$crate::typed::Either7<$a, $b, $c, $d, $e, $f, $g>};621}622pub use Either;623624pub type MyType = Either![u32, f64, String];625626impl Typed for ArrValue {627	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);628}629impl IntoUntyped for ArrValue {630	fn into_untyped(value: Self) -> Result<Val> {631		Ok(Val::Arr(value))632	}633}634impl FromUntyped for ArrValue {635	fn from_untyped(value: Val) -> Result<Self> {636		<Self as Typed>::TYPE.check(&value)?;637		match value {638			Val::Arr(a) => Ok(a),639			_ => unreachable!(),640		}641	}642}643644impl Typed for FuncVal {645	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);646}647impl IntoUntyped for FuncVal {648	fn into_untyped(value: Self) -> Result<Val> {649		Ok(Val::Func(value))650	}651}652impl FromUntyped for FuncVal {653	fn from_untyped(value: Val) -> Result<Self> {654		<Self as Typed>::TYPE.check(&value)?;655		match value {656			Val::Func(a) => Ok(a),657			_ => unreachable!(),658		}659	}660}661662impl Typed for ObjValue {663	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);664}665impl IntoUntyped for ObjValue {666	fn into_untyped(value: Self) -> Result<Val> {667		Ok(Val::Obj(value))668	}669}670impl FromUntyped for ObjValue {671	fn from_untyped(value: Val) -> Result<Self> {672		<Self as Typed>::TYPE.check(&value)?;673		match value {674			Val::Obj(a) => Ok(a),675			_ => unreachable!(),676		}677	}678}679680impl Typed for bool {681	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);682}683impl IntoUntyped for bool {684	fn into_untyped(value: Self) -> Result<Val> {685		Ok(Val::Bool(value))686	}687}688impl FromUntyped for bool {689	fn from_untyped(value: Val) -> Result<Self> {690		<Self as Typed>::TYPE.check(&value)?;691		match value {692			Val::Bool(a) => Ok(a),693			_ => unreachable!(),694		}695	}696}697698impl Typed for IndexableVal {699	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[700		&ComplexValType::Simple(ValType::Arr),701		&ComplexValType::Simple(ValType::Str),702	]);703}704impl IntoUntyped for IndexableVal {705	fn into_untyped(value: Self) -> Result<Val> {706		match value {707			Self::Str(s) => Ok(Val::string(s)),708			Self::Arr(a) => Ok(Val::Arr(a)),709		}710	}711}712impl FromUntyped for IndexableVal {713	fn from_untyped(value: Val) -> Result<Self> {714		<Self as Typed>::TYPE.check(&value)?;715		value.into_indexable()716	}717}718719pub struct Null;720impl Typed for Null {721	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);722}723impl IntoUntyped for Null {724	fn into_untyped(_: Self) -> Result<Val> {725		Ok(Val::Null)726	}727}728impl FromUntyped for Null {729	fn from_untyped(value: Val) -> Result<Self> {730		<Self as Typed>::TYPE.check(&value)?;731		Ok(Self)732	}733}734735impl<T> Typed for Option<T>736where737	T: Typed,738{739	const TYPE: &'static ComplexValType =740		&ComplexValType::UnionRef(&[&ComplexValType::Simple(ValType::Null), T::TYPE]);741}742impl<T> IntoUntyped for Option<T>743where744	T: Typed + IntoUntyped,745{746	fn into_untyped(typed: Self) -> Result<Val> {747		typed.map_or_else(|| Ok(Val::Null), |v| T::into_untyped(v))748	}749}750impl<T> FromUntyped for Option<T>751where752	T: Typed + FromUntyped,753{754	fn from_untyped(untyped: Val) -> Result<Self> {755		if matches!(untyped, Val::Null) {756			Ok(None)757		} else {758			T::from_untyped(untyped).map(Some)759		}760	}761}762763impl Typed for NumValue {764	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);765}766impl IntoUntyped for NumValue {767	fn into_untyped(typed: Self) -> Result<Val> {768		Ok(Val::Num(typed))769	}770}771impl FromUntyped for NumValue {772	fn from_untyped(untyped: Val) -> Result<Self> {773		Self::TYPE.check(&untyped)?;774		match untyped {775			Val::Num(v) => Ok(v),776			_ => unreachable!(),777		}778	}779}
after · 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	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}129130impl<T> IntoUntyped for Thunk<T>131where132	T: Typed + IntoUntyped + Trace + Clone,133{134	fn into_untyped(typed: Self) -> Result<Val> {135		T::into_untyped(typed.evaluate()?)136	}137	fn provides_lazy() -> bool {138		true139	}140141	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {142		inner.map(<ThunkIntoUntyped<T>>::default())143	}144}145146impl<T> FromUntyped for Thunk<T>147where148	T: Typed + FromUntyped + Trace + Clone,149{150	fn from_untyped(untyped: Val) -> Result<Self> {151		Self::from_lazy_untyped(Thunk::evaluated(untyped))152	}153154	fn wants_lazy() -> bool {155		true156	}157158	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {159		Ok(inner.map(<ThunkFromUntyped<T>>::default()))160	}161}162163pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;164pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;165166macro_rules! impl_int {167	($($ty:ty)*) => {$(168		impl Typed for $ty {169			const TYPE: &'static ComplexValType =170				&ComplexValType::BoundedNumber(Some(Self::MIN as f64), Some(Self::MAX as f64));171		}172		impl FromUntyped for $ty {173			fn from_untyped(value: Val) -> Result<Self> {174				<Self as Typed>::TYPE.check(&value)?;175				match value {176					Val::Num(n) => {177						let n = n.get();178						#[allow(clippy::float_cmp)]179						if n.trunc() != n {180							bail!(181								"cannot convert number with fractional part to {}",182								stringify!($ty)183							)184						}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		pub struct $name<const MIN: $ty, const MAX: $ty>($ty);205		impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {206			pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {207				if value >= MIN && value <= MAX {208					Some(Self(value))209				} else {210					None211				}212			}213			pub const fn value(self) -> $ty {214				self.0215			}216		}217		impl<const MIN: $ty, const MAX: $ty> Deref for $name<MIN, MAX> {218			type Target = $ty;219			fn deref(&self) -> &Self::Target {220				&self.0221			}222		}223224		impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {225			const TYPE: &'static ComplexValType =226				&ComplexValType::BoundedNumber(227					Some(MIN as f64),228					Some(MAX as f64),229				);230		}231232		impl<const MIN: $ty, const MAX: $ty> FromUntyped for $name<MIN, MAX> {233			fn from_untyped(value: Val) -> Result<Self> {234				<Self as Typed>::TYPE.check(&value)?;235				match value {236					Val::Num(n) => {237						let n = n.get();238						#[allow(clippy::float_cmp)]239						if n.trunc() != n {240							bail!(241								"cannot convert number with fractional part to {}",242								stringify!($ty)243							)244						}245						Ok(Self(n as $ty))246					}247					_ => unreachable!(),248				}249			}250		}251252		impl<const MIN: $ty, const MAX: $ty> IntoUntyped for $name<MIN, MAX> {253			#[allow(clippy::cast_lossless)]254			fn into_untyped(value: Self) -> Result<Val> {255				Ok(Val::try_num(value.0)?)256			}257		}258	)*};259}260261impl_bounded_int!(262	BoundedI8 = i8263	BoundedI16 = i16264	BoundedI32 = i32265	BoundedI64 = i64266	BoundedUsize = usize267);268269impl Typed for f64 {270	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);271}272impl IntoUntyped for f64 {273	fn into_untyped(value: Self) -> Result<Val> {274		Ok(Val::try_num(value)?)275	}276}277impl FromUntyped for f64 {278	fn from_untyped(value: Val) -> Result<Self> {279		<Self as Typed>::TYPE.check(&value)?;280		match value {281			Val::Num(n) => Ok(n.get()),282			_ => unreachable!(),283		}284	}285}286287pub struct PositiveF64(pub f64);288impl Typed for PositiveF64 {289	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);290}291impl IntoUntyped for PositiveF64 {292	fn into_untyped(value: Self) -> Result<Val> {293		Ok(Val::try_num(value.0)?)294	}295}296impl FromUntyped for PositiveF64 {297	fn from_untyped(value: Val) -> Result<Self> {298		<Self as Typed>::TYPE.check(&value)?;299		match value {300			Val::Num(n) => Ok(Self(n.get())),301			_ => unreachable!(),302		}303	}304}305impl Typed for usize {306	const TYPE: &'static ComplexValType =307		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));308}309impl IntoUntyped for usize {310	fn into_untyped(value: Self) -> Result<Val> {311		Ok(Val::try_num(value)?)312	}313}314impl FromUntyped for usize {315	fn from_untyped(value: Val) -> Result<Self> {316		<Self as Typed>::TYPE.check(&value)?;317		match value {318			Val::Num(n) => {319				let n = n.get();320				#[allow(clippy::float_cmp)]321				if n.trunc() != n {322					bail!("cannot convert number with fractional part to usize")323				}324				Ok(n as Self)325			}326			_ => unreachable!(),327		}328	}329}330331impl Typed for IStr {332	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);333}334impl IntoUntyped for IStr {335	fn into_untyped(value: Self) -> Result<Val> {336		Ok(Val::string(value))337	}338}339impl FromUntyped for IStr {340	fn from_untyped(value: Val) -> Result<Self> {341		<Self as Typed>::TYPE.check(&value)?;342		match value {343			Val::Str(s) => Ok(s.into_flat()),344			_ => unreachable!(),345		}346	}347}348349impl Typed for String {350	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);351}352impl IntoUntyped for String {353	fn into_untyped(value: Self) -> Result<Val> {354		Ok(Val::string(value))355	}356}357impl FromUntyped for String {358	fn from_untyped(value: Val) -> Result<Self> {359		<Self as Typed>::TYPE.check(&value)?;360		match value {361			Val::Str(s) => Ok(s.to_string()),362			_ => unreachable!(),363		}364	}365}366367impl Typed for StrValue {368	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);369}370impl IntoUntyped for StrValue {371	fn into_untyped(value: Self) -> Result<Val> {372		Ok(Val::Str(value))373	}374}375impl FromUntyped for StrValue {376	fn from_untyped(value: Val) -> Result<Self> {377		<Self as Typed>::TYPE.check(&value)?;378		match value {379			Val::Str(s) => Ok(s),380			_ => unreachable!(),381		}382	}383}384385impl Typed for char {386	const TYPE: &'static ComplexValType = &ComplexValType::Char;387}388impl IntoUntyped for char {389	fn into_untyped(value: Self) -> Result<Val> {390		Ok(Val::string(value))391	}392}393impl FromUntyped for char {394	fn from_untyped(value: Val) -> Result<Self> {395		<Self as Typed>::TYPE.check(&value)?;396		match value {397			Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),398			_ => unreachable!(),399		}400	}401}402403// TODO: View into vec using ArrayLike?404impl<T> Typed for Vec<T>405where406	T: Typed,407{408	const TYPE: &'static ComplexValType = &ComplexValType::ArrayRef(T::TYPE);409}410impl<T: Typed + IntoUntyped> IntoUntyped for Vec<T> {411	fn into_untyped(value: Self) -> Result<Val> {412		Ok(Val::Arr(413			value414				.into_iter()415				.map(T::into_untyped)416				.collect::<Result<ArrValue>>()?,417		))418	}419}420impl<T: Typed + FromUntyped> FromUntyped for Vec<T> {421	fn from_untyped(value: Val) -> Result<Self> {422		let Val::Arr(a) = value else {423			<Self as Typed>::TYPE.check(&value)?;424			unreachable!("typecheck should fail")425		};426		a.iter()427			.enumerate()428			.map(|(i, r)| {429				r.and_then(|t| {430					T::from_untyped(t).with_description(|| format!("parsing elem <{i}>"))431				})432			})433			.collect::<Result<Self>>()434	}435}436437// TODO: View into BTreeMap using ObjectCore?438impl<K, V> Typed for BTreeMap<K, V>439where440	K: Typed + Ord,441	V: Typed,442{443	const TYPE: &'static ComplexValType = &ComplexValType::AttrsOf(V::TYPE);444}445impl<K, V> IntoUntyped for BTreeMap<K, V>446where447	K: Typed + Ord + IntoUntyped,448	V: Typed + IntoUntyped,449{450	fn into_untyped(typed: Self) -> Result<Val> {451		let mut out = ObjValueBuilder::with_capacity(typed.len());452		for (k, v) in typed {453			let Some(key) = K::into_untyped(k)?.as_str() else {454				bail!("map key should serialize to string");455			};456			let value = V::into_untyped(v)?;457			out.field(key).value(value);458		}459		Ok(Val::Obj(out.build()))460	}461}462impl<K, V> FromUntyped for BTreeMap<K, V>463where464	K: FromUntyped + Ord,465	V: FromUntyped,466{467	fn from_untyped(value: Val) -> Result<Self> {468		Self::TYPE.check(&value)?;469		let obj = value.as_obj().expect("typecheck should fail");470471		let mut out = Self::new();472		if V::wants_lazy() {473			for key in obj.fields_ex(474				false,475				#[cfg(feature = "exp-preserve-order")]476				false,477			) {478				let value = obj.get_lazy(key.clone()).expect("field exists");479				let value = V::from_lazy_untyped(value)?;480				let key = K::from_untyped(Val::Str(key.into()))?;481				let _ = out.insert(key, value);482			}483		} else {484			for (key, value) in obj.iter(485				#[cfg(feature = "exp-preserve-order")]486				false,487			) {488				let key = K::from_untyped(Val::Str(key.into()))?;489				let value = V::from_untyped(value?)?;490				let _ = out.insert(key, value);491			}492		}493		Ok(out)494	}495}496497impl Typed for Val {498	const TYPE: &'static ComplexValType = &ComplexValType::Any;499}500impl IntoUntyped for Val {501	fn into_untyped(typed: Self) -> Result<Val> {502		Ok(typed)503	}504}505impl FromUntyped for Val {506	fn from_untyped(untyped: Val) -> Result<Self> {507		Ok(untyped)508	}509}510511#[doc(hidden)]512impl<T> Typed for Result<T>513where514	T: Typed,515{516	const TYPE: &'static ComplexValType = &ComplexValType::Any;517}518impl<T: IntoUntyped> IntoUntypedResult for Result<T> {519	fn into_untyped_result(typed: Self) -> Result<Val> {520		typed.map(T::into_untyped)?521	}522}523524/// Specialization525impl Typed for IBytes {526	const TYPE: &'static ComplexValType =527		&ComplexValType::ArrayRef(&ComplexValType::BoundedNumber(Some(0.0), Some(255.0)));528}529impl IntoUntyped for IBytes {530	fn into_untyped(value: Self) -> Result<Val> {531		Ok(Val::Arr(ArrValue::bytes(value)))532	}533}534impl FromUntyped for IBytes {535	fn from_untyped(value: Val) -> Result<Self> {536		let Val::Arr(a) = &value else {537			<Self as Typed>::TYPE.check(&value)?;538			unreachable!()539		};540		if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {541			return Ok(bytes.0.as_slice().into());542		}543		<Self as Typed>::TYPE.check(&value)?;544		// Any::downcast_ref::<ByteArray>(&a);545		let mut out = Vec::with_capacity(a.len());546		for e in a.iter() {547			let r = e?;548			out.push(u8::from_untyped(r)?);549		}550		Ok(out.as_slice().into())551	}552}553554pub struct M1;555impl Typed for M1 {556	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));557}558impl IntoUntyped for M1 {559	fn into_untyped(_: Self) -> Result<Val> {560		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))561	}562}563impl FromUntyped for M1 {564	fn from_untyped(value: Val) -> Result<Self> {565		<Self as Typed>::TYPE.check(&value)?;566		Ok(Self)567	}568}569570macro_rules! decl_either {571	($($name: ident, $($id: ident)*);*) => {$(572		#[derive(Clone)]573		pub enum $name<$($id),*> {574			$($id($id)),*575		}576		impl<$($id),*> Typed for $name<$($id),*>577		where578			$($id: Typed,)*579		{580			const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[$($id::TYPE),*]);581		}582		impl<$($id),*> IntoUntyped for $name<$($id),*>583		where584			$($id: Typed + IntoUntyped,)*585		{586			fn into_untyped(value: Self) -> Result<Val> {587				match value {$(588					$name::$id(v) => $id::into_untyped(v)589				),*}590			}591		}592593		impl<$($id),*> FromUntyped for $name<$($id),*>594		where595			$($id: Typed + FromUntyped,)*596		{597			fn from_untyped(value: Val) -> Result<Self> {598				$(599					if $id::TYPE.check(&value).is_ok() {600						$id::from_untyped(value).map(Self::$id)601					} else602				)* {603					<Self as Typed>::TYPE.check(&value)?;604					unreachable!()605				}606			}607		}608	)*}609}610decl_either!(611	Either1, A;612	Either2, A B;613	Either3, A B C;614	Either4, A B C D;615	Either5, A B C D E;616	Either6, A B C D E F;617	Either7, A B C D E F G618);619#[macro_export]620macro_rules! Either {621	($a:ty) => {$crate::typed::Either1<$a>};622	($a:ty, $b:ty) => {$crate::typed::Either2<$a, $b>};623	($a:ty, $b:ty, $c:ty) => {$crate::typed::Either3<$a, $b, $c>};624	($a:ty, $b:ty, $c:ty, $d:ty) => {$crate::typed::Either4<$a, $b, $c, $d>};625	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty) => {$crate::typed::Either5<$a, $b, $c, $d, $e>};626	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {$crate::typed::Either6<$a, $b, $c, $d, $e, $f>};627	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {$crate::typed::Either7<$a, $b, $c, $d, $e, $f, $g>};628}629pub use Either;630631pub type MyType = Either![u32, f64, String];632633impl Typed for ArrValue {634	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);635}636impl IntoUntyped for ArrValue {637	fn into_untyped(value: Self) -> Result<Val> {638		Ok(Val::Arr(value))639	}640}641impl FromUntyped for ArrValue {642	fn from_untyped(value: Val) -> Result<Self> {643		<Self as Typed>::TYPE.check(&value)?;644		match value {645			Val::Arr(a) => Ok(a),646			_ => unreachable!(),647		}648	}649}650651impl Typed for FuncVal {652	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);653}654impl IntoUntyped for FuncVal {655	fn into_untyped(value: Self) -> Result<Val> {656		Ok(Val::Func(value))657	}658}659impl FromUntyped for FuncVal {660	fn from_untyped(value: Val) -> Result<Self> {661		<Self as Typed>::TYPE.check(&value)?;662		match value {663			Val::Func(a) => Ok(a),664			_ => unreachable!(),665		}666	}667}668669impl Typed for ObjValue {670	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);671}672impl IntoUntyped for ObjValue {673	fn into_untyped(value: Self) -> Result<Val> {674		Ok(Val::Obj(value))675	}676}677impl FromUntyped for ObjValue {678	fn from_untyped(value: Val) -> Result<Self> {679		<Self as Typed>::TYPE.check(&value)?;680		match value {681			Val::Obj(a) => Ok(a),682			_ => unreachable!(),683		}684	}685}686687impl Typed for bool {688	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Bool);689}690impl IntoUntyped for bool {691	fn into_untyped(value: Self) -> Result<Val> {692		Ok(Val::Bool(value))693	}694}695impl FromUntyped for bool {696	fn from_untyped(value: Val) -> Result<Self> {697		<Self as Typed>::TYPE.check(&value)?;698		match value {699			Val::Bool(a) => Ok(a),700			_ => unreachable!(),701		}702	}703}704705impl Typed for IndexableVal {706	const TYPE: &'static ComplexValType = &ComplexValType::UnionRef(&[707		&ComplexValType::Simple(ValType::Arr),708		&ComplexValType::Simple(ValType::Str),709	]);710}711impl IntoUntyped for IndexableVal {712	fn into_untyped(value: Self) -> Result<Val> {713		match value {714			Self::Str(s) => Ok(Val::string(s)),715			Self::Arr(a) => Ok(Val::Arr(a)),716		}717	}718}719impl FromUntyped for IndexableVal {720	fn from_untyped(value: Val) -> Result<Self> {721		<Self as Typed>::TYPE.check(&value)?;722		value.into_indexable()723	}724}725726pub struct Null;727impl Typed for Null {728	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Null);729}730impl IntoUntyped for Null {731	fn into_untyped(_: Self) -> Result<Val> {732		Ok(Val::Null)733	}734}735impl FromUntyped for Null {736	fn from_untyped(value: Val) -> Result<Self> {737		<Self as Typed>::TYPE.check(&value)?;738		Ok(Self)739	}740}741742impl<T> Typed for Option<T>743where744	T: Typed,745{746	const TYPE: &'static ComplexValType =747		&ComplexValType::UnionRef(&[&ComplexValType::Simple(ValType::Null), T::TYPE]);748}749impl<T> IntoUntyped for Option<T>750where751	T: Typed + IntoUntyped,752{753	fn into_untyped(typed: Self) -> Result<Val> {754		typed.map_or_else(|| Ok(Val::Null), |v| T::into_untyped(v))755	}756}757impl<T> FromUntyped for Option<T>758where759	T: Typed + FromUntyped,760{761	fn from_untyped(untyped: Val) -> Result<Self> {762		if matches!(untyped, Val::Null) {763			Ok(None)764		} else {765			T::from_untyped(untyped).map(Some)766		}767	}768}769770impl Typed for NumValue {771	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);772}773impl IntoUntyped for NumValue {774	fn into_untyped(typed: Self) -> Result<Val> {775		Ok(Val::Num(typed))776	}777}778impl FromUntyped for NumValue {779	fn from_untyped(untyped: Val) -> Result<Self> {780		Self::TYPE.check(&untyped)?;781		match untyped {782			Val::Num(v) => Ok(v),783			_ => unreachable!(),784		}785	}786}
modifiedcrates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/comments.rs
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -136,7 +136,7 @@
 								}
 								line = new_line.to_string();
 							}
-							p!(out, string(line.to_string()) nl);
+							p!(out, string(line.clone()) nl);
 						}
 					}
 					if doc {
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -37,8 +37,7 @@
 		format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);
 		items.into_rc_path()
 	};
-	let items =
-		new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();
+	let items = new_line_group(pi!(@i; items(o) items(end_comments_items.into()))).into_rc_path();
 
 	let indented = with_indent(pi!(@i; nl items(items.into())));
 
@@ -355,48 +354,48 @@
 }
 impl Printable for ArgsDesc {
 	fn print(&self, out: &mut PrintItems) {
-		let start = LineNumber::new("args start line");
-		let end = LineNumber::new("args end line");
-		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {
-			is_multiple_lines(condition_context, start, end)
-		});
-
-		let (children, end_comments) = children_between::<Arg>(
-			self.syntax().clone(),
-			self.l_paren_token().map(Into::into).as_ref(),
-			self.r_paren_token().map(Into::into).as_ref(),
-			None,
-		);
-
 		fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {
-			let mut _out = PrintItems::new();
-			let out = &mut _out;
+			let mut out = PrintItems::new();
 
 			let mut args = children.into_iter().peekable();
 			while let Some(ele) = args.next() {
 				if ele.should_start_with_newline {
 					p!(out, nl);
 				}
-				format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);
+				format_comments(&ele.before_trivia, CommentLocation::AboveItem, &mut out);
 				let arg = ele.value;
 				if arg.name().is_some() || arg.assign_token().is_some() {
-					p!(out, {arg.name()} str(" = "));
+					p!(&mut out, {arg.name()} str(" = "));
 				}
-				p!(out, { arg.expr() });
+				p!(&mut out, { arg.expr() });
 				let has_more = args.peek().is_some();
 				if has_more {
 					p!(out, str(","));
 				} else {
 					p!(out, if("trailing comma", multi_line, str(",")));
 				}
-				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);
+				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, &mut out);
 				if has_more {
 					p!(out, if_else("arg separator", multi_line, nl)(sonl));
 				}
 			}
-			_out
+
+			out
 		}
 
+		let start = LineNumber::new("args start line");
+		let end = LineNumber::new("args end line");
+		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {
+			is_multiple_lines(condition_context, start, end)
+		});
+
+		let (children, end_comments) = children_between::<Arg>(
+			self.syntax().clone(),
+			self.l_paren_token().map(Into::into).as_ref(),
+			self.r_paren_token().map(Into::into).as_ref(),
+			None,
+		);
+
 		let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();
 		let args_indented = with_indent(pi!(@i; nl items(args_items.into())));
 
@@ -447,6 +446,7 @@
 }
 
 impl Printable for ObjBody {
+	#[allow(clippy::too_many_lines)]
 	fn print(&self, out: &mut PrintItems) {
 		match self {
 			Self::ObjBodyComp(l) => {
@@ -507,6 +507,30 @@
 				p!(out, nl <i str("}"));
 			}
 			Self::ObjBodyMemberList(l) => {
+				fn gen_members(
+					children: Vec<Child<Member>>,
+					multi_line: ConditionResolver,
+				) -> PrintItems {
+					let mut out = PrintItems::new();
+					let mut members = children.into_iter().peekable();
+					while let Some(mem) = members.next() {
+						if mem.should_start_with_newline {
+							p!(out, nl);
+						}
+						format_comments(&mem.before_trivia, CommentLocation::AboveItem, &mut out);
+						p!(&mut out, { mem.value });
+						let has_more = members.peek().is_some();
+						if has_more {
+							p!(out, str(","));
+						} else {
+							p!(out, if("trailing comma", multi_line, str(",")));
+						}
+						format_comments(&mem.inline_trivia, CommentLocation::ItemInline, &mut out);
+						p!(out, if_else("member separator", multi_line, nl)(sonl));
+					}
+					out
+				}
+
 				let (children, end_comments) = children_between::<Member>(
 					l.syntax().clone(),
 					l.l_brace_token().map(Into::into).as_ref(),
@@ -531,31 +555,6 @@
 					})
 				};
 
-				fn gen_members(
-					children: Vec<Child<Member>>,
-					multi_line: ConditionResolver,
-				) -> PrintItems {
-					let mut _out = PrintItems::new();
-					let out = &mut _out;
-					let mut members = children.into_iter().peekable();
-					while let Some(mem) = members.next() {
-						if mem.should_start_with_newline {
-							p!(out, nl);
-						}
-						format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
-						p!(out, { mem.value });
-						let has_more = members.peek().is_some();
-						if has_more {
-							p!(out, str(","));
-						} else {
-							p!(out, if("trailing comma", multi_line, str(",")));
-						}
-						format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
-						p!(out, if_else("member separator", multi_line, nl)(sonl));
-					}
-					_out
-				}
-
 				let members_items =
 					new_line_group(gen_members(children, multi_line.clone())).into_rc_path();
 
@@ -718,6 +717,27 @@
 
 impl Printable for ExprArray {
 	fn print(&self, out: &mut PrintItems) {
+		fn gen_elements(children: Vec<Child<Expr>>, multi_line: ConditionResolver) -> PrintItems {
+			let mut out = PrintItems::new();
+			let mut els = children.into_iter().peekable();
+			while let Some(el) = els.next() {
+				if el.should_start_with_newline {
+					p!(out, nl);
+				}
+				format_comments(&el.before_trivia, CommentLocation::AboveItem, &mut out);
+				p!(&mut out, { el.value });
+				let has_more = els.peek().is_some();
+				if has_more {
+					p!(out, str(","));
+				} else {
+					p!(out, if("trailing comma", multi_line, str(",")));
+				}
+				format_comments(&el.inline_trivia, CommentLocation::ItemInline, &mut out);
+				p!(out, if_else("element separator", multi_line, nl)(sonl));
+			}
+			out
+		}
+
 		let (children, end_comments) = children_between::<Expr>(
 			self.syntax().clone(),
 			self.l_brack_token().map(Into::into).as_ref(),
@@ -740,28 +760,6 @@
 			Rc::new(move |ctx: &mut ConditionResolverContext| is_multiple_lines(ctx, start, end))
 		};
 
-		fn gen_elements(children: Vec<Child<Expr>>, multi_line: ConditionResolver) -> PrintItems {
-			let mut _out = PrintItems::new();
-			let out = &mut _out;
-			let mut els = children.into_iter().peekable();
-			while let Some(el) = els.next() {
-				if el.should_start_with_newline {
-					p!(out, nl);
-				}
-				format_comments(&el.before_trivia, CommentLocation::AboveItem, out);
-				p!(out, { el.value });
-				let has_more = els.peek().is_some();
-				if has_more {
-					p!(out, str(","));
-				} else {
-					p!(out, if("trailing comma", multi_line, str(",")));
-				}
-				format_comments(&el.inline_trivia, CommentLocation::ItemInline, out);
-				p!(out, if_else("element separator", multi_line, nl)(sonl))
-			}
-			_out
-		}
-
 		let els_items = new_line_group(gen_elements(children, multi_line.clone())).into_rc_path();
 
 		let els = with_indent_eoi(multi_line, els_items.into(), end_comments);
@@ -800,7 +798,7 @@
 			Self::ExprString(s) => p!(out, { s.text() }),
 			Self::ExprNumber(n) => p!(out, { n.number() }),
 			Self::ExprArray(a) => {
-				p!(out, { a })
+				p!(out, { a });
 			}
 			Self::ExprObject(obj) => {
 				p!(out, { obj.obj_body() });
@@ -860,6 +858,11 @@
 	// 0 for hard tabs
 	pub indent: u8,
 }
+
+#[allow(
+	clippy::result_large_err,
+	reason = "TODO: there should be an intermediate representation for such reports"
+)]
 pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {
 	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
 	if !errors.is_empty() {
modifiedcrates/jrsonnet-macros/src/typed.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/typed.rs
+++ b/crates/jrsonnet-macros/src/typed.rs
@@ -156,7 +156,7 @@
 		// optional flatten is handled in same way as serde
 		if self.attr.flatten {
 			return quote! {
-				#ident: <#ty as TypedObj>::parse(&obj).ok(),
+				#ident: <#ty as ParseTypedObj>::parse(&obj).ok(),
 			};
 		}
 
@@ -190,7 +190,7 @@
 		// optional flatten is handled in same way as serde
 		if self.attr.flatten {
 			return quote! {
-				#ident: <#ty as TypedObj>::parse(&obj)?,
+				#ident: <#ty as ParseTypedObj>::parse(&obj)?,
 			};
 		}
 
@@ -232,12 +232,12 @@
 				if self.is_option {
 					quote! {
 						if let Some(value) = self.#ident {
-							<#ty as TypedObj>::serialize(value, out)?;
+							<#ty as SerializeTypedObj>::serialize(value, out)?;
 						}
 					}
 				} else {
 					quote! {
-						<#ty as TypedObj>::serialize(self.#ident, out)?;
+						<#ty as SerializeTypedObj>::serialize(self.#ident, out)?;
 					}
 				}
 			},
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -224,7 +224,8 @@
 	},
 	#[cfg(feature = "exp-destruct")]
 	Object {
-		fields: Vec<(IStr, Option<Destruct>, Option<Spanned<Expr>>)>,
+		#[allow(clippy::type_complexity)]
+		fields: Vec<(IStr, Option<Destruct>, Option<Rc<Spanned<Expr>>>)>,
 		rest: Option<DestructRest>,
 	},
 }
@@ -261,7 +262,7 @@
 				let mut out = 0;
 				for (_, into, _) in fields {
 					match into {
-						Some(v) => out += v.capacity_hint(),
+						Some(v) => out += v.binds_len(),
 						// Field is destructured to default name
 						None => out += 1,
 					}
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -119,7 +119,7 @@
 			}
 		pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
 			= "{" _
-				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default)})**comma()
+				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma()
 				rest:(
 					comma() rest:destruct_rest()? {rest}
 					/ comma()? {None}
modifiedcrates/jrsonnet-rowan-parser/src/ast.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/ast.rs
+++ b/crates/jrsonnet-rowan-parser/src/ast.rs
@@ -2,8 +2,9 @@
 
 use crate::{SyntaxKind, SyntaxNode, SyntaxNodeChildren, SyntaxToken};
 
-/// The main trait to go from untyped `SyntaxNode`  to a typed ast. The
-/// conversion itself has zero runtime cost: ast and syntax nodes have exactly
+/// The main trait to go from untyped `SyntaxNode`  to a typed ast.
+///
+/// The conversion itself has zero runtime cost: ast and syntax nodes have exactly
 /// the same representation: a pointer to the tree root and a pointer to the
 /// node itself.
 pub trait AstNode {
modifiedcrates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/event.rs
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -56,7 +56,7 @@
 	fn text_offset(&self) -> TextSize {
 		if self.offset == 0 {
 			return 0.into();
-		};
+		}
 		self.lexemes.get(self.offset).map_or_else(
 			|| {
 				self.lexemes
modifiedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/lib.rs
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -26,7 +26,7 @@
 
 use self::{
 	ast::support,
-	generated::nodes::{Expr, ExprBinary, ExprObjExtend},
+	generated::nodes::{Expr, ExprObjExtend},
 };
 
 pub fn parse(input: &str) -> (SourceFile, Vec<LocatedSyntaxError>) {
modifiedcrates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/marker.rs
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -141,7 +141,7 @@
 		new_m
 	}
 	/// Create new node around existing marker
-	/// If previous_pos is set - the wrapping node would not include everything that happened between wrapped node end and the current position of the parser
+	/// If `previous_pos` is set - the wrapping node would not include everything that happened between wrapped node end and the current position of the parser
 	fn wrap_raw(
 		self,
 		p: &mut Parser,
modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/parser.rs
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -52,8 +52,7 @@
 				write!(f, "unexpected {found:?}, expecting {expected}")
 			}
 			SyntaxError::Missing { expected } => write!(f, "missing {expected}"),
-			SyntaxError::Custom { error } => write!(f, "{error}"),
-			SyntaxError::Hint { error } => write!(f, "{error}"),
+			SyntaxError::Custom { error } | SyntaxError::Hint { error } => write!(f, "{error}"),
 		}
 	}
 }
@@ -492,7 +491,7 @@
 			} else {
 				m.complete(p, MEMBER_FIELD_NORMAL)
 			};
-		};
+		}
 		while p.at_ts(COMPSPEC) {
 			compspecs.push(compspec(p));
 		}
@@ -747,7 +746,7 @@
 	if p.at(T![:]) {
 		p.bump();
 		destruct(p);
-	};
+	}
 	if p.at(T![=]) {
 		p.bump();
 		expr(p);
modifiedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/string_block.rs
+++ b/crates/jrsonnet-rowan-parser/src/string_block.rs
@@ -11,7 +11,7 @@
 
 use crate::SyntaxKind;
 
-pub(crate) fn lex_str_block_test<'d>(lex: &mut Lexer<'d, SyntaxKind>) {
+pub(crate) fn lex_str_block_test(lex: &mut Lexer<'_, SyntaxKind>) {
 	let _ = lex_str_block(lex);
 }
 
@@ -48,7 +48,7 @@
 	}
 
 	fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {
-		if self.peek().map(f).unwrap_or(false) {
+		if self.peek().is_some_and(f) {
 			self.index += 1;
 			return 1;
 		}
@@ -141,9 +141,7 @@
 	}
 }
 
-pub fn collect_lexed_str_block<'s>(
-	input: &'s str,
-) -> Result<CollectStrBlock<'s>, StringBlockError> {
+pub fn collect_lexed_str_block(input: &str) -> Result<CollectStrBlock<'_>, StringBlockError> {
 	let mut collect = CollectStrBlock {
 		truncate: false,
 		lines: vec![],
@@ -179,7 +177,7 @@
 	}
 
 	fn mark_line(&mut self, line: &'d str) {
-		self.lines.push(line)
+		self.lines.push(line);
 	}
 }
 
modifiedcrates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/tests.rs
+++ b/crates/jrsonnet-rowan-parser/src/tests.rs
@@ -2,7 +2,6 @@
 #![cfg(test)]
 
 use hi_doc::{Formatting, SnippetBuilder, Text};
-use thiserror::Error;
 
 use crate::{parse, AstNode};
 
@@ -14,7 +13,7 @@
 	if !errors.is_empty() && !text.is_empty() {
 		writeln!(out, "===").unwrap();
 		for err in &errors {
-			writeln!(out, "{:?}", err).unwrap();
+			writeln!(out, "{err:?}").unwrap();
 		}
 		let mut code = text.to_string();
 
modifiedcrates/jrsonnet-stdlib/src/regex.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/regex.rs
+++ b/crates/jrsonnet-stdlib/src/regex.rs
@@ -4,7 +4,7 @@
 use jrsonnet_evaluator::{
 	error::{ErrorKind::*, Result},
 	rustc_hash::FxBuildHasher,
-	typed::Typed,
+	typed::{IntoUntyped, Typed},
 	val::StrValue,
 	IStr, ObjValue, ObjValueBuilder,
 };
@@ -41,7 +41,7 @@
 	}
 }
 
-#[derive(Typed)]
+#[derive(Typed, IntoUntyped)]
 pub struct RegexMatch {
 	string: IStr,
 	captures: Vec<IStr>,
modifiedtests/tests/common.rsdiffbeforeafterboth
--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -41,6 +41,7 @@
 }
 
 #[builtin]
+#[allow(dead_code)]
 fn assert_throw(lazy: Thunk<Val>, message: String) -> Result<bool> {
 	match lazy.evaluate() {
 		Ok(_) => {
@@ -55,6 +56,7 @@
 }
 
 #[builtin]
+#[allow(dead_code)]
 fn param_names(fun: FuncVal) -> Vec<String> {
 	fun.params()
 		.iter()
modifiedtests/tests/typed_obj.rsdiffbeforeafterboth
--- a/tests/tests/typed_obj.rs
+++ b/tests/tests/typed_obj.rs
@@ -9,7 +9,7 @@
 };
 use jrsonnet_stdlib::ContextInitializer;
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct A {
 	a: u32,
 	b: u16,
@@ -39,7 +39,7 @@
 	Ok(())
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct B {
 	a: u32,
 	#[typed(rename = "c")]
@@ -62,7 +62,7 @@
 	Ok(())
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct ObjectKind {
 	#[typed(rename = "apiVersion")]
 	api_version: String,
@@ -70,7 +70,7 @@
 	kind: String,
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct Object {
 	#[typed(flatten)]
 	kind: ObjectKind,
@@ -104,7 +104,7 @@
 	Ok(())
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct C {
 	a: Option<u32>,
 	b: u16,
@@ -142,14 +142,14 @@
 	Ok(())
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct D {
 	#[typed(flatten(ok))]
 	e: Option<E>,
 	b: u16,
 }
 
-#[derive(Clone, Typed, PartialEq, Debug)]
+#[derive(Clone, Typed, FromUntyped, IntoUntyped, PartialEq, Debug)]
 struct E {
 	v: u32,
 }
modifiedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/mod.rs
+++ b/xtask/src/sourcegen/mod.rs
@@ -65,9 +65,9 @@
 							is_lexer_error: true,
 						});
 					}
-				};
+				}
 				continue;
-			};
+			}
 			let name = to_upper_snake_case(token);
 			eprintln!("implicit kw: {token}");
 			kinds.define_token(TokenKind::Keyword {
@@ -447,7 +447,7 @@
 			let trait_name = format_ident!("{}", trait_name);
 			let kinds: Vec<_> = nodes
 				.iter()
-				.map(|name| format_ident!("{}", to_upper_snake_case(&name.name.to_string())))
+				.map(|name| format_ident!("{}", to_upper_snake_case(&name.name)))
 				.collect();
 
 			(
@@ -555,10 +555,10 @@
 	if "{}[]()$".contains(token) {
 		let c = token.chars().next().unwrap();
 		quote! { #c }
-	} else if token.contains(|v| v == '$') {
+	} else if token.contains('$') {
 		quote! { #token }
-	} else if token.chars().all(|v| ('a'..='z').contains(&v)) {
-		let i = Ident::new(&token, Span::call_site());
+	} else if token.chars().all(|v: char| v.is_ascii_lowercase()) {
+		let i = Ident::new(token, Span::call_site());
 		quote! { #i }
 	} else {
 		let cs = token.chars().map(|c| Punct::new(c, Spacing::Joint));