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

difftreelog

refactor fix clippy warnings

Yaroslav Bolyukin2024-04-07parent: #d349b9e.patch.diff
in: master

27 files changed

modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -150,6 +150,9 @@
 redundant_pub_crate = "allow"
 # Sometimes code is fancier without that
 manual_let_else = "allow"
+# Something is broken about that lint, can't be allowed for
+# codegenerated-stdlib block
+similar_names = "allow"
 
 #[profile.test]
 #opt-level = 1
modifiedcmds/jrsonnet-fmt/src/tests.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/tests.rs
+++ b/cmds/jrsonnet-fmt/src/tests.rs
@@ -1,4 +1,4 @@
-use dprint_core::formatting::{PrintOptions, PrintItems};
+use dprint_core::formatting::{PrintItems, PrintOptions};
 use indoc::indoc;
 
 use crate::Printable;
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -153,7 +153,7 @@
 		if let Error::Evaluation(e) = e {
 			let mut out = String::new();
 			trace.write_trace(&mut out, &e).expect("format error");
-			eprintln!("{out}")
+			eprintln!("{out}");
 		} else {
 			eprintln!("{e}");
 		}
modifiedcrates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/lib.rs
+++ b/crates/jrsonnet-cli/src/lib.rs
@@ -10,7 +10,7 @@
 	stack::{limit_stack_depth, StackDepthLimitOverrideGuard},
 	FileImportResolver,
 };
-use jrsonnet_gcmodule::with_thread_object_space;
+use jrsonnet_gcmodule::{with_thread_object_space, ObjectSpace};
 pub use manifest::*;
 pub use stdlib::*;
 pub use tla::*;
@@ -88,7 +88,7 @@
 
 impl Drop for LeakSpace {
 	fn drop(&mut self) {
-		with_thread_object_space(|s| s.leak())
+		with_thread_object_space(ObjectSpace::leak);
 	}
 }
 
@@ -102,6 +102,6 @@
 			let collected = jrsonnet_gcmodule::collect_thread_cycles();
 			eprintln!("Collected: {collected}");
 		}
-		eprintln!("Tracked: {}", jrsonnet_gcmodule::count_thread_tracked())
+		eprintln!("Tracked: {}", jrsonnet_gcmodule::count_thread_tracked());
 	}
 }
