git.delta.rocks / jrsonnet / refs/commits / 5df60b8b674f

difftreelog

style fix clippy warnings

rvmlxxrnYaroslav Bolyukin2026-03-21parent: #ac5b435.patch.diff
in: master

36 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -43,7 +43,7 @@
 		}
 		n_args.push(None);
 		let mut success = 1;
-		let v = unsafe { (self.cb)(self.ctx, n_args.as_ptr().cast(), &mut success) };
+		let v = unsafe { (self.cb)(self.ctx, n_args.as_ptr().cast(), &raw mut success) };
 		let v = unsafe { *Box::from_raw(v) };
 		if success == 1 {
 			Ok(v)
modifiedcrates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/tla.rs
+++ b/crates/jrsonnet-cli/src/tla.rs
@@ -7,6 +7,7 @@
 
 #[derive(Parser)]
 #[clap(next_help_heading = "TOP LEVEL ARGUMENTS")]
+#[allow(clippy::struct_field_names)]
 pub struct TlaOpts {
 	/// Add top level string argument.
 	/// Top level arguments will be passed to function before manifestification stage.
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -77,7 +77,7 @@
 			let i = i?;
 			if filter(&i)? {
 				out.push(i);
-			};
+			}
 		}
 		Ok(Self::eager(out))
 	}
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -38,7 +38,7 @@
 }
 impl ArrayLike for SliceArray {
 	fn len(&self) -> usize {
-		((self.to - self.from + self.step - 1) / self.step) as usize
+		(self.to - self.from).div_ceil(self.step) as usize
 	}
 
 	fn get(&self, index: usize) -> Result<Option<Val>> {
@@ -139,7 +139,7 @@
 			ArrayThunk::Errored(e) => return Err(e.clone()),
 			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
 			ArrayThunk::Waiting => {}
-		};
+		}
 
 		let ArrayThunk::Waiting =
 			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
@@ -158,15 +158,6 @@
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		if index >= self.len() {
-			return None;
-		}
-		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 => {}
-		};
-
 		#[derive(Trace)]
 		struct ExprArrThunk {
 			expr: ExprArray,
@@ -183,6 +174,15 @@
 			}
 		}
 
+		if index >= self.len() {
+			return None;
+		}
+		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 => {}
+		}
+
 		Some(Thunk::new(ExprArrThunk {
 			expr: self.clone(),
 			index,
@@ -441,7 +441,7 @@
 			ArrayThunk::Errored(e) => return Err(e.clone()),
 			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
 			ArrayThunk::Waiting => {}
-		};
+		}
 
 		let ArrayThunk::Waiting =
 			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
@@ -467,15 +467,6 @@
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		if index >= self.len() {
-			return None;
-		}
-		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 => {}
-		};
-
 		#[derive(Trace)]
 		struct MappedArrayThunk<const WITH_INDEX: bool> {
 			arr: MappedArray<WITH_INDEX>,
@@ -489,6 +480,15 @@
 			}
 		}
 
