git.delta.rocks / jrsonnet / refs/commits / 8a3d104167ad

difftreelog

build merge dependency updates

qwomztulYaroslav Bolyukin2026-02-07parent: #8954130.patch.diff
in: master

49 files changed

modifiedCargo.lockdiffbeforeafterboth
before · Cargo.lock
164 packageslockfile v3
after · Cargo.lock
170 packageslockfile v4
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@
 jrsonnet-stdlib = { path = "./crates/jrsonnet-stdlib", version = "0.5.0-pre97" }
 jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre97" }
 jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre97" }
-jrsonnet-gcmodule = { version = "0.3.7" }
+jrsonnet-gcmodule = { version = "0.4.0", path = "../gcmodule" }
 # Diagnostics.
 # hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
 # Previous implementation was based on annotate-snippets, which I don't like for many reasons.
@@ -27,8 +27,8 @@
 # I'm against using miette, because I want to reuse data between interpreter and annotations, yet miette
 #   and other libraries want to handle spans etc by itself, which is okay for compiler diagnostics, but is
 #   bad for interpreter, where interpreter and parser are paired much closer.
-hi-doc = "0.1.1"
-annotate-snippets = "0.10.1"
+hi-doc = "0.3.0"
+annotate-snippets = "0.12.11"
 
 # CLI
 clap = "4.5"
@@ -37,57 +37,57 @@
 # Parsing, manifestification is implemented manually everywhere
 # Note on serde_yaml_with_quirks: This is a fork of serde-yaml with legacy yaml 1.1 support:
 # https://github.com/dtolnay/serde-yaml/pull/225
-serde = "1.0.197"
-serde_json = "1.0.114"
-serde_yaml_with_quirks = "0.8.24"
+serde = "1.0.228"
+serde_json = "1.0.149"
+serde_yaml_with_quirks = "0.9.34"
 
 # Error handling
-anyhow = "1.0.83"
-thiserror = "1.0.60"
+anyhow = "1.0.101"
+thiserror = "2.0.18"
 
 # Code formatting
-dprint-core = "0.65.0"
+dprint-core = "0.67.4"
 
 # Stdlib hashing functions
-md5 = "0.7.0"
+md5 = "0.8.0"
 sha1 = "0.10.6"
-sha2 = "0.10.8"
+sha2 = "0.10.9"
 sha3 = "0.10.8"
 
 # Source code parsing.
 # Jrsonnet has two parsers for jsonnet - one is for execution, and another is for better parsing diagnostics/lints/LSP.
 # First (and fast one) is based on peg, second is based on rowan.
-peg = "0.8.3"
-logos = "0.14.0"
+peg = "0.8.5"
+logos = "0.16.1"
 ungrammar = "1.16.1"
-rowan = "0.15.15"
+rowan = "0.16.1"
 
 mimallocator = "0.1.3"
 indoc = "2.0"
-insta = "1.39"
-tempfile = "3.10"
-pathdiff = "0.2.1"
-hashbrown = "0.14.5"
+insta = "1.46"
+tempfile = "3.24"
+pathdiff = "0.2.3"
+hashbrown = "0.16.1"
 static_assertions = "1.1"
-rustc-hash = "1.1"
-num-bigint = "0.4.5"
-strsim = "0.11.0"
+rustc-hash = "2.1"
+num-bigint = "0.4.6"
+strsim = "0.11.1"
 proc-macro2 = "1.0"
 quote = "1.0"
 syn = "2.0"
 drop_bomb = "0.1.5"
 base64 = "0.22.1"
-indexmap = "2.2.3"
-itertools = "0.13.0"
-xshell = "0.2.6"
+indexmap = "2.13.0"
+itertools = "0.14.0"
+xshell = "0.2.7"
 
-lsp-server = "0.7.6"
-lsp-types = "0.96.0"
+lsp-server = "0.7.9"
+lsp-types = "0.97.0"
 
-regex = "1.10"
-lru = "0.12.3"
+regex = "1.12"
+lru = "0.16.3"
 
-json-structural-diff = "0.1.0"
+json-structural-diff = "0.2.0"
 syn-dissect-closure = "0.1.0"
 
 [workspace.lints.rust]
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -39,5 +39,5 @@
 interop-threading = []
 
 experimental = ["exp-preserve-order", "exp-destruct"]
-exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order", "jrsonnet-stdlib/exp-preserve-order"]
 exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -2,7 +2,6 @@
 
 use std::{
 	alloc::Layout,
-	any::Any,
 	cell::RefCell,
 	collections::HashMap,
 	env::current_dir,
@@ -17,7 +16,7 @@
 	error::{ErrorKind::*, Result},
 	ImportResolver,
 };
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
 
 use crate::VM;
@@ -32,11 +31,9 @@
 ) -> c_int;
 
 /// Resolves imports using callback
-#[derive(Trace)]
+#[derive(Acyclic)]
 pub struct CallbackImportResolver {
-	#[trace(skip)]
 	cb: JsonnetImportCallback,
-	#[trace(skip)]
 	ctx: *mut c_void,
 	out: RefCell<HashMap<SourcePath, Vec<u8>>>,
 }
@@ -101,14 +98,6 @@
 	}
 	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {
 		Ok(self.out.borrow().get(resolved).unwrap().clone())
-	}
-
-	fn as_any(&self) -> &dyn Any {
-		self
-	}
-
-	fn as_any_mut(&mut self) -> &mut dyn Any {
-		self
 	}
 }
 
modifiedbindings/jsonnet/src/interop.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/interop.rs
+++ b/bindings/jsonnet/src/interop.rs
@@ -60,7 +60,7 @@
 
 #[cfg(feature = "interop-common")]
 mod common {
-	use jrsonnet_evaluator::trace::{CompactFormat, ExplainingFormat, JsFormat, PathResolver};
+	use jrsonnet_evaluator::trace::{CompactFormat, HiDocFormat, JsFormat, PathResolver};
 
 	use crate::VM;
 
@@ -76,7 +76,7 @@
 			}
 			1 => vm.trace_format = Box::new(JsFormat { max_trace: 20 }),
 			2 => {
-				vm.trace_format = Box::new(ExplainingFormat {
+				vm.trace_format = Box::new(HiDocFormat {
 					resolver: PathResolver::new_cwd_fallback(),
 					max_trace: 20,
 				});
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -17,19 +17,20 @@
 	ffi::{CStr, CString, OsStr},
 	os::raw::{c_char, c_double, c_int, c_uint},
 	path::{Path, PathBuf},
+	rc::Rc,
 };
 
 use jrsonnet_evaluator::{
 	apply_tla, bail,
 	function::TlaArg,
-	gc::{GcHashMap, TraceBox},
+	gc::WithCapacityExt as _,
 	manifest::{JsonFormat, ManifestFormat, ToStringFormat},
+	rustc_hash::FxHashMap,
 	stack::set_stack_depth_limit,
-	tb,
 	trace::{CompactFormat, PathResolver, TraceFormat},
 	FileImportResolver, IStr, ImportResolver, Result, State, Val,
 };
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_parser::SourcePath;
 use jrsonnet_stdlib::ContextInitializer;
 
@@ -47,7 +48,7 @@
 	b"v0.20.0\0"
 }
 
-unsafe fn parse_path(input: &CStr) -> Cow<Path> {
+unsafe fn parse_path(input: &CStr) -> Cow<'_, Path> {
 	#[cfg(target_family = "unix")]
 	{
 		use std::os::unix::ffi::OsStrExt;
@@ -61,7 +62,7 @@
 	}
 }
 
-unsafe fn unparse_path(input: &Path) -> Cow<CStr> {
+unsafe fn unparse_path(input: &Path) -> Cow<'_, CStr> {
 	#[cfg(target_family = "unix")]
 	{
 		use std::os::unix::ffi::OsStrExt;
@@ -76,15 +77,14 @@
 	}
 }
 
-#[derive(Trace)]
+#[derive(Acyclic)]
 struct VMImportResolver {
-	#[trace(tracking(force))]
-	inner: RefCell<TraceBox<dyn ImportResolver>>,
+	inner: RefCell<Rc<dyn ImportResolver>>,
 }
 impl VMImportResolver {
 	fn new(value: impl ImportResolver) -> Self {
 		Self {
-			inner: RefCell::new(tb!(value)),
+			inner: RefCell::new(Rc::new(value)),
 		}
 	}
 }
@@ -103,13 +103,6 @@
 
 	fn resolve(&self, path: &Path) -> Result<SourcePath> {
 		self.inner.borrow().resolve(path)
-	}
-
-	fn as_any(&self) -> &dyn Any {
-		self
-	}
-	fn as_any_mut(&mut self) -> &mut dyn Any {
-		self
 	}
 }
 
@@ -117,28 +110,23 @@
 	state: State,
 	manifest_format: Box<dyn ManifestFormat>,
 	trace_format: Box<dyn TraceFormat>,