modifiedcrates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/stdlib.rs
+++ b/crates/jrsonnet-cli/src/stdlib.rs
@@ -39,11 +39,11 @@
 
 	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
 		match s.find('=') {
-			Some(idx) => Ok(ExtStr {
+			Some(idx) => Ok(Self {
 				name: s[..idx].to_owned(),
 				value: s[idx + 1..].to_owned(),
 			}),
-			None => Ok(ExtStr {
+			None => Ok(Self {
 				name: s.to_owned(),
 				value: std::env::var(s).or(Err("missing env var"))?,
 			}),
@@ -109,16 +109,16 @@
 			return Ok(None);
 		}
 		let ctx = ContextInitializer::new(s.clone(), PathResolver::new_cwd_fallback());
-		for ext in self.ext_str.iter() {
+		for ext in &self.ext_str {
 			ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
 		}
-		for ext in self.ext_str_file.iter() {
+		for ext in &self.ext_str_file {
 			ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
 		}
-		for ext in self.ext_code.iter() {
+		for ext in &self.ext_code {
 			ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
 		}
-		for ext in self.ext_code_file.iter() {
+		for ext in &self.ext_code_file {
 			ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
 		}
 		Ok(Some(ctx))
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -42,7 +42,7 @@
 		Self::new(EagerArray(values))
 	}
 
-	pub fn repeated(data: ArrValue, repeats: usize) -> Option<Self> {
+	pub fn repeated(data: Self, repeats: usize) -> Option<Self> {
 		Some(Self::new(RepeatedArray::new(data, repeats)?))
 	}
 
@@ -70,7 +70,7 @@
 		Ok(Self::eager(out))
 	}
 
-	pub fn extended(a: ArrValue, b: ArrValue) -> Self {
+	pub fn extended(a: Self, b: Self) -> Self {
 		// TODO: benchmark for an optimal value, currently just a arbitrary choice
 		const ARR_EXTEND_THRESHOLD: usize = 100;
 
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -61,8 +61,8 @@
 impl ArgLike for TlaArg {
 	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
 		match self {
-			TlaArg::String(s) => Ok(Thunk::evaluated(Val::string(s.clone()))),
-			TlaArg::Code(code) => Ok(if tailstrict {
+			Self::String(s) => Ok(Thunk::evaluated(Val::string(s.clone()))),
+			Self::Code(code) => Ok(if tailstrict {
 				Thunk::evaluated(evaluate(ctx, code)?)
 			} else {
 				Thunk::new(EvaluateThunk {
@@ -70,8 +70,8 @@
 					expr: code.clone(),
 				})
 			}),
-			TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())),
-			TlaArg::Lazy(lazy) => Ok(lazy.clone()),
+			Self::Val(val) => Ok(Thunk::evaluated(val.clone())),
+			Self::Lazy(lazy) => Ok(lazy.clone()),
 		}
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -237,7 +237,7 @@
 
 	pub fn evaluate_trivial(&self) -> Option<Val> {
 		match self {
-			FuncVal::Normal(n) => n.evaluate_trivial(),
+			Self::Normal(n) => n.evaluate_trivial(),
 			_ => None,
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -10,7 +10,7 @@
 use fs::File;
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IBytes;
-use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath, SourceFifo};
+use jrsonnet_parser::{SourceDirectory, SourceFifo, SourceFile, SourcePath};
 
 use crate::{
 	bail,
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -15,7 +15,7 @@
 };
 
 impl<'de> Deserialize<'de> for Val {
-	fn deserialize<D>(deserializer: D) -> Result<Val, D::Error>
+	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 	where
 		D: serde::Deserializer<'de>,
 	{
@@ -155,10 +155,10 @@
 		S: serde::Serializer,
 	{
 		match self {
-			Val::Bool(v) => serializer.serialize_bool(*v),
-			Val::Null => serializer.serialize_none(),
-			Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
-			Val::Num(n) => {
+			Self::Bool(v) => serializer.serialize_bool(*v),
+			Self::Null => serializer.serialize_none(),
+			Self::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
+			Self::Num(n) => {
 				if n.fract() == 0.0 {
 					let n = *n as i64;
 					serializer.serialize_i64(n)
@@ -167,8 +167,8 @@
 				}
 			}
 			#[cfg(feature = "exp-bigint")]
-			Val::BigInt(b) => b.serialize(serializer),
-			Val::Arr(arr) => {
+			Self::BigInt(b) => b.serialize(serializer),
+			Self::Arr(arr) => {
 				let mut seq = serializer.serialize_seq(Some(arr.len()))?;
 				for (i, element) in arr.iter().enumerate() {
 					let mut serde_error = None;
@@ -190,7 +190,7 @@
 				}
 				seq.end()
 			}
-			Val::Obj(obj) => {
+			Self::Obj(obj) => {
 				let mut map = serializer.serialize_map(Some(obj.len()))?;
 				for (field, value) in obj.iter(
 					#[cfg(feature = "exp-preserve-order")]
@@ -215,7 +215,7 @@
 				}
 				map.end()
 			}
-			Val::Func(_) => Err(S::Error::custom("tried to manifest function")),
+			Self::Func(_) => Err(S::Error::custom("tried to manifest function")),
 		}
 	}
 }
@@ -248,9 +248,9 @@
 	type Ok = Val;
 	type Error = JrError;
 
-	fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
+	fn serialize_element<T>(&mut self, value: &T) -> Result<()>
 	where
-		T: Serialize,
+		T: ?Sized + Serialize,
 	{
 		let value = value.serialize(IntoValSerializer)?;
 		self.data.push(value);
@@ -272,9 +272,9 @@
 	type Ok = Val;
 	type Error = JrError;
 
-	fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
+	fn serialize_element<T>(&mut self, value: &T) -> Result<()>
 	where
-		T: Serialize,
+		T: ?Sized + Serialize,
 	{
 		SerializeSeq::serialize_element(self, value)
 	}
@@ -287,9 +287,9 @@
 	type Ok = Val;
 	type Error = JrError;
 
-	fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
+	fn serialize_field<T>(&mut self, value: &T) -> Result<()>
 	where
-		T: Serialize,
+		T: ?Sized + Serialize,
 	{
 		SerializeSeq::serialize_element(self, value)
 	}
@@ -302,9 +302,9 @@
 	type Ok = Val;
 	type Error = JrError;
 
-	fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
+	fn serialize_field<T>(&mut self, value: &T) -> Result<()>
 	where
-		T: Serialize,
+		T: ?Sized + Serialize,
 	{
 		SerializeSeq::serialize_element(self, value)
 	}
@@ -607,7 +607,7 @@
 }
 
 impl Val {
-	pub fn from_serde(v: impl Serialize) -> Result<Val, JrError> {
+	pub fn from_serde(v: impl Serialize) -> Result<Self, JrError> {
 		v.serialize(IntoValSerializer)
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -45,7 +45,7 @@
 #[doc(hidden)]
 pub use jrsonnet_macros;
 pub use jrsonnet_parser as parser;
-use jrsonnet_parser::*;
+use jrsonnet_parser::{ExprLocation, LocExpr, ParserSettings, Source, SourcePath};
 pub use obj::*;
 use stack::check_depth;
 pub use tla::apply_tla;
modifiedcrates/jrsonnet-evaluator/src/stack.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stack.rs
+++ b/crates/jrsonnet-evaluator/src/stack.rs
@@ -24,7 +24,7 @@
 pub struct StackOverflowError;
 impl From<StackOverflowError> for ErrorKind {
 	fn from(_: StackOverflowError) -> Self {
-		ErrorKind::StackOverflow
+		Self::StackOverflow
 	}
 }
 impl From<StackOverflowError> for Error {
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -358,7 +358,7 @@
 		};
 		a.iter()
 			.map(|r| r.and_then(T::from_untyped))
-			.collect::<Result<Vec<T>>>()
+			.collect::<Result<Self>>()
 	}
 }
 
@@ -381,7 +381,7 @@
 		Self::TYPE.check(&value)?;
 		let obj = value.as_obj().expect("typecheck should fail");
 
-		let mut out = BTreeMap::new();
+		let mut out = Self::new();
 		if V::wants_lazy() {
 			for key in obj.fields_ex(
 				false,
@@ -623,8 +623,8 @@
 
 	fn into_untyped(value: Self) -> Result<Val> {
 		match value {
-			IndexableVal::Str(s) => Ok(Val::string(s)),
-			IndexableVal::Arr(a) => Ok(Val::Arr(a)),
+			Self::Str(s) => Ok(Val::string(s)),
+			Self::Arr(a) => Ok(Val::Arr(a)),
 		}
 	}
 
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -147,7 +147,7 @@
 	T: ThunkValue<Output = V>,
 {
 	fn from(value: T) -> Self {
-		Thunk::new(value)
+		Self::new(value)
 	}
 }
 
@@ -221,8 +221,8 @@
 impl IndexableVal {
 	pub fn to_array(self) -> ArrValue {
 		match self {
-			IndexableVal::Str(s) => ArrValue::chars(s.chars()),
-			IndexableVal::Arr(arr) => arr,
+			Self::Str(s) => ArrValue::chars(s.chars()),
+			Self::Arr(arr) => arr,
 		}
 	}
 	/// Slice the value.
@@ -239,7 +239,7 @@
 		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
 	) -> Result<Self> {
 		match &self {
-			IndexableVal::Str(s) => {
+			Self::Str(s) => {
 				let mut computed_len = None;
 				let mut get_len = || {
 					computed_len.map_or_else(
@@ -277,7 +277,7 @@
 					.into(),
 				))
 			}
-			IndexableVal::Arr(arr) => {
+			Self::Arr(arr) => {
 				let get_idx = |pos: Option<i32>, len: usize, default| match pos {
 					Some(v) if v < 0 => len.saturating_sub((-v) as usize),
 					Some(v) => (v as usize).min(len),
@@ -307,7 +307,7 @@
 	Tree(Rc<(StrValue, StrValue, usize)>),
 }
 impl StrValue {
-	pub fn concat(a: StrValue, b: StrValue) -> Self {
+	pub fn concat(a: Self, b: Self) -> Self {
 		// TODO: benchmark for an optimal value, currently just a arbitrary choice
 		const STRING_EXTEND_THRESHOLD: usize = 100;
 
@@ -334,8 +334,8 @@
 			}
 		}
 		match self {
-			StrValue::Flat(f) => f,
-			StrValue::Tree(_) => {
+			Self::Flat(f) => f,
+			Self::Tree(_) => {
 				let mut buf = String::with_capacity(self.len());
 				write_buf(&self, &mut buf);
 				buf.into()
@@ -344,8 +344,8 @@
 	}
 	pub fn len(&self) -> usize {
 		match self {
-			StrValue::Flat(v) => v.len(),
-			StrValue::Tree(t) => t.2,
+			Self::Flat(v) => v.len(),
+			Self::Tree(t) => t.2,
 		}
 	}
 	pub fn is_empty(&self) -> bool {
@@ -367,8 +367,8 @@
 impl Display for StrValue {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 		match self {
-			StrValue::Flat(v) => write!(f, "{v}"),
-			StrValue::Tree(t) => {
+			Self::Flat(v) => write!(f, "{v}"),
+			Self::Tree(t) => {
 				write!(f, "{}", t.0)?;
 				write!(f, "{}", t.1)
 			}
@@ -522,8 +522,8 @@
 
 	pub fn into_indexable(self) -> Result<IndexableVal> {
 		Ok(match self {
-			Val::Str(s) => IndexableVal::Str(s.into_flat()),
-			Val::Arr(arr) => IndexableVal::Arr(arr),
+			Self::Str(s) => IndexableVal::Str(s.into_flat()),
+			Self::Arr(arr) => IndexableVal::Arr(arr),
 			_ => bail!(ValueIsNotIndexable(self.value_type())),
 		})
 	}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-macros/src/lib.rs
1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4	parenthesized,5	parse::{Parse, ParseStream},6	parse_macro_input,7	punctuated::Punctuated,8	spanned::Spanned,9	token::{self, Comma},10	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,11	PathArguments, Result, ReturnType, Token, Type,12};1314fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>15where16	Ident: PartialEq<I>,17{18	let attrs = attrs19		.iter()20		.filter(|a| a.path().is_ident(&ident))21		.collect::<Vec<_>>();22	if attrs.len() > 1 {23		return Err(Error::new(24			attrs[1].span(),25			"this attribute may be specified only once",26		));27	} else if attrs.is_empty() {28		return Ok(None);29	}30	let attr = attrs[0];31	let attr = attr.parse_args::<A>()?;3233	Ok(Some(attr))34}3536fn path_is(path: &Path, needed: &str) -> bool {37	path.leading_colon.is_none()38		&& !path.segments.is_empty()39		&& path.segments.iter().last().unwrap().ident == needed40}4142fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {43	match ty {44		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {45			let args = &path.path.segments.iter().last().unwrap().arguments;46			Some(args)47		}48		_ => None,49	}50}5152fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {53	let Some(args) = type_is_path(ty, "Option") else {54		return Ok(None);55	};56	// It should have only on angle-bracketed param ("<String>"):57	let PathArguments::AngleBracketed(params) = args else {58		return Err(Error::new(args.span(), "missing option generic"));59	};60	let generic_arg = params.args.iter().next().unwrap();61	// This argument must be a type:62	let GenericArgument::Type(ty) = generic_arg else {63		return Err(Error::new(64			generic_arg.span(),65			"option generic should be a type",66		));67	};68	Ok(Some(ty))69}7071struct Field {72	attrs: Vec<Attribute>,73	name: Ident,74	_colon: Token![:],75	ty: Type,76}77impl Parse for Field {78	fn parse(input: ParseStream) -> syn::Result<Self> {79		Ok(Self {80			attrs: input.call(Attribute::parse_outer)?,81			name: input.parse()?,82			_colon: input.parse()?,83			ty: input.parse()?,84		})85	}86}8788mod kw {89	syn::custom_keyword!(fields);90	syn::custom_keyword!(rename);91	syn::custom_keyword!(flatten);92	syn::custom_keyword!(add);93	syn::custom_keyword!(hide);94	syn::custom_keyword!(ok);95}9697struct EmptyAttr;98impl Parse for EmptyAttr {99	fn parse(_input: ParseStream) -> Result<Self> {100		Ok(Self)101	}102}103104struct BuiltinAttrs {105	fields: Vec<Field>,106}107impl Parse for BuiltinAttrs {108	fn parse(input: ParseStream) -> syn::Result<Self> {109		if input.is_empty() {110			return Ok(Self { fields: Vec::new() });111		}112		input.parse::<kw::fields>()?;113		let fields;114		parenthesized!(fields in input);115		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;116		Ok(Self {117			fields: p.into_iter().collect(),118		})119	}120}121122enum ArgInfo {123	Normal {124		ty: Box<Type>,125		is_option: bool,126		name: Option<String>,127		cfg_attrs: Vec<Attribute>,128	},129	Lazy {130		is_option: bool,131		name: Option<String>,132	},133	Context,134	Location,135	This,136}137138impl ArgInfo {139	fn parse(name: &str, arg: &FnArg) -> Result<Self> {140		let FnArg::Typed(arg) = arg else {141			unreachable!()142		};143		let ident = match &arg.pat as &Pat {144			Pat::Ident(i) => Some(i.ident.clone()),145			_ => None,146		};147		let ty = &arg.ty;148		if type_is_path(ty, "Context").is_some() {149			return Ok(Self::Context);150		} else if type_is_path(ty, "CallLocation").is_some() {151			return Ok(Self::Location);152		} else if type_is_path(ty, "Thunk").is_some() {153			return Ok(Self::Lazy {154				is_option: false,155				name: ident.map(|v| v.to_string()),156			});157		}158159		match ty as &Type {160			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),161			_ => {}162		}163164		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {165			if type_is_path(ty, "Thunk").is_some() {166				return Ok(Self::Lazy {167					is_option: true,168					name: ident.map(|v| v.to_string()),169				});170			}171172			(true, Box::new(ty.clone()))173		} else {174			(false, ty.clone())175		};176177		let cfg_attrs = arg178			.attrs179			.iter()180			.filter(|a| a.path().is_ident("cfg"))181			.cloned()182			.collect();183184		Ok(Self::Normal {185			ty,186			is_option,187			name: ident.map(|v| v.to_string()),188			cfg_attrs,189		})190	}191}192193#[proc_macro_attribute]194pub fn builtin(195	attr: proc_macro::TokenStream,196	item: proc_macro::TokenStream,197) -> proc_macro::TokenStream {198	let attr = parse_macro_input!(attr as BuiltinAttrs);199	let item_fn = item.clone();200	let item_fn: ItemFn = parse_macro_input!(item_fn);201202	match builtin_inner(attr, item_fn, item.into()) {203		Ok(v) => v.into(),204		Err(e) => e.into_compile_error().into(),205	}206}207208fn builtin_inner(209	attr: BuiltinAttrs,210	fun: ItemFn,211	item: proc_macro2::TokenStream,212) -> syn::Result<TokenStream> {213	let ReturnType::Type(_, result) = &fun.sig.output else {214		return Err(Error::new(215			fun.sig.span(),216			"builtin should return something",217		));218	};219220	let name = fun.sig.ident.to_string();221	let args = fun222		.sig223		.inputs224		.iter()225		.map(|arg| ArgInfo::parse(&name, arg))226		.collect::<Result<Vec<_>>>()?;227228	let params_desc = args.iter().flat_map(|a| match a {229		ArgInfo::Normal {230			is_option,231			name,232			cfg_attrs,233			..234		} => {235			let name = name236				.as_ref()237				.map(|n| quote! {ParamName::new_static(#n)})238				.unwrap_or_else(|| quote! {None});239			Some(quote! {240				#(#cfg_attrs)*241				BuiltinParam::new(#name, #is_option),242			})243		}244		ArgInfo::Lazy { is_option, name } => {245			let name = name246				.as_ref()247				.map(|n| quote! {ParamName::new_static(#n)})248				.unwrap_or_else(|| quote! {None});249			Some(quote! {250				BuiltinParam::new(#name, #is_option),251			})252		}253		ArgInfo::Context => None,254		ArgInfo::Location => None,255		ArgInfo::This => None,256	});257258	let mut id = 0usize;259	let pass = args260		.iter()261		.map(|a| match a {262			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {263				let cid = id;264				id += 1;265				(quote! {#cid}, a)266			}267			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {268				(quote! {compile_error!("should not use id")}, a)269			}270		})271		.map(|(id, a)| match a {272			ArgInfo::Normal {273				ty,274				is_option,275				name,276				cfg_attrs,277			} => {278				let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");279				let eval = quote! {jrsonnet_evaluator::State::push_description(280					|| format!("argument <{}> evaluation", #name),281					|| <#ty>::from_untyped(value.evaluate()?),282				)?};283				let value = if *is_option {284					quote! {if let Some(value) = &parsed[#id] {285						Some(#eval)286					} else {287						None288					},}289				} else {290					quote! {{291						let value = parsed[#id].as_ref().expect("args shape is checked");292						#eval293					},}294				};295				quote! {296					#(#cfg_attrs)*297					#value298				}299			}300			ArgInfo::Lazy { is_option, .. } => {301				if *is_option {302					quote! {if let Some(value) = &parsed[#id] {303						Some(value.clone())304					} else {305						None306					}}307				} else {308					quote! {309						parsed[#id].as_ref().expect("args shape is correct").clone(),310					}311				}312			}313			ArgInfo::Context => quote! {ctx.clone(),},314			ArgInfo::Location => quote! {location,},315			ArgInfo::This => quote! {self,},316		});317318	let fields = attr.fields.iter().map(|field| {319		let attrs = &field.attrs;320		let name = &field.name;321		let ty = &field.ty;322		quote! {323			#(#attrs)*324			pub #name: #ty,325		}326	});327328	let name = &fun.sig.ident;329	let vis = &fun.vis;330	let static_ext = if attr.fields.is_empty() {331		quote! {332			impl #name {333				pub const INST: &'static dyn StaticBuiltin = &#name {};334			}335			impl StaticBuiltin for #name {}336		}337	} else {338		quote! {}339	};340	let static_derive_copy = if attr.fields.is_empty() {341		quote! {, Copy}342	} else {343		quote! {}344	};345346	Ok(quote! {347		#item348349		#[doc(hidden)]350		#[allow(non_camel_case_types)]351		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]352		#vis struct #name {353			#(#fields)*354		}355		const _: () = {356			use ::jrsonnet_evaluator::{357				State, Val,358				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},359				Result, Context, typed::Typed,360				parser::ExprLocation,361			};362			const PARAMS: &'static [BuiltinParam] = &[363				#(#params_desc)*364			];365366			#static_ext367			impl Builtin for #name368			where369				Self: 'static370			{371				fn name(&self) -> &str {372					stringify!(#name)373				}374				fn params(&self) -> &[BuiltinParam] {375					PARAMS376				}377				#[allow(unused_variable)]378				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {379					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;380381					let result: #result = #name(#(#pass)*);382					<_ as Typed>::into_result(result)383				}384				fn as_any(&self) -> &dyn ::std::any::Any {385					self386				}387			}388		};389	})390}391392#[derive(Default)]393struct TypedAttr {394	rename: Option<String>,395	flatten: bool,396	/// flatten(ok) strategy for flattened optionals397	/// field would be None in case of any parsing error (as in serde)398	flatten_ok: bool,399	// Should it be `field+:` instead of `field:`400	add: bool,401	// Should it be `field::` instead of `field:`402	hide: bool,403}404impl Parse for TypedAttr {405	fn parse(input: ParseStream) -> syn::Result<Self> {406		let mut out = Self::default();407		loop {408			let lookahead = input.lookahead1();409			if lookahead.peek(kw::rename) {410				input.parse::<kw::rename>()?;411				input.parse::<Token![=]>()?;412				let name = input.parse::<LitStr>()?;413				if out.rename.is_some() {414					return Err(Error::new(415						name.span(),416						"rename attribute may only be specified once",417					));418				}419				out.rename = Some(name.value());420			} else if lookahead.peek(kw::flatten) {421				input.parse::<kw::flatten>()?;422				out.flatten = true;423				if input.peek(token::Paren) {424					let content;425					parenthesized!(content in input);426					let lookahead = content.lookahead1();427					if lookahead.peek(kw::ok) {428						content.parse::<kw::ok>()?;429						out.flatten_ok = true;430					} else {431						return Err(lookahead.error());432					}433				}434			} else if lookahead.peek(kw::add) {435				input.parse::<kw::add>()?;436				out.add = true;437			} else if lookahead.peek(kw::hide) {438				input.parse::<kw::hide>()?;439				out.hide = true;440			} else if input.is_empty() {441				break;442			} else {443				return Err(lookahead.error());444			}445			if input.peek(Token![,]) {446				input.parse::<Token![,]>()?;447			} else {448				break;449			}450		}451		Ok(out)452	}453}454455struct TypedField {456	attr: TypedAttr,457	ident: Ident,458	ty: Type,459	is_option: bool,460}461impl TypedField {462	fn parse(field: &syn::Field) -> Result<Self> {463		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();464		let Some(ident) = field.ident.clone() else {465			return Err(Error::new(466				field.span(),467				"this field should appear in output object, but it has no visible name",468			));469		};470		let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {471			(true, ty.clone())472		} else {473			(false, field.ty.clone())474		};475		if is_option && attr.flatten {476			if !attr.flatten_ok {477				return Err(Error::new(478					field.span(),479					"strategy should be set when flattening Option",480				));481			}482		} else if attr.flatten_ok {483			return Err(Error::new(484				field.span(),485				"flatten(ok) is only useable on optional fields",486			));487		}488489		Ok(Self {490			attr,491			ident,492			ty,493			is_option,494		})495	}496	/// None if this field is flattened in jsonnet output497	fn name(&self) -> Option<String> {498		if self.attr.flatten {499			return None;500		}501		Some(502			self.attr503				.rename504				.clone()505				.unwrap_or_else(|| self.ident.to_string()),506		)507	}508509	fn expand_field(&self) -> Option<TokenStream> {510		if self.is_option {511			return None;512		}513		let name = self.name()?;514		let ty = &self.ty;515		Some(quote! {516			(#name, <#ty as Typed>::TYPE)517		})518	}519	fn expand_parse(&self) -> TokenStream {520		let ident = &self.ident;521		let ty = &self.ty;522		if self.attr.flatten {523			// optional flatten is handled in same way as serde524			return if self.is_option {525				quote! {526					#ident: <#ty as TypedObj>::parse(&obj).ok(),527				}528			} else {529				quote! {530					#ident: <#ty as TypedObj>::parse(&obj)?,531				}532			};533		};534535		let name = self.name().unwrap();536		let value = if self.is_option {537			quote! {538				if let Some(value) = obj.get(#name.into())? {539					Some(<#ty as Typed>::from_untyped(value)?)540				} else {541					None542				}543			}544		} else {545			quote! {546				<#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?547			}548		};549550		quote! {551			#ident: #value,552		}553	}554	fn expand_serialize(&self) -> Result<TokenStream> {555		let ident = &self.ident;556		let ty = &self.ty;557		Ok(if let Some(name) = self.name() {558			let hide = if self.attr.hide {559				quote! {.hide()}560			} else {561				quote! {}562			};563			let add = if self.attr.add {564				quote! {.add()}565			} else {566				quote! {}567			};568			if self.is_option {569				quote! {570					if let Some(value) = self.#ident {571						out.field(#name)572							#hide573							#add574							.try_value(<#ty as Typed>::into_untyped(value)?)?;575					}576				}577			} else {578				quote! {579					out.field(#name)580						#hide581						#add582						.try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;583				}584			}585		} else if self.is_option {586			quote! {587				if let Some(value) = self.#ident {588					<#ty as TypedObj>::serialize(value, out)?;589				}590			}591		} else {592			quote! {593				<#ty as TypedObj>::serialize(self.#ident, out)?;594			}595		})596	}597}598599#[proc_macro_derive(Typed, attributes(typed))]600pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {601	let input = parse_macro_input!(item as DeriveInput);602603	match derive_typed_inner(input) {604		Ok(v) => v.into(),605		Err(e) => e.to_compile_error().into(),606	}607}608609fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {610	let syn::Data::Struct(data) = &input.data else {611		return Err(Error::new(input.span(), "only structs supported"));612	};613614	let ident = &input.ident;615	let fields = data616		.fields617		.iter()618		.map(TypedField::parse)619		.collect::<Result<Vec<_>>>()?;620621	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();622623	let typed = {624		let fields = fields625			.iter()626			.flat_map(TypedField::expand_field)627			.collect::<Vec<_>>();628		quote! {629			impl #impl_generics Typed for #ident #ty_generics #where_clause {630				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[631					#(#fields,)*632				]);633634				fn from_untyped(value: Val) -> JrResult<Self> {635					let obj = value.as_obj().expect("shape is correct");636					Self::parse(&obj)637				}638639				fn into_untyped(value: Self) -> JrResult<Val> {640					let mut out = ObjValueBuilder::new();641					value.serialize(&mut out)?;642					Ok(Val::Obj(out.build()))643				}644645			}646		}647	};648649	let fields_parse = fields.iter().map(TypedField::expand_parse);650	let fields_serialize = fields651		.iter()652		.map(TypedField::expand_serialize)653		.collect::<Result<Vec<_>>>()?;654655	Ok(quote! {656		const _: () = {657			use ::jrsonnet_evaluator::{658				typed::{ComplexValType, Typed, TypedObj, CheckType},659				Val, State,660				error::{ErrorKind, Result as JrResult},661				ObjValueBuilder, ObjValue,662			};663664			#typed665666			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {667				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {668					#(#fields_serialize)*669670					Ok(())671				}672				fn parse(obj: &ObjValue) -> JrResult<Self> {673					Ok(Self {674						#(#fields_parse)*675					})676				}677			}678		};679	})680}681682struct FormatInput {683	formatting: LitStr,684	arguments: Vec<Expr>,685}686impl Parse for FormatInput {687	fn parse(input: ParseStream) -> Result<Self> {688		let formatting = input.parse()?;689		let mut arguments = Vec::new();690691		while input.peek(Token![,]) {692			input.parse::<Token![,]>()?;693			if input.is_empty() {694				// Trailing comma695				break;696			}697			let expr = input.parse()?;698			arguments.push(expr);699		}700701		if !input.is_empty() {702			return Err(syn::Error::new(input.span(), "unexpected trailing input"));703		}704705		Ok(Self {706			formatting,707			arguments,708		})709	}710}711fn is_format_str(i: &str) -> bool {712	let mut is_plain = true;713	// -1 = {714	// +1 = }715	let mut is_bracket = 0i8;716	for ele in i.chars() {717		match ele {718			'{' if is_bracket == -1 => {719				is_bracket = 0;720			}721			'}' if is_bracket == -1 => {722				is_plain = false;723				break;724			}725			'}' if is_bracket == 1 => {726				is_bracket = 0;727			}728			'{' if is_bracket == 1 => {729				is_plain = false;730				break;731			}732			'{' => {733				is_bracket = -1;734			}735			'}' => {736				is_bracket = 1;737			}738			_ if is_bracket != 0 => {739				is_plain = false;740				break;741			}742			_ => {}743		}744	}745	!is_plain || is_bracket != 0746}747impl FormatInput {748	fn expand(self) -> TokenStream {749		let format = self.formatting;750		if is_format_str(&format.value()) {751			let args = self.arguments;752			quote! {753				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))754			}755		} else {756			if let Some(first) = self.arguments.first() {757				return syn::Error::new(758					first.span(),759					"string has no formatting codes, it should not have the arguments",760				)761				.into_compile_error();762			}763			quote! {764				::jrsonnet_evaluator::IStr::from(#format)765			}766		}767	}768}769770/// IStr formatting helper771///772/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`773/// This macro looks for formatting codes in the input string, and uses774/// `format!()` only when necessary775#[proc_macro]776pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {777	let input = parse_macro_input!(input as FormatInput);778	input.expand().into()779}
after · crates/jrsonnet-macros/src/lib.rs
1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6	parenthesized,7	parse::{Parse, ParseStream},8	parse_macro_input,9	punctuated::Punctuated,10	spanned::Spanned,11	token::{self, Comma},12	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13	PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18	Ident: PartialEq<I>,19{20	let attrs = attrs21		.iter()22		.filter(|a| a.path().is_ident(&ident))23		.collect::<Vec<_>>();24	if attrs.len() > 1 {25		return Err(Error::new(26			attrs[1].span(),27			"this attribute may be specified only once",28		));29	} else if attrs.is_empty() {30		return Ok(None);31	}32	let attr = attrs[0];33	let attr = attr.parse_args::<A>()?;3435	Ok(Some(attr))36}3738fn path_is(path: &Path, needed: &str) -> bool {39	path.leading_colon.is_none()40		&& !path.segments.is_empty()41		&& path.segments.iter().last().unwrap().ident == needed42}4344fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {45	match ty {46		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {47			let args = &path.path.segments.iter().last().unwrap().arguments;48			Some(args)49		}50		_ => None,51	}52}5354fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {55	let Some(args) = type_is_path(ty, "Option") else {56		return Ok(None);57	};58	// It should have only on angle-bracketed param ("<String>"):59	let PathArguments::AngleBracketed(params) = args else {60		return Err(Error::new(args.span(), "missing option generic"));61	};62	let generic_arg = params.args.iter().next().unwrap();63	// This argument must be a type:64	let GenericArgument::Type(ty) = generic_arg else {65		return Err(Error::new(66			generic_arg.span(),67			"option generic should be a type",68		));69	};70	Ok(Some(ty))71}7273struct Field {74	attrs: Vec<Attribute>,75	name: Ident,76	_colon: Token![:],77	ty: Type,78}79impl Parse for Field {80	fn parse(input: ParseStream) -> syn::Result<Self> {81		Ok(Self {82			attrs: input.call(Attribute::parse_outer)?,83			name: input.parse()?,84			_colon: input.parse()?,85			ty: input.parse()?,86		})87	}88}8990mod kw {91	syn::custom_keyword!(fields);92	syn::custom_keyword!(rename);93	syn::custom_keyword!(flatten);94	syn::custom_keyword!(add);95	syn::custom_keyword!(hide);96	syn::custom_keyword!(ok);97}9899struct EmptyAttr;100impl Parse for EmptyAttr {101	fn parse(_input: ParseStream) -> Result<Self> {102		Ok(Self)103	}104}105106struct BuiltinAttrs {107	fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110	fn parse(input: ParseStream) -> syn::Result<Self> {111		if input.is_empty() {112			return Ok(Self { fields: Vec::new() });113		}114		input.parse::<kw::fields>()?;115		let fields;116		parenthesized!(fields in input);117		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118		Ok(Self {119			fields: p.into_iter().collect(),120		})121	}122}123124enum ArgInfo {125	Normal {126		ty: Box<Type>,127		is_option: bool,128		name: Option<String>,129		cfg_attrs: Vec<Attribute>,130	},131	Lazy {132		is_option: bool,133		name: Option<String>,134	},135	Context,136	Location,137	This,138}139140impl ArgInfo {141	fn parse(name: &str, arg: &FnArg) -> Result<Self> {142		let FnArg::Typed(arg) = arg else {143			unreachable!()144		};145		let ident = match &arg.pat as &Pat {146			Pat::Ident(i) => Some(i.ident.clone()),147			_ => None,148		};149		let ty = &arg.ty;150		if type_is_path(ty, "Context").is_some() {151			return Ok(Self::Context);152		} else if type_is_path(ty, "CallLocation").is_some() {153			return Ok(Self::Location);154		} else if type_is_path(ty, "Thunk").is_some() {155			return Ok(Self::Lazy {156				is_option: false,157				name: ident.map(|v| v.to_string()),158			});159		}160161		match ty as &Type {162			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),163			_ => {}164		}165166		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {167			if type_is_path(ty, "Thunk").is_some() {168				return Ok(Self::Lazy {169					is_option: true,170					name: ident.map(|v| v.to_string()),171				});172			}173174			(true, Box::new(ty.clone()))175		} else {176			(false, ty.clone())177		};178179		let cfg_attrs = arg180			.attrs181			.iter()182			.filter(|a| a.path().is_ident("cfg"))183			.cloned()184			.collect();185186		Ok(Self::Normal {187			ty,188			is_option,189			name: ident.map(|v| v.to_string()),190			cfg_attrs,191		})192	}193}194195#[proc_macro_attribute]196pub fn builtin(197	attr: proc_macro::TokenStream,198	item: proc_macro::TokenStream,199) -> proc_macro::TokenStream {200	let attr = parse_macro_input!(attr as BuiltinAttrs);201	let item_fn = item.clone();202	let item_fn: ItemFn = parse_macro_input!(item_fn);203204	match builtin_inner(attr, item_fn, item.into()) {205		Ok(v) => v.into(),206		Err(e) => e.into_compile_error().into(),207	}208}209210#[allow(clippy::too_many_lines)]211fn builtin_inner(212	attr: BuiltinAttrs,213	fun: ItemFn,214	item: proc_macro2::TokenStream,215) -> syn::Result<TokenStream> {216	let ReturnType::Type(_, result) = &fun.sig.output else {217		return Err(Error::new(218			fun.sig.span(),219			"builtin should return something",220		));221	};222223	let name = fun.sig.ident.to_string();224	let args = fun225		.sig226		.inputs227		.iter()228		.map(|arg| ArgInfo::parse(&name, arg))229		.collect::<Result<Vec<_>>>()?;230231	let params_desc = args.iter().filter_map(|a| match a {232		ArgInfo::Normal {233			is_option,234			name,235			cfg_attrs,236			..237		} => {238			let name = name239				.as_ref()240				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});241			Some(quote! {242				#(#cfg_attrs)*243				BuiltinParam::new(#name, #is_option),244			})245		}246		ArgInfo::Lazy { is_option, name } => {247			let name = name248				.as_ref()249				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});250			Some(quote! {251				BuiltinParam::new(#name, #is_option),252			})253		}254		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,255	});256257	let mut id = 0usize;258	let pass = args259		.iter()260		.map(|a| match a {261			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {262				let cid = id;263				id += 1;264				(quote! {#cid}, a)265			}266			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {267				(quote! {compile_error!("should not use id")}, a)268			}269		})270		.map(|(id, a)| match a {271			ArgInfo::Normal {272				ty,273				is_option,274				name,275				cfg_attrs,276			} => {277				let name = name.as_ref().map_or("<unnamed>", String::as_str);278				let eval = quote! {jrsonnet_evaluator::State::push_description(279					|| format!("argument <{}> evaluation", #name),280					|| <#ty>::from_untyped(value.evaluate()?),281				)?};282				let value = if *is_option {283					quote! {if let Some(value) = &parsed[#id] {284						Some(#eval)285					} else {286						None287					},}288				} else {289					quote! {{290						let value = parsed[#id].as_ref().expect("args shape is checked");291						#eval292					},}293				};294				quote! {295					#(#cfg_attrs)*296					#value297				}298			}299			ArgInfo::Lazy { is_option, .. } => {300				if *is_option {301					quote! {if let Some(value) = &parsed[#id] {302						Some(value.clone())303					} else {304						None305					}}306				} else {307					quote! {308						parsed[#id].as_ref().expect("args shape is correct").clone(),309					}310				}311			}312			ArgInfo::Context => quote! {ctx.clone(),},313			ArgInfo::Location => quote! {location,},314			ArgInfo::This => quote! {self,},315		});316317	let fields = attr.fields.iter().map(|field| {318		let attrs = &field.attrs;319		let name = &field.name;320		let ty = &field.ty;321		quote! {322			#(#attrs)*323			pub #name: #ty,324		}325	});326327	let name = &fun.sig.ident;328	let vis = &fun.vis;329	let static_ext = if attr.fields.is_empty() {330		quote! {331			impl #name {332				pub const INST: &'static dyn StaticBuiltin = &#name {};333			}334			impl StaticBuiltin for #name {}335		}336	} else {337		quote! {}338	};339	let static_derive_copy = if attr.fields.is_empty() {340		quote! {, Copy}341	} else {342		quote! {}343	};344345	Ok(quote! {346		#item347348		#[doc(hidden)]349		#[allow(non_camel_case_types)]350		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]351		#vis struct #name {352			#(#fields)*353		}354		const _: () = {355			use ::jrsonnet_evaluator::{356				State, Val,357				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},358				Result, Context, typed::Typed,359				parser::ExprLocation,360			};361			const PARAMS: &'static [BuiltinParam] = &[362				#(#params_desc)*363			];364365			#static_ext366			impl Builtin for #name367			where368				Self: 'static369			{370				fn name(&self) -> &str {371					stringify!(#name)372				}373				fn params(&self) -> &[BuiltinParam] {374					PARAMS375				}376				#[allow(unused_variable)]377				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {378					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;379380					let result: #result = #name(#(#pass)*);381					<_ as Typed>::into_result(result)382				}383				fn as_any(&self) -> &dyn ::std::any::Any {384					self385				}386			}387		};388	})389}390391#[derive(Default)]392#[allow(clippy::struct_excessive_bools)]393struct TypedAttr {394	rename: Option<String>,395	flatten: bool,396	/// flatten(ok) strategy for flattened optionals397	/// field would be None in case of any parsing error (as in serde)398	flatten_ok: bool,399	// Should it be `field+:` instead of `field:`400	add: bool,401	// Should it be `field::` instead of `field:`402	hide: bool,403}404impl Parse for TypedAttr {405	fn parse(input: ParseStream) -> syn::Result<Self> {406		let mut out = Self::default();407		loop {408			let lookahead = input.lookahead1();409			if lookahead.peek(kw::rename) {410				input.parse::<kw::rename>()?;411				input.parse::<Token![=]>()?;412				let name = input.parse::<LitStr>()?;413				if out.rename.is_some() {414					return Err(Error::new(415						name.span(),416						"rename attribute may only be specified once",417					));418				}419				out.rename = Some(name.value());420			} else if lookahead.peek(kw::flatten) {421				input.parse::<kw::flatten>()?;422				out.flatten = true;423				if input.peek(token::Paren) {424					let content;425					parenthesized!(content in input);426					let lookahead = content.lookahead1();427					if lookahead.peek(kw::ok) {428						content.parse::<kw::ok>()?;429						out.flatten_ok = true;430					} else {431						return Err(lookahead.error());432					}433				}434			} else if lookahead.peek(kw::add) {435				input.parse::<kw::add>()?;436				out.add = true;437			} else if lookahead.peek(kw::hide) {438				input.parse::<kw::hide>()?;439				out.hide = true;440			} else if input.is_empty() {441				break;442			} else {443				return Err(lookahead.error());444			}445			if input.peek(Token![,]) {446				input.parse::<Token![,]>()?;447			} else {448				break;449			}450		}451		Ok(out)452	}453}454455struct TypedField {456	attr: TypedAttr,457	ident: Ident,458	ty: Type,459	is_option: bool,460}461impl TypedField {462	fn parse(field: &syn::Field) -> Result<Self> {463		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();464		let Some(ident) = field.ident.clone() else {465			return Err(Error::new(466				field.span(),467				"this field should appear in output object, but it has no visible name",468			));469		};470		let (is_option, ty) = extract_type_from_option(&field.ty)?471			.map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));472		if is_option && attr.flatten {473			if !attr.flatten_ok {474				return Err(Error::new(475					field.span(),476					"strategy should be set when flattening Option",477				));478			}479		} else if attr.flatten_ok {480			return Err(Error::new(481				field.span(),482				"flatten(ok) is only useable on optional fields",483			));484		}485486		Ok(Self {487			attr,488			ident,489			ty,490			is_option,491		})492	}493	/// None if this field is flattened in jsonnet output494	fn name(&self) -> Option<String> {495		if self.attr.flatten {496			return None;497		}498		Some(499			self.attr500				.rename501				.clone()502				.unwrap_or_else(|| self.ident.to_string()),503		)504	}505506	fn expand_field(&self) -> Option<TokenStream> {507		if self.is_option {508			return None;509		}510		let name = self.name()?;511		let ty = &self.ty;512		Some(quote! {513			(#name, <#ty as Typed>::TYPE)514		})515	}516	fn expand_parse(&self) -> TokenStream {517		let ident = &self.ident;518		let ty = &self.ty;519		if self.attr.flatten {520			// optional flatten is handled in same way as serde521			return if self.is_option {522				quote! {523					#ident: <#ty as TypedObj>::parse(&obj).ok(),524				}525			} else {526				quote! {527					#ident: <#ty as TypedObj>::parse(&obj)?,528				}529			};530		};531532		let name = self.name().unwrap();533		let value = if self.is_option {534			quote! {535				if let Some(value) = obj.get(#name.into())? {536					Some(<#ty as Typed>::from_untyped(value)?)537				} else {538					None539				}540			}541		} else {542			quote! {543				<#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?544			}545		};546547		quote! {548			#ident: #value,549		}550	}551	fn expand_serialize(&self) -> TokenStream {552		let ident = &self.ident;553		let ty = &self.ty;554		self.name().map_or_else(555			|| {556				if self.is_option {557					quote! {558						if let Some(value) = self.#ident {559							<#ty as TypedObj>::serialize(value, out)?;560						}561					}562				} else {563					quote! {564						<#ty as TypedObj>::serialize(self.#ident, out)?;565					}566				}567			},568			|name| {569				let hide = if self.attr.hide {570					quote! {.hide()}571				} else {572					quote! {}573				};574				let add = if self.attr.add {575					quote! {.add()}576				} else {577					quote! {}578				};579				if self.is_option {580					quote! {581						if let Some(value) = self.#ident {582							out.field(#name)583								#hide584								#add585								.try_value(<#ty as Typed>::into_untyped(value)?)?;586						}587					}588				} else {589					quote! {590						out.field(#name)591							#hide592							#add593							.try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;594					}595				}596			},597		)598	}599}600601#[proc_macro_derive(Typed, attributes(typed))]602pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {603	let input = parse_macro_input!(item as DeriveInput);604605	match derive_typed_inner(input) {606		Ok(v) => v.into(),607		Err(e) => e.to_compile_error().into(),608	}609}610611fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {612	let syn::Data::Struct(data) = &input.data else {613		return Err(Error::new(input.span(), "only structs supported"));614	};615616	let ident = &input.ident;617	let fields = data618		.fields619		.iter()620		.map(TypedField::parse)621		.collect::<Result<Vec<_>>>()?;622623	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();624625	let typed = {626		let fields = fields627			.iter()628			.filter_map(TypedField::expand_field)629			.collect::<Vec<_>>();630		quote! {631			impl #impl_generics Typed for #ident #ty_generics #where_clause {632				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[633					#(#fields,)*634				]);635636				fn from_untyped(value: Val) -> JrResult<Self> {637					let obj = value.as_obj().expect("shape is correct");638					Self::parse(&obj)639				}640641				fn into_untyped(value: Self) -> JrResult<Val> {642					let mut out = ObjValueBuilder::new();643					value.serialize(&mut out)?;644					Ok(Val::Obj(out.build()))645				}646647			}648		}649	};650651	let fields_parse = fields.iter().map(TypedField::expand_parse);652	let fields_serialize = fields653		.iter()654		.map(TypedField::expand_serialize)655		.collect::<Vec<_>>();656657	Ok(quote! {658		const _: () = {659			use ::jrsonnet_evaluator::{660				typed::{ComplexValType, Typed, TypedObj, CheckType},661				Val, State,662				error::{ErrorKind, Result as JrResult},663				ObjValueBuilder, ObjValue,664			};665666			#typed667668			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {669				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {670					#(#fields_serialize)*671672					Ok(())673				}674				fn parse(obj: &ObjValue) -> JrResult<Self> {675					Ok(Self {676						#(#fields_parse)*677					})678				}679			}680		};681	})682}683684struct FormatInput {685	formatting: LitStr,686	arguments: Vec<Expr>,687}688impl Parse for FormatInput {689	fn parse(input: ParseStream) -> Result<Self> {690		let formatting = input.parse()?;691		let mut arguments = Vec::new();692693		while input.peek(Token![,]) {694			input.parse::<Token![,]>()?;695			if input.is_empty() {696				// Trailing comma697				break;698			}699			let expr = input.parse()?;700			arguments.push(expr);701		}702703		if !input.is_empty() {704			return Err(syn::Error::new(input.span(), "unexpected trailing input"));705		}706707		Ok(Self {708			formatting,709			arguments,710		})711	}712}713fn is_format_str(i: &str) -> bool {714	let mut is_plain = true;715	// -1 = {716	// +1 = }717	let mut is_bracket = 0i8;718	for ele in i.chars() {719		match ele {720			'{' if is_bracket == -1 => {721				is_bracket = 0;722			}723			'}' if is_bracket == -1 => {724				is_plain = false;725				break;726			}727			'}' if is_bracket == 1 => {728				is_bracket = 0;729			}730			'{' if is_bracket == 1 => {731				is_plain = false;732				break;733			}734			'{' => {735				is_bracket = -1;736			}737			'}' => {738				is_bracket = 1;739			}740			_ if is_bracket != 0 => {741				is_plain = false;742				break;743			}744			_ => {}745		}746	}747	!is_plain || is_bracket != 0748}749impl FormatInput {750	fn expand(self) -> TokenStream {751		let format = self.formatting;752		if is_format_str(&format.value()) {753			let args = self.arguments;754			quote! {755				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))756			}757		} else {758			if let Some(first) = self.arguments.first() {759				return syn::Error::new(760					first.span(),761					"string has no formatting codes, it should not have the arguments",762				)763				.into_compile_error();764			}765			quote! {766				::jrsonnet_evaluator::IStr::from(#format)767			}768		}769	}770}771772/// `IStr` formatting helper773///774/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`775/// This macro looks for formatting codes in the input string, and uses776/// `format!()` only when necessary777#[proc_macro]778pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {779	let input = parse_macro_input!(input as FormatInput);780	input.expand().into()781}
modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/parser.rs
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -758,10 +758,10 @@
 			} else if p.at(T![...]) {
 				// let m_err = p.start_ranger();
 				destruct_rest(p);
-				// if had_rest {
-				// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");
-				// }
-				// had_rest = true;
+			// if had_rest {
+			// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");
+			// }
+			// had_rest = true;
 			} else {
 				destruct(p);
 			}
modifiedcrates/jrsonnet-stdlib/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/build.rs
+++ b/crates/jrsonnet-stdlib/build.rs
@@ -26,7 +26,9 @@
 			let dest_path = Path::new(&out_dir).join("stdlib.rs");
 			let mut f = File::create(dest_path).unwrap();
 			f.write_all(
-				("#[allow(clippy::redundant_clone)]".to_owned() + &v.to_string()).as_bytes(),
+				("#[allow(clippy::redundant_clone, clippy::similar_names)]".to_owned()
+					+ &v.to_string())
+					.as_bytes(),
 			)
 			.unwrap();
 		}
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -9,7 +9,7 @@
 	Either, IStr, ObjValueBuilder, Result, ResultExt, Thunk, Val,
 };
 
-pub(crate) fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {
+pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {
 	if let Some(on_empty) = on_empty {
 		on_empty.evaluate()
 	} else {
@@ -270,8 +270,8 @@
 	let newArrRight = arr.slice(Some(at + 1), None, None);
 
 	Ok(ArrValue::extended(
-		newArrLeft.unwrap_or(ArrValue::empty()),
-		newArrRight.unwrap_or(ArrValue::empty()),
+		newArrLeft.unwrap_or_else(ArrValue::empty),
+		newArrRight.unwrap_or_else(ArrValue::empty),
 	))
 }
 
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -313,10 +313,10 @@
 	settings: Rc<RefCell<Settings>>,
 }
 impl ContextInitializer {
-	pub fn new(_s: State, resolver: PathResolver) -> Self {
+	pub fn new(s: State, resolver: PathResolver) -> Self {
 		let settings = Settings {
-			ext_vars: Default::default(),
-			ext_natives: Default::default(),
+			ext_vars: HashMap::new(),
+			ext_natives: HashMap::new(),
 			trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),
 			path_resolver: resolver,
 		};
@@ -324,10 +324,12 @@
 		let stdlib_obj = stdlib_uncached(settings.clone());
 		#[cfg(not(feature = "legacy-this-file"))]
 		let stdlib_thunk = Thunk::evaluated(Val::Obj(stdlib_obj));
+		#[cfg(feature = "legacy-this-file")]
+		let _ = s;
 		Self {
 			#[cfg(not(feature = "legacy-this-file"))]
 			context: {
-				let mut context = ContextBuilder::with_capacity(_s, 1);
+				let mut context = ContextBuilder::with_capacity(s, 1);
 				context.bind("std", stdlib_thunk.clone());
 				context.build()
 			},
modifiedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/toml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -180,7 +180,9 @@
 		options.preserve_order,
 	) {
 		let value = value?;
-		if !is_section(&value)? {
+		if is_section(&value)? {
+			sections.push((key, value));
+		} else {
 			if !first {
 				buf.push('\n');
 			}
@@ -189,8 +191,6 @@
 			escape_key_toml_buf(&key, buf);
 			buf.push_str(" = ");
 			manifest_value(&value, false, buf, cur_padding, options)?;
-		} else {
-			sections.push((key, value));
 		}
 	}
 	for (k, v) in sections {
modifiedcrates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -131,16 +131,19 @@
 }
 
 #[builtin]
+#[allow(clippy::float_cmp)]
 pub fn builtin_is_odd(x: f64) -> bool {
 	builtin_round(x) % 2.0 == 1.0
 }
 
 #[builtin]
+#[allow(clippy::float_cmp)]
 pub fn builtin_is_integer(x: f64) -> bool {
 	builtin_round(x) == x
 }
 
 #[builtin]
+#[allow(clippy::float_cmp)]
 pub fn builtin_is_decimal(x: f64) -> bool {
 	builtin_round(x) != x
 }
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -67,11 +67,7 @@
 			v => v.manifest(JsonFormat::debug())?.into(),
 		},
 	);
-	if let Some(rest) = rest {
-		rest.evaluate()
-	} else {
-		Ok(str)
-	}
+	rest.map_or_else(|| Ok(str), |rest| rest.evaluate())
 }
 
 #[allow(clippy::comparison_chain)]
@@ -84,16 +80,15 @@
 				return Ok(false);
 			} else if b.len() == a.len() {
 				return equals(&Val::Arr(a), &Val::Arr(b));
-			} else {
-				for (a, b) in a.iter().take(b.len()).zip(b.iter()) {
-					let a = a?;
-					let b = b?;
-					if !equals(&a, &b)? {
-						return Ok(false);
-					}
+			}
+			for (a, b) in a.iter().take(b.len()).zip(b.iter()) {
+				let a = a?;
+				let b = b?;
+				if !equals(&a, &b)? {
+					return Ok(false);
 				}
-				true
 			}
+			true
 		}
 		_ => bail!("both arguments should be of the same type"),
 	})
@@ -109,17 +104,16 @@
 				return Ok(false);
 			} else if b.len() == a.len() {
 				return equals(&Val::Arr(a), &Val::Arr(b));
-			} else {
-				let a_len = a.len();
-				for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {
-					let a = a?;
-					let b = b?;
-					if !equals(&a, &b)? {
-						return Ok(false);
-					}
+			}
+			let a_len = a.len();
+			for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {
+				let a = a?;
+				let b = b?;
+				if !equals(&a, &b)? {
+					return Ok(false);
 				}
-				true
 			}
+			true
 		}
 		_ => bail!("both arguments should be of the same type"),
 	})
modifiedcrates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -155,7 +155,7 @@
 		if k == key {
 			continue;
 		}
-		new_obj.field(k).value(v.unwrap())
+		new_obj.field(k).value(v.unwrap());
 	}
 
 	new_obj.build()
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -36,7 +36,7 @@
 
 fn get_sort_type<T>(values: &[T], key_getter: impl Fn(&T) -> &Val) -> Result<SortKeyType> {
 	let mut sort_type = SortKeyType::Unknown;
-	for i in values.iter() {
+	for i in values {
 		let i = key_getter(i);
 		match (i, sort_type) {
 			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -75,7 +75,7 @@
 		.enumerate()
 	{
 		if &strb[i..i + pat.len()] == pat {
-			out.push(Val::Num(ch_idx as f64))
+			out.push(Val::Num(ch_idx as f64));
 		}
 	}
 	out.into()
@@ -117,11 +117,6 @@
 }
 
 fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {
-	debug_assert!(
-		1 <= BASE && BASE <= 16,
-		"integer base should be between 1 and 16"
-	);
-
 	const ZERO_CODE: u32 = '0' as u32;
 	const UPPER_A_CODE: u32 = 'A' as u32;
 	const LOWER_A_CODE: u32 = 'a' as u32;
@@ -135,10 +130,17 @@
 		}
 	}
 
-	let base = BASE as f64;
+	debug_assert!(
+		1 <= BASE && BASE <= 16,
+		"integer base should be between 1 and 16"
+	);
+
+	let base = f64::from(BASE);
 
 	raw.chars().try_fold(0f64, |aggregate, digit| {
 		let digit = digit as u32;
+		// if-let-else looks better here than Option combinators
+		#[allow(clippy::option_if_let_else)]
 		let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {
 			digit + 10
 		} else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {
@@ -148,7 +150,7 @@
 		};
 
 		if digit < BASE {
-			Ok(base * aggregate + digit as f64)
+			Ok(base.mul_add(aggregate, f64::from(digit)))
 		} else {
 			bail!("{raw:?} is not a base {BASE} integer");
 		}
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -166,9 +166,9 @@
 
 fn print_array(a: &ComplexValType, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 	if *a == ComplexValType::Any {
-		write!(f, "array")?
+		write!(f, "array")?;
 	} else {
-		write!(f, "Array<{a}>")?
+		write!(f, "Array<{a}>")?;
 	}
 	Ok(())
 }
@@ -176,18 +176,20 @@
 impl Display for ComplexValType {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		match self {
-			ComplexValType::Any => write!(f, "any")?,
-			ComplexValType::Simple(s) => write!(f, "{s}")?,
-			ComplexValType::Char => write!(f, "char")?,
-			ComplexValType::BoundedNumber(a, b) => write!(
+			Self::Any => write!(f, "any")?,
+			Self::Simple(s) => write!(f, "{s}")?,
+			Self::Char => write!(f, "char")?,
+			Self::BoundedNumber(a, b) => write!(
 				f,
 				"BoundedNumber<{}, {}>",
-				a.map(|e| e.to_string()).unwrap_or_else(|| "".into()),
-				b.map(|e| e.to_string()).unwrap_or_else(|| "".into())
+				a.map(|e| e.to_string())
+					.unwrap_or_else(|| "open".to_owned()),
+				b.map(|e| e.to_string())
+					.unwrap_or_else(|| "open".to_owned())
 			)?,
-			ComplexValType::ArrayRef(a) => print_array(a, f)?,
-			ComplexValType::Array(a) => print_array(a, f)?,
-			ComplexValType::ObjectRef(fields) => {
+			Self::ArrayRef(a) => print_array(a, f)?,
+			Self::Array(a) => print_array(a, f)?,
+			Self::ObjectRef(fields) => {
 				write!(f, "{{")?;
 				for (i, (k, v)) in fields.iter().enumerate() {
 					if i != 0 {
@@ -197,18 +199,18 @@
 				}
 				write!(f, "}}")?;
 			}
-			ComplexValType::AttrsOf(a) => {
-				if matches!(a, ComplexValType::Any) {
+			Self::AttrsOf(a) => {
+				if matches!(a, Self::Any) {
 					write!(f, "object")?;
 				} else {
 					write!(f, "AttrsOf<{a}>")?;
 				}
 			}
-			ComplexValType::Union(v) => write_union(f, true, v.iter())?,
-			ComplexValType::UnionRef(v) => write_union(f, true, v.iter().copied())?,
-			ComplexValType::Sum(v) => write_union(f, false, v.iter())?,
-			ComplexValType::SumRef(v) => write_union(f, false, v.iter().copied())?,
-			ComplexValType::Lazy(lazy) => write!(f, "Lazy<{lazy}>")?,
+			Self::Union(v) => write_union(f, true, v.iter())?,
+			Self::UnionRef(v) => write_union(f, true, v.iter().copied())?,
+			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/suite/std_param_names.jsonnetdiffbeforeafterboth
--- a/tests/suite/std_param_names.jsonnet
+++ b/tests/suite/std_param_names.jsonnet
@@ -49,6 +49,7 @@
     min: ['a', 'b'],
     clamp: ['x', 'minVal', 'maxVal'],
     flattenArrays: ['arrs'],
+    flattenDeepArray: ['value'],
     manifestIni: ['ini'],
     manifestToml: ['value'],
     manifestTomlEx: ['value', 'indent'],