+		if index >= self.len() {
+			return None;
+		}
+		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 => {}
+		}
+
 		Some(Thunk::new(MappedArrayThunk {
 			arr: self.clone(),
 			index,
modifiedcrates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -21,7 +21,8 @@
 // Visits all nodes, trying to find import statements
 #[allow(clippy::too_many_lines)]
 pub fn find_imports(expr: &Spanned<Expr>, out: &mut FoundImports) {
-	fn in_destruct(dest: &Destruct, #[allow(unused_variables)] out: &mut FoundImports) {
+	#[allow(unused_variables, clippy::needless_pass_by_ref_mut)]
+	fn in_destruct(dest: &Destruct, out: &mut FoundImports) {
 		match dest {
 			#[cfg(feature = "exp-destruct")]
 			Destruct::Array {
@@ -295,8 +296,6 @@
 	let resolved = (s.import_resolver() as &dyn Any)
 		.downcast_ref::<ResolvedImportResolver>()
 		.expect("for async imports, import_resolver should be set to ResolvedImportResolver");
-
-	let mut resolved_map = resolved.resolved.borrow_mut();
 
 	let mut queue = vec![Job::LoadFile {
 		path: handler.resolve_from_default(path).await?,
@@ -340,14 +339,17 @@
 				}
 			}
 			Job::ResolveImport { from, import } => {
-				if let Some((resolved, expression)) =
-					resolved_map.get_mut(&(from.clone(), import.path.clone()))
 				{
-					if import.expression && !*expression {
-						*expression = true;
-						queue.push(Job::ParseFile(resolved.clone()));
+					let mut resolved_map = resolved.resolved.borrow_mut();
+					if let Some((resolved, expression)) =
+						resolved_map.get_mut(&(from.clone(), import.path.clone()))
+					{
+						if import.expression && !*expression {
+							*expression = true;
+							queue.push(Job::ParseFile(resolved.clone()));
+						}
+						continue;
 					}
-					continue;
 				}
 				let resolved = handler.resolve_from(&from, &import.path).await?;
 				queue.push(Job::LoadFile {
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,6 +1,7 @@
+use std::{collections::HashMap, hash::BuildHasher};
+
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{BindSpec, Destruct};
-use rustc_hash::FxHashMap;
 
 use crate::{
 	bail,
@@ -10,11 +11,11 @@
 
 #[allow(clippy::too_many_lines)]
 #[allow(unused_variables)]
-pub fn destruct(
+pub fn destruct<H: BuildHasher>(
 	d: &Destruct,
 	parent: Thunk<Val>,
 	fctx: Pending<Context>,
-	new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,
+	new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,
 ) -> Result<()> {
 	match d {
 		Destruct::Full(v) => {
@@ -159,10 +160,10 @@
 	Ok(())
 }
 
-pub fn evaluate_dest(
+pub fn evaluate_dest<H: BuildHasher>(
 	d: &BindSpec,
 	fctx: Pending<Context>,
-	new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,
+	new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,
 ) -> Result<()> {
 	match d {
 		BindSpec::Field { into, value } => {
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -291,7 +291,7 @@
 	let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));
 
 	for field in &members.fields {
-		evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?;
+		evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;
 	}
 
 	if !members.asserts.is_empty() {
@@ -304,13 +304,13 @@
 			fn run(&self, sup_this: SupThis) -> Result<()> {
 				let ctx = self.uctx.bind(sup_this)?;
 				for assert in &*self.asserts {
-					evaluate_assert(ctx.clone(), &assert)?;
+					evaluate_assert(ctx.clone(), assert)?;
 				}
 				Ok(())
 			}
 		}
 		builder.assert(ObjectAssert {
-			uctx: uctx.clone(),
+			uctx,
 			asserts: members.asserts.clone(),
 		});
 	}
@@ -567,7 +567,7 @@
 				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;
 			}
 			let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);
-			evaluate(ctx, &returned.clone())?
+			evaluate(ctx, returned)?
 		}
 		Arr(items) => {
 			if items.is_empty() {
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -95,9 +95,9 @@
 		// string format
 		(Str(_), _) => false,
 
-		(_, Num(b)) => return **b == 0.,
+		(_, Num(b)) => **b == 0.,
 		#[cfg(feature = "exp-bigint")]
-		(_, BigInt(b)) => return **b == num_bigint::BigInt::ZERO,
+		(_, BigInt(b)) => **b == num_bigint::BigInt::ZERO,
 
 		// something else
 		_ => false,
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -239,7 +239,7 @@
 	}
 
 	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		for (name, _) in self {
+		for name in self.keys() {
 			handler(name);
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,5 +1,3 @@
-use std::mem::replace;
-
 use jrsonnet_parser::{
 	function::{FunctionSignature, ParamName},
 	ExprParams,
@@ -87,7 +85,7 @@
 			}
 
 			destruct(
-				&into,
+				into,
 				{
 					let ctx = fctx.clone();
 					let name = into.name();
@@ -97,7 +95,7 @@
 				fctx.clone(),
 				&mut defaults,
 			)?;
-			if !into.name().is_anonymous() {
+			if into.name().is_named() {
 				filled_named += 1;
 			} else {
 				filled_positionals += 1;
@@ -165,7 +163,7 @@
 			.iter()
 			.position(|p| p.name() == name)
 			.ok_or_else(|| UnknownFunctionParameter(name.clone()))?;
-		if replace(&mut passed_args[id], Some(arg)).is_some() {
+		if passed_args[id].replace(arg).is_some() {
 			bail!(BindingParameterASecondTime(name.clone()));
 		}
 		filled_args += 1;
@@ -230,7 +228,7 @@
 					let params = params.clone();
 					Thunk!(move || Err(FunctionParameterNotBoundInCall(
 						param_name,
-						params.signature.clone()
+						params.signature
 					)
 					.into()))
 				},
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -1,3 +1,8 @@
+#![allow(
+	clippy::implicit_hasher,
+	reason = "those methods exist exactly because with_capacity is only present for default BuildHasher"
+)]
+
 /// Macros to help deal with Gc
 use jrsonnet_gcmodule::Trace;
 use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
@@ -8,20 +13,20 @@
 }
 impl<V> WithCapacityExt for FxHashSet<V> {
 	fn with_capacity(capacity: usize) -> Self {
-		Self::with_capacity_and_hasher(capacity, FxBuildHasher::default())
+		Self::with_capacity_and_hasher(capacity, FxBuildHasher)
 	}
 
 	fn new() -> Self {
-		Self::with_hasher(FxBuildHasher::default())
+		Self::with_hasher(FxBuildHasher)
 	}
 }
 impl<K, V> WithCapacityExt for FxHashMap<K, V> {
 	fn with_capacity(capacity: usize) -> Self {
-		Self::with_capacity_and_hasher(capacity, FxBuildHasher::default())
+		Self::with_capacity_and_hasher(capacity, FxBuildHasher)
 	}
 
 	fn new() -> Self {
-		Self::with_hasher(FxBuildHasher::default())
+		Self::with_hasher(FxBuildHasher)
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -367,7 +367,7 @@
 		let res = evaluate(self.create_default_context(file_name), &parsed);
 
 		let mut file_cache = self.file_cache();
-		let mut file = file_cache.entry(path.clone());
+		let mut file = file_cache.entry(path);
 
 		let Entry::Occupied(file) = &mut file else {
 			unreachable!("this file was just here")
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -240,7 +240,7 @@
 					}
 					ToString if i != 0 => buf.push(' '),
 					Minify | ToString => {}
-				};
+				}
 
 				in_description_frame(
 					|| format!("elem <{i}> manifestification"),
@@ -335,7 +335,7 @@
 			buf.push('}');
 		}
 		Val::Func(_) => bail!("tried to manifest function"),
-	};
+	}
 	Ok(())
 }
 
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -16,7 +16,7 @@
 
 impl LayeredHashMap {
 	pub fn iter_keys(self, mut handler: impl FnMut(IStr)) {
-		for (k, _) in &self.0.current {
+		for k in self.0.current.keys() {
 			handler(k.clone());
 		}
 		if let Some(parent) = self.0.parent.clone() {
@@ -47,11 +47,7 @@
 
 	pub fn contains_key(&self, key: &IStr) -> bool {
 		(self.0).current.contains_key(key)
-			|| self
-				.0
-				.parent
-				.as_ref()
-				.map_or(false, |p| p.contains_key(key))
+			|| self.0.parent.as_ref().is_some_and(|p| p.contains_key(key))
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -1,6 +1,7 @@
 use std::{
 	any::Any,
 	cell::{Cell, RefCell},
+	clone::Clone,
 	collections::hash_map::Entry,
 	fmt::{self, Debug},
 	hash::{Hash, Hasher},
@@ -272,7 +273,7 @@
 
 impl ObjValue {
 	pub fn empty() -> Self {
-		EMPTY_OBJ.with(|v| v.clone())
+		EMPTY_OBJ.with(Clone::clone)
 	}
 	pub fn is_empty(&self) -> bool {
 		self.0.cores.is_empty() || self.len() == 0
@@ -306,14 +307,13 @@
 			return Ok(GetFor::NotFound);
 		}
 		let v = self.this.get_idx(key, self.sup)?;
-		Ok(v.map_or(GetFor::NotFound, |v| GetFor::Final(v)))
+		Ok(v.map_or(GetFor::NotFound, GetFor::Final))
 	}
 
 	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {
-		match self.this.field_visibility_idx(field, self.sup) {
-			Some(c) => FieldVisibility::Found(c),
-			None => FieldVisibility::NotFound,
-		}
+		self.this
+			.field_visibility_idx(field, self.sup)
+			.map_or(FieldVisibility::NotFound, FieldVisibility::Found)
 	}
 
 	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {
modifiedcrates/jrsonnet-evaluator/src/obj/oop.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/oop.rs
+++ b/crates/jrsonnet-evaluator/src/obj/oop.rs
@@ -1,4 +1,4 @@
-use std::cell::Cell;
+use std::cell::{Cell, RefCell};
 use std::ops::ControlFlow;
 use std::{fmt, mem};
 
@@ -105,7 +105,7 @@
 
 	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {
 		if let Some(assertion) = &self.assertion {
-			assertion.0.run(sup_this.clone())?;
+			assertion.0.run(sup_this)?;
 		}
 		Ok(())
 	}
@@ -196,7 +196,7 @@
 		ObjValue(Cc::new(ObjValueInner {
 			cores: self.sup,
 			assertions_ran: Cell::new(false),
-			value_cache: Default::default(),
+			value_cache: RefCell::default(),
 		}))
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/stack.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stack.rs
+++ b/crates/jrsonnet-evaluator/src/stack.rs
@@ -17,6 +17,7 @@
 	}
 }
 #[cfg(not(nightly))]
+#[allow(dead_code)]
 type NightlyLocalKey<T> = std::thread::LocalKey<T>;
 
 #[cfg(nightly)]
@@ -60,7 +61,7 @@
 pub struct StackDepthGuard(PhantomData<()>);
 impl Drop for StackDepthGuard {
 	fn drop(&mut self) {
-		STACK_LIMIT.with(|limit| limit.current_depth.set(limit.current_depth.get() - 1))
+		STACK_LIMIT.with(|limit| limit.current_depth.set(limit.current_depth.get() - 1));
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/stdlib/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use jrsonnet_gcmodule::Trace;5use jrsonnet_interner::IStr;6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{10	bail,11	error::{format_found, suggest_object_fields, ErrorKind::*},12	typed::Typed,13	Error, ObjValue, Result, Val,14};1516#[derive(Debug, Clone, Error, Trace)]17pub enum FormatError {18	#[error("truncated format code")]19	TruncatedFormatCode,20	#[error("unrecognized conversion type: {0}")]21	UnrecognizedConversionType(char),2223	#[error("not enough values")]24	NotEnoughValues,2526	#[error("cannot use * width with object")]27	CannotUseStarWidthWithObject,28	#[error("mapping keys required")]29	MappingKeysRequired,30	#[error("no such format field: {0}")]31	NoSuchFormatField(IStr),3233	#[error("expected subfield <{0}> to be an object, got {1} instead")]34	SubfieldDidntYieldAnObject(IStr, ValType),35	#[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]36	SubfieldNotFound {37		current: IStr,38		full: IStr,39		found: Box<Vec<IStr>>,40	},41}4243impl From<FormatError> for Error {44	fn from(e: FormatError) -> Self {45		Self::new(Format(e))46	}47}4849use FormatError::*;5051type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;5253pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> {54	if str.is_empty() {55		return Err(TruncatedFormatCode);56	}57	let bytes = str.as_bytes();58	if bytes[0] == b'(' {59		let mut i = 1;60		while i < bytes.len() {61			if bytes[i] == b')' {62				return Ok((&str[1..i], &str[i + 1..]));63			}64			i += 1;65		}66		Err(TruncatedFormatCode)67	} else {68		Ok(("", str))69	}70}7172#[cfg(test)]73pub mod tests_key {74	use super::*;7576	#[test]77	fn parse_key() {78		assert_eq!(79			try_parse_mapping_key("(hello ) world").unwrap(),80			("hello ", " world")81		);82		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));83		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));84		assert_eq!(85			try_parse_mapping_key(" () world").unwrap(),86			("", " () world")87		);88	}8990	#[test]91	#[should_panic = "TruncatedFormatCode"]92	fn parse_key_missing_start() {93		try_parse_mapping_key("").unwrap();94	}9596	#[test]97	#[should_panic = "TruncatedFormatCode"]98	fn parse_key_missing_end() {99		try_parse_mapping_key("(   ").unwrap();100	}101}102103#[allow(clippy::struct_excessive_bools)]104#[derive(Default, Debug)]105pub struct CFlags {106	pub alt: bool,107	pub zero: bool,108	pub left: bool,109	pub blank: bool,110	pub sign: bool,111}112113pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> {114	if str.is_empty() {115		return Err(TruncatedFormatCode);116	}117	let bytes = str.as_bytes();118	let mut i = 0;119	let mut out = CFlags::default();120	loop {121		if bytes.len() == i {122			return Err(TruncatedFormatCode);123		}124		match bytes[i] {125			b'#' => out.alt = true,126			b'0' => out.zero = true,127			b'-' => out.left = true,128			b' ' => out.blank = true,129			b'+' => out.sign = true,130			_ => break,131		}132		i += 1;133	}134	Ok((out, &str[i..]))135}136137#[derive(Debug, PartialEq, Eq)]138pub enum Width {139	Star,140	Fixed(u16),141}142pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {143	if str.is_empty() {144		return Err(TruncatedFormatCode);145	}146	let bytes = str.as_bytes();147	if bytes[0] == b'*' {148		return Ok((Width::Star, &str[1..]));149	}150	let mut out: u16 = 0;151	let mut digits = 0;152	while let Some(digit) = (bytes[digits] as char).to_digit(10) {153		out *= 10;154		out += digit as u16;155		digits += 1;156		if digits == bytes.len() {157			return Err(TruncatedFormatCode);158		}159	}160	Ok((Width::Fixed(out), &str[digits..]))161}162163pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {164	if str.is_empty() {165		return Err(TruncatedFormatCode);166	}167	let bytes = str.as_bytes();168	if bytes[0] == b'.' {169		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))170	} else {171		Ok((None, str))172	}173}174175// Only skips176pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> {177	if str.is_empty() {178		return Err(TruncatedFormatCode);179	}180	let bytes = str.as_bytes();181	let mut idx = 0;182	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {183		idx += 1;184		if bytes.len() == idx {185			return Err(TruncatedFormatCode);186		}187	}188	Ok(((), &str[idx..]))189}190191#[derive(Debug, PartialEq, Eq)]192pub enum ConvTypeV {193	Decimal,194	Octal,195	Hexadecimal,196	Scientific,197	Float,198	Shorter,199	Char,200	String,201	Percent,202}203pub struct ConvType {204	v: ConvTypeV,205	caps: bool,206}207208pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> {209	if str.is_empty() {210		return Err(TruncatedFormatCode);211	}212213	let code = str.as_bytes()[0];214	let v: (ConvTypeV, bool) = match code {215		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),216		b'o' => (ConvTypeV::Octal, false),217		b'x' => (ConvTypeV::Hexadecimal, false),218		b'X' => (ConvTypeV::Hexadecimal, true),219		b'e' => (ConvTypeV::Scientific, false),220		b'E' => (ConvTypeV::Scientific, true),221		b'f' => (ConvTypeV::Float, false),222		b'F' => (ConvTypeV::Float, true),223		b'g' => (ConvTypeV::Shorter, false),224		b'G' => (ConvTypeV::Shorter, true),225		b'c' => (ConvTypeV::Char, false),226		b's' => (ConvTypeV::String, false),227		b'%' => (ConvTypeV::Percent, false),228		c => return Err(UnrecognizedConversionType(c as char)),229	};230231	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))232}233234#[derive(Debug)]235pub struct Code<'s> {236	mkey: &'s str,237	cflags: CFlags,238	width: Width,239	precision: Option<Width>,240	convtype: ConvTypeV,241	caps: bool,242}243pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> {244	if str.is_empty() {245		return Err(TruncatedFormatCode);246	}247	let (mkey, str) = try_parse_mapping_key(str)?;248	let (cflags, str) = try_parse_cflags(str)?;249	let (width, str) = try_parse_field_width(str)?;250	let (precision, str) = try_parse_precision(str)?;251	let ((), str) = try_parse_length_modifier(str)?;252	let (convtype, str) = parse_conversion_type(str)?;253254	Ok((255		Code {256			mkey,257			cflags,258			width,259			precision,260			convtype: convtype.v,261			caps: convtype.caps,262		},263		str,264	))265}266267#[derive(Debug)]268pub enum Element<'s> {269	String(&'s str),270	Code(Code<'s>),271}272pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {273	let mut bytes = str.as_bytes();274	let mut out = vec![];275	let mut offset = 0;276277	loop {278		while offset != bytes.len() && bytes[offset] != b'%' {279			offset += 1;280		}281		if offset != 0 {282			out.push(Element::String(&str[0..offset]));283		}284		if offset == bytes.len() {285			return Ok(out);286		}287		str = &str[offset + 1..];288		let code;289		(code, str) = parse_code(str)?;290		bytes = str.as_bytes();291		offset = 0;292293		out.push(Element::Code(code));294	}295}296297const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";298299#[inline]300pub fn render_integer(301	out: &mut String,302	neg: bool,303	iv: f64,304	padding: u16,305	precision: u16,306	blank: bool,307	sign: bool,308	radix: i64,309	zero_prefix: &str,310	prefix_in_padding: bool,311	caps: bool,312) {313	debug_assert!(iv >= 0.0, "render_integer receives sign using arg");314	let iv = iv.floor() as i64;315	// Digit char indexes in reverse order, i.e316	// for radix = 16 and n = 12f: [15, 2, 1]317	let digits = if iv == 0 {318		vec![0u8]319	} else {320		let mut v = iv.abs();321		let mut nums = Vec::with_capacity(1);322		while v != 0 {323			nums.push((v % radix) as u8);324			v /= radix;325		}326		nums327	};328	#[allow(clippy::bool_to_int_with_if)]329	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });330331	let pref_len = zero_prefix.len() as u16;332	let zp2 = zp333		.saturating_sub(if !prefix_in_padding { pref_len } else { 0 })334		.max(precision)335		.saturating_sub(if prefix_in_padding { pref_len } else { 0 } + digits.len() as u16);336337	if neg {338		out.push('-');339	} else if sign {340		out.push('+');341	} else if blank {342		out.push(' ');343	}344345	out.reserve(zp2 as usize);346	if iv != 0 {347		out.push_str(zero_prefix);348	}349	for _ in 0..zp2 {350		out.push('0');351	}352353	for digit in digits.into_iter().rev() {354		let ch = NUMBERS[digit as usize] as char;355		out.push(if caps { ch.to_ascii_uppercase() } else { ch });356	}357}358359pub fn render_decimal(360	out: &mut String,361	neg: bool,362	iv: f64,363	padding: u16,364	precision: u16,365	blank: bool,366	sign: bool,367) {368	render_integer(369		out, neg, iv, padding, precision, blank, sign, 10, "", false, false,370	);371}372pub fn render_octal(373	out: &mut String,374	neg: bool,375	iv: f64,376	padding: u16,377	precision: u16,378	alt: bool,379	blank: bool,380	sign: bool,381) {382	render_integer(383		out,384		neg,385		iv,386		padding,387		precision,388		blank,389		sign,390		8,391		if alt && iv != 0.0 { "0" } else { "" },392		true,393		false,394	);395}396397#[allow(clippy::fn_params_excessive_bools)]398pub fn render_hexadecimal(399	out: &mut String,400	iv: f64,401	padding: u16,402	precision: u16,403	alt: bool,404	blank: bool,405	sign: bool,406	caps: bool,407) {408	render_integer(409		out,410		iv < 0.0,411		iv.abs(),412		padding,413		precision,414		blank,415		sign,416		16,417		match (alt, caps) {418			(true, true) => "0X",419			(true, false) => "0x",420			(false, _) => "",421		},422		false,423		caps,424	);425}426427#[allow(clippy::fn_params_excessive_bools)]428pub fn render_float(429	out: &mut String,430	n: f64,431	mut padding: u16,432	precision: u16,433	blank: bool,434	sign: bool,435	ensure_pt: bool,436	trailing: bool,437) {438	// Represent the rounded number as an integer * 1/10**prec.439	// Note that it can also be equal to 10**prec and we'll need to carry440	// over to the wholes.  We operate on the absolute numbers, so that we441	// don't have trouble with the rounding direction.442	let denominator = 10.0f64.powi(precision as i32);443	let numerator = n.abs() * denominator + 0.5;444	let whole = (numerator / denominator).floor();445	let frac = numerator.floor() % denominator;446447	#[allow(clippy::bool_to_int_with_if)]448	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };449	padding = padding.saturating_sub(dot_size + precision);450	render_decimal(out, n < 0.0, whole, padding, 0, blank, sign);451	if precision == 0 {452		if ensure_pt {453			out.push('.');454		}455		return;456	}457	if trailing || frac > 0.0 {458		out.push('.');459		let mut frac_str = String::new();460		render_decimal(&mut frac_str, false, frac, precision, 0, false, false);461		let mut trim = frac_str.len();462		if !trailing {463			for b in frac_str.as_bytes().iter().rev() {464				if *b == b'0' {465					trim -= 1;466				} else {467					break;468				}469			}470		}471		out.push_str(&frac_str[..trim]);472	} else if ensure_pt {473		out.push('.');474	}475}476477#[allow(clippy::fn_params_excessive_bools)]478pub fn render_float_sci(479	out: &mut String,480	n: f64,481	mut padding: u16,482	precision: u16,483	blank: bool,484	sign: bool,485	ensure_pt: bool,486	trailing: bool,487	caps: bool,488) {489	let exponent = if n == 0.0 {490		0.0491	} else {492		n.abs().log10().floor()493	};494495	let mantissa = if exponent as i16 == -324 {496		n * 10.0 / 10.0_f64.powf(exponent + 1.0)497	} else {498		n / 10.0_f64.powf(exponent)499	};500	let mut exponent_str = String::new();501	render_decimal(502		&mut exponent_str,503		exponent < 0.0,504		exponent.abs(),505		3,506		0,507		false,508		true,509	);510511	// +1 for e512	padding = padding.saturating_sub(exponent_str.len() as u16 + 1);513514	render_float(515		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,516	);517	out.push(if caps { 'E' } else { 'e' });518	out.push_str(&exponent_str);519}520521#[allow(clippy::too_many_lines)]522pub fn format_code(523	out: &mut String,524	value: &Val,525	code: &Code<'_>,526	width: u16,527	precision: Option<u16>,528) -> Result<()> {529	let clfags = &code.cflags;530	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));531	let padding = if clfags.zero && !clfags.left {532		width533	} else {534		0535	};536537	// TODO: If left padded, can optimize by writing directly to out538	let mut tmp_out = String::new();539540	match code.convtype {541		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),542		ConvTypeV::Decimal => {543			let value = f64::from_untyped(value.clone())?;544			render_decimal(545				&mut tmp_out,546				value <= -1.0,547				value.abs(),548				padding,549				iprec,550				clfags.blank,551				clfags.sign,552			);553		}554		ConvTypeV::Octal => {555			let value = f64::from_untyped(value.clone())?;556			render_octal(557				&mut tmp_out,558				value <= -1.0,559				value.abs(),560				padding,561				iprec,562				clfags.alt,563				clfags.blank,564				clfags.sign,565			);566		}567		ConvTypeV::Hexadecimal => {568			let value = f64::from_untyped(value.clone())?;569			render_hexadecimal(570				&mut tmp_out,571				value,572				padding,573				iprec,574				clfags.alt,575				clfags.blank,576				clfags.sign,577				code.caps,578			);579		}580		ConvTypeV::Scientific => {581			let value = f64::from_untyped(value.clone())?;582			render_float_sci(583				&mut tmp_out,584				value,585				padding,586				fpprec,587				clfags.blank,588				clfags.sign,589				clfags.alt,590				true,591				code.caps,592			);593		}594		ConvTypeV::Float => {595			let value = f64::from_untyped(value.clone())?;596			render_float(597				&mut tmp_out,598				value,599				padding,600				fpprec,601				clfags.blank,602				clfags.sign,603				clfags.alt,604				true,605			);606		}607		ConvTypeV::Shorter => {608			let value = f64::from_untyped(value.clone())?;609			let exponent = if value == 0.0 {610				0.0611			} else {612				value.abs().log10().floor()613			};614			if exponent < -4.0 || exponent >= fpprec as f64 {615				render_float_sci(616					&mut tmp_out,617					value,618					padding,619					fpprec - 1,620					clfags.blank,621					clfags.sign,622					clfags.alt,623					clfags.alt,624					code.caps,625				);626			} else {627				let digits_before_pt = 1.max(exponent as u16 + 1);628				render_float(629					&mut tmp_out,630					value,631					padding,632					fpprec - digits_before_pt,633					clfags.blank,634					clfags.sign,635					clfags.alt,636					clfags.alt,637				);638			}639		}640		ConvTypeV::Char => match value.clone() {641			Val::Num(n) => {642				let n = n.get();643				tmp_out.push(644					std::char::from_u32(n as u32)645						.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,646				);647			}648			Val::Str(s) => {649				let s = s.into_flat();650				if s.chars().count() != 1 {651					bail!("%c expected 1 char string, got {}", s.chars().count());652				}653				tmp_out.push_str(&s);654			}655			_ => {656				bail!(TypeMismatch(657					"%c requires number/string",658					vec![ValType::Num, ValType::Str],659					value.value_type(),660				));661			}662		},663		ConvTypeV::Percent => tmp_out.push('%'),664	};665666	let padding = width.saturating_sub(tmp_out.len() as u16);667668	if !clfags.left {669		for _ in 0..padding {670			out.push(' ');671		}672	}673	out.push_str(&tmp_out);674	if clfags.left {675		for _ in 0..padding {676			out.push(' ');677		}678	}679680	Ok(())681}682683pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {684	let codes = parse_codes(str)?;685	let mut out = String::new();686	let value_count = values.len();687688	for code in codes {689		match code {690			Element::String(s) => {691				out.push_str(s);692			}693			Element::Code(c) => {694				let width = match c.width {695					Width::Star => {696						if values.is_empty() {697							bail!(NotEnoughValues);698						}699						let value = &values[0];700						values = &values[1..];701						u16::from_untyped(value.clone())?702					}703					Width::Fixed(n) => n,704				};705				let precision = match c.precision {706					Some(Width::Star) => {707						if values.is_empty() {708							bail!(NotEnoughValues);709						}710						let value = &values[0];711						values = &values[1..];712						Some(u16::from_untyped(value.clone())?)713					}714					Some(Width::Fixed(n)) => Some(n),715					None => None,716				};717718				// %% should not consume a value719				let value = if c.convtype == ConvTypeV::Percent {720					&Val::Null721				} else {722					if values.is_empty() {723						bail!(NotEnoughValues);724					}725					let value = &values[0];726					values = &values[1..];727					value728				};729730				format_code(&mut out, value, &c, width, precision)?;731			}732		}733	}734735	if !values.is_empty() {736		bail!(737			"too many values to format, expected {value_count}, got {}",738			value_count + values.len()739		)740	}741742	Ok(out)743}744745fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {746	let mut current = Val::Obj(obj);747	let mut name_offset = 0;748	for component in field.split('.') {749		let end_offset = name_offset + component.len();750		current = if let Val::Obj(obj) = current {751			if let Some(value) = obj.get(component.into())? {752				value753			} else {754				let current = &field[name_offset..end_offset];755				let full = &field[..name_offset];756				let found = Box::new(suggest_object_fields(&obj, current.into()));757				bail!(SubfieldNotFound {758					current: current.into(),759					full: full.into(),760					found,761				})762			}763		} else {764			// No underflow may happen, initially we always start with an object765			let subfield = &field[..name_offset - 1];766			bail!(SubfieldDidntYieldAnObject(767				subfield.into(),768				current.value_type()769			));770		};771		name_offset = end_offset + 1;772	}773	Ok(current)774}775776pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {777	let codes = parse_codes(str)?;778	let mut out = String::new();779780	for code in codes {781		match code {782			Element::String(s) => {783				out.push_str(s);784			}785			Element::Code(c) => {786				// TODO: Operate on ref787				let f: IStr = c.mkey.into();788				let width = match c.width {789					Width::Star => {790						bail!(CannotUseStarWidthWithObject);791					}792					Width::Fixed(n) => n,793				};794				let precision = match c.precision {795					Some(Width::Star) => {796						bail!(CannotUseStarWidthWithObject);797					}798					Some(Width::Fixed(n)) => Some(n),799					None => None,800				};801802				let value = if c.convtype == ConvTypeV::Percent {803					Val::Null804				} else {805					if f.is_empty() {806						bail!(MappingKeysRequired);807					}808					if let Some(v) = values.get(f.clone())? {809						v810					} else {811						get_dotted_field(values.clone(), &f)?812					}813				};814815				format_code(&mut out, &value, &c, width, precision)?;816			}817		}818	}819820	Ok(out)821}822823#[cfg(test)]824pub mod test_format {825	use super::*;826	use crate::val::NumValue;827828	#[test]829	fn parse() {830		assert_eq!(831			parse_codes(832				"How much error budget is left looking at our %.3f%% availability gurantees?"833			)834			.unwrap()835			.len(),836			4837		);838	}839840	fn num(v: f64) -> Val {841		Val::Num(NumValue::new(v).expect("finite"))842	}843844	#[test]845	fn octals() {846		assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");847		assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");848		assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), "  10");849		assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");850		assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");851		assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");852		assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10  ");853		assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");854		assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");855	}856857	#[test]858	fn percent_doesnt_consumes_values() {859		assert_eq!(860			format_arr(861				"How much error budget is left looking at our %.3f%% availability gurantees?",862				&[num(4.0)]863			)864			.unwrap(),865			"How much error budget is left looking at our 4.000% availability gurantees?"866		);867	}868}
after · crates/jrsonnet-evaluator/src/stdlib/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use jrsonnet_gcmodule::Trace;5use jrsonnet_interner::IStr;6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{10	bail,11	error::{format_found, suggest_object_fields, ErrorKind::*},12	typed::Typed,13	Error, ObjValue, Result, Val,14};1516#[derive(Debug, Clone, Error, Trace)]17pub enum FormatError {18	#[error("truncated format code")]19	TruncatedFormatCode,20	#[error("unrecognized conversion type: {0}")]21	UnrecognizedConversionType(char),2223	#[error("not enough values")]24	NotEnoughValues,2526	#[error("cannot use * width with object")]27	CannotUseStarWidthWithObject,28	#[error("mapping keys required")]29	MappingKeysRequired,30	#[error("no such format field: {0}")]31	NoSuchFormatField(IStr),3233	#[error("expected subfield <{0}> to be an object, got {1} instead")]34	SubfieldDidntYieldAnObject(IStr, ValType),35	#[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]36	SubfieldNotFound {37		current: IStr,38		full: IStr,39		found: Box<Vec<IStr>>,40	},41}4243impl From<FormatError> for Error {44	fn from(e: FormatError) -> Self {45		Self::new(Format(e))46	}47}4849use FormatError::*;5051type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;5253pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> {54	if str.is_empty() {55		return Err(TruncatedFormatCode);56	}57	let bytes = str.as_bytes();58	if bytes[0] == b'(' {59		let mut i = 1;60		while i < bytes.len() {61			if bytes[i] == b')' {62				return Ok((&str[1..i], &str[i + 1..]));63			}64			i += 1;65		}66		Err(TruncatedFormatCode)67	} else {68		Ok(("", str))69	}70}7172#[cfg(test)]73pub mod tests_key {74	use super::*;7576	#[test]77	fn parse_key() {78		assert_eq!(79			try_parse_mapping_key("(hello ) world").unwrap(),80			("hello ", " world")81		);82		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));83		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));84		assert_eq!(85			try_parse_mapping_key(" () world").unwrap(),86			("", " () world")87		);88	}8990	#[test]91	#[should_panic = "TruncatedFormatCode"]92	fn parse_key_missing_start() {93		try_parse_mapping_key("").unwrap();94	}9596	#[test]97	#[should_panic = "TruncatedFormatCode"]98	fn parse_key_missing_end() {99		try_parse_mapping_key("(   ").unwrap();100	}101}102103#[allow(clippy::struct_excessive_bools)]104#[derive(Default, Debug)]105pub struct CFlags {106	pub alt: bool,107	pub zero: bool,108	pub left: bool,109	pub blank: bool,110	pub sign: bool,111}112113pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> {114	if str.is_empty() {115		return Err(TruncatedFormatCode);116	}117	let bytes = str.as_bytes();118	let mut i = 0;119	let mut out = CFlags::default();120	loop {121		if bytes.len() == i {122			return Err(TruncatedFormatCode);123		}124		match bytes[i] {125			b'#' => out.alt = true,126			b'0' => out.zero = true,127			b'-' => out.left = true,128			b' ' => out.blank = true,129			b'+' => out.sign = true,130			_ => break,131		}132		i += 1;133	}134	Ok((out, &str[i..]))135}136137#[derive(Debug, PartialEq, Eq)]138pub enum Width {139	Star,140	Fixed(u16),141}142pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {143	if str.is_empty() {144		return Err(TruncatedFormatCode);145	}146	let bytes = str.as_bytes();147	if bytes[0] == b'*' {148		return Ok((Width::Star, &str[1..]));149	}150	let mut out: u16 = 0;151	let mut digits = 0;152	while let Some(digit) = (bytes[digits] as char).to_digit(10) {153		out *= 10;154		out += digit as u16;155		digits += 1;156		if digits == bytes.len() {157			return Err(TruncatedFormatCode);158		}159	}160	Ok((Width::Fixed(out), &str[digits..]))161}162163pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {164	if str.is_empty() {165		return Err(TruncatedFormatCode);166	}167	let bytes = str.as_bytes();168	if bytes[0] == b'.' {169		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))170	} else {171		Ok((None, str))172	}173}174175// Only skips176pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> {177	if str.is_empty() {178		return Err(TruncatedFormatCode);179	}180	let bytes = str.as_bytes();181	let mut idx = 0;182	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {183		idx += 1;184		if bytes.len() == idx {185			return Err(TruncatedFormatCode);186		}187	}188	Ok(((), &str[idx..]))189}190191#[derive(Debug, PartialEq, Eq)]192pub enum ConvTypeV {193	Decimal,194	Octal,195	Hexadecimal,196	Scientific,197	Float,198	Shorter,199	Char,200	String,201	Percent,202}203pub struct ConvType {204	v: ConvTypeV,205	caps: bool,206}207208pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> {209	if str.is_empty() {210		return Err(TruncatedFormatCode);211	}212213	let code = str.as_bytes()[0];214	let v: (ConvTypeV, bool) = match code {215		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),216		b'o' => (ConvTypeV::Octal, false),217		b'x' => (ConvTypeV::Hexadecimal, false),218		b'X' => (ConvTypeV::Hexadecimal, true),219		b'e' => (ConvTypeV::Scientific, false),220		b'E' => (ConvTypeV::Scientific, true),221		b'f' => (ConvTypeV::Float, false),222		b'F' => (ConvTypeV::Float, true),223		b'g' => (ConvTypeV::Shorter, false),224		b'G' => (ConvTypeV::Shorter, true),225		b'c' => (ConvTypeV::Char, false),226		b's' => (ConvTypeV::String, false),227		b'%' => (ConvTypeV::Percent, false),228		c => return Err(UnrecognizedConversionType(c as char)),229	};230231	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))232}233234#[derive(Debug)]235pub struct Code<'s> {236	mkey: &'s str,237	cflags: CFlags,238	width: Width,239	precision: Option<Width>,240	convtype: ConvTypeV,241	caps: bool,242}243pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> {244	if str.is_empty() {245		return Err(TruncatedFormatCode);246	}247	let (mkey, str) = try_parse_mapping_key(str)?;248	let (cflags, str) = try_parse_cflags(str)?;249	let (width, str) = try_parse_field_width(str)?;250	let (precision, str) = try_parse_precision(str)?;251	let ((), str) = try_parse_length_modifier(str)?;252	let (convtype, str) = parse_conversion_type(str)?;253254	Ok((255		Code {256			mkey,257			cflags,258			width,259			precision,260			convtype: convtype.v,261			caps: convtype.caps,262		},263		str,264	))265}266267#[derive(Debug)]268pub enum Element<'s> {269	String(&'s str),270	Code(Code<'s>),271}272pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {273	let mut bytes = str.as_bytes();274	let mut out = vec![];275	let mut offset = 0;276277	loop {278		while offset != bytes.len() && bytes[offset] != b'%' {279			offset += 1;280		}281		if offset != 0 {282			out.push(Element::String(&str[0..offset]));283		}284		if offset == bytes.len() {285			return Ok(out);286		}287		str = &str[offset + 1..];288		let code;289		(code, str) = parse_code(str)?;290		bytes = str.as_bytes();291		offset = 0;292293		out.push(Element::Code(code));294	}295}296297const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";298299#[inline]300#[allow(clippy::fn_params_excessive_bools)]301pub fn render_integer(302	out: &mut String,303	neg: bool,304	iv: f64,305	padding: u16,306	precision: u16,307	blank: bool,308	sign: bool,309	radix: i64,310	zero_prefix: &str,311	prefix_in_padding: bool,312	caps: bool,313) {314	debug_assert!(iv >= 0.0, "render_integer receives sign using arg");315	let iv = iv.floor() as i64;316	// Digit char indexes in reverse order, i.e317	// for radix = 16 and n = 12f: [15, 2, 1]318	let digits = if iv == 0 {319		vec![0u8]320	} else {321		let mut v = iv.abs();322		let mut nums = Vec::with_capacity(1);323		while v != 0 {324			nums.push((v % radix) as u8);325			v /= radix;326		}327		nums328	};329	#[allow(clippy::bool_to_int_with_if)]330	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });331332	let pref_len = zero_prefix.len() as u16;333	let zp2 = zp334		.saturating_sub(if prefix_in_padding { 0 } else { pref_len })335		.max(precision)336		.saturating_sub(if prefix_in_padding { pref_len } else { 0 } + digits.len() as u16);337338	if neg {339		out.push('-');340	} else if sign {341		out.push('+');342	} else if blank {343		out.push(' ');344	}345346	out.reserve(zp2 as usize);347	if iv != 0 {348		out.push_str(zero_prefix);349	}350	for _ in 0..zp2 {351		out.push('0');352	}353354	for digit in digits.into_iter().rev() {355		let ch = NUMBERS[digit as usize] as char;356		out.push(if caps { ch.to_ascii_uppercase() } else { ch });357	}358}359360pub fn render_decimal(361	out: &mut String,362	neg: bool,363	iv: f64,364	padding: u16,365	precision: u16,366	blank: bool,367	sign: bool,368) {369	render_integer(370		out, neg, iv, padding, precision, blank, sign, 10, "", false, false,371	);372}373#[allow(clippy::fn_params_excessive_bools)]374pub fn render_octal(375	out: &mut String,376	neg: bool,377	iv: f64,378	padding: u16,379	precision: u16,380	alt: bool,381	blank: bool,382	sign: bool,383) {384	render_integer(385		out,386		neg,387		iv,388		padding,389		precision,390		blank,391		sign,392		8,393		if alt && iv != 0.0 { "0" } else { "" },394		true,395		false,396	);397}398399#[allow(clippy::fn_params_excessive_bools)]400pub fn render_hexadecimal(401	out: &mut String,402	iv: f64,403	padding: u16,404	precision: u16,405	alt: bool,406	blank: bool,407	sign: bool,408	caps: bool,409) {410	render_integer(411		out,412		iv < 0.0,413		iv.abs(),414		padding,415		precision,416		blank,417		sign,418		16,419		match (alt, caps) {420			(true, true) => "0X",421			(true, false) => "0x",422			(false, _) => "",423		},424		false,425		caps,426	);427}428429#[allow(clippy::fn_params_excessive_bools)]430pub fn render_float(431	out: &mut String,432	n: f64,433	mut padding: u16,434	precision: u16,435	blank: bool,436	sign: bool,437	ensure_pt: bool,438	trailing: bool,439) {440	// Represent the rounded number as an integer * 1/10**prec.441	// Note that it can also be equal to 10**prec and we'll need to carry442	// over to the wholes.  We operate on the absolute numbers, so that we443	// don't have trouble with the rounding direction.444	let denominator = 10.0f64.powi(i32::from(precision));445	let numerator = n.abs().mul_add(denominator, 0.5);446	let whole = (numerator / denominator).floor();447	let frac = numerator.floor() % denominator;448449	#[allow(clippy::bool_to_int_with_if)]450	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };451	padding = padding.saturating_sub(dot_size + precision);452	render_decimal(out, n < 0.0, whole, padding, 0, blank, sign);453	if precision == 0 {454		if ensure_pt {455			out.push('.');456		}457		return;458	}459	if trailing || frac > 0.0 {460		out.push('.');461		let mut frac_str = String::new();462		render_decimal(&mut frac_str, false, frac, precision, 0, false, false);463		let mut trim = frac_str.len();464		if !trailing {465			for b in frac_str.as_bytes().iter().rev() {466				if *b == b'0' {467					trim -= 1;468				} else {469					break;470				}471			}472		}473		out.push_str(&frac_str[..trim]);474	} else if ensure_pt {475		out.push('.');476	}477}478479#[allow(clippy::fn_params_excessive_bools)]480pub fn render_float_sci(481	out: &mut String,482	n: f64,483	mut padding: u16,484	precision: u16,485	blank: bool,486	sign: bool,487	ensure_pt: bool,488	trailing: bool,489	caps: bool,490) {491	let exponent = if n == 0.0 {492		0.0493	} else {494		n.abs().log10().floor()495	};496497	let mantissa = if exponent as i16 == -324 {498		n * 10.0 / 10.0_f64.powf(exponent + 1.0)499	} else {500		n / 10.0_f64.powf(exponent)501	};502	let mut exponent_str = String::new();503	render_decimal(504		&mut exponent_str,505		exponent < 0.0,506		exponent.abs(),507		3,508		0,509		false,510		true,511	);512513	// +1 for e514	padding = padding.saturating_sub(exponent_str.len() as u16 + 1);515516	render_float(517		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,518	);519	out.push(if caps { 'E' } else { 'e' });520	out.push_str(&exponent_str);521}522523#[allow(clippy::too_many_lines)]524pub fn format_code(525	out: &mut String,526	value: &Val,527	code: &Code<'_>,528	width: u16,529	precision: Option<u16>,530) -> Result<()> {531	let clfags = &code.cflags;532	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));533	let padding = if clfags.zero && !clfags.left {534		width535	} else {536		0537	};538539	// TODO: If left padded, can optimize by writing directly to out540	let mut tmp_out = String::new();541542	match code.convtype {543		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),544		ConvTypeV::Decimal => {545			let value = f64::from_untyped(value.clone())?;546			render_decimal(547				&mut tmp_out,548				value <= -1.0,549				value.abs(),550				padding,551				iprec,552				clfags.blank,553				clfags.sign,554			);555		}556		ConvTypeV::Octal => {557			let value = f64::from_untyped(value.clone())?;558			render_octal(559				&mut tmp_out,560				value <= -1.0,561				value.abs(),562				padding,563				iprec,564				clfags.alt,565				clfags.blank,566				clfags.sign,567			);568		}569		ConvTypeV::Hexadecimal => {570			let value = f64::from_untyped(value.clone())?;571			render_hexadecimal(572				&mut tmp_out,573				value,574				padding,575				iprec,576				clfags.alt,577				clfags.blank,578				clfags.sign,579				code.caps,580			);581		}582		ConvTypeV::Scientific => {583			let value = f64::from_untyped(value.clone())?;584			render_float_sci(585				&mut tmp_out,586				value,587				padding,588				fpprec,589				clfags.blank,590				clfags.sign,591				clfags.alt,592				true,593				code.caps,594			);595		}596		ConvTypeV::Float => {597			let value = f64::from_untyped(value.clone())?;598			render_float(599				&mut tmp_out,600				value,601				padding,602				fpprec,603				clfags.blank,604				clfags.sign,605				clfags.alt,606				true,607			);608		}609		ConvTypeV::Shorter => {610			let value = f64::from_untyped(value.clone())?;611			let exponent = if value == 0.0 {612				0.0613			} else {614				value.abs().log10().floor()615			};616			if exponent < -4.0 || exponent >= f64::from(fpprec) {617				render_float_sci(618					&mut tmp_out,619					value,620					padding,621					fpprec - 1,622					clfags.blank,623					clfags.sign,624					clfags.alt,625					clfags.alt,626					code.caps,627				);628			} else {629				let digits_before_pt = 1.max(exponent as u16 + 1);630				render_float(631					&mut tmp_out,632					value,633					padding,634					fpprec - digits_before_pt,635					clfags.blank,636					clfags.sign,637					clfags.alt,638					clfags.alt,639				);640			}641		}642		ConvTypeV::Char => match value.clone() {643			Val::Num(n) => {644				let n = n.get();645				tmp_out.push(646					std::char::from_u32(n as u32)647						.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,648				);649			}650			Val::Str(s) => {651				let s = s.into_flat();652				if s.chars().count() != 1 {653					bail!("%c expected 1 char string, got {}", s.chars().count());654				}655				tmp_out.push_str(&s);656			}657			_ => {658				bail!(TypeMismatch(659					"%c requires number/string",660					vec![ValType::Num, ValType::Str],661					value.value_type(),662				));663			}664		},665		ConvTypeV::Percent => tmp_out.push('%'),666	}667668	let padding = width.saturating_sub(tmp_out.len() as u16);669670	if !clfags.left {671		for _ in 0..padding {672			out.push(' ');673		}674	}675	out.push_str(&tmp_out);676	if clfags.left {677		for _ in 0..padding {678			out.push(' ');679		}680	}681682	Ok(())683}684685pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {686	let codes = parse_codes(str)?;687	let mut out = String::new();688	let value_count = values.len();689690	for code in codes {691		match code {692			Element::String(s) => {693				out.push_str(s);694			}695			Element::Code(c) => {696				let width = match c.width {697					Width::Star => {698						if values.is_empty() {699							bail!(NotEnoughValues);700						}701						let value = &values[0];702						values = &values[1..];703						u16::from_untyped(value.clone())?704					}705					Width::Fixed(n) => n,706				};707				let precision = match c.precision {708					Some(Width::Star) => {709						if values.is_empty() {710							bail!(NotEnoughValues);711						}712						let value = &values[0];713						values = &values[1..];714						Some(u16::from_untyped(value.clone())?)715					}716					Some(Width::Fixed(n)) => Some(n),717					None => None,718				};719720				// %% should not consume a value721				let value = if c.convtype == ConvTypeV::Percent {722					&Val::Null723				} else {724					if values.is_empty() {725						bail!(NotEnoughValues);726					}727					let value = &values[0];728					values = &values[1..];729					value730				};731732				format_code(&mut out, value, &c, width, precision)?;733			}734		}735	}736737	if !values.is_empty() {738		bail!(739			"too many values to format, expected {value_count}, got {}",740			value_count + values.len()741		)742	}743744	Ok(out)745}746747fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {748	let mut current = Val::Obj(obj);749	let mut name_offset = 0;750	for component in field.split('.') {751		let end_offset = name_offset + component.len();752		current = if let Val::Obj(obj) = current {753			if let Some(value) = obj.get(component.into())? {754				value755			} else {756				let current = &field[name_offset..end_offset];757				let full = &field[..name_offset];758				let found = Box::new(suggest_object_fields(&obj, current.into()));759				bail!(SubfieldNotFound {760					current: current.into(),761					full: full.into(),762					found,763				})764			}765		} else {766			// No underflow may happen, initially we always start with an object767			let subfield = &field[..name_offset - 1];768			bail!(SubfieldDidntYieldAnObject(769				subfield.into(),770				current.value_type()771			));772		};773		name_offset = end_offset + 1;774	}775	Ok(current)776}777778pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {779	let codes = parse_codes(str)?;780	let mut out = String::new();781782	for code in codes {783		match code {784			Element::String(s) => {785				out.push_str(s);786			}787			Element::Code(c) => {788				// TODO: Operate on ref789				let f: IStr = c.mkey.into();790				let width = match c.width {791					Width::Star => {792						bail!(CannotUseStarWidthWithObject);793					}794					Width::Fixed(n) => n,795				};796				let precision = match c.precision {797					Some(Width::Star) => {798						bail!(CannotUseStarWidthWithObject);799					}800					Some(Width::Fixed(n)) => Some(n),801					None => None,802				};803804				let value = if c.convtype == ConvTypeV::Percent {805					Val::Null806				} else {807					if f.is_empty() {808						bail!(MappingKeysRequired);809					}810					if let Some(v) = values.get(f.clone())? {811						v812					} else {813						get_dotted_field(values.clone(), &f)?814					}815				};816817				format_code(&mut out, &value, &c, width, precision)?;818			}819		}820	}821822	Ok(out)823}824825#[cfg(test)]826pub mod test_format {827	use super::*;828	use crate::val::NumValue;829830	#[test]831	fn parse() {832		assert_eq!(833			parse_codes(834				"How much error budget is left looking at our %.3f%% availability gurantees?"835			)836			.unwrap()837			.len(),838			4839		);840	}841842	fn num(v: f64) -> Val {843		Val::Num(NumValue::new(v).expect("finite"))844	}845846	#[test]847	fn octals() {848		assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");849		assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");850		assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), "  10");851		assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");852		assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");853		assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");854		assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10  ");855		assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");856		assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");857	}858859	#[test]860	fn percent_doesnt_consumes_values() {861		assert_eq!(862			format_arr(863				"How much error budget is left looking at our %.3f%% availability gurantees?",864				&[num(4.0)]865			)866			.unwrap(),867			"How much error budget is left looking at our 4.000% availability gurantees?"868		);869	}870}
modifiedcrates/jrsonnet-evaluator/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/tla.rs
+++ b/crates/jrsonnet-evaluator/src/tla.rs
@@ -1,13 +1,14 @@
+use std::{collections::HashMap, hash::BuildHasher};
+
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::Source;
-use rustc_hash::FxHashMap;
 
 use crate::{
 	function::{CallLocation, TlaArg},
 	in_description_frame, with_state, Result, Val,
 };
 
-pub fn apply_tla(args: &FxHashMap<IStr, TlaArg>, val: Val) -> Result<Val> {
+pub fn apply_tla<H: BuildHasher>(args: &HashMap<IStr, TlaArg, H>, val: Val) -> Result<Val> {
 	Ok(if let Val::Func(func) = val {
 		in_description_frame(
 			|| "during TLA call".to_owned(),
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -462,7 +462,7 @@
 		};
 		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());
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -65,7 +65,7 @@
 			MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),
 			MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
 			MemoizedClusureThunkInner::Waiting { .. } => (),
-		};
+		}
 		let MemoizedClusureThunkInner::Waiting { env, closure } = replace(
 			&mut *self.0.borrow_mut(),
 			MemoizedClusureThunkInner::Pending,
@@ -288,14 +288,11 @@
 			Self::Str(s) => {
 				let mut computed_len = None;
 				let mut get_len = || {
-					computed_len.map_or_else(
-						|| {
-							let len = s.chars().count();
-							let _ = computed_len.insert(len);
-							len
-						},
-						|len| len,
-					)
+					computed_len.unwrap_or_else(|| {
+						let len = s.chars().count();
+						let _ = computed_len.insert(len);
+						len
+					})
 				};
 				let mut get_idx = |pos: Option<i32>, default| {
 					match pos {
@@ -446,7 +443,7 @@
 	pub const fn get(&self) -> f64 {
 		self.0
 	}
-	pub(crate) fn truncate_for_bitwise(&self) -> Result<i64> {
+	pub(crate) fn truncate_for_bitwise(self) -> Result<i64> {
 		if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {
 			bail!("numberic value outside of safe integer range for bitwise operation");
 		}
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -227,9 +227,11 @@
 type PoolMap = HashMap<Inner, (), FxBuildHasher>;
 
 thread_local! {
-	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, FxBuildHasher::default()));
+	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, FxBuildHasher));
 }
 
+/// Utils for embedding jrsonnet in non-rust.
+///
 /// Jrsonnet golang bindings require that it is possible to move jsonnet
 /// VM between OS threads, and this is not possible due to usage of
 /// `thread_local`. Instead, there is two methods added, one should be
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -127,6 +127,7 @@
 	Default(Expr),
 }
 
+#[allow(clippy::large_enum_variant, reason = "this macro is not that hot for it to matter")]
 enum ArgInfo {
 	Normal {
 		ty: Box<Type>,
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -166,6 +166,10 @@
 	pub fn len(&self) -> usize {
 		self.exprs.len()
 	}
+	pub fn is_empty(&self) -> bool {
+		self.exprs.is_empty()
+	}
+
 	pub fn binds_len(&self) -> usize {
 		self.binds_len
 	}
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -98,9 +98,9 @@
 			for c in str.chars() {
 				match func(Either2::A(c.to_string()))? {
 					Val::Str(o) => write!(out, "{o}").unwrap(),
-					Val::Null => continue,
+					Val::Null => {},
 					_ => bail!("in std.join all items should be strings"),
-				};
+				}
 			}
 			Ok(IndexableVal::Str(out.into()))
 		}
@@ -114,9 +114,9 @@
 							out.push(oe?);
 						}
 					}
-					Val::Null => continue,
+					Val::Null => {},
 					_ => bail!("in std.join all items should be arrays"),
-				};
+				}
 			}
 			Ok(IndexableVal::Arr(out.into()))
 		}
@@ -205,7 +205,6 @@
 						out.push(item?);
 					}
 				} else if matches!(item, Val::Null) {
-					continue;
 				} else {
 					bail!("in std.join all items should be arrays");
 				}
@@ -226,7 +225,6 @@
 					first = false;
 					write!(out, "{item}").unwrap();
 				} else if matches!(item, Val::Null) {
-					continue;
 				} else {
 					bail!("in std.join all items should be strings");
 				}
modifiedcrates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/xml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -46,7 +46,7 @@
 		};
 		if arr.is_empty() {
 			bail!("JSONML value should have tag (array length should be >=1)");
-		};
+		}
 		let tag = String::from_untyped(
 			arr.get(0)
 				.description("getting JSONML tag")?
modifiedcrates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -90,6 +90,7 @@
 		RESERVED.iter().any(|k| key.eq_ignore_ascii_case(k))
 	}
 
+	#[allow(clippy::if_same_then_else)]
 	// Check for unsafe characters
 	if !key
 		.chars()
@@ -98,7 +99,7 @@
 		return false;
 	}
 	// Check for reserved words
-	if is_reserved(key) {
+	else if is_reserved(key) {
 		return false;
 	}
 	// Check for timestamp values.  Since spaces and colons are already forbidden,
@@ -107,7 +108,7 @@
 	// - all characters match [0-9\-]
 	// - has exactly 2 dashes
 	// are considered dates.
-	if key.chars().all(|v| matches!(v, '0'..='9' | '-')) && count_char(key, '-') == 2 {
+	else if key.chars().all(|v| matches!(v, '0'..='9' | '-')) && count_char(key, '-') == 2 {
 		return false;
 	}
 	// Check for integers.  Keys that meet all of the following:
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -172,7 +172,7 @@
 	let Some(patch) = patch.as_obj() else {
 		return Ok(patch);
 	};
-	let target = target.as_obj().unwrap_or_else(|| ObjValue::empty());
+	let target = target.as_obj().unwrap_or_else(ObjValue::empty);
 	let target_fields = target
 		.fields(
 			// FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?
modifiedcrates/jrsonnet-stdlib/src/sets.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sets.rs
+++ b/crates/jrsonnet-stdlib/src/sets.rs
@@ -21,7 +21,7 @@
 	let x = keyF(x)?;
 
 	while low < high {
-		let middle = (high + low) / 2;
+		let middle = usize::midpoint(high, low);
 		let comp = keyF(arr.get_lazy(middle).expect("in bounds"))?;
 		match evaluate_compare_op(&comp, &x, BinaryOpType::Lt)? {
 			Ordering::Less => low = middle + 1,
@@ -66,7 +66,7 @@
 				bv = b.next();
 				bk = bv.map(keyF).transpose()?;
 			}
-		};
+		}
 	}
 	Ok(ArrValue::lazy(out))
 }
@@ -106,7 +106,7 @@
 				bv = b.next();
 				bk = bv.map(keyF).transpose()?;
 			}
-		};
+		}
 	}
 	while let Some(_ac) = &ak {
 		// In a, but not in b
@@ -154,7 +154,7 @@
 				bv = b.next();
 				bk = bv.clone().map(keyF).transpose()?;
 			}
-		};
+		}
 	}
 	// a.len() > b.len()
 	while let Some(_ac) = &ak {
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -66,7 +66,7 @@
 				return Err(err);
 			}
 		}
-	};
+	}
 	Ok(values)
 }
 