-	tla_args: GcHashMap<IStr, TlaArg>,
+	tla_args: FxHashMap<IStr, TlaArg>,
 }
 impl VM {
 	fn replace_import_resolver(&self, resolver: impl ImportResolver) {
-		*self
-			.state
-			.import_resolver()
-			.as_any()
+		*(self.state.import_resolver() as &dyn Any)
 			.downcast_ref::<VMImportResolver>()
 			.expect("valid resolver ty")
 			.inner
-			.borrow_mut() = tb!(resolver);
+			.borrow_mut() = Rc::new(resolver);
 	}
 	fn add_jpath(&self, path: PathBuf) {
-		self.state
-			.import_resolver()
-			.as_any()
+		let ir = self.state.import_resolver();
+		let vmi = (ir as &dyn Any)
 			.downcast_ref::<VMImportResolver>()
-			.expect("valid resolver ty")
-			.inner
-			.borrow_mut()
-			.as_any_mut()
+			.expect("valid resolver ty");
+		let vmi = &mut *vmi.inner.borrow_mut();
+		(vmi as &mut dyn Any)
 			.downcast_mut::<FileImportResolver>()
 			.expect("jpaths are not compatible with callback imports!")
 			.add_jpath(path);
@@ -158,7 +146,7 @@
 		state,
 		manifest_format: Box::new(JsonFormat::default()),
 		trace_format: Box::new(CompactFormat::default()),
-		tla_args: GcHashMap::new(),
+		tla_args: FxHashMap::new(),
 	}))
 }
 
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -46,7 +46,7 @@
 		o
 	}};
 	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{
-		$o.push_str($e);
+		$o.push_string($e.to_owned());
 		pi!(@s; $o: $($t)*);
 	}};
 	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{
@@ -711,8 +711,8 @@
 		let mut builder = hi_doc::SnippetBuilder::new(input);
 		for error in errors {
 			builder
-				.error(hi_doc::Text::single(
-					format!("{:?}", error.error).chars(),
+				.error(hi_doc::Text::fragment(
+					format!("{:?}", error.error),
 					Formatting::default(),
 				))
 				.range(
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -44,8 +44,6 @@
 # --exp-apply
 exp-apply = []
 
-nightly = ["jrsonnet-evaluator/nightly"]
-
 [dependencies]
 jrsonnet-evaluator.workspace = true
 jrsonnet-parser.workspace = true
modifiedcrates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/tla.rs
+++ b/crates/jrsonnet-cli/src/tla.rs
@@ -2,7 +2,8 @@
 use jrsonnet_evaluator::{
 	error::{ErrorKind, Result},
 	function::TlaArg,
-	gc::GcHashMap,
+	gc::WithCapacityExt as _,
+	rustc_hash::FxHashMap,
 	IStr,
 };
 use jrsonnet_parser::{ParserSettings, Source};
@@ -32,8 +33,8 @@
 	tla_code_file: Vec<ExtFile>,
 }
 impl TlaOpts {
-	pub fn tla_opts(&self) -> Result<GcHashMap<IStr, TlaArg>> {
-		let mut out = GcHashMap::new();
+	pub fn tla_opts(&self) -> Result<FxHashMap<IStr, TlaArg>> {
+		let mut out = FxHashMap::new();
 		for (name, value) in self
 			.tla_str
 			.iter()
modifiedcrates/jrsonnet-cli/src/trace.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/trace.rs
+++ b/crates/jrsonnet-cli/src/trace.rs
@@ -1,7 +1,5 @@
 use clap::{Parser, ValueEnum};
-use jrsonnet_evaluator::trace::{
-	CompactFormat, ExplainingFormat, HiDocFormat, PathResolver, TraceFormat,
-};
+use jrsonnet_evaluator::trace::{CompactFormat, HiDocFormat, PathResolver, TraceFormat};
 
 #[derive(PartialEq, Eq, ValueEnum, Clone)]
 pub enum TraceFormatName {
@@ -9,7 +7,7 @@
 	Compact,
 	/// Display source code with attached trace annotations
 	Explaining,
-	/// Experimental trace formatting based on hi-doc library
+	/// Trace formatting based on hi-doc library
 	HiDoc,
 }
 
@@ -38,11 +36,7 @@
 				padding: 4,
 				max_trace,
 			}),
-			TraceFormatName::Explaining => Box::new(ExplainingFormat {
-				resolver,
-				max_trace,
-			}),
-			TraceFormatName::HiDoc => Box::new(HiDocFormat {
+			TraceFormatName::Explaining | TraceFormatName::HiDoc => Box::new(HiDocFormat {
 				resolver,
 				max_trace,
 			}),
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -7,6 +7,8 @@
 repository.workspace = true
 version.workspace = true
 
+build = "build.rs"
+
 [lints]
 workspace = true
 
@@ -16,8 +18,6 @@
 explaining-traces = ["annotate-snippets", "hi-doc"]
 # Allows library authors to throw custom errors
 anyhow-error = ["anyhow"]
-# Adds ability to build import closure in async
-async-import = []
 
 # Allows to preserve field order in objects
 exp-preserve-order = []
@@ -29,9 +29,6 @@
 exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
 # obj?.field, obj?.['field']
 exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse"]
-
-# Improves performance, and implements some useful things using nightly-only features
-nightly = ["hashbrown/nightly"]
 
 [dependencies]
 jrsonnet-interner.workspace = true
@@ -41,7 +38,6 @@
 jrsonnet-gcmodule.workspace = true
 
 pathdiff.workspace = true
-hashbrown.workspace = true
 static_assertions.workspace = true
 
 rustc-hash.workspace = true
@@ -59,4 +55,8 @@
 hi-doc = { workspace = true, optional = true }
 # Bigint
 num-bigint = { workspace = true, features = ["serde"], optional = true }
-stacker = "0.1.15"
+
+stacker = "0.1.23"
+
+[build-dependencies]
+rustversion = "1.0.22"
addedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -0,0 +1,6 @@
+fn main() {
+	println!("cargo:rustc-check-cfg=cfg(nightly)");
+	if rustversion::cfg!(nightly) {
+		println!("cargo:rustc-cfg=nightly");
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -1,19 +1,30 @@
-use std::{any::Any, num::NonZeroU32};
+use std::{
+	any::Any,
+	fmt::{self},
+	num::NonZeroU32,
+};
 
-use jrsonnet_gcmodule::{Cc, Trace};
+use jrsonnet_gcmodule::{cc_dyn, Cc};
 use jrsonnet_interner::IBytes;
 use jrsonnet_parser::LocExpr;
 
-use crate::{function::FuncVal, gc::TraceBox, tb, Context, Result, Thunk, Val};
+use crate::{function::FuncVal, Context, Result, Thunk, Val};
 
 mod spec;
 pub use spec::{ArrayLike, *};
 
-/// Represents a Jsonnet array value.
-#[derive(Debug, Clone, Trace)]
-// may contain other ArrValue
-#[trace(tracking(force))]
-pub struct ArrValue(Cc<TraceBox<dyn ArrayLike>>);
+cc_dyn!(
+	#[doc = "Represents a Jsonnet array value."]
+	#[derive(Clone)]
+	ArrValue,
+	ArrayLike,
+	pub fn new() {...}
+);
+impl fmt::Debug for ArrValue {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		self.0.fmt(f)
+	}
+}
 
 pub trait ArrayLikeIter<T>: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator {}
 impl<I, T> ArrayLikeIter<T> for I where
@@ -22,9 +33,6 @@
 }
 
 impl ArrValue {
-	pub fn new(v: impl ArrayLike) -> Self {
-		Self(Cc::new(tb!(v)))
-	}
 	pub fn empty() -> Self {
 		Self::new(RangeArray::empty())
 	}
@@ -232,6 +240,3 @@
 		self.0.is_cheap()
 	}
 }
-
-#[cfg(target_pointer_width = "64")]
-static_assertions::assert_eq_size!(ArrValue, [u8; 8]);
modifiedcrates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -1,14 +1,15 @@
-use std::{cell::RefCell, future::Future, path::Path};
+use std::{any::Any, cell::RefCell, future::Future, path::Path};
 
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{
 	ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName, ForSpecData,
 	IfSpecData, LocExpr, Member, ObjBody, Param, ParamsDesc, ParserSettings, SliceDesc, Source,
 	SourcePath,
 };
+use rustc_hash::FxHashMap;
 
-use crate::{bail, gc::GcHashMap, FileData, ImportResolver, State};
+use crate::{bail, FileData, ImportResolver, State};
 
 pub struct Import {
 	path: IStr,
@@ -132,12 +133,12 @@
 			ObjBody::ObjComp(_) => todo!(),
 		}
 	}
-	match &*expr.0 {
+	match &*expr.expr() {
 		Expr::Import(v) | Expr::ImportStr(v) | Expr::ImportBin(v) => {
-			if let Expr::Str(s) = &*v.0 {
+			if let Expr::Str(s) = &*v.expr() {
 				out.0.push(Import {
 					path: s.clone(),
-					expression: matches!(&*expr.0, Expr::Import(_)),
+					expression: matches!(&*expr.expr(), Expr::Import(_)),
 				});
 			}
 			// Non-string import will fail in runtime
@@ -250,9 +251,9 @@
 	) -> impl Future<Output = Result<Vec<u8>, Self::Error>>;
 }
 
-#[derive(Trace)]
+#[derive(Acyclic)]
 struct ResolvedImportResolver {
-	resolved: RefCell<GcHashMap<(SourcePath, IStr), (SourcePath, bool)>>,
+	resolved: RefCell<FxHashMap<(SourcePath, IStr), (SourcePath, bool)>>,
 }
 impl ImportResolver for ResolvedImportResolver {
 	fn load_file_contents(&self, _resolved: &SourcePath) -> crate::Result<Vec<u8>> {
@@ -278,10 +279,6 @@
 			path.to_owned()
 		))
 	}
-
-	fn as_any(&self) -> &dyn std::any::Any {
-		self
-	}
 }
 
 enum Job {
@@ -295,13 +292,12 @@
 where
 	H: AsyncImportResolver,
 {
-	let mut resolved = s
-		.import_resolver()
-		.as_any()
+	let resolved = (s.import_resolver() as &dyn Any)
 		.downcast_ref::<ResolvedImportResolver>()
-		.map_or_else(GcHashMap::new, |resolver| {
-			std::mem::take(&mut *resolver.resolved.borrow_mut())
-		});
+		.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(path.as_ref()).await?,
 		parse: true,
@@ -344,7 +340,7 @@
 			}
 			Job::ResolveImport { from, import } => {
 				if let Some((resolved, expression)) =
-					resolved.get_mut(&(from.clone(), import.path.clone()))
+					resolved_map.get_mut(&(from.clone(), import.path.clone()))
 				{
 					if import.expression && !*expression {
 						*expression = true;
@@ -360,8 +356,5 @@
 			}
 		}
 	}
-	s.set_import_resolver(ResolvedImportResolver {
-		resolved: RefCell::new(resolved),
-	});
 	Ok(())
 }
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -2,10 +2,11 @@
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
+use rustc_hash::FxHashMap;
 
 use crate::{
-	error::ErrorKind::*, gc::GcHashMap, map::LayeredHashMap, ObjValue, Pending, Result, State,
-	Thunk, Val,
+	error::ErrorKind::*, gc::WithCapacityExt as _, map::LayeredHashMap, ObjValue, Pending, Result,
+	State, Thunk, Val,
 };
 
 #[derive(Trace)]
@@ -88,7 +89,7 @@
 
 	#[must_use]
 	pub fn with_var(self, name: impl Into<IStr>, value: Val) -> Self {
-		let mut new_bindings = GcHashMap::with_capacity(1);
+		let mut new_bindings = FxHashMap::with_capacity(1);
 		new_bindings.insert(name.into(), Thunk::evaluated(value));
 		self.extend(new_bindings, None, None, None)
 	}
@@ -96,7 +97,7 @@
 	#[must_use]
 	pub fn extend(
 		self,
-		new_bindings: GcHashMap<IStr, Thunk<Val>>,
+		new_bindings: FxHashMap<IStr, Thunk<Val>>,
 		new_dollar: Option<ObjValue>,
 		new_sup: Option<ObjValue>,
 		new_this: Option<ObjValue>,
@@ -128,7 +129,7 @@
 
 pub struct ContextBuilder {
 	state: Option<State>,
-	bindings: GcHashMap<IStr, Thunk<Val>>,
+	bindings: FxHashMap<IStr, Thunk<Val>>,
 	extend: Option<Context>,
 }
 
@@ -138,7 +139,7 @@
 	pub fn dangerous_empty_state() -> Self {
 		Self {
 			state: None,
-			bindings: GcHashMap::new(),
+			bindings: FxHashMap::new(),
 			extend: None,
 		}
 	}
@@ -148,14 +149,14 @@
 	pub fn with_capacity(state: State, capacity: usize) -> Self {
 		Self {
 			state: Some(state),
-			bindings: GcHashMap::with_capacity(capacity),
+			bindings: FxHashMap::with_capacity(capacity),
 			extend: None,
 		}
 	}
 	pub fn extend(parent: Context) -> Self {
 		Self {
 			state: parent.0.state.clone(),
-			bindings: GcHashMap::new(),
+			bindings: FxHashMap::new(),
 			extend: Some(parent),
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -138,12 +138,12 @@
 	#[error("assert failed: {}", format_empty_str(.0))]
 	AssertionFailed(IStr),
 
-	#[error("local is not defined: {0}{}", format_found(.1, "local"))]
+	#[error("local is not defined: {0}{found}", found = format_found(.1, "local"))]
 	VariableIsNotDefined(IStr, Vec<IStr>),
 	#[error("duplicate local var: {0}")]
 	DuplicateLocalVar(IStr),
 
-	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]
+	#[error("type mismatch: expected {expected}, got {2} {0}", expected = .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
 	#[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]
 	NoSuchField(IStr, Vec<IStr>),
@@ -154,7 +154,7 @@
 	UnknownFunctionParameter(String),
 	#[error("argument {0} is already bound")]
 	BindingParameterASecondTime(IStr),
-	#[error("too many args, function has {0}{}", format_signature(.1))]
+	#[error("too many args, function has {0}{sig}", sig = format_signature(.1))]
 	TooManyArgsFunctionHas(usize, FunctionSignature),
 	#[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]
 	FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,12 +1,11 @@
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{BindSpec, Destruct};
+use rustc_hash::FxHashMap;
 
 use crate::{
 	bail,
 	error::{ErrorKind::*, Result},
-	evaluate, evaluate_method, evaluate_named,
-	gc::GcHashMap,
-	Context, Pending, Thunk, Val,
+	evaluate, evaluate_method, evaluate_named, Context, Pending, Thunk, Val,
 };
 
 #[allow(clippy::too_many_lines)]
@@ -15,7 +14,7 @@
 	d: &Destruct,
 	parent: Thunk<Val>,
 	fctx: Pending<Context>,
-	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
+	new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,
 ) -> Result<()> {
 	match d {
 		Destruct::Full(v) => {
@@ -163,7 +162,7 @@
 pub fn evaluate_dest(
 	d: &BindSpec,
 	fctx: Pending<Context>,
-	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
+	new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,
 ) -> 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
@@ -7,6 +7,7 @@
 	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,
 };
 use jrsonnet_types::ValType;
+use rustc_hash::FxHashMap;
 
 use self::destructure::destruct;
 use crate::{
@@ -16,11 +17,12 @@
 	error::{suggest_object_fields, ErrorKind::*},
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
 	function::{CallLocation, FuncDesc, FuncVal},
+	gc::WithCapacityExt as _,
 	in_frame,
 	typed::Typed,
 	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
-	Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
-	ResultExt, Unbound, Val,
+	Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt,
+	Unbound, Val,
 };
 pub mod destructure;
 pub mod operator;
@@ -122,7 +124,7 @@
 			Val::Arr(list) => {
 				for item in list.iter_lazy() {
 					let fctx = Pending::new();
-					let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
+					let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());
 					destruct(var, item, fctx.clone(), &mut new_bindings)?;
 					let ctx = ctx
 						.clone()
@@ -140,7 +142,7 @@
 					false,
 				) {
 					let fctx = Pending::new();
-					let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
+					let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());
 					let obj = obj.clone();
 					let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
 						Thunk::evaluated(Val::string(field.clone())),
@@ -181,7 +183,7 @@
 		fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {
 			let fctx = Context::new_future();
 			let mut new_bindings =
-				GcHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());
+				FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());
 			for b in self.locals.iter() {
 				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;
 			}
@@ -329,7 +331,7 @@
 		}
 	}
 	let this = builder.build();
-	fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));
+	fctx.fill(ctx.extend(FxHashMap::new(), None, None, Some(this.clone())));
 	Ok(this)
 }
 
@@ -357,7 +359,7 @@
 			let this = builder.build();
 			for (ctx, fctx) in ctxs {
 				let _ctx = ctx
-					.extend(GcHashMap::new(), None, None, Some(this.clone()))
+					.extend(FxHashMap::new(), None, None, Some(this.clone()))
 					.into_future(fctx);
 			}
 			this
@@ -581,8 +583,8 @@
 			Ok(indexable)
 		})?,
 		LocalExpr(bindings, returned) => {
-			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =
-				GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());
+			let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =
+				FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());
 			let fctx = Context::new_future();
 			for b in bindings {
 				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -2,11 +2,6 @@
 
 use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType};
 
