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

difftreelog

feat convert ParamName into enum

sytvpywqYaroslav Bolyukin2026-03-21parent: #b915f23.patch.diff
in: master

10 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -5,7 +5,7 @@
 use crate::{
 	bail,
 	error::{ErrorKind::*, Result},
-	evaluate, evaluate_method, evaluate_named, Context, Pending, Thunk, Val,
+	evaluate_method, evaluate_named_param, Context, Pending, Thunk, Val,
 };
 
 #[allow(clippy::too_many_lines)]
@@ -170,10 +170,7 @@
 			let value = value.clone();
 			let data = {
 				let fctx = fctx.clone();
-				Thunk!(move || name.0.map_or_else(
-					|| evaluate(fctx.unwrap(), &value),
-					|name| evaluate_named(fctx.unwrap(), &value, name),
-				))
+				Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))
 			};
 			destruct(into, data, fctx, new_bindings)?;
 		}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -380,9 +380,9 @@
 }
 
 pub fn evaluate_named_param(ctx: Context, expr: &Spanned<Expr>, name: ParamName) -> Result<Val> {
-	match name.0 {
-		Some(name) => evaluate_named(ctx, expr, name),
-		None => evaluate(ctx, expr),
+	match name {
+		ParamName::Named(name) => evaluate_named(ctx, expr, name),
+		ParamName::Unnamed => evaluate(ctx, expr),
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -1,8 +1,6 @@
 use std::any::Any;
-use std::fmt;
 
-use jrsonnet_gcmodule::{cc_dyn, Acyclic, Trace, TraceBox};
-use jrsonnet_interner::IStr;
+use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox};
 use jrsonnet_parser::function::{FunctionSignature, ParamDefault, ParamName, ParamParse};
 
 use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
@@ -10,8 +8,8 @@
 
 #[macro_export]
 macro_rules! params {
-	(@name unnamed) => { ParamName::ANONYMOUS };
-	(@name named $name:literal) => { ParamName::new($crate::IStr::from($name)) };
+	(@name unnamed) => { ParamName::Unnamed };
+	(@name named $name:literal) => { ParamName::Named($crate::IStr::from($name)) };
 	($($(#[$meta:meta])* [$kind:ident $(($lit:literal))? => $default:expr]),* $(,)?) => {
 		thread_local! {
 			static PARAMS: FunctionSignature = FunctionSignature::new([
@@ -79,7 +77,7 @@
 			params: FunctionSignature::new(
 				params
 					.into_iter()
-					.map(|n| ParamParse::new(ParamName::new(n.into()), ParamDefault::None))
+					.map(|n| ParamParse::new(ParamName::Named(n.into()), ParamDefault::None))
 					.collect(),
 			),
 			handler: TraceBox(Box::new(handler)),
@@ -88,7 +86,7 @@
 }
 
 impl Builtin for NativeCallback {
-	fn name(&self) -> &str {
+	fn name(&self) -> &'static str {
 		// TODO: standard natives gets their names from definition
 		// But builitins should already have them
 		"<native>"
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -14,8 +14,8 @@
 	parse::{parse_default_function_call, parse_function_call},
 };
 use crate::{
-	bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, params,
-	Context, ContextBuilder, Result, Thunk, Val,
+	bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, Context,
+	ContextBuilder, Result, Thunk, Val,
 };
 
 pub mod arglike;
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,7 +1,9 @@
 use std::mem::replace;
 
-use jrsonnet_interner::IStr;
-use jrsonnet_parser::{function::FunctionSignature, ExprParams};
+use jrsonnet_parser::{
+	function::{FunctionSignature, ParamName},
+	ExprParams,
+};
 use rustc_hash::FxHashMap;
 
 use super::arglike::ArgsLike;
@@ -9,7 +11,7 @@
 	bail,
 	destructure::destruct,
 	error::{ErrorKind::*, Result},
-	evaluate_named, evaluate_named_param,
+	evaluate_named_param,
 	gc::WithCapacityExt as _,
 	Context, Pending, Thunk, Val,
 };
@@ -76,7 +78,7 @@
 			.enumerate()
 			.filter_map(|(i, p)| Some((i, &p.destruct, p.default.as_ref()?)))
 		{
-			if let Some(name) = into.name().0 {
+			if let ParamName::Named(name) = into.name() {
 				if passed_args.contains_key(&name) {
 					continue;
 				}
modifiedcrates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -4,9 +4,8 @@
 
 use crate::destructure::destruct;
 use crate::gc::WithCapacityExt;
-use crate::val::ThunkValue as _;
 use crate::{bail, error::ErrorKind::*, Result};
-use crate::{evaluate_named, evaluate_named_param, Context, ContextBuilder, Pending, Thunk, Val};
+use crate::{evaluate_named_param, Context, ContextBuilder, Pending, Thunk, Val};
 
 pub struct PreparedCall {
 	// Param, named input.
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-interner/src/lib.rs
1#![deny(2	unsafe_op_in_unsafe_fn,3	clippy::missing_safety_doc,4	clippy::undocumented_unsafe_blocks5)]6#![warn(clippy::pedantic, clippy::nursery)]7#![allow(clippy::missing_const_for_fn)]8use std::{9	borrow::Cow,10	cell::RefCell,11	fmt::{self, Display},12	hash::{Hash, Hasher},13	ops::Deref,14	str,15};1617use hashbrown::{hash_map::RawEntryMut, HashMap};18use jrsonnet_gcmodule::{Acyclic, Trace};19use rustc_hash::FxBuildHasher;2021mod inner;22use inner::Inner;2324/// Interned string25///26/// Provides O(1) comparsions and hashing, cheap copy, and cheap conversion to [`IBytes`]27#[derive(Clone, PartialOrd, Ord, Eq)]28pub struct IStr(Inner);29impl Trace for IStr {30	fn is_type_tracked() -> bool {31		false32	}33}34unsafe impl Acyclic for IStr {}3536impl IStr {37	#[must_use]38	pub fn empty() -> Self {39		"".into()40	}41	#[must_use]42	pub fn as_str(&self) -> &str {43		self as &str44	}4546	#[must_use]47	pub fn cast_bytes(self) -> IBytes {48		IBytes(self.0.clone())49	}50}5152impl Deref for IStr {53	type Target = str;5455	fn deref(&self) -> &Self::Target {56		// SAFETY: Inner::check_utf8 is called on IStr construction, data is utf-857		unsafe { self.0.as_str_unchecked() }58	}59}6061impl PartialEq for IStr {62	fn eq(&self, other: &Self) -> bool {63		// all IStr should be inlined into same pool64		Inner::ptr_eq(&self.0, &other.0)65	}66}6768impl PartialEq<str> for IStr {69	fn eq(&self, other: &str) -> bool {70		self as &str == other71	}72}7374impl Hash for IStr {75	fn hash<H: Hasher>(&self, state: &mut H) {76		// IStr is always obtained from pool, where no string have duplicate, thus every unique string has unique address77		state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);78	}79}8081impl Drop for IStr {82	fn drop(&mut self) {83		maybe_unpool(&self.0);84	}85}8687impl fmt::Debug for IStr {88	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {89		fmt::Debug::fmt(self as &str, f)90	}91}9293impl Display for IStr {94	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {95		fmt::Display::fmt(self as &str, f)96	}97}9899/// Interned byte array100#[derive(Clone, PartialOrd, Ord, Eq)]101pub struct IBytes(Inner);102impl Trace for IBytes {103	fn is_type_tracked() -> bool {104		false105	}106}107108impl IBytes {109	#[must_use]110	pub fn cast_str(self) -> Option<IStr> {111		if Inner::check_utf8(&self.0) {112			Some(IStr(self.0.clone()))113		} else {114			None115		}116	}117	/// # Safety118	/// data should be valid utf8119	unsafe fn cast_str_unchecked(self) -> IStr {120		// SAFETY: data is utf8121		unsafe { Inner::assume_utf8(&self.0) };122		IStr(self.0.clone())123	}124125	#[must_use]126	pub fn as_slice(&self) -> &[u8] {127		self.0.as_slice()128	}129}130131impl Deref for IBytes {132	type Target = [u8];133134	fn deref(&self) -> &Self::Target {135		self.0.as_slice()136	}137}138139impl PartialEq for IBytes {140	fn eq(&self, other: &Self) -> bool {141		// all IStr should be inlined into same pool142		Inner::ptr_eq(&self.0, &other.0)143	}144}145146impl Hash for IBytes {147	fn hash<H: Hasher>(&self, state: &mut H) {148		// IBytes is always obtained from pool, where no string have duplicate, thus every unique string has unique address149		state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);150	}151}152153impl Drop for IBytes {154	fn drop(&mut self) {155		maybe_unpool(&self.0);156	}157}158159fn maybe_unpool(inner: &Inner) {160	#[cold]161	#[inline(never)]162	fn unpool(inner: &Inner) {163		// May fail on program termination164		let _ = POOL.try_with(|pool| {165			let mut pool = pool.borrow_mut();166167			if pool.remove(inner).is_none() {168				// On some platforms (i.e i686-windows), try_with will not fail after TLS169				// destructor is called, but instead re-initialize the TLS with the empty pool.170				// Allow non-pooled Drop in this case.171				// https://github.com/CertainLach/jrsonnet/issues/98#issuecomment-1591624016172				//173				// However, if pool is not empty, most likely this is issue #113, and then I don't174				// have any explainations for now.175				assert!(pool.is_empty(), "received an unpooled string not during the program termination, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");176			}177		});178	}179	// First reference - current object, second - POOL180	if Inner::strong_count(inner) <= 2 {181		unpool(inner);182	}183}184185impl fmt::Debug for IBytes {186	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {187		fmt::Debug::fmt(self as &[u8], f)188	}189}190191impl<'c> From<Cow<'c, str>> for IStr {192	fn from(v: Cow<'c, str>) -> Self {193		intern_str(&v)194	}195}196impl From<&str> for IStr {197	fn from(v: &str) -> Self {198		intern_str(v)199	}200}201impl From<String> for IStr {202	fn from(s: String) -> Self {203		s.as_str().into()204	}205}206impl From<&String> for IStr {207	fn from(s: &String) -> Self {208		s.as_str().into()209	}210}211impl From<char> for IStr {212	fn from(value: char) -> Self {213		let mut buf = [0; 5];214		Self::from(&*value.encode_utf8(&mut buf))215	}216}217impl From<&[u8]> for IBytes {218	fn from(v: &[u8]) -> Self {219		intern_bytes(v)220	}221}222223type PoolMap = HashMap<Inner, (), FxBuildHasher>;224225thread_local! {226	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, FxBuildHasher::default()));227}228229/// Jrsonnet golang bindings require that it is possible to move jsonnet230/// VM between OS threads, and this is not possible due to usage of231/// `thread_local`. Instead, there is two methods added, one should be232/// called at the end of current thread work, and one that should be233/// used when using other thread.234pub mod interop {235	use std::mem;236237	use crate::{PoolMap, POOL};238239	/// Type-erased interned string pool240	pub enum PoolState {}241242	/// Dump current interned string pool, to be restored by243	/// `reenter_thread`244	pub fn exit_thread() -> *mut PoolState {245		Box::into_raw(Box::new(POOL.with_borrow_mut(mem::take))).cast()246	}247248	/// Reenter thread, using state dumped by `exit_thread`.249	///250	/// # Safety251	///252	/// `state` should be acquired from `exit_thread`, it is not allowed253	/// to reuse state to reenter multiple threads.254	pub unsafe fn reenter_thread(state: *mut PoolState) {255		let ptr: *mut PoolMap = state.cast();256		// SAFETY: ptr is an unique state per method safety requirements.257		let ptr: Box<PoolMap> = unsafe { Box::from_raw(ptr) };258		let ptr: PoolMap = *ptr;259		POOL.with_borrow_mut(|pool| {260			let _ = mem::replace(pool, ptr);261		});262	}263}264265#[must_use]266pub fn intern_bytes(bytes: &[u8]) -> IBytes {267	POOL.with(|pool| {268		let mut pool = pool.borrow_mut();269		let entry = pool.raw_entry_mut().from_key(bytes);270		match entry {271			RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),272			RawEntryMut::Vacant(e) => {273				let (k, ()) = e.insert(Inner::new_bytes(bytes), ());274				IBytes(k.clone())275			}276		}277	})278}279280#[must_use]281pub fn intern_str(str: &str) -> IStr {282	// SAFETY: Rust strings always utf8283	unsafe { intern_bytes(str.as_bytes()).cast_str_unchecked() }284}285286#[cfg(test)]287mod tests {288	use crate::IStr;289290	#[test]291	fn simple() {292		let a = IStr::from("a");293		let b = IStr::from("a");294295		assert_eq!(a.as_ptr(), b.as_ptr());296	}297}
after · crates/jrsonnet-interner/src/lib.rs
1#![deny(2	unsafe_op_in_unsafe_fn,3	clippy::missing_safety_doc,4	clippy::undocumented_unsafe_blocks5)]6#![warn(clippy::pedantic, clippy::nursery)]7#![allow(clippy::missing_const_for_fn)]8use std::{9	borrow::Cow,10	cell::RefCell,11	fmt::{self, Display},12	hash::{Hash, Hasher},13	ops::Deref,14	str,15};1617use hashbrown::{hash_map::RawEntryMut, HashMap};18use jrsonnet_gcmodule::{Acyclic, Trace};19use rustc_hash::FxBuildHasher;2021mod inner;22use inner::Inner;2324/// Interned string25///26/// Provides O(1) comparsions and hashing, cheap copy, and cheap conversion to [`IBytes`]27#[derive(Clone, PartialOrd, Ord, Eq)]28pub struct IStr(Inner);29impl Trace for IStr {30	fn is_type_tracked() -> bool {31		false32	}33}3435/// SAFETY:36///37/// `IStr` is acyclic38unsafe impl Acyclic for IStr {}3940impl IStr {41	#[must_use]42	pub fn empty() -> Self {43		"".into()44	}45	#[must_use]46	pub fn as_str(&self) -> &str {47		self as &str48	}4950	#[must_use]51	pub fn cast_bytes(self) -> IBytes {52		IBytes(self.0.clone())53	}54}5556impl Deref for IStr {57	type Target = str;5859	fn deref(&self) -> &Self::Target {60		// SAFETY: Inner::check_utf8 is called on IStr construction, data is utf-861		unsafe { self.0.as_str_unchecked() }62	}63}6465impl PartialEq for IStr {66	fn eq(&self, other: &Self) -> bool {67		// all IStr should be inlined into same pool68		Inner::ptr_eq(&self.0, &other.0)69	}70}7172impl PartialEq<str> for IStr {73	fn eq(&self, other: &str) -> bool {74		self as &str == other75	}76}7778impl Hash for IStr {79	fn hash<H: Hasher>(&self, state: &mut H) {80		// IStr is always obtained from pool, where no string have duplicate, thus every unique string has unique address81		state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);82	}83}8485impl Drop for IStr {86	fn drop(&mut self) {87		maybe_unpool(&self.0);88	}89}9091impl fmt::Debug for IStr {92	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {93		fmt::Debug::fmt(self as &str, f)94	}95}9697impl Display for IStr {98	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {99		fmt::Display::fmt(self as &str, f)100	}101}102103/// Interned byte array104#[derive(Clone, PartialOrd, Ord, Eq)]105pub struct IBytes(Inner);106impl Trace for IBytes {107	fn is_type_tracked() -> bool {108		false109	}110}111112impl IBytes {113	#[must_use]114	pub fn cast_str(self) -> Option<IStr> {115		if Inner::check_utf8(&self.0) {116			Some(IStr(self.0.clone()))117		} else {118			None119		}120	}121	/// # Safety122	/// data should be valid utf8123	unsafe fn cast_str_unchecked(self) -> IStr {124		// SAFETY: data is utf8125		unsafe { Inner::assume_utf8(&self.0) };126		IStr(self.0.clone())127	}128129	#[must_use]130	pub fn as_slice(&self) -> &[u8] {131		self.0.as_slice()132	}133}134135impl Deref for IBytes {136	type Target = [u8];137138	fn deref(&self) -> &Self::Target {139		self.0.as_slice()140	}141}142143impl PartialEq for IBytes {144	fn eq(&self, other: &Self) -> bool {145		// all IStr should be inlined into same pool146		Inner::ptr_eq(&self.0, &other.0)147	}148}149150impl Hash for IBytes {151	fn hash<H: Hasher>(&self, state: &mut H) {152		// IBytes is always obtained from pool, where no string have duplicate, thus every unique string has unique address153		state.write_usize(Inner::as_ptr(&self.0).cast::<()>() as usize);154	}155}156157impl Drop for IBytes {158	fn drop(&mut self) {159		maybe_unpool(&self.0);160	}161}162163fn maybe_unpool(inner: &Inner) {164	#[cold]165	#[inline(never)]166	fn unpool(inner: &Inner) {167		// May fail on program termination168		let _ = POOL.try_with(|pool| {169			let mut pool = pool.borrow_mut();170171			if pool.remove(inner).is_none() {172				// On some platforms (i.e i686-windows), try_with will not fail after TLS173				// destructor is called, but instead re-initialize the TLS with the empty pool.174				// Allow non-pooled Drop in this case.175				// https://github.com/CertainLach/jrsonnet/issues/98#issuecomment-1591624016176				//177				// However, if pool is not empty, most likely this is issue #113, and then I don't178				// have any explainations for now.179				assert!(pool.is_empty(), "received an unpooled string not during the program termination, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");180			}181		});182	}183	// First reference - current object, second - POOL184	if Inner::strong_count(inner) <= 2 {185		unpool(inner);186	}187}188189impl fmt::Debug for IBytes {190	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {191		fmt::Debug::fmt(self as &[u8], f)192	}193}194195impl<'c> From<Cow<'c, str>> for IStr {196	fn from(v: Cow<'c, str>) -> Self {197		intern_str(&v)198	}199}200impl From<&str> for IStr {201	fn from(v: &str) -> Self {202		intern_str(v)203	}204}205impl From<String> for IStr {206	fn from(s: String) -> Self {207		s.as_str().into()208	}209}210impl From<&String> for IStr {211	fn from(s: &String) -> Self {212		s.as_str().into()213	}214}215impl From<char> for IStr {216	fn from(value: char) -> Self {217		let mut buf = [0; 5];218		Self::from(&*value.encode_utf8(&mut buf))219	}220}221impl From<&[u8]> for IBytes {222	fn from(v: &[u8]) -> Self {223		intern_bytes(v)224	}225}226227type PoolMap = HashMap<Inner, (), FxBuildHasher>;228229thread_local! {230	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, FxBuildHasher::default()));231}232233/// Jrsonnet golang bindings require that it is possible to move jsonnet234/// VM between OS threads, and this is not possible due to usage of235/// `thread_local`. Instead, there is two methods added, one should be236/// called at the end of current thread work, and one that should be237/// used when using other thread.238pub mod interop {239	use std::mem;240241	use crate::{PoolMap, POOL};242243	/// Type-erased interned string pool244	pub enum PoolState {}245246	/// Dump current interned string pool, to be restored by247	/// `reenter_thread`248	pub fn exit_thread() -> *mut PoolState {249		Box::into_raw(Box::new(POOL.with_borrow_mut(mem::take))).cast()250	}251252	/// Reenter thread, using state dumped by `exit_thread`.253	///254	/// # Safety255	///256	/// `state` should be acquired from `exit_thread`, it is not allowed257	/// to reuse state to reenter multiple threads.258	pub unsafe fn reenter_thread(state: *mut PoolState) {259		let ptr: *mut PoolMap = state.cast();260		// SAFETY: ptr is an unique state per method safety requirements.261		let ptr: Box<PoolMap> = unsafe { Box::from_raw(ptr) };262		let ptr: PoolMap = *ptr;263		POOL.with_borrow_mut(|pool| {264			let _ = mem::replace(pool, ptr);265		});266	}267}268269#[must_use]270pub fn intern_bytes(bytes: &[u8]) -> IBytes {271	POOL.with(|pool| {272		let mut pool = pool.borrow_mut();273		let entry = pool.raw_entry_mut().from_key(bytes);274		match entry {275			RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),276			RawEntryMut::Vacant(e) => {277				let (k, ()) = e.insert(Inner::new_bytes(bytes), ());278				IBytes(k.clone())279			}280		}281	})282}283284#[must_use]285pub fn intern_str(str: &str) -> IStr {286	// SAFETY: Rust strings always utf8287	unsafe { intern_bytes(str.as_bytes()).cast_str_unchecked() }288}289290#[cfg(test)]291mod tests {292	use crate::IStr;293294	#[test]295	fn simple() {296		let a = IStr::from("a");297		let b = IStr::from("a");298299		assert_eq!(a.as_ptr(), b.as_ptr());300	}301}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -227,11 +227,11 @@
 impl Destruct {
 	/// Name of destructure, used for function parameter names
 	pub fn name(&self) -> ParamName {
-		ParamName(match self {
-			Self::Full(name) => Some(name.clone()),
+		match self {
+			Self::Full(name) => ParamName::Named(name.clone()),
 			#[cfg(feature = "exp-destruct")]
-			_ => None,
-		})
+			_ => ParamName::Unnamed,
+		}
 	}
 	pub fn binds_len(&self) -> usize {
 		#[cfg(feature = "exp-destruct")]
modifiedcrates/jrsonnet-parser/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/function.rs
+++ b/crates/jrsonnet-parser/src/function.rs
@@ -6,32 +6,38 @@
 use jrsonnet_interner::IStr;
 
 #[derive(Clone, Acyclic, Debug, PartialEq, Eq)]
-pub struct ParamName(pub Option<IStr>);
+pub enum ParamName {
+	Unnamed,
+	Named(IStr),
+}
 impl ParamName {
-	pub const ANONYMOUS: Self = Self(None);
-	pub fn new(name: IStr) -> Self {
-		Self(Some(name))
-	}
 	pub fn as_str(&self) -> Option<&str> {
-		self.0.as_deref()
+		match self {
+			ParamName::Unnamed => None,
+			ParamName::Named(istr) => Some(istr),
+		}
 	}
 	pub fn is_anonymous(&self) -> bool {
-		self.0.is_none()
+		matches!(self, Self::Unnamed)
+	}
+	pub fn is_named(&self) -> bool {
+		matches!(self, Self::Named(_))
 	}
 }
 impl PartialEq<IStr> for ParamName {
 	fn eq(&self, other: &IStr) -> bool {
-		self.0
-			.as_ref()
-			.map_or(false, |s| s.as_bytes() == other.as_bytes())
+		match self {
+			ParamName::Unnamed => false,
+			ParamName::Named(istr) => istr == other,
+		}
 	}
 }
 
 impl fmt::Display for ParamName {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		match &self.0 {
-			Some(v) => write!(f, "{v}"),
-			None => write!(f, "<unnamed>"),
+		match &self {
+			Self::Named(v) => write!(f, "{v}"),
+			Self::Unnamed => write!(f, "<unnamed>"),
 		}
 	}
 }
modifiedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap
@@ -28,18 +28,14 @@
                 signature: FunctionSignature(
                     [
                         ParamParse {
-                            name: ParamName(
-                                Some(
-                                    "foo",
-                                ),
+                            name: Named(
+                                "foo",
                             ),
                             default: Exists,
                         },
                         ParamParse {
-                            name: ParamName(
-                                Some(
-                                    "bar",
-                                ),
+                            name: Named(
+                                "bar",
                             ),
                             default: None,
                         },