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

difftreelog

style fix clippy warnings

Yaroslav Bolyukin2024-01-16parent: #4a33c1a.patch.diff
in: master

17 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -372,7 +372,7 @@
 	pub fn new_inclusive(start: i32, end: i32) -> Self {
 		Self { start, end }
 	}
-	fn range(&self) -> impl Iterator<Item = i32> + ExactSizeIterator + DoubleEndedIterator {
+	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {
 		WithExactSize(
 			self.start..=self.end,
 			(self.end as usize)
@@ -461,7 +461,7 @@
 			ArrayThunk::Waiting(..) => {}
 		};
 
-		let ArrayThunk::Waiting(_) =
+		let ArrayThunk::Waiting(()) =
 			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
 		else {
 			unreachable!()
@@ -508,7 +508,7 @@
 		match &self.cached.borrow()[index] {
 			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
 			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
-			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
+			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
 		};
 
 		Some(Thunk::new(ArrayElement {
@@ -597,9 +597,7 @@
 	}
 
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		let Some(key) = self.keys.get(index) else {
-			return None;
-		};
+		let key = self.keys.get(index)?;
 		Some(self.obj.get_lazy_or_bail(key.clone()))
 	}
 
@@ -649,9 +647,7 @@
 	}
 
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		let Some(key) = self.keys.get(index) else {
-			return None;
-		};
+		let key = self.keys.get(index)?;
 		// Nothing can fail in the key part, yet value is still
 		// lazy-evaluated
 		Some(Thunk::evaluated(
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -89,7 +89,7 @@
 	specs: &[CompSpec],
 	callback: &mut impl FnMut(Context) -> Result<()>,
 ) -> Result<()> {
-	match specs.get(0) {
+	match specs.first() {
 		None => callback(ctx)?,
 		Some(CompSpec::IfSpec(IfSpecData(cond))) => {
 			if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -6,8 +6,8 @@
 use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
 use crate::{gc::TraceBox, tb, Context, Result, Val};
 
-/// Can't have str | IStr, because constant BuiltinParam causes
-/// E0492: constant functions cannot refer to interior mutable data
+/// Can't have `str` | `IStr`, because constant `BuiltinParam` causes
+/// `E0492: constant functions cannot refer to interior mutable data`
 #[derive(Clone, Trace)]
 pub struct ParamName(Option<Cow<'static, str>>);
 impl ParamName {
@@ -27,10 +27,9 @@
 }
 impl PartialEq<IStr> for ParamName {
 	fn eq(&self, other: &IStr) -> bool {
-		match &self.0 {
-			Some(s) => s.as_bytes() == other.as_bytes(),
-			None => false,
-		}
+		self.0
+			.as_ref()
+			.map_or(false, |s| s.as_bytes() == other.as_bytes())
 	}
 }
 
@@ -87,7 +86,7 @@
 			params: params
 				.into_iter()
 				.map(|n| BuiltinParam {
-					name: ParamName::new_dynamic(n.to_string()),
+					name: ParamName::new_dynamic(n),
 					has_default: false,
 				})
 				.collect(),
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -159,11 +159,11 @@
 			Val::Null => serializer.serialize_none(),
 			Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
 			Val::Num(n) => {
-				if n.fract() != 0.0 {
-					serializer.serialize_f64(*n)
-				} else {
+				if n.fract() == 0.0 {
 					let n = *n as i64;
 					serializer.serialize_i64(n)
+				} else {
+					serializer.serialize_f64(*n)
 				}
 			}
 			#[cfg(feature = "exp-bigint")]
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -41,10 +41,12 @@
 	clippy::missing_const_for_fn,
 	// too many false-positives with .expect() calls
 	clippy::missing_panics_doc,
-    // false positive for IStr type. There is an configuration option for
-    // such cases, but it doesn't work:
-    // https://github.com/rust-lang/rust-clippy/issues/9801
-    clippy::mutable_key_type,
+	// false positive for IStr type. There is an configuration option for
+	// such cases, but it doesn't work:
+	// https://github.com/rust-lang/rust-clippy/issues/9801
+	clippy::mutable_key_type,
+	// false positives
+	clippy::redundant_pub_crate,
 )]
 
 // For jrsonnet-macros
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -175,6 +175,8 @@
 	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;
 	Ok(out)
 }
+
+#[allow(clippy::too_many_lines)]
 fn manifest_json_ex_buf(
 	val: &Val,
 	buf: &mut String,
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -171,7 +171,7 @@
 			// .field("assertions_ran", &self.assertions_ran)
 			.field("this_entries", &self.this_entries)
 			// .field("value_cache", &self.value_cache)
-			.finish()
+			.finish_non_exhaustive()
 	}
 }
 
@@ -347,7 +347,7 @@
 		out.with_super(self);
 		let mut member = out.field(key);
 		if value.flags.add() {
-			member = member.add()
+			member = member.add();
 		}
 		if let Some(loc) = value.location {
 			member = member.with_location(loc);
@@ -395,7 +395,7 @@
 
 	pub fn get(&self, key: IStr) -> Result<Option<Val>> {
 		self.run_assertions()?;
-		self.get_for(key, self.0.this().unwrap_or(self.clone()))
+		self.get_for(key, self.0.this().unwrap_or_else(|| self.clone()))
 	}
 
 	pub fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
@@ -474,7 +474,7 @@
 			type Output = Val;
 
 			fn get(self: Box<Self>) -> Result<Self::Output> {
-				Ok(self.obj.get_or_bail(self.key)?)
+				self.obj.get_or_bail(self.key)
 			}
 		}
 
@@ -495,7 +495,7 @@
 			SuperDepth::default(),
 			&mut |depth, index, name, visibility| {
 				let new_sort_key = FieldSortKey::new(depth, index);
-				let entry = out.entry(name.clone());
+				let entry = out.entry(name);
 				let (visible, _) = entry.or_insert((true, new_sort_key));
 				match visibility {
 					Visibility::Normal => {}
@@ -634,7 +634,7 @@
 			SuperDepth::default(),
 			&mut |depth, index, name, visibility| {
 				let new_sort_key = FieldSortKey::new(depth, index);
-				let entry = out.entry(name.clone());
+				let entry = out.entry(name);
 				let (visible, _) = entry.or_insert((true, new_sort_key));
 				match visibility {
 					Visibility::Normal => {}
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -248,7 +248,7 @@
 	let (cflags, str) = try_parse_cflags(str)?;
 	let (width, str) = try_parse_field_width(str)?;
 	let (precision, str) = try_parse_precision(str)?;
-	let (_, str) = try_parse_length_modifier(str)?;
+	let ((), str) = try_parse_length_modifier(str)?;
 	let (convtype, str) = parse_conversion_type(str)?;
 
 	Ok((
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -449,25 +449,21 @@
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
-		match &value {
-			Val::Arr(a) => {
-				if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {
-					return Ok(bytes.0.as_slice().into());
-				};
-				<Self as Typed>::TYPE.check(&value)?;
-				// Any::downcast_ref::<ByteArray>(&a);
-				let mut out = Vec::with_capacity(a.len());
-				for e in a.iter() {
-					let r = e?;
-					out.push(u8::from_untyped(r)?);
-				}
-				Ok(out.as_slice().into())
-			}
-			_ => {
-				<Self as Typed>::TYPE.check(&value)?;
-				unreachable!()
-			}
+		let Val::Arr(a) = &value else {
+			<Self as Typed>::TYPE.check(&value)?;
+			unreachable!()
+		};
+		if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {
+			return Ok(bytes.0.as_slice().into());
+		};
+		<Self as Typed>::TYPE.check(&value)?;
+		// Any::downcast_ref::<ByteArray>(&a);
+		let mut out = Vec::with_capacity(a.len());
+		for e in a.iter() {
+			let r = e?;
+			out.push(u8::from_untyped(r)?);
 		}
+		Ok(out.as_slice().into())
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -90,7 +90,7 @@
 	item: impl Fn() -> Result<()>,
 ) -> Result<()> {
 	State::push_description(error_reason, || match item() {
-		Ok(_) => Ok(()),
+		Ok(()) => Ok(()),
 		Err(mut e) => {
 			if let ErrorKind::TypeError(e) = &mut e.error_mut() {
 				(e.1).0.push(path());
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::{ArrValue, ArrayLike};13use crate::{14	bail,15	error::{Error, ErrorKind::*},16	function::FuncVal,17	gc::{GcHashMap, TraceBox},18	manifest::{ManifestFormat, ToStringFormat},19	tb,20	typed::BoundedUsize,21	ObjValue, Result, Unbound, WeakObjValue,22};2324pub trait ThunkValue: Trace {25	type Output;26	fn get(self: Box<Self>) -> Result<Self::Output>;27}2829#[derive(Trace)]30enum ThunkInner<T: Trace> {31	Computed(T),32	Errored(Error),33	Waiting(TraceBox<dyn ThunkValue<Output = T>>),34	Pending,35}3637/// Lazily evaluated value38#[allow(clippy::module_name_repetitions)]39#[derive(Clone, Trace)]40pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4142impl<T: Trace> Thunk<T> {43	pub fn evaluated(val: T) -> Self {44		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45	}46	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {47		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))48	}49	pub fn errored(e: Error) -> Self {50		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))51	}52	pub fn result(res: Result<T, Error>) -> Self {53		match res {54			Ok(o) => Self::evaluated(o),55			Err(e) => Self::errored(e),56		}57	}58}5960impl<T> Thunk<T>61where62	T: Clone + Trace,63{64	pub fn force(&self) -> Result<()> {65		self.evaluate()?;66		Ok(())67	}6869	/// Evaluate thunk, or return cached value70	///71	/// # Errors72	///73	/// - Lazy value evaluation returned error74	/// - This method was called during inner value evaluation75	pub fn evaluate(&self) -> Result<T> {76		match &*self.0.borrow() {77			ThunkInner::Computed(v) => return Ok(v.clone()),78			ThunkInner::Errored(e) => return Err(e.clone()),79			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),80			ThunkInner::Waiting(..) => (),81		};82		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)83		else {84			unreachable!();85		};86		let new_value = match value.0.get() {87			Ok(v) => v,88			Err(e) => {89				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());90				return Err(e);91			}92		};93		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());94		Ok(new_value)95	}96}9798pub trait ThunkMapper<Input>: Trace {99	type Output;100	fn map(self, from: Input) -> Result<Self::Output>;101}102impl<Input> Thunk<Input>103where104	Input: Trace + Clone,105{106	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>107	where108		M: ThunkMapper<Input>,109		M::Output: Trace,110	{111		#[derive(Trace)]112		struct Mapped<Input: Trace, Mapper: Trace> {113			inner: Thunk<Input>,114			mapper: Mapper,115		}116		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>117		where118			Input: Trace + Clone,119			Mapper: ThunkMapper<Input>,120		{121			type Output = Mapper::Output;122123			fn get(self: Box<Self>) -> Result<Self::Output> {124				let value = self.inner.evaluate()?;125				let mapped = self.mapper.map(value)?;126				Ok(mapped)127			}128		}129130		Thunk::new(Mapped::<Input, M> {131			inner: self,132			mapper,133		})134	}135}136137impl<T: Trace> From<Result<T>> for Thunk<T> {138	fn from(value: Result<T>) -> Self {139		match value {140			Ok(o) => Self::evaluated(o),141			Err(e) => Self::errored(e),142		}143	}144}145impl<T, V: Trace> From<T> for Thunk<V>146where147	T: ThunkValue<Output = V>,148{149	fn from(value: T) -> Self {150		Thunk::new(value)151	}152}153154impl<T: Trace + Default> Default for Thunk<T> {155	fn default() -> Self {156		Self::evaluated(T::default())157	}158}159160type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);161162#[derive(Trace, Clone)]163pub struct CachedUnbound<I, T>164where165	I: Unbound<Bound = T>,166	T: Trace,167{168	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,169	value: I,170}171impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {172	pub fn new(value: I) -> Self {173		Self {174			cache: Cc::new(RefCell::new(GcHashMap::new())),175			value,176		}177	}178}179impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {180	type Bound = T;181	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {182		let cache_key = (183			sup.as_ref().map(|s| s.clone().downgrade()),184			this.as_ref().map(|t| t.clone().downgrade()),185		);186		{187			if let Some(t) = self.cache.borrow().get(&cache_key) {188				return Ok(t.clone());189			}190		}191		let bound = self.value.bind(sup, this)?;192193		{194			let mut cache = self.cache.borrow_mut();195			cache.insert(cache_key, bound.clone());196		}197198		Ok(bound)199	}200}201202impl<T: Debug + Trace> Debug for Thunk<T> {203	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {204		write!(f, "Lazy")205	}206}207impl<T: Trace> PartialEq for Thunk<T> {208	fn eq(&self, other: &Self) -> bool {209		Cc::ptr_eq(&self.0, &other.0)210	}211}212213/// Represents a Jsonnet value, which can be sliced or indexed (string or array).214#[allow(clippy::module_name_repetitions)]215pub enum IndexableVal {216	/// String.217	Str(IStr),218	/// Array.219	Arr(ArrValue),220}221impl IndexableVal {222	pub fn to_array(self) -> ArrValue {223		match self {224			IndexableVal::Str(s) => ArrValue::chars(s.chars()),225			IndexableVal::Arr(arr) => arr,226		}227	}228	/// Slice the value.229	///230	/// # Implementation231	///232	/// For strings, will create a copy of specified interval.233	///234	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.235	pub fn slice(236		self,237		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,238		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,239		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,240	) -> Result<Self> {241		match &self {242			IndexableVal::Str(s) => {243				let index = index.as_deref().copied().unwrap_or(0);244				let end = end.as_deref().copied().unwrap_or(usize::MAX);245				let step = step.as_deref().copied().unwrap_or(1);246247				if index >= end {248					return Ok(Self::Str("".into()));249				}250251				Ok(Self::Str(252					(s.chars()253						.skip(index)254						.take(end - index)255						.step_by(step)256						.collect::<String>())257					.into(),258				))259			}260			IndexableVal::Arr(arr) => {261				let index = index.as_deref().copied().unwrap_or(0);262				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());263				let step = step.as_deref().copied().unwrap_or(1);264265				if index >= end {266					return Ok(Self::Arr(ArrValue::empty()));267				}268269				Ok(Self::Arr(270					arr.clone()271						.slice(Some(index), Some(end), Some(step))272						.expect("arguments checked"),273				))274			}275		}276	}277}278279#[derive(Debug, Clone, Trace)]280pub enum StrValue {281	Flat(IStr),282	Tree(Rc<(StrValue, StrValue, usize)>),283}284impl StrValue {285	pub fn concat(a: StrValue, b: StrValue) -> Self {286		// TODO: benchmark for an optimal value, currently just a arbitrary choice287		const STRING_EXTEND_THRESHOLD: usize = 100;288289		if a.is_empty() {290			b291		} else if b.is_empty() {292			a293		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {294			Self::Flat(format!("{a}{b}").into())295		} else {296			let len = a.len() + b.len();297			Self::Tree(Rc::new((a, b, len)))298		}299	}300	pub fn into_flat(self) -> IStr {301		#[cold]302		fn write_buf(s: &StrValue, out: &mut String) {303			match s {304				StrValue::Flat(f) => out.push_str(f),305				StrValue::Tree(t) => {306					write_buf(&t.0, out);307					write_buf(&t.1, out);308				}309			}310		}311		match self {312			StrValue::Flat(f) => f,313			StrValue::Tree(_) => {314				let mut buf = String::with_capacity(self.len());315				write_buf(&self, &mut buf);316				buf.into()317			}318		}319	}320	pub fn len(&self) -> usize {321		match self {322			StrValue::Flat(v) => v.len(),323			StrValue::Tree(t) => t.2,324		}325	}326	pub fn is_empty(&self) -> bool {327		match self {328			Self::Flat(v) => v.is_empty(),329			// Can't create non-flat empty string330			Self::Tree(_) => false,331		}332	}333}334impl<T> From<T> for StrValue335where336	IStr: From<T>,337{338	fn from(value: T) -> Self {339		Self::Flat(IStr::from(value))340	}341}342impl Display for StrValue {343	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {344		match self {345			StrValue::Flat(v) => write!(f, "{v}"),346			StrValue::Tree(t) => {347				write!(f, "{}", t.0)?;348				write!(f, "{}", t.1)349			}350		}351	}352}353impl PartialEq for StrValue {354	fn eq(&self, other: &Self) -> bool {355		let a = self.clone().into_flat();356		let b = other.clone().into_flat();357		a == b358	}359}360impl Eq for StrValue {}361impl PartialOrd for StrValue {362	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {363		Some(self.cmp(other))364	}365}366impl Ord for StrValue {367	fn cmp(&self, other: &Self) -> std::cmp::Ordering {368		let a = self.clone().into_flat();369		let b = other.clone().into_flat();370		a.cmp(&b)371	}372}373374/// Represents any valid Jsonnet value.375#[derive(Debug, Clone, Trace, Default)]376pub enum Val {377	/// Represents a Jsonnet boolean.378	Bool(bool),379	/// Represents a Jsonnet null value.380	#[default]381	Null,382	/// Represents a Jsonnet string.383	Str(StrValue),384	/// Represents a Jsonnet number.385	/// Should be finite, and not NaN386	/// This restriction isn't enforced by enum, as enum field can't be marked as private387	Num(f64),388	/// Experimental bigint389	#[cfg(feature = "exp-bigint")]390	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),391	/// Represents a Jsonnet array.392	Arr(ArrValue),393	/// Represents a Jsonnet object.394	Obj(ObjValue),395	/// Represents a Jsonnet function.396	Func(FuncVal),397}398399#[cfg(target_pointer_width = "64")]400static_assertions::assert_eq_size!(Val, [u8; 24]);401402impl From<IndexableVal> for Val {403	fn from(v: IndexableVal) -> Self {404		match v {405			IndexableVal::Str(s) => Self::string(s),406			IndexableVal::Arr(a) => Self::Arr(a),407		}408	}409}410411impl Val {412	pub const fn as_bool(&self) -> Option<bool> {413		match self {414			Self::Bool(v) => Some(*v),415			_ => None,416		}417	}418	pub const fn as_null(&self) -> Option<()> {419		match self {420			Self::Null => Some(()),421			_ => None,422		}423	}424	pub fn as_str(&self) -> Option<IStr> {425		match self {426			Self::Str(s) => Some(s.clone().into_flat()),427			_ => None,428		}429	}430	pub const fn as_num(&self) -> Option<f64> {431		match self {432			Self::Num(n) => Some(*n),433			_ => None,434		}435	}436	pub fn as_arr(&self) -> Option<ArrValue> {437		match self {438			Self::Arr(a) => Some(a.clone()),439			_ => None,440		}441	}442	pub fn as_obj(&self) -> Option<ObjValue> {443		match self {444			Self::Obj(o) => Some(o.clone()),445			_ => None,446		}447	}448	pub fn as_func(&self) -> Option<FuncVal> {449		match self {450			Self::Func(f) => Some(f.clone()),451			_ => None,452		}453	}454455	/// Creates `Val::Num` after checking for numeric overflow.456	/// As numbers are `f64`, we can just check for their finity.457	pub fn new_checked_num(num: f64) -> Result<Self> {458		if num.is_finite() {459			Ok(Self::Num(num))460		} else {461			bail!("overflow")462		}463	}464465	pub const fn value_type(&self) -> ValType {466		match self {467			Self::Str(..) => ValType::Str,468			Self::Num(..) => ValType::Num,469			#[cfg(feature = "exp-bigint")]470			Self::BigInt(..) => ValType::BigInt,471			Self::Arr(..) => ValType::Arr,472			Self::Obj(..) => ValType::Obj,473			Self::Bool(_) => ValType::Bool,474			Self::Null => ValType::Null,475			Self::Func(..) => ValType::Func,476		}477	}478479	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {480		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {481			manifest.manifest(val.clone())482		}483		manifest_dyn(self, &format)484	}485486	pub fn to_string(&self) -> Result<IStr> {487		Ok(match self {488			Self::Bool(true) => "true".into(),489			Self::Bool(false) => "false".into(),490			Self::Null => "null".into(),491			Self::Str(s) => s.clone().into_flat(),492			_ => self.manifest(ToStringFormat).map(IStr::from)?,493		})494	}495496	pub fn into_indexable(self) -> Result<IndexableVal> {497		Ok(match self {498			Val::Str(s) => IndexableVal::Str(s.into_flat()),499			Val::Arr(arr) => IndexableVal::Arr(arr),500			_ => bail!(ValueIsNotIndexable(self.value_type())),501		})502	}503504	pub fn function(function: impl Into<FuncVal>) -> Self {505		Self::Func(function.into())506	}507	pub fn string(string: impl Into<StrValue>) -> Self {508		Self::Str(string.into())509	}510}511512impl From<IStr> for Val {513	fn from(value: IStr) -> Self {514		Self::string(value)515	}516}517impl From<String> for Val {518	fn from(value: String) -> Self {519		Self::string(value)520	}521}522impl From<&str> for Val {523	fn from(value: &str) -> Self {524		Self::string(value)525	}526}527impl From<ObjValue> for Val {528	fn from(value: ObjValue) -> Self {529		Self::Obj(value)530	}531}532533const fn is_function_like(val: &Val) -> bool {534	matches!(val, Val::Func(_))535}536537/// Native implementation of `std.primitiveEquals`538pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {539	Ok(match (val_a, val_b) {540		(Val::Bool(a), Val::Bool(b)) => a == b,541		(Val::Null, Val::Null) => true,542		(Val::Str(a), Val::Str(b)) => a == b,543		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,544		#[cfg(feature = "exp-bigint")]545		(Val::BigInt(a), Val::BigInt(b)) => a == b,546		(Val::Arr(_), Val::Arr(_)) => {547			bail!("primitiveEquals operates on primitive types, got array")548		}549		(Val::Obj(_), Val::Obj(_)) => {550			bail!("primitiveEquals operates on primitive types, got object")551		}552		(a, b) if is_function_like(a) && is_function_like(b) => {553			bail!("cannot test equality of functions")554		}555		(_, _) => false,556	})557}558559/// Native implementation of `std.equals`560pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {561	if val_a.value_type() != val_b.value_type() {562		return Ok(false);563	}564	match (val_a, val_b) {565		(Val::Arr(a), Val::Arr(b)) => {566			if ArrValue::ptr_eq(a, b) {567				return Ok(true);568			}569			if a.len() != b.len() {570				return Ok(false);571			}572			for (a, b) in a.iter().zip(b.iter()) {573				if !equals(&a?, &b?)? {574					return Ok(false);575				}576			}577			Ok(true)578		}579		(Val::Obj(a), Val::Obj(b)) => {580			if ObjValue::ptr_eq(a, b) {581				return Ok(true);582			}583			let fields = a.fields(584				#[cfg(feature = "exp-preserve-order")]585				false,586			);587			if fields588				!= b.fields(589					#[cfg(feature = "exp-preserve-order")]590					false,591				) {592				return Ok(false);593			}594			for field in fields {595				if !equals(596					&a.get(field.clone())?.expect("field exists"),597					&b.get(field)?.expect("field exists"),598				)? {599					return Ok(false);600				}601			}602			Ok(true)603		}604		(a, b) => Ok(primitive_equals(a, b)?),605	}606}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::{ArrValue, ArrayLike};13use crate::{14	bail,15	error::{Error, ErrorKind::*},16	function::FuncVal,17	gc::{GcHashMap, TraceBox},18	manifest::{ManifestFormat, ToStringFormat},19	tb,20	typed::BoundedUsize,21	ObjValue, Result, Unbound, WeakObjValue,22};2324pub trait ThunkValue: Trace {25	type Output;26	fn get(self: Box<Self>) -> Result<Self::Output>;27}2829#[derive(Trace)]30enum ThunkInner<T: Trace> {31	Computed(T),32	Errored(Error),33	Waiting(TraceBox<dyn ThunkValue<Output = T>>),34	Pending,35}3637/// Lazily evaluated value38#[allow(clippy::module_name_repetitions)]39#[derive(Clone, Trace)]40pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4142impl<T: Trace> Thunk<T> {43	pub fn evaluated(val: T) -> Self {44		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45	}46	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {47		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))48	}49	pub fn errored(e: Error) -> Self {50		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))51	}52	pub fn result(res: Result<T, Error>) -> Self {53		match res {54			Ok(o) => Self::evaluated(o),55			Err(e) => Self::errored(e),56		}57	}58}5960impl<T> Thunk<T>61where62	T: Clone + Trace,63{64	pub fn force(&self) -> Result<()> {65		self.evaluate()?;66		Ok(())67	}6869	/// Evaluate thunk, or return cached value70	///71	/// # Errors72	///73	/// - Lazy value evaluation returned error74	/// - This method was called during inner value evaluation75	pub fn evaluate(&self) -> Result<T> {76		match &*self.0.borrow() {77			ThunkInner::Computed(v) => return Ok(v.clone()),78			ThunkInner::Errored(e) => return Err(e.clone()),79			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),80			ThunkInner::Waiting(..) => (),81		};82		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)83		else {84			unreachable!();85		};86		let new_value = match value.0.get() {87			Ok(v) => v,88			Err(e) => {89				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());90				return Err(e);91			}92		};93		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());94		Ok(new_value)95	}96}9798pub trait ThunkMapper<Input>: Trace {99	type Output;100	fn map(self, from: Input) -> Result<Self::Output>;101}102impl<Input> Thunk<Input>103where104	Input: Trace + Clone,105{106	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>107	where108		M: ThunkMapper<Input>,109		M::Output: Trace,110	{111		#[derive(Trace)]112		struct Mapped<Input: Trace, Mapper: Trace> {113			inner: Thunk<Input>,114			mapper: Mapper,115		}116		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>117		where118			Input: Trace + Clone,119			Mapper: ThunkMapper<Input>,120		{121			type Output = Mapper::Output;122123			fn get(self: Box<Self>) -> Result<Self::Output> {124				let value = self.inner.evaluate()?;125				let mapped = self.mapper.map(value)?;126				Ok(mapped)127			}128		}129130		Thunk::new(Mapped::<Input, M> {131			inner: self,132			mapper,133		})134	}135}136137impl<T: Trace> From<Result<T>> for Thunk<T> {138	fn from(value: Result<T>) -> Self {139		match value {140			Ok(o) => Self::evaluated(o),141			Err(e) => Self::errored(e),142		}143	}144}145impl<T, V: Trace> From<T> for Thunk<V>146where147	T: ThunkValue<Output = V>,148{149	fn from(value: T) -> Self {150		Thunk::new(value)151	}152}153154impl<T: Trace + Default> Default for Thunk<T> {155	fn default() -> Self {156		Self::evaluated(T::default())157	}158}159160type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);161162#[derive(Trace, Clone)]163pub struct CachedUnbound<I, T>164where165	I: Unbound<Bound = T>,166	T: Trace,167{168	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,169	value: I,170}171impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {172	pub fn new(value: I) -> Self {173		Self {174			cache: Cc::new(RefCell::new(GcHashMap::new())),175			value,176		}177	}178}179impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {180	type Bound = T;181	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {182		let cache_key = (183			sup.as_ref().map(|s| s.clone().downgrade()),184			this.as_ref().map(|t| t.clone().downgrade()),185		);186		{187			if let Some(t) = self.cache.borrow().get(&cache_key) {188				return Ok(t.clone());189			}190		}191		let bound = self.value.bind(sup, this)?;192193		{194			let mut cache = self.cache.borrow_mut();195			cache.insert(cache_key, bound.clone());196		}197198		Ok(bound)199	}200}201202impl<T: Debug + Trace> Debug for Thunk<T> {203	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {204		write!(f, "Lazy")205	}206}207impl<T: Trace> PartialEq for Thunk<T> {208	fn eq(&self, other: &Self) -> bool {209		Cc::ptr_eq(&self.0, &other.0)210	}211}212213/// Represents a Jsonnet value, which can be sliced or indexed (string or array).214#[allow(clippy::module_name_repetitions)]215pub enum IndexableVal {216	/// String.217	Str(IStr),218	/// Array.219	Arr(ArrValue),220}221impl IndexableVal {222	pub fn to_array(self) -> ArrValue {223		match self {224			IndexableVal::Str(s) => ArrValue::chars(s.chars()),225			IndexableVal::Arr(arr) => arr,226		}227	}228	/// Slice the value.229	///230	/// # Implementation231	///232	/// For strings, will create a copy of specified interval.233	///234	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.235	pub fn slice(236		self,237		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,238		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,239		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,240	) -> Result<Self> {241		match &self {242			IndexableVal::Str(s) => {243				let index = index.as_deref().copied().unwrap_or(0);244				let end = end.as_deref().copied().unwrap_or(usize::MAX);245				let step = step.as_deref().copied().unwrap_or(1);246247				if index >= end {248					return Ok(Self::Str("".into()));249				}250251				Ok(Self::Str(252					(s.chars()253						.skip(index)254						.take(end - index)255						.step_by(step)256						.collect::<String>())257					.into(),258				))259			}260			IndexableVal::Arr(arr) => {261				let index = index.as_deref().copied().unwrap_or(0);262				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());263				let step = step.as_deref().copied().unwrap_or(1);264265				if index >= end {266					return Ok(Self::Arr(ArrValue::empty()));267				}268269				Ok(Self::Arr(270					arr.clone()271						.slice(Some(index), Some(end), Some(step))272						.expect("arguments checked"),273				))274			}275		}276	}277}278279#[derive(Debug, Clone, Trace)]280pub enum StrValue {281	Flat(IStr),282	Tree(Rc<(StrValue, StrValue, usize)>),283}284impl StrValue {285	pub fn concat(a: StrValue, b: StrValue) -> Self {286		// TODO: benchmark for an optimal value, currently just a arbitrary choice287		const STRING_EXTEND_THRESHOLD: usize = 100;288289		if a.is_empty() {290			b291		} else if b.is_empty() {292			a293		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {294			Self::Flat(format!("{a}{b}").into())295		} else {296			let len = a.len() + b.len();297			Self::Tree(Rc::new((a, b, len)))298		}299	}300	pub fn into_flat(self) -> IStr {301		#[cold]302		fn write_buf(s: &StrValue, out: &mut String) {303			match s {304				StrValue::Flat(f) => out.push_str(f),305				StrValue::Tree(t) => {306					write_buf(&t.0, out);307					write_buf(&t.1, out);308				}309			}310		}311		match self {312			StrValue::Flat(f) => f,313			StrValue::Tree(_) => {314				let mut buf = String::with_capacity(self.len());315				write_buf(&self, &mut buf);316				buf.into()317			}318		}319	}320	pub fn len(&self) -> usize {321		match self {322			StrValue::Flat(v) => v.len(),323			StrValue::Tree(t) => t.2,324		}325	}326	pub fn is_empty(&self) -> bool {327		match self {328			Self::Flat(v) => v.is_empty(),329			// Can't create non-flat empty string330			Self::Tree(_) => false,331		}332	}333}334impl<T> From<T> for StrValue335where336	IStr: From<T>,337{338	fn from(value: T) -> Self {339		Self::Flat(IStr::from(value))340	}341}342impl Display for StrValue {343	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {344		match self {345			StrValue::Flat(v) => write!(f, "{v}"),346			StrValue::Tree(t) => {347				write!(f, "{}", t.0)?;348				write!(f, "{}", t.1)349			}350		}351	}352}353impl PartialEq for StrValue {354	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.355	#[allow(clippy::unconditional_recursion)]356	fn eq(&self, other: &Self) -> bool {357		let a = self.clone().into_flat();358		let b = other.clone().into_flat();359		a == b360	}361}362impl Eq for StrValue {}363impl PartialOrd for StrValue {364	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {365		Some(self.cmp(other))366	}367}368impl Ord for StrValue {369	fn cmp(&self, other: &Self) -> std::cmp::Ordering {370		let a = self.clone().into_flat();371		let b = other.clone().into_flat();372		a.cmp(&b)373	}374}375376/// Represents any valid Jsonnet value.377#[derive(Debug, Clone, Trace, Default)]378pub enum Val {379	/// Represents a Jsonnet boolean.380	Bool(bool),381	/// Represents a Jsonnet null value.382	#[default]383	Null,384	/// Represents a Jsonnet string.385	Str(StrValue),386	/// Represents a Jsonnet number.387	/// Should be finite, and not NaN388	/// This restriction isn't enforced by enum, as enum field can't be marked as private389	Num(f64),390	/// Experimental bigint391	#[cfg(feature = "exp-bigint")]392	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),393	/// Represents a Jsonnet array.394	Arr(ArrValue),395	/// Represents a Jsonnet object.396	Obj(ObjValue),397	/// Represents a Jsonnet function.398	Func(FuncVal),399}400401#[cfg(target_pointer_width = "64")]402static_assertions::assert_eq_size!(Val, [u8; 24]);403404impl From<IndexableVal> for Val {405	fn from(v: IndexableVal) -> Self {406		match v {407			IndexableVal::Str(s) => Self::string(s),408			IndexableVal::Arr(a) => Self::Arr(a),409		}410	}411}412413impl Val {414	pub const fn as_bool(&self) -> Option<bool> {415		match self {416			Self::Bool(v) => Some(*v),417			_ => None,418		}419	}420	pub const fn as_null(&self) -> Option<()> {421		match self {422			Self::Null => Some(()),423			_ => None,424		}425	}426	pub fn as_str(&self) -> Option<IStr> {427		match self {428			Self::Str(s) => Some(s.clone().into_flat()),429			_ => None,430		}431	}432	pub const fn as_num(&self) -> Option<f64> {433		match self {434			Self::Num(n) => Some(*n),435			_ => None,436		}437	}438	pub fn as_arr(&self) -> Option<ArrValue> {439		match self {440			Self::Arr(a) => Some(a.clone()),441			_ => None,442		}443	}444	pub fn as_obj(&self) -> Option<ObjValue> {445		match self {446			Self::Obj(o) => Some(o.clone()),447			_ => None,448		}449	}450	pub fn as_func(&self) -> Option<FuncVal> {451		match self {452			Self::Func(f) => Some(f.clone()),453			_ => None,454		}455	}456457	/// Creates `Val::Num` after checking for numeric overflow.458	/// As numbers are `f64`, we can just check for their finity.459	pub fn new_checked_num(num: f64) -> Result<Self> {460		if num.is_finite() {461			Ok(Self::Num(num))462		} else {463			bail!("overflow")464		}465	}466467	pub const fn value_type(&self) -> ValType {468		match self {469			Self::Str(..) => ValType::Str,470			Self::Num(..) => ValType::Num,471			#[cfg(feature = "exp-bigint")]472			Self::BigInt(..) => ValType::BigInt,473			Self::Arr(..) => ValType::Arr,474			Self::Obj(..) => ValType::Obj,475			Self::Bool(_) => ValType::Bool,476			Self::Null => ValType::Null,477			Self::Func(..) => ValType::Func,478		}479	}480481	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {482		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {483			manifest.manifest(val.clone())484		}485		manifest_dyn(self, &format)486	}487488	pub fn to_string(&self) -> Result<IStr> {489		Ok(match self {490			Self::Bool(true) => "true".into(),491			Self::Bool(false) => "false".into(),492			Self::Null => "null".into(),493			Self::Str(s) => s.clone().into_flat(),494			_ => self.manifest(ToStringFormat).map(IStr::from)?,495		})496	}497498	pub fn into_indexable(self) -> Result<IndexableVal> {499		Ok(match self {500			Val::Str(s) => IndexableVal::Str(s.into_flat()),501			Val::Arr(arr) => IndexableVal::Arr(arr),502			_ => bail!(ValueIsNotIndexable(self.value_type())),503		})504	}505506	pub fn function(function: impl Into<FuncVal>) -> Self {507		Self::Func(function.into())508	}509	pub fn string(string: impl Into<StrValue>) -> Self {510		Self::Str(string.into())511	}512}513514impl From<IStr> for Val {515	fn from(value: IStr) -> Self {516		Self::string(value)517	}518}519impl From<String> for Val {520	fn from(value: String) -> Self {521		Self::string(value)522	}523}524impl From<&str> for Val {525	fn from(value: &str) -> Self {526		Self::string(value)527	}528}529impl From<ObjValue> for Val {530	fn from(value: ObjValue) -> Self {531		Self::Obj(value)532	}533}534535const fn is_function_like(val: &Val) -> bool {536	matches!(val, Val::Func(_))537}538539/// Native implementation of `std.primitiveEquals`540pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {541	Ok(match (val_a, val_b) {542		(Val::Bool(a), Val::Bool(b)) => a == b,543		(Val::Null, Val::Null) => true,544		(Val::Str(a), Val::Str(b)) => a == b,545		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,546		#[cfg(feature = "exp-bigint")]547		(Val::BigInt(a), Val::BigInt(b)) => a == b,548		(Val::Arr(_), Val::Arr(_)) => {549			bail!("primitiveEquals operates on primitive types, got array")550		}551		(Val::Obj(_), Val::Obj(_)) => {552			bail!("primitiveEquals operates on primitive types, got object")553		}554		(a, b) if is_function_like(a) && is_function_like(b) => {555			bail!("cannot test equality of functions")556		}557		(_, _) => false,558	})559}560561/// Native implementation of `std.equals`562pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {563	if val_a.value_type() != val_b.value_type() {564		return Ok(false);565	}566	match (val_a, val_b) {567		(Val::Arr(a), Val::Arr(b)) => {568			if ArrValue::ptr_eq(a, b) {569				return Ok(true);570			}571			if a.len() != b.len() {572				return Ok(false);573			}574			for (a, b) in a.iter().zip(b.iter()) {575				if !equals(&a?, &b?)? {576					return Ok(false);577				}578			}579			Ok(true)580		}581		(Val::Obj(a), Val::Obj(b)) => {582			if ObjValue::ptr_eq(a, b) {583				return Ok(true);584			}585			let fields = a.fields(586				#[cfg(feature = "exp-preserve-order")]587				false,588			);589			if fields590				!= b.fields(591					#[cfg(feature = "exp-preserve-order")]592					false,593				) {594				return Ok(false);595			}596			for field in fields {597				if !equals(598					&a.get(field.clone())?.expect("field exists"),599					&b.get(field)?.expect("field exists"),600				)? {601					return Ok(false);602				}603			}604			Ok(true)605		}606		(a, b) => Ok(primitive_equals(a, b)?),607	}608}
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -6,7 +6,7 @@
 #![warn(clippy::pedantic, clippy::nursery)]
 #![allow(clippy::missing_const_for_fn)]
 use std::{
-	borrow::{Borrow, Cow},
+	borrow::Cow,
 	cell::RefCell,
 	fmt::{self, Display},
 	hash::{BuildHasherDefault, Hash, Hasher},
@@ -14,7 +14,7 @@
 	str,
 };
 
-use hashbrown::HashMap;
+use hashbrown::{hash_map::RawEntryMut, HashMap};
 use jrsonnet_gcmodule::Trace;
 use rustc_hash::FxHasher;
 
@@ -57,17 +57,6 @@
 	}
 }
 
-impl Borrow<str> for IStr {
-	fn borrow(&self) -> &str {
-		self.as_str()
-	}
-}
-impl Borrow<[u8]> for IStr {
-	fn borrow(&self) -> &[u8] {
-		self.as_bytes()
-	}
-}
-
 impl PartialEq for IStr {
 	fn eq(&self, other: &Self) -> bool {
 		// all IStr should be inlined into same pool
@@ -142,12 +131,6 @@
 	type Target = [u8];
 
 	fn deref(&self) -> &Self::Target {
-		self.0.as_slice()
-	}
-}
-
-impl Borrow<[u8]> for IBytes {
-	fn borrow(&self) -> &[u8] {
 		self.0.as_slice()
 	}
 }
@@ -285,9 +268,9 @@
 		let mut pool = pool.borrow_mut();
 		let entry = pool.raw_entry_mut().from_key(bytes);
 		match entry {
-			hashbrown::hash_map::RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),
-			hashbrown::hash_map::RawEntryMut::Vacant(e) => {
-				let (k, _) = e.insert(Inner::new_bytes(bytes), ());
+			RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),
+			RawEntryMut::Vacant(e) => {
+				let (k, ()) = e.insert(Inner::new_bytes(bytes), ());
 				IBytes(k.clone())
 			}
 		}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -374,6 +374,7 @@
 				fn params(&self) -> &[BuiltinParam] {
 					PARAMS
 				}
+				#[allow(unused_variable)]
 				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
 					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;
 
modifiedcrates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/encoding.rs
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -39,5 +39,5 @@
 	let bytes = STANDARD
 		.decode(str.as_bytes())
 		.map_err(|e| runtime_error!("invalid base64: {e}"))?;
-	Ok(String::from_utf8(bytes).map_err(|_| runtime_error!("bad utf8"))?)
+	String::from_utf8(bytes).map_err(|_| runtime_error!("bad utf8"))
 }
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -47,7 +47,7 @@
 		.ext_natives
 		.get(&x)
 		.cloned()
-		.map_or(Val::Null, |v| Val::Func(v))
+		.map_or(Val::Null, Val::Func)
 }
 
 #[builtin(fields(
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
         "systems": "systems"
       },
       "locked": {
-        "lastModified": 1694529238,
-        "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+        "lastModified": 1705309234,
+        "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+        "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
         "type": "github"
       },
       "original": {
@@ -20,11 +20,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1701376520,
-        "narHash": "sha256-U3iGiOZqgu7wvVzgfoQzGGFMqNsDj/q/6zPIjCy7ajg=",
+        "lastModified": 1705391267,
+        "narHash": "sha256-gGVm9QudiRtYTX8PN9cTTy7uuJcL4I2lRMoPx496kXk=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "c74cc3c3db2ed5e68895953d75c397797d499133",
+        "rev": "41a9a7f170c740acb24f3390323877d11c69d5ee",
         "type": "github"
       },
       "original": {
@@ -50,11 +50,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1701310566,
-        "narHash": "sha256-CL9J3xUR2Ejni4LysrEGX0IdO+Y4BXCiH/By0lmF3eQ=",
+        "lastModified": 1705371439,
+        "narHash": "sha256-P1kulUXpYWkcrjiX3sV4j8ACJZh9XXSaaD+jDLBDLKo=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "6d3c6e185198b8bf7ad639f22404a75aa9a09bff",
+        "rev": "b21f3c0d5bf0f0179f5f0140e8e0cd099618bd04",
         "type": "github"
       },
       "original": {
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -25,14 +25,14 @@
         lib = pkgs.lib;
         rust =
           (pkgs.rustChannelOf {
-            date = "2023-10-28";
+            date = "2024-01-10";
             channel = "nightly";
           })
           .default
           .override {
             extensions = ["rust-src" "miri" "rust-analyzer" "clippy"];
           };
-      in rec {
+      in {
         packages = rec {
           go-jsonnet = pkgs.callPackage ./nix/go-jsonnet.nix {};
           sjsonnet = pkgs.callPackage ./nix/sjsonnet.nix {};