-#[cfg(feature = "exp-bigint")]
-use num_traits::{FromPrimitive, ToPrimitive};
-
-#[cfg(feature = "exp-bigint")]
-use crate::val::NumValue;
 use crate::{
 	arr::ArrValue,
 	bail,
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -1,9 +1,10 @@
-use hashbrown::HashMap;
+use std::collections::HashMap;
+
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ArgsDesc, LocExpr};
 
-use crate::{evaluate, gc::GcHashMap, typed::Typed, Context, Result, Thunk, Val};
+use crate::{evaluate, typed::Typed, Context, Result, Thunk, Val};
 
 /// Marker for arguments, which can be evaluated with context set to None
 pub trait OptionalContext {}
@@ -204,38 +205,6 @@
 	}
 }
 impl<V, S> OptionalContext for HashMap<IStr, V, S> where V: ArgLike + OptionalContext {}
-
-impl<A: ArgLike> ArgsLike for GcHashMap<IStr, A> {
-	fn unnamed_len(&self) -> usize {
-		self.0.unnamed_len()
-	}
-
-	fn unnamed_iter(
-		&self,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
-	) -> Result<()> {
-		self.0.unnamed_iter(ctx, tailstrict, handler)
-	}
-
-	fn named_iter(
-		&self,
-		ctx: Context,
-		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
-	) -> Result<()> {
-		self.0.named_iter(ctx, tailstrict, handler)
-	}
-
-	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
-		self.0.named_names(handler);
-	}
-
-	fn is_empty(&self) -> bool {
-		self.0.is_empty()
-	}
-}
 
 macro_rules! impl_args_like {
 	($count:expr; $($gen:ident)*) => {
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -1,10 +1,10 @@
 use std::{any::Any, borrow::Cow};
 
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox};
 use jrsonnet_interner::IStr;
 
 use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
-use crate::{gc::TraceBox, tb, Context, Result, Val};
+use crate::{Context, Result, Val};
 
 /// Can't have `str` | `IStr`, because constant `BuiltinParam` causes
 /// `E0492: constant functions cannot refer to interior mutable data`
@@ -70,6 +70,30 @@
 	}
 }
 
+cc_dyn!(
+	#[derive(Clone)]
+	BuiltinFunc,
+	Builtin,
+	pub(crate) fn new() {...}
+);
+impl Builtin for BuiltinFunc {
+	fn name(&self) -> &str {
+		self.0.name()
+	}
+
+	fn params(&self) -> &[BuiltinParam] {
+		self.0.params()
+	}
+
+	fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {
+		self.0.call(ctx, loc, args)
+	}
+
+	fn as_any(&self) -> &dyn Any {
+		self.0.as_any()
+	}
+}
+
 /// Description of function defined by native code
 ///
 /// Prefer to use #[builtin] macro, instead of manual implementation of this trait
@@ -108,7 +132,7 @@
 					default: ParamDefault::None,
 				})
 				.collect(),
-			handler: tb!(handler),
+			handler: TraceBox(Box::new(handler)),
 		}
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -13,7 +13,7 @@
 	parse::{parse_default_function_call, parse_function_call},
 };
 use crate::{
-	bail, error::ErrorKind::*, evaluate, evaluate_trivial, gc::TraceBox, tb, Context,
+	bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, Context,
 	ContextBuilder, Result, Thunk, Val,
 };
 
@@ -102,7 +102,7 @@
 	/// Standard library function.
 	StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),
 	/// User-provided function.