@@ -107,7 +107,7 @@
 				return Err(err);
 			}
 		}
-	};
+	}
 	Ok(vk.into_iter().map(|v| v.0).collect())
 }
 
@@ -204,7 +204,7 @@
 	}
 }
 
-fn eval_keyf(val: Val, key_f: &Option<FuncVal>) -> Result<Val> {
+fn eval_keyf(val: Val, key_f: Option<&FuncVal>) -> Result<Val> {
 	if let Some(key_f) = key_f {
 		key_f.evaluate_simple(&(val,), false)
 	} else {
@@ -212,13 +212,13 @@
 	}
 }
 
-fn array_top1(arr: ArrValue, key_f: Option<FuncVal>, ordering: Ordering) -> Result<Val> {
+fn array_top1(arr: ArrValue, key_f: Option<&FuncVal>, ordering: Ordering) -> Result<Val> {
 	let mut iter = arr.iter();
 	let mut min = iter.next().expect("not empty")?;
-	let mut min_key = eval_keyf(min.clone(), &key_f)?;
+	let mut min_key = eval_keyf(min.clone(), key_f)?;
 	for item in iter {
 		let cur = item?;
-		let cur_key = eval_keyf(cur.clone(), &key_f)?;
+		let cur_key = eval_keyf(cur.clone(), key_f)?;
 		if evaluate_compare_op(&cur_key, &min_key, BinaryOpType::Lt)? == ordering {
 			min = cur;
 			min_key = cur_key;
@@ -236,7 +236,7 @@
 	if arr.is_empty() {
 		return eval_on_empty(onEmpty);
 	}
-	array_top1(arr, keyF, Ordering::Less)
+	array_top1(arr, keyF.as_ref(), Ordering::Less)
 }
 #[builtin]
 pub fn builtin_max_array(
@@ -247,5 +247,5 @@
 	if arr.is_empty() {
 		return eval_on_empty(onEmpty);
 	}
-	array_top1(arr, keyF, Ordering::Greater)
+	array_top1(arr, keyF.as_ref(), Ordering::Greater)
 }
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -53,7 +53,7 @@
 
 #[builtin]
 pub fn builtin_equals_ignore_case(str1: String, str2: String) -> bool {
-	str1.to_ascii_lowercase() == str2.to_ascii_lowercase()
+	str1.eq_ignore_ascii_case(&str2)
 }
 
 #[builtin]
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -133,7 +133,7 @@
 			Self::Sum(v) => write_union(f, false, v.iter())?,
 			Self::SumRef(v) => write_union(f, false, v.iter().copied())?,
 			Self::Lazy(lazy) => write!(f, "Lazy<{lazy}>")?,
-		};
+		}
 		Ok(())
 	}
 }