-	Builtin(Cc<TraceBox<dyn Builtin>>),
+	Builtin(BuiltinFunc),
 }
 
 impl Debug for FuncVal {
@@ -128,7 +128,7 @@
 
 impl FuncVal {
 	pub fn builtin(builtin: impl Builtin) -> Self {
-		Self::Builtin(Cc::new(tb!(builtin)))
+		Self::Builtin(BuiltinFunc::new(builtin))
 	}
 	pub fn static_builtin(static_builtin: &'static dyn StaticBuiltin) -> Self {
 		Self::StaticBuiltin(static_builtin)
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -2,6 +2,7 @@
 
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::ParamsDesc;
+use rustc_hash::FxHashMap;
 
 use super::{arglike::ArgsLike, builtin::BuiltinParam};
 use crate::{
@@ -10,7 +11,7 @@
 	error::{ErrorKind::*, Result},
 	evaluate_named,
 	function::builtin::ParamDefault,
-	gc::GcHashMap,
+	gc::WithCapacityExt as _,
 	Context, Pending, Thunk, Val,
 };
 
@@ -30,7 +31,7 @@
 	tailstrict: bool,
 ) -> Result<Context> {
 	let mut passed_args =
-		GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
+		FxHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
 	if args.unnamed_len() > params.len() {
 		bail!(TooManyArgsFunctionHas(
 			params.len(),
@@ -72,7 +73,7 @@
 		// Some args are unset, but maybe we have defaults for them
 		// Default values should be created in newly created context
 		let fctx = Context::new_future();
-		let mut defaults = GcHashMap::with_capacity(
+		let mut defaults = FxHashMap::with_capacity(
 			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()
 				- filled_named
 				- filled_positionals,
@@ -220,7 +221,7 @@
 pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
 	let fctx = Context::new_future();
 
-	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
+	let mut bindings = FxHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
 
 	for param in params.iter() {
 		if let Some(v) = &param.1 {
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -1,161 +1,27 @@
 /// Macros to help deal with Gc
-use std::{
-	borrow::{Borrow, BorrowMut},
-	collections::HashSet,
-	hash::BuildHasherDefault,
-	ops::{Deref, DerefMut},
-};
-
-use hashbrown::HashMap;
-use jrsonnet_gcmodule::{Trace, Tracer};
-use rustc_hash::{FxHashSet, FxHasher};
+use jrsonnet_gcmodule::Trace;
+use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
 
-/// Replacement for box, which assumes that the underlying type is [`Trace`]
-/// Used in places, where `Cc<dyn Trait>` should be used instead, but it can't, because `CoerceUnsiced` is not stable
-#[derive(Debug, Clone)]
-pub struct TraceBox<T: ?Sized>(pub Box<T>);
-#[macro_export]
-macro_rules! tb {
-	($v:expr) => {
-		$crate::gc::TraceBox(Box::new($v))
-	};
+pub trait WithCapacityExt {
+	fn new() -> Self;
+	fn with_capacity(capacity: usize) -> Self;
 }
-
-impl<T: ?Sized + Trace> Trace for TraceBox<T> {
-	fn trace(&self, tracer: &mut Tracer<'_>) {
-		self.0.trace(tracer);
+impl<V> WithCapacityExt for FxHashSet<V> {
+	fn with_capacity(capacity: usize) -> Self {
+		Self::with_capacity_and_hasher(capacity, FxBuildHasher::default())
 	}
 
-	fn is_type_tracked() -> bool {
-		true
+	fn new() -> Self {
+		Self::with_hasher(FxBuildHasher::default())
 	}
 }
-
-// TODO: Replace with CoerceUnsized
-impl<T: ?Sized> From<Box<T>> for TraceBox<T> {
-	fn from(inner: Box<T>) -> Self {
-		Self(inner)
+impl<K, V> WithCapacityExt for FxHashMap<K, V> {
+	fn with_capacity(capacity: usize) -> Self {
+		Self::with_capacity_and_hasher(capacity, FxBuildHasher::default())
 	}
-}
-
-impl<T: ?Sized> Deref for TraceBox<T> {
-	type Target = T;
 
-	fn deref(&self) -> &Self::Target {
-		&self.0
-	}
-}
-impl<T: Trace + ?Sized> DerefMut for TraceBox<T> {
-	fn deref_mut(&mut self) -> &mut Self::Target {
-		&mut self.0
-	}
-}
-
-impl<T: ?Sized> Borrow<T> for TraceBox<T> {
-	fn borrow(&self) -> &T {
-		&self.0
-	}
-}
-
-impl<T: ?Sized> BorrowMut<T> for TraceBox<T> {
-	fn borrow_mut(&mut self) -> &mut T {
-		&mut self.0
-	}
-}
-
-impl<T: ?Sized> AsRef<T> for TraceBox<T> {
-	fn as_ref(&self) -> &T {
-		&self.0
-	}
-}
-
-impl<T: ?Sized> AsMut<T> for TraceBox<T> {
-	fn as_mut(&mut self) -> &mut T {
-		&mut self.0
-	}
-}
-
-#[derive(Clone)]
-pub struct GcHashSet<V>(pub FxHashSet<V>);
-impl<V> GcHashSet<V> {
-	pub fn new() -> Self {
-		Self(HashSet::default())
-	}
-	pub fn with_capacity(capacity: usize) -> Self {
-		Self(FxHashSet::with_capacity_and_hasher(
-			capacity,
-			BuildHasherDefault::default(),
-		))
-	}
-}
-impl<V> Trace for GcHashSet<V>
-where
-	V: Trace,
-{
-	fn trace(&self, tracer: &mut Tracer<'_>) {
-		for v in &self.0 {
-			v.trace(tracer);
-		}
-	}
-}
-impl<V> Deref for GcHashSet<V> {
-	type Target = FxHashSet<V>;
-
-	fn deref(&self) -> &Self::Target {
-		&self.0
-	}
-}
-impl<V> DerefMut for GcHashSet<V> {
-	fn deref_mut(&mut self) -> &mut Self::Target {
-		&mut self.0
-	}
-}
-impl<V> Default for GcHashSet<V> {
-	fn default() -> Self {
-		Self::new()
-	}
-}
-
-#[derive(Debug)]
-pub struct GcHashMap<K, V>(pub HashMap<K, V, BuildHasherDefault<FxHasher>>);
-impl<K, V> GcHashMap<K, V> {
-	pub fn new() -> Self {
-		Self(HashMap::default())
-	}
-	pub fn with_capacity(capacity: usize) -> Self {
-		Self(HashMap::with_capacity_and_hasher(
-			capacity,
-			BuildHasherDefault::default(),
-		))
-	}
-}
-impl<K, V> Trace for GcHashMap<K, V>
-where
-	K: Trace,
-	V: Trace,
-{
-	fn trace(&self, tracer: &mut Tracer<'_>) {
-		for (k, v) in &self.0 {
-			k.trace(tracer);
-			v.trace(tracer);
-		}
-	}
-}
-impl<K, V> Deref for GcHashMap<K, V> {
-	type Target = HashMap<K, V, BuildHasherDefault<FxHasher>>;
-
-	fn deref(&self) -> &Self::Target {
-		&self.0
-	}
-}
-impl<K, V> DerefMut for GcHashMap<K, V> {
-	fn deref_mut(&mut self) -> &mut Self::Target {
-		&mut self.0
-	}
-}
-impl<K, V> Default for GcHashMap<K, V> {
-	fn default() -> Self {
-		Self::new()
+	fn new() -> Self {
+		Self::with_hasher(FxBuildHasher::default())
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -7,7 +7,7 @@
 };
 
 use fs::File;
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::IBytes;
 use jrsonnet_parser::{SourceDirectory, SourceFifo, SourceFile, SourcePath};
 
@@ -17,7 +17,7 @@
 };
 
 /// Implements file resolution logic for `import` and `importStr`
-pub trait ImportResolver: Trace {
+pub trait ImportResolver: Acyclic + Any {
 	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
 	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`
 	/// where `${vendor}` is a library path.
@@ -39,26 +39,14 @@
 	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],
 	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]
 	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;
-
-	// For downcasts, will be removed after trait_upcasting_coercion
-	// stabilization.
-	fn as_any(&self) -> &dyn Any;
-	fn as_any_mut(&mut self) -> &mut dyn Any;
 }
 
 /// Dummy resolver, can't resolve/load any file
-#[derive(Trace)]
+#[derive(Acyclic)]
 pub struct DummyImportResolver;
 impl ImportResolver for DummyImportResolver {
 	fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {
 		panic!("dummy resolver can't load any file")
-	}
-
-	fn as_any(&self) -> &dyn Any {
-		self
-	}
-	fn as_any_mut(&mut self) -> &mut dyn Any {
-		self
 	}
 }
 #[allow(clippy::use_self)]
@@ -69,7 +57,7 @@
 }
 
 /// File resolver, can load file from both FS and library paths
-#[derive(Default, Trace)]
+#[derive(Default, Acyclic)]
 pub struct FileImportResolver {
 	/// Library directories to search for file.
 	/// Referred to as `jpath` in original jsonnet implementation.
@@ -169,13 +157,5 @@
 
 	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
 		self.resolve_from(&SourcePath::default(), path)
-	}
-
-	fn as_any(&self) -> &dyn Any {
-		self
-	}
-
-	fn as_any_mut(&mut self) -> &mut dyn Any {
-		self
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -1,11 +1,10 @@
 //! jsonnet interpreter implementation
-#![cfg_attr(feature = "nightly", feature(thread_local, type_alias_impl_trait))]
+#![cfg_attr(nightly, feature(thread_local, type_alias_impl_trait))]
 
 // For jrsonnet-macros
 extern crate self as jrsonnet_evaluator;
 
 mod arr;
-#[cfg(feature = "async-import")]
 pub mod async_import;
 mod ctx;
 mod dynamic;
@@ -28,8 +27,10 @@
 use std::{
 	any::Any,
 	cell::{RefCell, RefMut},
+	collections::hash_map::Entry,
 	fmt::{self, Debug},
 	path::Path,
+	rc::Rc,
 };
 
 pub use ctx::*;
@@ -37,20 +38,28 @@
 pub use error::{Error, ErrorKind::*, Result, ResultExt};
 pub use evaluate::*;
 use function::CallLocation;
-use gc::{GcHashMap, TraceBox};
-use hashbrown::hash_map::RawEntryMut;
 pub use import::*;
-use jrsonnet_gcmodule::{Cc, Trace};
+use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};
 pub use jrsonnet_interner::{IBytes, IStr};
 #[doc(hidden)]
 pub use jrsonnet_macros;
 pub use jrsonnet_parser as parser;
 use jrsonnet_parser::{LocExpr, ParserSettings, Source, SourcePath};
 pub use obj::*;
+pub use rustc_hash;
+use rustc_hash::FxHashMap;
 use stack::check_depth;
 pub use tla::apply_tla;
 pub use val::{Thunk, Val};
 
+use crate::gc::WithCapacityExt as _;
+
+cc_dyn!(
+	#[derive(Clone)]
+	CcUnbound<V>,
+	Unbound<Bound = V>
+);
+
 /// Thunk without bound `super`/`this`
 /// object inheritance may be overriden multiple times, and will be fixed only on field read
 pub trait Unbound: Trace {
@@ -65,7 +74,7 @@
 #[derive(Clone, Trace)]
 pub enum MaybeUnbound {
 	/// Value needs to be bound to `this`/`super`
-	Unbound(Cc<TraceBox<dyn Unbound<Bound = Val>>>),
+	Unbound(CcUnbound<Val>),
 	/// Value is object-independent
 	Bound(Thunk<Val>),
 }
@@ -79,12 +88,14 @@
 	/// Attach object context to value, if required
 	pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {
 		match self {
-			Self::Unbound(v) => v.bind(sup, this),
+			Self::Unbound(v) => v.0.bind(sup, this),
 			Self::Bound(v) => Ok(v.evaluate()?),
 		}
 	}
 }
 
+cc_dyn!(CcContextInitializer, ContextInitializer);
+
 /// During import, this trait will be called to create initial context for file.
 /// It may initialize global variables, stdlib for example.
 pub trait ContextInitializer: Trace {
@@ -215,12 +226,12 @@
 #[derive(Trace)]
 pub struct EvaluationStateInternals {
 	/// Internal state
-	file_cache: RefCell<GcHashMap<SourcePath, FileData>>,
+	file_cache: RefCell<FxHashMap<SourcePath, FileData>>,
 	/// Context initializer, which will be used for imports and everything
 	/// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`
-	context_initializer: TraceBox<dyn ContextInitializer>,
+	context_initializer: CcContextInitializer,
 	/// Used to resolve file locations/contents
-	import_resolver: TraceBox<dyn ImportResolver>,
+	import_resolver: Rc<dyn ImportResolver>,
 }
 
 /// Maintains stack trace and import resolution
@@ -231,21 +242,17 @@
 	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
 	pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
 		let mut file_cache = self.file_cache();
-		let mut file = file_cache.raw_entry_mut().from_key(&path);
+		let mut file = file_cache.entry(path.clone());
 
 		let file = match file {
-			RawEntryMut::Occupied(ref mut d) => d.get_mut(),
-			RawEntryMut::Vacant(v) => {
+			Entry::Occupied(ref mut d) => d.get_mut(),
+			Entry::Vacant(v) => {
 				let data = self.import_resolver().load_file_contents(&path)?;
-				v.insert(
-					path.clone(),
-					FileData::new_string(
-						std::str::from_utf8(&data)
-							.map_err(|_| ImportBadFileUtf8(path.clone()))?
-							.into(),
-					),
-				)
-				.1
+				v.insert(FileData::new_string(
+					std::str::from_utf8(&data)
+						.map_err(|_| ImportBadFileUtf8(path.clone()))?
+						.into(),
+				))
 			}
 		};
 		Ok(file
@@ -255,14 +262,13 @@
 	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
 	pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
 		let mut file_cache = self.file_cache();
-		let mut file = file_cache.raw_entry_mut().from_key(&path);
+		let mut file = file_cache.entry(path.clone());
 
 		let file = match file {
-			RawEntryMut::Occupied(ref mut d) => d.get_mut(),
-			RawEntryMut::Vacant(v) => {
+			Entry::Occupied(ref mut d) => d.get_mut(),
+			Entry::Vacant(v) => {
 				let data = self.import_resolver().load_file_contents(&path)?;
-				v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))
-					.1
+				v.insert(FileData::new_bytes(data.as_slice().into()))
 			}
 		};
 		if let Some(str) = &file.bytes {
@@ -282,21 +288,17 @@
 	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
 	pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
 		let mut file_cache = self.file_cache();
-		let mut file = file_cache.raw_entry_mut().from_key(&path);
+		let mut file = file_cache.entry(path.clone());
 
 		let file = match file {
-			RawEntryMut::Occupied(ref mut d) => d.get_mut(),
-			RawEntryMut::Vacant(v) => {
+			Entry::Occupied(ref mut d) => d.get_mut(),
+			Entry::Vacant(v) => {
 				let data = self.import_resolver().load_file_contents(&path)?;
-				v.insert(
-					path.clone(),
-					FileData::new_string(
-						std::str::from_utf8(&data)
-							.map_err(|_| ImportBadFileUtf8(path.clone()))?
-							.into(),
-					),
-				)
-				.1
+				v.insert(FileData::new_string(
+					std::str::from_utf8(&data)
+						.map_err(|_| ImportBadFileUtf8(path.clone()))?
+						.into(),
+				))
 			}
 		};
 		if let Some(val) = &file.evaluated {
@@ -330,10 +332,10 @@
 		let res = evaluate(self.create_default_context(file_name), &parsed);
 
 		let mut file_cache = self.file_cache();
-		let mut file = file_cache.raw_entry_mut().from_key(&path);
+		let mut file = file_cache.entry(path.clone());
 
-		let RawEntryMut::Occupied(file) = &mut file else {
-			unreachable!("this file was just here!")
+		let Entry::Occupied(file) = &mut file else {
+			unreachable!("this file was just here")
 		};
 		let file = file.get_mut();
 		file.evaluating = false;
@@ -381,7 +383,7 @@
 
 /// Internals
 impl State {
-	fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {
+	fn file_cache(&self) -> RefMut<'_, FxHashMap<SourcePath, FileData>> {
 		self.0.file_cache.borrow_mut()
 	}
 }
@@ -479,7 +481,7 @@
 		&*self.0.import_resolver
 	}
 	pub fn context_initializer(&self) -> &dyn ContextInitializer {
-		&*self.0.context_initializer
+		&*self.0.context_initializer.0
 	}
 }
 
@@ -497,29 +499,34 @@
 
 #[derive(Default)]
 pub struct StateBuilder {
-	import_resolver: Option<TraceBox<dyn ImportResolver>>,
-	context_initializer: Option<TraceBox<dyn ContextInitializer>>,
+	import_resolver: Option<Rc<dyn ImportResolver>>,
+	context_initializer: Option<CcContextInitializer>,
 }
 impl StateBuilder {
 	pub fn import_resolver(&mut self, import_resolver: impl ImportResolver) -> &mut Self {
-		let _ = self.import_resolver.insert(tb!(import_resolver));
+		let _ = self.import_resolver.insert(Rc::new(import_resolver));
 		self
 	}
 	pub fn context_initializer(
 		&mut self,
 		context_initializer: impl ContextInitializer,
 	) -> &mut Self {
-		let _ = self.context_initializer.insert(tb!(context_initializer));
+		let _ = self
+			.context_initializer
+			.insert(CcContextInitializer::new(context_initializer));
 		self
 	}
 	pub fn build(mut self) -> State {
 		State(Cc::new(EvaluationStateInternals {
-			file_cache: RefCell::new(GcHashMap::new()),
-			context_initializer: self.context_initializer.take().unwrap_or_else(|| tb!(())),
+			file_cache: RefCell::new(FxHashMap::new()),
+			context_initializer: self
+				.context_initializer
+				.take()
+				.unwrap_or_else(|| CcContextInitializer::new(())),
 			import_resolver: self
 				.import_resolver
 				.take()
-				.unwrap_or_else(|| tb!(DummyImportResolver)),
+				.unwrap_or_else(|| Rc::new(DummyImportResolver)),
 		}))
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -1,13 +1,14 @@
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
+use rustc_hash::FxHashMap;
 
-use crate::{GcHashMap, Thunk, Val};
+use crate::{gc::WithCapacityExt as _, Thunk, Val};
 
 #[derive(Trace)]
 #[trace(tracking(force))]
 pub struct LayeredHashMapInternals {
 	parent: Option<LayeredHashMap>,
-	current: GcHashMap<IStr, Thunk<Val>>,
+	current: FxHashMap<IStr, Thunk<Val>>,
 }
 
 #[derive(Trace)]
@@ -15,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 {
 			handler(k.clone());
 		}
 		if let Some(parent) = self.0.parent.clone() {
@@ -23,14 +24,14 @@
 		}
 	}
 
-	pub(crate) fn new(layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
+	pub(crate) fn new(layer: FxHashMap<IStr, Thunk<Val>>) -> Self {
 		Self(Cc::new(LayeredHashMapInternals {
 			parent: None,
 			current: layer,
 		}))
 	}
 
-	pub fn extend(self, new_layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
+	pub fn extend(self, new_layer: FxHashMap<IStr, Thunk<Val>>) -> Self {
 		Self(Cc::new(LayeredHashMapInternals {
 			parent: Some(self),
 			current: new_layer,
@@ -64,7 +65,7 @@
 	fn default() -> Self {
 		Self(Cc::new(LayeredHashMapInternals {
 			parent: None,
-			current: GcHashMap::new(),
+			current: FxHashMap::new(),
 		}))
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -6,22 +6,21 @@
 	ptr::addr_of,
 };
 
-use jrsonnet_gcmodule::{Cc, Trace, Weak};
+use jrsonnet_gcmodule::{cc_dyn, Cc, Trace, Weak};
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{Span, Visibility};
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 
 use crate::{
 	arr::{PickObjectKeyValues, PickObjectValues},
 	bail,
 	error::{suggest_object_fields, Error, ErrorKind::*},
 	function::{CallLocation, FuncVal},
-	gc::{GcHashMap, GcHashSet, TraceBox},
+	gc::WithCapacityExt as _,
 	in_frame,
 	operator::evaluate_add_op,
-	tb,
 	val::ArrValue,
-	MaybeUnbound, Result, Thunk, Unbound, Val,
+	CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,
 };
 
 #[cfg(not(feature = "exp-preserve-order"))]
@@ -139,6 +138,7 @@
 	pub location: Option<Span>,
 }
 
+cc_dyn!(CcObjectAssertion, ObjectAssertion);
 pub trait ObjectAssertion: Trace {
 	fn run(&self, super_obj: Option<ObjValue>, this: Option<ObjValue>) -> Result<()>;
 }
@@ -159,10 +159,10 @@
 pub struct OopObject {
 	sup: Option<ObjValue>,
 	// this: Option<ObjValue>,
-	assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
-	assertions_ran: RefCell<GcHashSet<ObjValue>>,
-	this_entries: Cc<GcHashMap<IStr, ObjMember>>,
-	value_cache: RefCell<GcHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,
+	assertions: Cc<Vec<CcObjectAssertion>>,
+	assertions_ran: RefCell<FxHashSet<ObjValue>>,
+	this_entries: Cc<FxHashMap<IStr, ObjMember>>,
+	value_cache: RefCell<FxHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,
 }
 impl Debug for OopObject {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -203,7 +203,7 @@
 }
 
 #[derive(Clone, Trace)]
-pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<TraceBox<dyn ObjectLike>>);
+pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<dyn ObjectLike>);
 
 impl PartialEq for WeakObjValue {
 	fn eq(&self, other: &Self) -> bool {
@@ -220,9 +220,11 @@
 	}
 }
 
-#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, Trace, Debug)]
-pub struct ObjValue(pub(crate) Cc<TraceBox<dyn ObjectLike>>);
+cc_dyn!(
+	#[derive(Clone, Debug)]
+	ObjValue, ObjectLike,
+	pub fn new() {...}
+);
 
 #[derive(Debug, Trace)]
 struct EmptyObject;
@@ -331,9 +333,6 @@
 }
 
 impl ObjValue {
-	pub fn new(v: impl ObjectLike) -> Self {
-		Self(Cc::new(tb!(v)))
-	}
 	pub fn new_empty() -> Self {
 		Self::new(EmptyObject)
 	}
@@ -582,16 +581,16 @@
 impl OopObject {
 	pub fn new(
 		sup: Option<ObjValue>,
-		this_entries: Cc<GcHashMap<IStr, ObjMember>>,
-		assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
+		this_entries: Cc<FxHashMap<IStr, ObjMember>>,
+		assertions: Cc<Vec<CcObjectAssertion>>,
 	) -> Self {
 		Self {
 			sup,
 			// this: None,
 			assertions,
-			assertions_ran: RefCell::new(GcHashSet::new()),
+			assertions_ran: RefCell::new(FxHashSet::new()),
 			this_entries,
-			value_cache: RefCell::new(GcHashMap::new()),
+			value_cache: RefCell::new(FxHashMap::new()),
 		}
 	}
 
@@ -762,7 +761,7 @@
 		}
 		if self.assertions_ran.borrow_mut().insert(real_this.clone()) {
 			for assertion in self.assertions.iter() {
-				if let Err(e) = assertion.run(self.sup.clone(), Some(real_this.clone())) {
+				if let Err(e) = assertion.0.run(self.sup.clone(), Some(real_this.clone())) {
 					self.assertions_ran.borrow_mut().remove(&real_this);
 					return Err(e);
 				}
@@ -784,15 +783,15 @@
 impl Eq for ObjValue {}
 impl Hash for ObjValue {
 	fn hash<H: Hasher>(&self, hasher: &mut H) {
-		hasher.write_usize(addr_of!(*self.0) as usize);
+		hasher.write_usize(addr_of!(*self.0).expose_provenance() as usize);
 	}
 }
 
 #[allow(clippy::module_name_repetitions)]
 pub struct ObjValueBuilder {
 	sup: Option<ObjValue>,
-	map: GcHashMap<IStr, ObjMember>,
-	assertions: Vec<TraceBox<dyn ObjectAssertion>>,
+	map: FxHashMap<IStr, ObjMember>,
+	assertions: Vec<CcObjectAssertion>,
 	next_field_index: FieldIndex,
 }
 impl ObjValueBuilder {
@@ -802,7 +801,7 @@
 	pub fn with_capacity(capacity: usize) -> Self {
 		Self {
 			sup: None,
-			map: GcHashMap::with_capacity(capacity),
+			map: FxHashMap::with_capacity(capacity),
 			assertions: Vec::new(),
 			next_field_index: FieldIndex::default(),
 		}
@@ -817,7 +816,7 @@
 	}
 
 	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {
-		self.assertions.push(tb!(assertion));
+		self.assertions.push(CcObjectAssertion::new(assertion));
 		self
 	}
 	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {
@@ -922,7 +921,7 @@
 		let (receiver, name, member) =
 			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));
 		let entry = receiver.0.map.entry(name);
-		entry.insert(member);
+		entry.insert_entry(member);
 	}
 
 	/// Tries to insert value, returns an error if it was already defined
@@ -933,7 +932,7 @@
 		self.binding(MaybeUnbound::Bound(value.into()))
 	}
 	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {
-		self.binding(MaybeUnbound::Unbound(Cc::new(tb!(bindable))))
+		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))
 	}
 	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {
 		let (receiver, name, member) = self.build_member(binding);
@@ -955,8 +954,8 @@
 	pub fn value(self, value: impl Into<Val>) {
 		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));
 	}
-	pub fn bindable(self, bindable: TraceBox<dyn Unbound<Bound = Val>>) {
-		self.binding(MaybeUnbound::Unbound(Cc::new(bindable)));
+	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) {
+		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)));
 	}
 	pub fn binding(self, binding: MaybeUnbound) {
 		let (receiver, name, member) = self.build_member(binding);
modifiedcrates/jrsonnet-evaluator/src/stack.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stack.rs
+++ b/crates/jrsonnet-evaluator/src/stack.rs
@@ -7,23 +7,41 @@
 	current_depth: Cell<usize>,
 }
 
-#[cfg(feature = "nightly")]
-#[allow(clippy::thread_local_initializer_can_be_made_const)]
-#[thread_local]
-static STACK_LIMIT: StackLimit = StackLimit {
-	max_stack_size: Cell::new(200),
-	current_depth: Cell::new(0),
-};
-#[cfg(not(feature = "nightly"))]
-thread_local! {
-	static STACK_LIMIT: StackLimit = const {
-		StackLimit {
-			max_stack_size: Cell::new(200),
-			current_depth: Cell::new(0),
+#[cfg(nightly)]
+struct NightlyLocalKey<T>(pub T);
+#[cfg(nightly)]
+impl<T> NightlyLocalKey<T> {
+	#[inline(always)]
+	fn with<U>(&self, v: impl FnOnce(&T) -> U) -> U {
+		v(&self.0)
+	}
+}
+#[cfg(not(nightly))]
+type NightlyLocalKey<T> = std::thread::LocalKey<T>;
+
+#[cfg(nightly)]
+macro_rules! const_tls {
+	(const $name:ident: $t:ty = $expr:expr;) => {
+		#[thread_local]
+		static $name: NightlyLocalKey<$t> = NightlyLocalKey($expr);
+	};
+}
+#[cfg(not(nightly))]
+macro_rules! const_tls {
+	(const $name:ident: $t:ty = $expr:expr;) => {
+		thread_local! {
+			static $name: $t = const { $expr };
 		}
 	};
 }
 
+const_tls! {
+	const STACK_LIMIT: StackLimit = StackLimit {
+		max_stack_size: Cell::new(200),
+		current_depth: Cell::new(0),
+	};
+}
+
 pub struct StackOverflowError;
 impl From<StackOverflowError> for ErrorKind {
 	fn from(_: StackOverflowError) -> Self {
@@ -39,21 +57,14 @@
 /// Used to implement stack depth limitation
 pub struct StackDepthGuard(PhantomData<()>);
 impl Drop for StackDepthGuard {
-	#[cfg(feature = "nightly")]
 	fn drop(&mut self) {
-		STACK_LIMIT
-			.current_depth
-			.set(STACK_LIMIT.current_depth.get() - 1);
-	}
-	#[cfg(not(feature = "nightly"))]
-	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))
 	}
 }
 
 // #[cfg(feature = "nightly")]
 pub fn check_depth() -> Result<StackDepthGuard, StackOverflowError> {
-	fn internal(limit: &StackLimit) -> Result<StackDepthGuard, StackOverflowError> {
+	STACK_LIMIT.with(|limit| {
 		let current = limit.current_depth.get();
 		if current < limit.max_stack_size.get() {
 			limit.current_depth.set(current + 1);
@@ -61,47 +72,26 @@
 		} else {
 			Err(StackOverflowError)
 		}
-	}
-	#[cfg(feature = "nightly")]
-	{
-		internal(&STACK_LIMIT)
-	}
-	#[cfg(not(feature = "nightly"))]
-	{
-		STACK_LIMIT.with(internal)
-	}
+	})
 }
 
 pub struct StackDepthLimitOverrideGuard {
 	old_limit: usize,
 }
 impl Drop for StackDepthLimitOverrideGuard {
-	#[cfg(feature = "nightly")]
-	fn drop(&mut self) {
-		STACK_LIMIT.max_stack_size.set(self.old_limit);
-	}
-	#[cfg(not(feature = "nightly"))]
 	fn drop(&mut self) {
 		STACK_LIMIT.with(|limit| limit.max_stack_size.set(self.old_limit));
 	}
 }
 
 pub fn limit_stack_depth(depth_limit: usize) -> StackDepthLimitOverrideGuard {
-	fn internal(limit: &StackLimit, depth_limit: usize) -> StackDepthLimitOverrideGuard {
+	STACK_LIMIT.with(|limit| {
 		let old_limit = limit.max_stack_size.get();
 		let current_depth = limit.current_depth.get();
 
 		limit.max_stack_size.set(current_depth + depth_limit);
 		StackDepthLimitOverrideGuard { old_limit }
-	}
-	#[cfg(feature = "nightly")]
-	{
-		internal(&STACK_LIMIT, depth_limit)
-	}
-	#[cfg(not(feature = "nightly"))]
-	{
-		STACK_LIMIT.with(|limit| internal(limit, depth_limit))
-	}
+	})
 }
 
 /// Like [`limit_stack_depth`], but set depth is not guarded, and will be kept
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -1,11 +1,14 @@
+#[cfg(feature = "explaining-traces")]
+use std::cell::RefCell;
 use std::{
 	any::Any,
-	cell::RefCell,
 	path::{Path, PathBuf},
 };
 
 use jrsonnet_gcmodule::Trace;
-use jrsonnet_parser::{CodeLocation, Source, Span};
+use jrsonnet_parser::CodeLocation;
+#[cfg(feature = "explaining-traces")]
+use jrsonnet_parser::Span;
 
 use crate::{error::ErrorKind, Error};
 
@@ -225,71 +228,6 @@
 				)?;
 			} else {
 				write!(out, "    during {desc}")?;
-			}
-		}
-		Ok(())
-	}
-
-	fn as_any(&self) -> &dyn Any {
-		self
-	}
-
-	fn as_any_mut(&mut self) -> &mut dyn Any {
-		self
-	}
-}
-
-/// rustc-like trace displaying
-#[cfg(feature = "explaining-traces")]
-#[derive(Trace)]
-pub struct ExplainingFormat {
-	pub resolver: PathResolver,
-	pub max_trace: usize,
-}
-#[cfg(feature = "explaining-traces")]
-impl TraceFormat for ExplainingFormat {
-	fn write_trace(
-		&self,
-		out: &mut dyn std::fmt::Write,
-		error: &Error,
-	) -> Result<(), std::fmt::Error> {
-		write!(out, "{}", error.error())?;
-		if let ErrorKind::ImportSyntaxError { path, error } = error.error() {
-			writeln!(out)?;
-			let offset = error.location.offset;
-			let location = path
-				.map_source_locations(&[offset as u32])
-				.into_iter()
-				.next()
-				.unwrap();
-			let mut end_location = location;
-			end_location.offset += 1;
-
-			self.print_snippet(
-				out,
-				path.code(),
-				path,
-				&location,
-				&end_location,
-				"syntax error",
-			)?;
-		}
-		let trace = &error.trace();
-		for item in &trace.0 {
-			writeln!(out)?;
-			let desc = &item.desc;
-			if let Some(source) = &item.location {
-				let start_end = source.0.map_source_locations(&[source.1, source.2]);
-				self.print_snippet(
-					out,
-					source.0.code(),
-					&source.0,
-					&start_end[0],
-					&start_end[1],
-					desc,
-				)?;
-			} else {
-				write!(out, "{desc}")?;
 			}
 		}
 		Ok(())
@@ -305,68 +243,6 @@
 }
 
 #[cfg(feature = "explaining-traces")]
-impl ExplainingFormat {
-	fn print_snippet(
-		&self,
-		out: &mut dyn std::fmt::Write,
-		source: &str,
-		origin: &Source,
-		start: &CodeLocation,
-		end: &CodeLocation,
-		desc: &str,
-	) -> Result<(), std::fmt::Error> {
-		use annotate_snippets::{
-			// DisplayList, FormatOptions,
-			AnnotationType,
-			Renderer,
-			Slice,
-			Snippet,
-			SourceAnnotation,
-		};
-
-		let source_fragment: String = source
-			.chars()
-			.skip(start.line_start_offset)
-			.take(end.line_end_offset - end.line_start_offset)
-			.collect();
-
-		let origin = origin.source_path().path().map_or_else(
-			|| origin.source_path().to_string(),
-			|r| self.resolver.resolve(r),
-		);
-		let snippet = Snippet {
-			// opt: FormatOptions {
-			// 	color: true,
-			// 	..FormatOptions::default()
-			// },
-			title: None,
-			footer: vec![],
-			slices: vec![Slice {
-				source: &source_fragment,
-				line_start: start.line,
-				origin: Some(&origin),
-				fold: false,
-				annotations: vec![SourceAnnotation {
-					label: desc,
-					annotation_type: AnnotationType::Error,
-					range: (
-						start.offset - start.line_start_offset,
-						(end.offset.saturating_sub(start.line_start_offset))
-							.min(source_fragment.len()),
-					),
-				}],
-			}],
-		};
-
-		let renderer = Renderer::styled();
-		let dl = renderer.render(snippet);
-		write!(out, "{dl}")?;
-
-		Ok(())
-	}
-}
-
-#[cfg(feature = "explaining-traces")]
 #[derive(Trace)]
 pub struct HiDocFormat {
 	pub resolver: PathResolver,
@@ -390,7 +266,7 @@
 			let offset = error.location.offset;
 			let mut builder = SnippetBuilder::new(path.code());
 			builder
-				.error(Text::single("syntax error".chars(), Formatting::default()))
+				.error(Text::fragment("syntax error", Formatting::default()))
 				.range(offset..=offset)
 				.build();
 			let source = builder.build();
@@ -448,7 +324,7 @@
 				let mut builder = snippet_builder.borrow_mut();
 				let builder = builder.as_mut().unwrap();
 				builder
-					.note(Text::single(desc.chars(), Formatting::default()))
+					.note(Text::fragment(desc, Formatting::default()))
 					.range(source.1 as usize..=(source.2 as usize - 1).max(source.1 as usize))
 					.build();
 			} else {
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -20,9 +20,9 @@
 	#[error("every failed from {0}:\n{1}")]
 	UnionFailed(ComplexValType, TypeLocErrorList),
 	#[error(
-		"number out of bounds: {0} not in {}..{}",
-		.1.map(|v|v.to_string()).unwrap_or_default(),
-		.2.map(|v|v.to_string()).unwrap_or_default(),
+		"number out of bounds: {0} not in {start}..{end}",
+		start = .1.map(|v|v.to_string()).unwrap_or_default(),
+		end = .2.map(|v|v.to_string()).unwrap_or_default(),
 	)]
 	BoundsFailed(f64, Option<f64>, Option<f64>),
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -8,10 +8,11 @@
 	rc::Rc,
 };
 
-use jrsonnet_gcmodule::{Cc, Trace};
+use jrsonnet_gcmodule::{Acyclic, Cc, Trace, TraceBox};
 use jrsonnet_interner::IStr;
 pub use jrsonnet_macros::Thunk;
 use jrsonnet_types::ValType;
+use rustc_hash::FxHashMap;
 use thiserror::Error;
 
 pub use crate::arr::{ArrValue, ArrayLike};
@@ -19,9 +20,8 @@
 	bail,
 	error::{Error, ErrorKind::*},
 	function::FuncVal,
-	gc::{GcHashMap, TraceBox},
+	gc::WithCapacityExt as _,
 	manifest::{ManifestFormat, ToStringFormat},
-	tb,
 	typed::BoundedUsize,
 	ObjValue, Result, Unbound, WeakObjValue,
 };
@@ -70,7 +70,9 @@
 		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))
 	}
 	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {
-		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))
+		Self(Cc::new(RefCell::new(ThunkInner::Waiting(TraceBox(
+			Box::new(f),
+		)))))
 	}
 	pub fn errored(e: Error) -> Self {
 		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))
@@ -174,13 +176,13 @@
 	I: Unbound<Bound = T>,
 	T: Trace,
 {
-	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,
+	cache: Cc<RefCell<FxHashMap<CacheKey, T>>>,
 	value: I,
 }
 impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {
 	pub fn new(value: I) -> Self {
 		Self {
-			cache: Cc::new(RefCell::new(GcHashMap::new())),
+			cache: Cc::new(RefCell::new(FxHashMap::new())),
 			value,
 		}
 	}
@@ -302,7 +304,7 @@
 	}
 }
 
-#[derive(Debug, Clone, Trace)]
+#[derive(Debug, Clone, Acyclic)]
 pub enum StrValue {
 	Flat(IStr),
 	Tree(Rc<(StrValue, StrValue, usize)>),
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -9,14 +9,14 @@
 	borrow::Cow,
 	cell::RefCell,
 	fmt::{self, Display},
-	hash::{BuildHasherDefault, Hash, Hasher},
+	hash::{Hash, Hasher},
 	ops::Deref,
 	str,
 };
 
 use hashbrown::{hash_map::RawEntryMut, HashMap};
-use jrsonnet_gcmodule::Trace;
-use rustc_hash::FxHasher;
+use jrsonnet_gcmodule::{Acyclic, Trace};
+use rustc_hash::FxBuildHasher;
 
 mod inner;
 use inner::Inner;
@@ -31,6 +31,7 @@
 		false
 	}
 }
+unsafe impl Acyclic for IStr {}
 
 impl IStr {
 	#[must_use]
@@ -219,10 +220,10 @@
 	}
 }
 
-type PoolMap = HashMap<Inner, (), BuildHasherDefault<FxHasher>>;
+type PoolMap = HashMap<Inner, (), FxBuildHasher>;
 
 thread_local! {
-	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, BuildHasherDefault::default()));
+	static POOL: RefCell<PoolMap> = RefCell::new(HashMap::with_capacity_and_hasher(200, FxBuildHasher::default()));
 }
 
 /// Jrsonnet golang bindings require that it is possible to move jsonnet
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -103,13 +103,6 @@
 	syn::custom_keyword!(ok);
 }
 
-struct EmptyAttr;
-impl Parse for EmptyAttr {
-	fn parse(_input: ParseStream) -> Result<Self> {
-		Ok(Self)
-	}
-}
-
 struct BuiltinAttrs {
 	fields: Vec<Field>,
 }
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -4,12 +4,12 @@
 	rc::Rc,
 };
 
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::IStr;
 
 use crate::source::Source;
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum FieldName {
 	/// {fixed: 2}
 	Fixed(IStr),
@@ -17,7 +17,7 @@
 	Dyn(LocExpr),
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
 #[repr(u8)]
 pub enum Visibility {
 	/// :
@@ -34,10 +34,10 @@
 	}
 }
 
-#[derive(Clone, Debug, PartialEq, Trace)]
+#[derive(Clone, Debug, PartialEq, Acyclic)]
 pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct FieldMember {
 	pub name: FieldName,
 	pub plus: bool,
@@ -46,14 +46,14 @@
 	pub value: LocExpr,
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum Member {
 	Field(FieldMember),
 	BindStmt(BindSpec),
 	AssertStmt(AssertStmt),
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
 pub enum UnaryOpType {
 	Plus,
 	Minus,
@@ -77,7 +77,7 @@
 	}
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
 pub enum BinaryOpType {
 	Mul,
 	Div,
@@ -146,11 +146,11 @@
 }
 
 /// name, default value
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct Param(pub Destruct, pub Option<LocExpr>);
 
 /// Defined function parameters
-#[derive(Debug, Clone, PartialEq, Trace)]
+#[derive(Debug, Clone, PartialEq, Acyclic)]
 pub struct ParamsDesc(pub Rc<Vec<Param>>);
 
 impl Deref for ParamsDesc {
@@ -160,7 +160,7 @@
 	}
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct ArgsDesc {
 	pub unnamed: Vec<LocExpr>,
 	pub named: Vec<(IStr, LocExpr)>,
@@ -171,7 +171,7 @@
 	}
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, PartialEq, Eq, Acyclic)]
 pub enum DestructRest {
 	/// ...rest
 	Keep(IStr),
@@ -179,7 +179,7 @@
 	Drop,
 }
 
-#[derive(Debug, Clone, PartialEq, Trace)]
+#[derive(Debug, Clone, PartialEq, Acyclic)]
 pub enum Destruct {
 	Full(IStr),
 	#[cfg(feature = "exp-destruct")]
@@ -240,7 +240,7 @@
 	}
 }
 
-#[derive(Debug, Clone, PartialEq, Trace)]
+#[derive(Debug, Clone, PartialEq, Acyclic)]
 pub enum BindSpec {
 	Field {
 		into: Destruct,
@@ -261,19 +261,19 @@
 	}
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct IfSpecData(pub LocExpr);
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct ForSpecData(pub Destruct, pub LocExpr);
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum CompSpec {
 	IfSpec(IfSpecData),
 	ForSpec(ForSpecData),
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct ObjComp {
 	pub pre_locals: Vec<BindSpec>,
 	pub field: FieldMember,
@@ -281,13 +281,13 @@
 	pub compspecs: Vec<CompSpec>,
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum ObjBody {
 	MemberList(Vec<Member>),
 	ObjComp(ObjComp),
 }
 
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Acyclic)]
 pub enum LiteralType {
 	This,
 	Super,
@@ -297,7 +297,7 @@
 	False,
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct SliceDesc {
 	pub start: Option<LocExpr>,
 	pub end: Option<LocExpr>,
@@ -305,7 +305,7 @@
 }
 
 /// Syntax base
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum Expr {
 	Literal(LiteralType),
 
@@ -374,7 +374,7 @@
 	Slice(LocExpr, SliceDesc),
 }
 
-#[derive(Debug, PartialEq, Trace)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub struct IndexPart {
 	pub value: LocExpr,
 	#[cfg(feature = "exp-null-coaelse")]
@@ -382,8 +382,7 @@
 }
 
 /// file, begin offset, end offset
-#[derive(Clone, PartialEq, Eq, Trace)]
-#[trace(skip)]
+#[derive(Clone, PartialEq, Eq, Acyclic)]
 #[repr(C)]
 pub struct Span(pub Source, pub u32, pub u32);
 impl Span {
@@ -401,7 +400,7 @@
 }
 
 /// Holds AST expression and its location in source file
-#[derive(Clone, PartialEq, Trace)]
+#[derive(Clone, PartialEq, Acyclic)]
 pub struct LocExpr(Rc<(Expr, Span)>);
 impl LocExpr {
 	pub fn new(expr: Expr, span: Span) -> Self {
modifiedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -6,7 +6,7 @@
 	rc::Rc,
 };
 
-use jrsonnet_gcmodule::{Trace, Tracer};
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::{IBytes, IStr};
 
 use crate::location::{location_to_offset, offset_to_location, CodeLocation};
@@ -56,7 +56,7 @@
 		impl Eq for dyn $T {}
 	};
 }