modifiedtests/tests/common.rsdiffbeforeafterboth
--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -57,12 +57,13 @@
 #[builtin]
 fn param_names(fun: FuncVal) -> Vec<String> {
 	fun.params()
-		.into_iter()
+		.iter()
 		.map(|v| v.name().as_str().unwrap_or("<unnamed>").to_owned())
 		.collect()
 }
 
 #[derive(Trace)]
+#[allow(dead_code)]
 pub struct ContextInitializer;
 impl ContextInitializerT for ContextInitializer {
 	fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
modifiedtests/tests/cpp_test_suite.rsdiffbeforeafterboth
--- a/tests/tests/cpp_test_suite.rs
+++ b/tests/tests/cpp_test_suite.rs
@@ -23,29 +23,29 @@
 	// C++ test suite
 	std_context.add_ext_str("var1".into(), "test".into());
 	std_context
-		.add_ext_code("var2".into(), "{x:1,y:2}")
+		.add_ext_code("var2", "{x:1,y:2}")
 		.expect("code is valid");
 
 	// Golang test suite
 	std_context
-		.add_ext_code("codeVar".into(), "3+3")
+		.add_ext_code("codeVar", "3+3")
 		.expect("code is valid");
 	std_context.add_ext_str("stringVar".into(), "2 + 2".into());
 	std_context
 		.add_ext_code(
-			"selfRecursiveVar".into(),
+			"selfRecursiveVar",
 			r#"[42, std.extVar("selfRecursiveVar")[0] + 1]"#,
 		)
 		.expect("code is valid");
 	std_context
 		.add_ext_code(
-			"mutuallyRecursiveVar1".into(),
+			"mutuallyRecursiveVar1",
 			r#"[42, std.extVar("mutuallyRecursiveVar2")[0] + 1]"#,
 		)
 		.expect("code is valid");
 	std_context
 		.add_ext_code(
-			"mutuallyRecursiveVar2".into(),
+			"mutuallyRecursiveVar2",
 			r#"[42, std.extVar("mutuallyRecursiveVar1")[0] + 1]"#,
 		)
 		.expect("code is valid");
@@ -203,9 +203,9 @@
 		let root = root_tests.join(root_dir);
 		let root_override = root_tests.join(format!("{root_dir}_golden_override"));
 
-		for entry in fs::read_dir(&root).map_err(|e| io::Error::new(ErrorKind::Other, format!("failed to enumerate cpp_test_suite dir (Note: it needs to be cloned from C++ jsonnet repo for this test): {e}")))? {
+		for entry in fs::read_dir(&root).map_err(|e| io::Error::other(format!("failed to enumerate cpp_test_suite dir (Note: it needs to be cloned from C++ jsonnet repo for this test): {e}")))? {
 		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+		if entry.path().extension().is_none_or(|e| e != "jsonnet") {
 			continue;
 		}
 
@@ -213,7 +213,7 @@
 			.path()
 			.file_name()
 			.and_then(|v| v.to_str())
-			.map_or(false, |v| SKIPPED.contains(&v))
+			.is_some_and(|v| SKIPPED.contains(&v))
 		{
 			continue;
 		}
@@ -227,7 +227,7 @@
 		golden_path2.set_extension("golden");
 
 		let golden_override =
-			root_override.join(&golden_path.file_name().expect("file has basename"));
+			root_override.join(golden_path.file_name().expect("file has basename"));
 
 		// .jsonnet.golden for C++ tests
 		let mut golden = read_file(&golden_path)?;
@@ -282,7 +282,7 @@
 					}
 				}
 			}
-		};
+		}
 	}
 	}
 
modifiedtests/tests/golden.rsdiffbeforeafterboth
--- a/tests/tests/golden.rs
+++ b/tests/tests/golden.rs
@@ -40,8 +40,8 @@
 #[test]
 fn golden() {
 	glob!("../", "golden/*.jsonnet", |path| {
-		let result = run(&path);
+		let result = run(path);
 
-		assert_snapshot!(result)
+		assert_snapshot!(result);
 	});
 }
modifiedtests/tests/suite.rsdiffbeforeafterboth
--- a/tests/tests/suite.rs
+++ b/tests/tests/suite.rs
@@ -32,7 +32,7 @@
 			file.display(),
 			trace_format.format(&e).unwrap()
 		),
-	};
+	}
 }
 
 #[test]
@@ -42,11 +42,9 @@
 
 	for entry in fs::read_dir(&root)? {
 		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
-			continue;
+		if entry.path().extension().is_some_and(|e| e == "jsonnet") {
+			run(&entry.path());
 		}
-
-		run(&entry.path());
 	}
 
 	Ok(())