-pub trait SourcePathT: Trace + Debug + Display {
+pub trait SourcePathT: Acyclic + Debug + Display {
 	/// This method should be checked by resolver before panicking with bad SourcePath input
 	/// if `true` - then resolver may threat this path as default, and default is usally a CWD
 	fn is_default(&self) -> bool;
@@ -79,7 +79,7 @@
 /// search location is applicable
 ///
 /// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
-#[derive(Eq, Debug, Clone)]
+#[derive(Eq, Debug, Clone, Acyclic)]
 pub struct SourcePath(Rc<dyn SourcePathT>);
 impl SourcePath {
 	pub fn new(inner: impl SourcePathT) -> Self {
@@ -106,18 +106,6 @@
 		&*self.0 == &*other.0
 	}
 }
-impl Trace for SourcePath {
-	fn trace(&self, tracer: &mut Tracer) {
-		(*self.0).trace(tracer)
-	}
-
-	fn is_type_tracked() -> bool
-	where
-		Self: Sized,
-	{
-		true
-	}
-}
 impl Display for SourcePath {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 		write!(f, "{}", self.0)
@@ -129,7 +117,7 @@
 	}
 }
 
-#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
 struct SourceDefault;
 impl Display for SourceDefault {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -151,7 +139,7 @@
 ///
 /// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
 /// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
-#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
 pub struct SourceFile(PathBuf);
 impl SourceFile {
 	pub fn new(path: PathBuf) -> Self {
@@ -179,7 +167,7 @@
 /// Represents path to the directory on the disk
 ///
 /// See also [`SourceFile`]
-#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
 pub struct SourceDirectory(PathBuf);
 impl SourceDirectory {
 	pub fn new(path: PathBuf) -> Self {
@@ -208,7 +196,7 @@
 ///
 /// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
 /// and user can construct arbitrary values by hand, without asking import resolver
-#[derive(Trace, Hash, PartialEq, Eq, Debug, Clone)]
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug, Clone)]
 pub struct SourceVirtual(pub IStr);
 impl Display for SourceVirtual {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -231,7 +219,7 @@
 /// for better cross-platform support.
 // PartialEq is limited to ptr equality
 #[allow(clippy::derived_hash_with_manual_eq)]
-#[derive(Trace, Debug, Hash)]
+#[derive(Acyclic, Debug, Hash)]
 pub struct SourceFifo(pub String, pub IBytes);
 impl PartialEq for SourceFifo {
 	fn eq(&self, other: &Self) -> bool {
@@ -258,16 +246,8 @@
 
 /// Either real file, or virtual
 /// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
-#[derive(Clone, PartialEq, Eq, Debug)]
+#[derive(Clone, PartialEq, Eq, Debug, Acyclic)]
 pub struct Source(pub Rc<(SourcePath, IStr)>);
-
-impl Trace for Source {
-	fn trace(&self, _tracer: &mut Tracer) {}
-
-	fn is_type_tracked() -> bool {
-		false
-	}
-}
 
 impl Source {
 	pub fn new(path: SourcePath, code: IStr) -> Self {
modifiedcrates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
+++ b/crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
@@ -127,9 +127,9 @@
 	IDENT,
 	#[regex("[ \\t\\n\\r]+")]
 	WHITESPACE,
-	#[regex("//[^\\r\\n]*(\\r\\n|\\n)?")]
+	#[regex("//[^\\r\\n]*?(\\r\\n|\\n)?")]
 	SINGLE_LINE_SLASH_COMMENT,
-	#[regex("#[^\\r\\n]*(\\r\\n|\\n)?")]
+	#[regex("#[^\\r\\n]*?(\\r\\n|\\n)?")]
 	SINGLE_LINE_HASH_COMMENT,
 	#[regex("/\\*([^*]|\\*[^/])*\\*/")]
 	MULTI_LINE_COMMENT,
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -16,7 +16,7 @@
 	trace::PathResolver,
 	ContextBuilder, IStr, ObjValue, ObjValueBuilder, Thunk, Val,
 };
-use jrsonnet_gcmodule::Trace;
+use jrsonnet_gcmodule::{Acyclic, Cc, Trace};
 use jrsonnet_parser::Source;
 pub use manifest::*;
 pub use math::*;
@@ -50,7 +50,7 @@
 mod types;
 
 #[allow(clippy::too_many_lines)]
-pub fn stdlib_uncached(settings: Rc<RefCell<Settings>>) -> ObjValue {
+pub fn stdlib_uncached(settings: Cc<RefCell<Settings>>) -> ObjValue {
 	let mut builder = ObjValueBuilder::new();
 
 	// FIXME: Use PHF
@@ -279,10 +279,11 @@
 	builder.build()
 }
 
-pub trait TracePrinter {
+pub trait TracePrinter: Acyclic {
 	fn print_trace(&self, loc: CallLocation, value: IStr);
 }
 
+#[derive(Acyclic)]
 pub struct StdTracePrinter {
 	resolver: PathResolver,
 }
@@ -309,13 +310,14 @@
 	}
 }
 
+#[derive(Clone, Trace)]
 pub struct Settings {
 	/// Used for `std.extVar`
 	pub ext_vars: HashMap<IStr, TlaArg>,
 	/// Used for `std.native`
 	pub ext_natives: HashMap<IStr, FuncVal>,
 	/// Used for `std.trace`
-	pub trace_printer: Box<dyn TracePrinter>,
+	pub trace_printer: Rc<dyn TracePrinter>,
 	/// Used for `std.thisFile`
 	pub path_resolver: PathResolver,
 }
@@ -329,27 +331,27 @@
 pub struct ContextInitializer {
 	/// std without applied thisFile overlay
 	stdlib_obj: ObjValue,
-	settings: Rc<RefCell<Settings>>,
+	settings: Cc<RefCell<Settings>>,
 }
 impl ContextInitializer {
 	pub fn new(resolver: PathResolver) -> Self {
 		let settings = Settings {
 			ext_vars: HashMap::new(),
 			ext_natives: HashMap::new(),
-			trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),
+			trace_printer: Rc::new(StdTracePrinter::new(resolver.clone())),
 			path_resolver: resolver,
 		};
-		let settings = Rc::new(RefCell::new(settings));
+		let settings = Cc::new(RefCell::new(settings));
 		let stdlib_obj = stdlib_uncached(settings.clone());
 		Self {
 			stdlib_obj,
 			settings,
 		}
 	}
-	pub fn settings(&self) -> Ref<Settings> {
+	pub fn settings(&self) -> Ref<'_, Settings> {
 		self.settings.borrow()
 	}
-	pub fn settings_mut(&self) -> RefMut<Settings> {
+	pub fn settings_mut(&self) -> RefMut<'_, Settings> {
 		self.settings.borrow_mut()
 	}
 	pub fn add_ext_var(&self, name: IStr, value: Val) {
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -1,4 +1,4 @@
-use std::{cell::RefCell, collections::BTreeSet, rc::Rc};
+use std::{cell::RefCell, collections::BTreeSet};
 
 use jrsonnet_evaluator::{
 	bail,
@@ -9,6 +9,7 @@
 	val::{equals, ArrValue},
 	Context, Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,
 };
+use jrsonnet_gcmodule::Cc;
 
 use crate::{extvar_source, Settings};
 
@@ -47,7 +48,7 @@
 }
 
 #[builtin(fields(
-	settings: Rc<RefCell<Settings>>,
+	settings: Cc<RefCell<Settings>>,
 ))]
 pub fn builtin_ext_var(this: &builtin_ext_var, ctx: Context, x: IStr) -> Result<Val> {
 	let ctx = ctx.state().create_default_context(extvar_source(&x, ""));
@@ -62,7 +63,7 @@
 }
 
 #[builtin(fields(
-	settings: Rc<RefCell<Settings>>,
+	settings: Cc<RefCell<Settings>>,
 ))]
 pub fn builtin_native(this: &builtin_native, x: IStr) -> Val {
 	this.settings
@@ -74,7 +75,7 @@
 }
 
 #[builtin(fields(
-	settings: Rc<RefCell<Settings>>,
+	settings: Cc<RefCell<Settings>>,
 ))]
 pub fn builtin_trace(
 	this: &builtin_trace,
modifiedcrates/jrsonnet-stdlib/src/regex.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/regex.rs
+++ b/crates/jrsonnet-stdlib/src/regex.rs
@@ -1,24 +1,26 @@
-use std::{cell::RefCell, hash::BuildHasherDefault, num::NonZeroUsize, rc::Rc};
+use std::{cell::RefCell, num::NonZeroUsize, rc::Rc};
 
 use ::regex::Regex;
 use jrsonnet_evaluator::{
 	error::{ErrorKind::*, Result},
+	rustc_hash::FxBuildHasher,
 	val::StrValue,
 	IStr, ObjValueBuilder, Val,
 };
+use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_macros::builtin;
 use lru::LruCache;
-use rustc_hash::FxHasher;
 
+#[derive(Acyclic)]
 pub struct RegexCacheInner {
-	cache: RefCell<LruCache<IStr, Rc<Regex>, BuildHasherDefault<FxHasher>>>,
+	cache: RefCell<LruCache<IStr, Rc<Regex>, FxBuildHasher>>,
 }
 impl Default for RegexCacheInner {
 	fn default() -> Self {
 		Self {
 			cache: RefCell::new(LruCache::with_hasher(
 				NonZeroUsize::new(20).unwrap(),
-				BuildHasherDefault::default(),
+				FxBuildHasher::default(),
 			)),
 		}
 	}
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -1,17 +1,11 @@
 {
   "nodes": {
     "crane": {
-      "inputs": {
-        "nixpkgs": [
-          "nixpkgs"
-        ]
-      },
       "locked": {
-        "lastModified": 1724377159,
-        "narHash": "sha256-ixjje1JO8ucKT41hs6n2NCde1Vc0+Zc2p2gUbJpCsMw=",
+        "lastModified": 1770419512,
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "3e47b7a86c19142bd3675da49d6acef488b4dac1",
+        "rev": "2510f2cbc3ccd237f700bb213756a8f35c32d8d7",
         "type": "github"
       },
       "original": {
@@ -27,11 +21,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1722555600,
-        "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
+        "lastModified": 1769996383,
         "owner": "hercules-ci",
         "repo": "flake-parts",
-        "rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
+        "rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
         "type": "github"
       },
       "original": {
@@ -42,15 +35,15 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1724519568,
-        "narHash": "sha256-CmfrenY4cEi/mIslKy8XOGdqxUUVgT6/qMzNcAN/7z8=",
+        "lastModified": 1770468184,
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "eb0e6df0cdd2641ec0651cd9802ff4cff3b3e915",
+        "rev": "a124a10ea33a73329c42d67f30efcdbfb60a4e04",
         "type": "github"
       },
       "original": {
         "owner": "nixos",
+        "ref": "release-25.11",
         "repo": "nixpkgs",
         "type": "github"
       }
@@ -71,11 +64,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1755743804,
-        "narHash": "sha256-M6qT02voARH5e9eTXQBzpYIE/hAp6jPgBCyxLmw5uBM=",
+        "lastModified": 1770433312,
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "80322e975e27d834451d6b66e63f8abae9d74bf2",
+        "rev": "9922ff9f99a6436756cbe6f5d11f9c3630e58cf0",
         "type": "github"
       },
       "original": {
@@ -85,20 +77,11 @@
       }
     },
     "shelly": {
-      "inputs": {
-        "flake-parts": [
-          "flake-parts"
-        ],
-        "nixpkgs": [
-          "nixpkgs"
-        ]
-      },
       "locked": {
-        "lastModified": 1718420551,
-        "narHash": "sha256-NU8NBXVPj0KuY4Tl/LtZPrbX3PmmmgPuhk/1pzm9cyk=",
+        "lastModified": 1756323923,
         "owner": "CertainLach",
         "repo": "shelly",
-        "rev": "4f70221f3f9ad9058f590eefb25251b6760aaa47",
+        "rev": "b5dd29a500db04f54a9f1c2bf81cdd84df8b0cd7",
         "type": "github"
       },
       "original": {
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -1,7 +1,7 @@
 {
   description = "Jrsonnet";
   inputs = {
-    nixpkgs.url = "github:nixos/nixpkgs";
+    nixpkgs.url = "github:nixos/nixpkgs/release-25.11";
     rust-overlay = {
       url = "github:oxalica/rust-overlay";
       inputs.nixpkgs.follows = "nixpkgs";
@@ -10,148 +10,162 @@
       url = "github:hercules-ci/flake-parts";
       inputs.nixpkgs-lib.follows = "nixpkgs";
     };
-    crane = {
-      url = "github:ipetkov/crane";
-      inputs.nixpkgs.follows = "nixpkgs";
-    };
-    shelly = {
-      url = "github:CertainLach/shelly";
-      inputs = {
-        flake-parts.follows = "flake-parts";
-        nixpkgs.follows = "nixpkgs";
-      };
-    };
+    crane.url = "github:ipetkov/crane";
+    shelly.url = "github:CertainLach/shelly";
   };
-  outputs = inputs @ {
-    nixpkgs,
-    flake-parts,
-    rust-overlay,
-    crane,
-    shelly,
-    ...
-  }:
-    flake-parts.lib.mkFlake {inherit inputs;} {
-      imports = [shelly.flakeModule];
-      systems = ["x86_64-linux" "aarch64-linux" "armv7l-linux" "armv6l-linux" "mingw-w64"];
-      perSystem = {
-        config,
-        system,
-        ...
-      }: let
-        pkgs = import nixpkgs {
-          inherit system;
-          overlays = [rust-overlay.overlays.default];
-          config.allowUnsupportedSystem = true;
-        };
-        rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
-        craneLib = (crane.mkLib pkgs).overrideToolchain rust;
-      in {
-        legacyPackages = {
-          jsonnetImpls = {
-            go-jsonnet = pkgs.callPackage ./nix/go-jsonnet.nix {};
-            sjsonnet = pkgs.callPackage ./nix/sjsonnet.nix {};
-            jsonnet = pkgs.callPackage ./nix/jsonnet.nix {};
-            # I didn't managed to build it, and nixpkgs version is marked as broken
-            # haskell-jsonnet = pkgs.callPackage ./nix/haskell-jsonnet.nix { };
-            rsjsonnet = pkgs.callPackage ./nix/rsjsonnet.nix {};
+  outputs =
+    inputs@{
+      nixpkgs,
+      flake-parts,
+      rust-overlay,
+      crane,
+      shelly,
+      ...
+    }:
+    flake-parts.lib.mkFlake { inherit inputs; } {
+      imports = [ shelly.flakeModule ];
+      systems = inputs.nixpkgs.lib.systems.flakeExposed;
+      perSystem =
+        {
+          config,
+          system,
+          ...
+        }:
+        let
+          pkgs = import nixpkgs {
+            inherit system;
+            overlays = [ rust-overlay.overlays.default ];
+            config.allowUnsupportedSystem = true;
+          };
+          rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
+          craneLib = (crane.mkLib pkgs).overrideToolchain rust;
+        in
+        {
+          legacyPackages = {
+            jsonnetImpls = {
+              go-jsonnet = pkgs.callPackage ./nix/go-jsonnet.nix { };
+              sjsonnet = pkgs.callPackage ./nix/sjsonnet.nix { };
+              jsonnet = pkgs.callPackage ./nix/jsonnet.nix { };
+              # I didn't managed to build it, and nixpkgs version is marked as broken
+              # haskell-jsonnet = pkgs.callPackage ./nix/haskell-jsonnet.nix { };
+              rsjsonnet = pkgs.callPackage ./nix/rsjsonnet.nix { };
+            };
           };
-        };
-        packages = rec {
-          default = jrsonnet;
+          packages = rec {
+            default = jrsonnet;
 
-          jrsonnet = pkgs.callPackage ./nix/jrsonnet.nix {
-            inherit craneLib;
-          };
-          jrsonnet-nightly = pkgs.callPackage ./nix/jrsonnet.nix {
-            inherit craneLib;
-            withNightlyFeatures = true;
-          };
-          jrsonnet-experimental = pkgs.callPackage ./nix/jrsonnet.nix {
-            inherit craneLib;
-            withExperimentalFeatures = true;
-          };
+            jrsonnet = pkgs.callPackage ./nix/jrsonnet.nix {
+              inherit craneLib;
+            };
+            jrsonnet-nightly = pkgs.callPackage ./nix/jrsonnet.nix {
+              inherit craneLib;
+              withNightlyFeatures = true;
+            };
+            jrsonnet-experimental = pkgs.callPackage ./nix/jrsonnet.nix {
+              inherit craneLib;
+              withExperimentalFeatures = true;
+            };
 
-          jrsonnet-release = pkgs.callPackage ./nix/jrsonnet-release.nix {
-            rustPlatform = pkgs.makeRustPlatform {
-              rustc = rust;
-              cargo = rust;
+            jrsonnet-release = pkgs.callPackage ./nix/jrsonnet-release.nix {
+              rustPlatform = pkgs.makeRustPlatform {
+                rustc = rust;
+                cargo = rust;
+              };
             };
-          };
 
-          benchmarks = pkgs.callPackage ./nix/benchmarks.nix {
-            inherit (config.legacyPackages.jsonnetImpls) go-jsonnet sjsonnet jsonnet rsjsonnet;
-            jrsonnetVariants = [
-              {
-                drv = jrsonnet.override {forBenchmarks = true;};
-                name = "";
-              }
-            ];
+            benchmarks = pkgs.callPackage ./nix/benchmarks.nix {
+              inherit (config.legacyPackages.jsonnetImpls)
+                go-jsonnet
+                sjsonnet
+                jsonnet
+                rsjsonnet
+                ;
+              jrsonnetVariants = [
+                {
+                  drv = jrsonnet.override { forBenchmarks = true; };
+                  name = "";
+                }
+              ];
+            };
+            benchmarks-quick = pkgs.callPackage ./nix/benchmarks.nix {
+              inherit (config.legacyPackages.jsonnetImpls)
+                go-jsonnet
+                sjsonnet
+                jsonnet
+                rsjsonnet
+                ;
+              quick = true;
+              jrsonnetVariants = [
+                {
+                  drv = jrsonnet.override { forBenchmarks = true; };
+                  name = "";
+                }
+              ];
+            };
+            benchmarks-against-release = pkgs.callPackage ./nix/benchmarks.nix {
+              inherit (config.legacyPackages.jsonnetImpls)
+                go-jsonnet
+                sjsonnet
+                jsonnet
+                rsjsonnet
+                ;
+              jrsonnetVariants = [
+                {
+                  drv = jrsonnet.override { forBenchmarks = true; };
+                  name = "current";
+                }
+                {
+                  drv = jrsonnet-nightly.override { forBenchmarks = true; };
+                  name = "current-nightly";
+                }
+                {
+                  drv = jrsonnet-release.override { forBenchmarks = true; };
+                  name = "release";
+                }
+              ];
+            };
+            benchmarks-quick-against-release = pkgs.callPackage ./nix/benchmarks.nix {
+              inherit (config.legacyPackages.jsonnetImpls)
+                go-jsonnet
+                sjsonnet
+                jsonnet
+                rsjsonnet
+                ;
+              quick = true;
+              jrsonnetVariants = [
+                {
+                  drv = jrsonnet.override { forBenchmarks = true; };
+                  name = "current";
+                }
+                {
+                  drv = jrsonnet-nightly.override { forBenchmarks = true; };
+                  name = "current-nightly";
+                }
+                {
+                  drv = jrsonnet-release.override { forBenchmarks = true; };
+                  name = "release";
+                }
+              ];
+            };
           };
-          benchmarks-quick = pkgs.callPackage ./nix/benchmarks.nix {
-            inherit (config.legacyPackages.jsonnetImpls) go-jsonnet sjsonnet jsonnet rsjsonnet;
-            quick = true;
-            jrsonnetVariants = [
-              {
-                drv = jrsonnet.override {forBenchmarks = true;};
-                name = "";
-              }
-            ];
-          };
-          benchmarks-against-release = pkgs.callPackage ./nix/benchmarks.nix {
-            inherit (config.legacyPackages.jsonnetImpls) go-jsonnet sjsonnet jsonnet rsjsonnet;
-            jrsonnetVariants = [
-              {
-                drv = jrsonnet.override {forBenchmarks = true;};
-                name = "current";
-              }
-              {
-                drv = jrsonnet-nightly.override {forBenchmarks = true;};
-                name = "current-nightly";
-              }
-              {
-                drv = jrsonnet-release.override {forBenchmarks = true;};
-                name = "release";
-              }
-            ];
-          };
-          benchmarks-quick-against-release = pkgs.callPackage ./nix/benchmarks.nix {
-            inherit (config.legacyPackages.jsonnetImpls) go-jsonnet sjsonnet jsonnet rsjsonnet;
-            quick = true;
-            jrsonnetVariants = [
-              {
-                drv = jrsonnet.override {forBenchmarks = true;};
-                name = "current";
-              }
-              {
-                drv = jrsonnet-nightly.override {forBenchmarks = true;};
-                name = "current-nightly";
-              }
-              {
-                drv = jrsonnet-release.override {forBenchmarks = true;};
-                name = "release";
-              }
-            ];
+          shelly.shells.default = {
+            factory = craneLib.devShell;
+            packages =
+              with pkgs;
+              [
+                cargo-edit
+                cargo-outdated
+                cargo-watch
+                cargo-insta
+                cargo-hack
+                lld
+                hyperfine
+                graphviz
+              ]
+              ++ lib.optionals (!stdenv.isDarwin) [
+                valgrind
+              ];
           };
         };
-        shelly.shells.default = {
-          factory = craneLib.devShell;
-          packages = with pkgs;
-            [
-              alejandra
-              cargo-edit
-              cargo-asm
-              cargo-outdated
-              cargo-watch
-              cargo-insta
-              lld
-              hyperfine
-              graphviz
-            ]
-            ++ lib.optionals (!stdenv.isDarwin) [
-              valgrind
-              kcachegrind
-            ];
-        };
-      };
     };
 }
addedresultdiffbeforeafterboth
--- /dev/null
+++ b/result
@@ -0,0 +1 @@
+/nix/store/nd6v7jksg1dqhpx4x4vqgy5ry1nkb9lk-jrsonnet-current
\ No newline at end of file
modifiedrust-toolchain.tomldiffbeforeafterboth
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
-channel = "1.89.0"
+channel = "1.93.0"
 components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]
modifiedtests/golden/issue172.jsonnet.goldendiffbeforeafterboth
--- a/tests/golden/issue172.jsonnet.golden
+++ b/tests/golden/issue172.jsonnet.golden
@@ -1,4 +1,4 @@
-variable is not defined: b
-    issue172.jsonnet:1:45-47: variable <b> access
+local is not defined: b
+    issue172.jsonnet:1:45-47: local <b> access
     issue172.jsonnet:1:4-10:  field <value> access
     elem <0> evaluation
\ No newline at end of file
modifiedtests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth
--- a/tests/golden/missing_binding.jsonnet.golden
+++ b/tests/golden/missing_binding.jsonnet.golden
@@ -1,3 +1,3 @@
-variable is not defined: sta
-There is variable with similar name present: std
-    missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
+local is not defined: sta
+There is a local with similar name present: std
+    missing_binding.jsonnet:1:1-5: local <sta> access
\ No newline at end of file
modifiedtests/tests/common.rsdiffbeforeafterboth
--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -56,25 +56,10 @@
 
 #[builtin]
 fn param_names(fun: FuncVal) -> Vec<String> {
-	match fun {
-		FuncVal::Id => vec!["x".to_string()],
-		FuncVal::Normal(func) => func
-			.params
-			.iter()
-			.map(|p| p.0.name().unwrap_or_else(|| "<unnamed>".into()).to_string())
-			.collect(),
-		FuncVal::StaticBuiltin(b) => b
-			.params()
-			.iter()
-			.map(|p| p.name().as_str().unwrap_or("<unnamed>").to_string())
-			.collect(),
-		FuncVal::Builtin(b) => b
-			.params()
-			.iter()
-			.map(|p| p.name().as_str().unwrap_or("<unnamed>").to_string())
-			.collect(),
-		FuncVal::Thunk(_) => vec![],
-	}
+	fun.params()
+		.into_iter()
+		.map(|v| v.name().as_str().unwrap_or("<unnamed>").to_owned())
+		.collect()
 }
 
 #[derive(Trace)]
modifiedxtask/src/sourcegen/kinds.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/kinds.rs
+++ b/xtask/src/sourcegen/kinds.rs
@@ -158,7 +158,6 @@
 }
 use std::{collections::HashSet, str::FromStr};
 
-pub use define_kinds;
 use indexmap::IndexMap;
 use proc_macro2::{Ident, TokenStream};
 use quote::{format_ident, quote};
@@ -266,8 +265,8 @@
 		error("STRING_BLOCK_MISSING_INDENT", lexer = true);
 		lit("IDENT") => r"[_a-zA-Z][_a-zA-Z0-9]*";
 		lit("WHITESPACE") => r"[ \t\n\r]+";
-		lit("SINGLE_LINE_SLASH_COMMENT") => r"//[^\r\n]*(\r\n|\n)?";
-		lit("SINGLE_LINE_HASH_COMMENT") => r"#[^\r\n]*(\r\n|\n)?";
+		lit("SINGLE_LINE_SLASH_COMMENT") => r"//[^\r\n]*?(\r\n|\n)?";
+		lit("SINGLE_LINE_HASH_COMMENT") => r"#[^\r\n]*?(\r\n|\n)?";
 		lit("MULTI_LINE_COMMENT") => r"/\*([^*]|\*[^/])*\*/";
 		error("COMMENT_TOO_SHORT") => r"/\*/";
 		error("COMMENT_UNTERMINATED") =>  r"/\*([^*/]|\*[^/])+";
modifiedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/mod.rs
+++ b/xtask/src/sourcegen/mod.rs
@@ -4,7 +4,7 @@
 use ast::{lower, AstSrc};
 use itertools::Itertools;
 use kinds::{KindsSrc, TokenKind};
-use proc_macro2::{Punct, Spacing, TokenStream};
+use proc_macro2::{Ident, Punct, Spacing, Span, TokenStream};
 use quote::{format_ident, quote};
 use ungrammar::Grammar;
 use util::{ensure_file_contents, reformat, to_pascal_case, to_upper_snake_case};
@@ -533,8 +533,11 @@
 	if "{}[]()$".contains(token) {
 		let c = token.chars().next().unwrap();
 		quote! { #c }
-	} else if token.contains('$') {
+	} else if token.contains(|v| v == '$') {
 		quote! { #token }
+	} else if token.chars().all(|v| ('a'..='z').contains(&v)) {
+		let i = Ident::new(&token, Span::call_site());
+		quote! { #i }
 	} else {
 		let cs = token.chars().map(|c| Punct::new(c, Spacing::Joint));
 		quote! { #(#cs)* }