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
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,27 +1,21 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
-name = "ahash"
-version = "0.8.12"
+name = "aho-corasick"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
 dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
- "zerocopy",
+ "memchr",
 ]
 
 [[package]]
-name = "aho-corasick"
-version = "1.1.3"
+name = "aliasable"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
-dependencies = [
- "memchr",
-]
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
 
 [[package]]
 name = "allocator-api2"
@@ -31,19 +25,30 @@
 
 [[package]]
 name = "annotate-snippets"
-version = "0.10.2"
+version = "0.12.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d9b665789884a7e8fb06c84b295e923b03ca51edbb7d08f91a6a50322ecbfe6"
+checksum = "16e4850548ff4a25a77ce3bda7241874e17fb702ea551f0cc62a2dbe052f1272"
 dependencies = [
  "anstyle",
  "unicode-width",
 ]
 
 [[package]]
+name = "annotated-string"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298ed730801db3c02f2edba003c9420a0f57ea48d37fdc5601c536113668c059"
+dependencies = [
+ "hi-doc-jumprope",
+ "itertools",
+ "ouroboros",
+]
+
+[[package]]
 name = "anstream"
-version = "0.6.20"
+version = "0.6.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -56,9 +61,9 @@
 
 [[package]]
 name = "anstyle"
-version = "1.0.11"
+version = "1.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
 
 [[package]]
 name = "anstyle-parse"
@@ -71,31 +76,40 @@
 
 [[package]]
 name = "anstyle-query"
-version = "1.1.4"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
 dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.10"
+version = "3.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
 dependencies = [
  "anstyle",
  "once_cell_polyfill",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "anyhow"
-version = "1.0.99"
+version = "1.0.101"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
 
 [[package]]
+name = "ar_archive_writer"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
+dependencies = [
+ "object",
+]
+
+[[package]]
 name = "autocfg"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -108,16 +122,10 @@
 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
 
 [[package]]
-name = "beef"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
-
-[[package]]
 name = "bitflags"
-version = "2.9.2"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
 
 [[package]]
 name = "block-buffer"
@@ -130,30 +138,34 @@
 
 [[package]]
 name = "bumpalo"
-version = "3.19.0"
+version = "3.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+dependencies = [
+ "allocator-api2",
+]
 
 [[package]]
 name = "cc"
-version = "1.2.33"
+version = "1.2.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f"
+checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
 dependencies = [
+ "find-msvc-tools",
  "shlex",
 ]
 
 [[package]]
 name = "cfg-if"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
 
 [[package]]
 name = "clap"
-version = "4.5.45"
+version = "4.5.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
+checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -161,9 +173,9 @@
 
 [[package]]
 name = "clap_builder"
-version = "4.5.44"
+version = "4.5.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8"
+checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
 dependencies = [
  "anstream",
  "anstyle",
@@ -173,20 +185,20 @@
 
 [[package]]
 name = "clap_complete"
-version = "4.5.57"
+version = "4.5.65"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d9501bd3f5f09f7bbee01da9a511073ed30a80cd7a509f1214bb74eadea71ad"
+checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
 dependencies = [
  "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "4.5.45"
+version = "4.5.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
+checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
 dependencies = [
- "heck",
+ "heck 0.5.0",
  "proc-macro2",
  "quote",
  "syn",
@@ -194,9 +206,9 @@
 
 [[package]]
 name = "clap_lex"
-version = "0.7.5"
+version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
 
 [[package]]
 name = "colorchoice"
@@ -233,9 +245,9 @@
 
 [[package]]
 name = "crypto-common"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
 dependencies = [
  "generic-array",
  "typenum",
@@ -259,14 +271,15 @@
 
 [[package]]
 name = "dprint-core"
-version = "0.65.0"
+version = "0.67.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b569f4e3085ae957ecc37588e6b2227791b72745434eae966db29e122ba27f0d"
+checksum = "2c1d827947704a9495f705d6aeed270fa21a67f825f22902c28f38dc3af7a9ae"
 dependencies = [
  "anyhow",
  "bumpalo",
- "indexmap 2.10.0",
- "rustc-hash",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "rustc-hash 2.1.1",
  "serde",
  "unicode-width",
 ]
@@ -297,12 +310,23 @@
 
 [[package]]
 name = "errno"
-version = "0.3.13"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
 dependencies = [
  "libc",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "extension-trait"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd65f1b59dd22d680c7a626cc4a000c1e03d241c51c3e034d2bc9f1e90734f9b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -312,6 +336,12 @@
 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
 name = "fnv"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -324,6 +354,12 @@
 checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
 
 [[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
 name = "generic-array"
 version = "0.14.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -335,131 +371,137 @@
 
 [[package]]
 name = "getrandom"
-version = "0.2.16"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
 dependencies = [
  "cfg-if",
  "libc",
  "r-efi",
- "wasi 0.14.2+wasi-0.2.4",
+ "wasip2",
 ]
 
 [[package]]
 name = "hashbrown"
-version = "0.12.3"
+version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 
 [[package]]
 name = "hashbrown"
-version = "0.14.5"
+version = "0.15.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
 dependencies = [
- "ahash",
  "allocator-api2",
+ "equivalent",
+ "foldhash 0.1.5",
 ]
 
 [[package]]
 name = "hashbrown"
-version = "0.15.5"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
 dependencies = [
  "allocator-api2",
  "equivalent",
- "foldhash",
+ "foldhash 0.2.0",
 ]
 
 [[package]]
 name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hi-doc"
-version = "0.1.1"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2390a0c9be1370168ef9557833bad3bfa37e1720df61d7f7034f18c07b4e006"
+checksum = "f70fb920ba34768415fb239d7d607486083bfc38ad35e3f1d558697f9f646f11"
 dependencies = [
+ "annotated-string",
+ "extension-trait",
+ "itertools",
  "num-traits",
  "rand",
  "random_color",
  "range-map",
  "smallvec",
+ "tree-sitter-highlight",
+ "unicode-box-drawing",
 ]
 
 [[package]]
-name = "indexmap"
-version = "1.9.3"
+name = "hi-doc-jumprope"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+checksum = "236c25809a9c0a0249b3488feb57744e12aa64e4f3db851980eab303719c7bdd"
 dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
+ "rand",
+ "str_indices",
 ]
 
 [[package]]
 name = "indexmap"
-version = "2.10.0"
+version = "2.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
 dependencies = [
  "equivalent",
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
  "serde",
+ "serde_core",
 ]
 
 [[package]]
 name = "indoc"
-version = "2.0.6"
+version = "2.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
 
 [[package]]
 name = "insta"
-version = "1.43.1"
+version = "1.46.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
+checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
 dependencies = [
  "console",
  "once_cell",
  "similar",
+ "tempfile",
 ]
 
 [[package]]
 name = "is_terminal_polyfill"
-version = "1.70.1"
+version = "1.70.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
 
 [[package]]
 name = "itertools"
-version = "0.13.0"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.15"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
 
 [[package]]
 name = "jrsonnet"
@@ -495,7 +537,6 @@
 dependencies = [
  "annotate-snippets",
  "anyhow",
- "hashbrown 0.14.5",
  "hi-doc",
  "jrsonnet-gcmodule",
  "jrsonnet-interner",
@@ -504,7 +545,8 @@
  "jrsonnet-types",
  "num-bigint",
  "pathdiff",
- "rustc-hash",
+ "rustc-hash 2.1.1",
+ "rustversion",
  "serde",
  "stacker",
  "static_assertions",
@@ -528,18 +570,14 @@
 
 [[package]]
 name = "jrsonnet-gcmodule"
-version = "0.3.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87122f4aa9b77c38f96e43d9ba4b7717d63aa87b137090a16ec0107cf17abdac"
+version = "0.4.0"
 dependencies = [
  "jrsonnet-gcmodule-derive",
 ]
 
 [[package]]
 name = "jrsonnet-gcmodule-derive"
-version = "0.3.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17eefaaba135591284e11cda15ed067247bec5fe9ece4fabc2c0df650d7b955a"
+version = "0.4.0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -550,9 +588,9 @@
 name = "jrsonnet-interner"
 version = "0.5.0-pre97"
 dependencies = [
- "hashbrown 0.14.5",
+ "hashbrown 0.16.1",
  "jrsonnet-gcmodule",
- "rustc-hash",
+ "rustc-hash 2.1.1",
 ]
 
 [[package]]
@@ -601,7 +639,7 @@
  "md5",
  "num-bigint",
  "regex",
- "rustc-hash",
+ "rustc-hash 2.1.1",
  "serde",
  "serde_json",
  "serde_yaml_with_quirks",
@@ -620,9 +658,9 @@
 
 [[package]]
 name = "json-structural-diff"
-version = "0.1.0"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25c7940d3c84d2079306c176c7b2b37622b6bc5e43fbd1541b1e4a4e1fd02045"
+checksum = "e878e36a8a44c158505c2c818abdc1350413ad83dcb774a0459f6a7ef2b65cbf"
 dependencies = [
  "difflib",
  "regex",
@@ -637,18 +675,12 @@
 dependencies = [
  "cpufeatures",
 ]
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.175"
+version = "0.2.180"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
 
 [[package]]
 name = "libjsonnet"
@@ -662,79 +694,63 @@
 ]
 
 [[package]]
-name = "linked-hash-map"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-
-[[package]]
 name = "linux-raw-sys"
-version = "0.9.4"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
 
 [[package]]
 name = "logos"
-version = "0.14.4"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458"
+checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f"
 dependencies = [
  "logos-derive",
 ]
 
 [[package]]
 name = "logos-codegen"
-version = "0.14.4"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f"
+checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970"
 dependencies = [
- "beef",
  "fnv",
- "lazy_static",
  "proc-macro2",
  "quote",
+ "regex-automata",
  "regex-syntax",
  "syn",
 ]
 
 [[package]]
 name = "logos-derive"
-version = "0.14.4"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d"
+checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31"
 dependencies = [
  "logos-codegen",
 ]
 
 [[package]]
 name = "lru"
-version = "0.12.5"
+version = "0.16.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
 dependencies = [
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
 ]
 
 [[package]]
 name = "md5"
-version = "0.7.0"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
 
 [[package]]
 name = "memchr"
-version = "2.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
-
-[[package]]
-name = "memoffset"
-version = "0.9.1"
+version = "2.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
-dependencies = [
- "autocfg",
-]
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
 
 [[package]]
 name = "mimalloc-sys"
@@ -785,6 +801,15 @@
 ]
 
 [[package]]
+name = "object"
+version = "0.37.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
 name = "once_cell"
 version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -792,11 +817,35 @@
 
 [[package]]
 name = "once_cell_polyfill"
-version = "1.70.1"
+version = "1.70.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "ouroboros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
 
 [[package]]
+name = "ouroboros_macro"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "pathdiff"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -840,27 +889,41 @@
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.101"
+version = "1.0.106"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+ "yansi",
+]
+
+[[package]]
 name = "psm"
-version = "0.1.26"
+version = "0.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
+checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
 dependencies = [
+ "ar_archive_writer",
  "cc",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.40"
+version = "1.0.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
 dependencies = [
  "proc-macro2",
 ]
@@ -873,20 +936,19 @@
 
 [[package]]
 name = "rand"
-version = "0.8.5"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
 dependencies = [
- "libc",
  "rand_chacha",
  "rand_core",
 ]
 
 [[package]]
 name = "rand_chacha"
-version = "0.3.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
 dependencies = [
  "ppv-lite86",
  "rand_core",
@@ -894,18 +956,18 @@
 
 [[package]]
 name = "rand_core"
-version = "0.6.4"
+version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
 dependencies = [
- "getrandom 0.2.16",
+ "getrandom",
 ]
 
 [[package]]
 name = "random_color"
-version = "0.8.0"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0085421bc527effa7ed6d46bac0a28734663c47abe03d80a5e78e441fad85196"
+checksum = "d635c5e80ae160390ac62ca027d2d06c94c1dc69e5c0a12f1e3a53664dc84966"
 dependencies = [
  "rand",
 ]
@@ -921,9 +983,9 @@
 
 [[package]]
 name = "regex"
-version = "1.11.1"
+version = "1.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -933,9 +995,9 @@
 
 [[package]]
 name = "regex-automata"
-version = "0.4.9"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -944,20 +1006,19 @@
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.5"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
 
 [[package]]
 name = "rowan"
-version = "0.15.17"
+version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b"
+checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
 dependencies = [
  "countme",
  "hashbrown 0.14.5",
- "memoffset",
- "rustc-hash",
+ "rustc-hash 1.1.0",
  "text-size",
 ]
 
@@ -968,38 +1029,60 @@
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
 name = "rustix"
-version = "1.0.8"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
 dependencies = [
  "bitflags",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
 name = "ryu"
-version = "1.0.20"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
 
 [[package]]
 name = "serde"
-version = "1.0.219"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
 dependencies = [
+ "serde_core",
  "serde_derive",
 ]
 
 [[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
 name = "serde_derive"
-version = "1.0.219"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1008,26 +1091,29 @@
 
 [[package]]
 name = "serde_json"
-version = "1.0.143"
+version = "1.0.149"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
 dependencies = [
+ "indexmap",
  "itoa",
  "memchr",
- "ryu",
  "serde",
+ "serde_core",
+ "zmij",
 ]
 
 [[package]]
 name = "serde_yaml_with_quirks"
-version = "0.8.24"
+version = "0.9.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47c5983eba86eae2d0058c35fb1065ccffb23af7f8965871069269088098321a"
+checksum = "d852180e55e824bb347a8e3cdbbca1f02513ea1fa00188f1b2a8a255ac3d6cf9"
 dependencies = [
- "indexmap 1.9.3",
+ "indexmap",
+ "itoa",
  "ryu",
  "serde",
- "yaml-rust",
+ "unsafe-libyaml",
 ]
 
 [[package]]
@@ -1082,9 +1168,9 @@
 
 [[package]]
 name = "stacker"
-version = "0.1.21"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
+checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
 dependencies = [
  "cc",
  "cfg-if",
@@ -1100,6 +1186,18 @@
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
 [[package]]
+name = "str_indices"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6"
+
+[[package]]
+name = "streaming-iterator"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+
+[[package]]
 name = "strsim"
 version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1107,9 +1205,9 @@
 
 [[package]]
 name = "syn"
-version = "2.0.106"
+version = "2.0.114"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1129,15 +1227,15 @@
 
 [[package]]
 name = "tempfile"
-version = "3.21.0"
+version = "3.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
 dependencies = [
  "fastrand",
- "getrandom 0.3.3",
+ "getrandom",
  "once_cell",
  "rustix",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
@@ -1160,18 +1258,18 @@
 
 [[package]]
 name = "thiserror"
-version = "1.0.69"
+version = "2.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.69"
+version = "2.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1179,10 +1277,42 @@
 ]
 
 [[package]]
+name = "tree-sitter"
+version = "0.26.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12987371f54efc9b9306a20dc87ed5aaee9f320c8a8b115e28515c412b2efe39"
+dependencies = [
+ "cc",
+ "regex",
+ "regex-syntax",
+ "serde_json",
+ "streaming-iterator",
+ "tree-sitter-language",
+]
+
+[[package]]
+name = "tree-sitter-highlight"
+version = "0.26.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b688407049ea1b55a7e872f138947d22389118b9c4d09b459cb34ca205e41c0"
+dependencies = [
+ "regex",
+ "streaming-iterator",
+ "thiserror",
+ "tree-sitter",
+]
+
+[[package]]
+name = "tree-sitter-language"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782"
+
+[[package]]
 name = "typenum"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
 
 [[package]]
 name = "ungrammar"
@@ -1191,16 +1321,28 @@
 checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f"
 
 [[package]]
+name = "unicode-box-drawing"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a1f97719cf40224391201fc11e7f5b0cc0ba21416367cfc914e2d45af4e42ef"
+
+[[package]]
 name = "unicode-ident"
-version = "1.0.18"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.14"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
 
 [[package]]
 name = "utf8parse"
@@ -1215,25 +1357,19 @@
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
 [[package]]
-name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "wasi"
-version = "0.14.2+wasi-0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
 dependencies = [
- "wit-bindgen-rt",
+ "wit-bindgen",
 ]
 
 [[package]]
 name = "windows-link"
-version = "0.1.3"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
 name = "windows-sys"
@@ -1241,16 +1377,16 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
 dependencies = [
- "windows-targets 0.52.6",
+ "windows-targets",
 ]
 
 [[package]]
 name = "windows-sys"
-version = "0.60.2"
+version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
 dependencies = [
- "windows-targets 0.53.3",
+ "windows-link",
 ]
 
 [[package]]
@@ -1259,31 +1395,14 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.53.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
-dependencies = [
- "windows-link",
- "windows_aarch64_gnullvm 0.53.0",
- "windows_aarch64_msvc 0.53.0",
- "windows_i686_gnu 0.53.0",
- "windows_i686_gnullvm 0.53.0",
- "windows_i686_msvc 0.53.0",
- "windows_x86_64_gnu 0.53.0",
- "windows_x86_64_gnullvm 0.53.0",
- "windows_x86_64_msvc 0.53.0",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
 ]
 
 [[package]]
@@ -1293,58 +1412,28 @@
 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
-
-[[package]]
 name = "windows_aarch64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
-
-[[package]]
 name = "windows_i686_gnu"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
-name = "windows_i686_gnu"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
-
-[[package]]
 name = "windows_i686_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
-
-[[package]]
 name = "windows_i686_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -1353,43 +1442,22 @@
 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
-
-[[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
 
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
 
 [[package]]
-name = "wit-bindgen-rt"
-version = "0.39.0"
+name = "wit-bindgen"
+version = "0.51.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
-dependencies = [
- "bitflags",
-]
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
 
 [[package]]
 name = "xshell"
@@ -1412,7 +1480,7 @@
 dependencies = [
  "anyhow",
  "clap",
- "indexmap 2.10.0",
+ "indexmap",
  "itertools",
  "proc-macro2",
  "quote",
@@ -1421,30 +1489,33 @@
 ]
 
 [[package]]
-name = "yaml-rust"
-version = "0.4.5"
+name = "yansi"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
-dependencies = [
- "linked-hash-map",
-]
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
 
 [[package]]
 name = "zerocopy"
-version = "0.8.26"
+version = "0.8.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.26"
+version = "0.8.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn",
 ]
+
+[[package]]
+name = "zmij"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
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
before · crates/jrsonnet-macros/src/lib.rs
1use std::string::String;23use proc_macro2::TokenStream;4use quote::{quote, quote_spanned};5use syn::{6	parenthesized,7	parse::{Parse, ParseStream},8	parse_macro_input,9	punctuated::Punctuated,10	spanned::Spanned,11	token::{self, Comma},12	Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,13	LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18	Ident: PartialEq<I>,19{20	let attrs = attrs21		.iter()22		.filter(|a| a.path().is_ident(&ident))23		.collect::<Vec<_>>();24	if attrs.len() > 1 {25		return Err(Error::new(26			attrs[1].span(),27			"this attribute may be specified only once",28		));29	} else if attrs.is_empty() {30		return Ok(None);31	}32	let attr = attrs[0];33	let attr = attr.parse_args::<A>()?;3435	Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39	Ident: PartialEq<I>,40{41	attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45	path.leading_colon.is_none()46		&& !path.segments.is_empty()47		&& path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51	match ty {52		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53			let args = &path.path.segments.iter().last().unwrap().arguments;54			Some(args)55		}56		_ => None,57	}58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61	let Some(args) = type_is_path(ty, "Option") else {62		return Ok(None);63	};64	// It should have only on angle-bracketed param ("<String>"):65	let PathArguments::AngleBracketed(params) = args else {66		return Err(Error::new(args.span(), "missing option generic"));67	};68	let generic_arg = params.args.iter().next().unwrap();69	// This argument must be a type:70	let GenericArgument::Type(ty) = generic_arg else {71		return Err(Error::new(72			generic_arg.span(),73			"option generic should be a type",74		));75	};76	Ok(Some(ty))77}7879struct Field {80	attrs: Vec<Attribute>,81	name: Ident,82	_colon: Token![:],83	ty: Type,84}85impl Parse for Field {86	fn parse(input: ParseStream) -> syn::Result<Self> {87		Ok(Self {88			attrs: input.call(Attribute::parse_outer)?,89			name: input.parse()?,90			_colon: input.parse()?,91			ty: input.parse()?,92		})93	}94}9596mod kw {97	syn::custom_keyword!(fields);98	syn::custom_keyword!(rename);99	syn::custom_keyword!(alias);100	syn::custom_keyword!(flatten);101	syn::custom_keyword!(add);102	syn::custom_keyword!(hide);103	syn::custom_keyword!(ok);104}105106struct EmptyAttr;107impl Parse for EmptyAttr {108	fn parse(_input: ParseStream) -> Result<Self> {109		Ok(Self)110	}111}112113struct BuiltinAttrs {114	fields: Vec<Field>,115}116impl Parse for BuiltinAttrs {117	fn parse(input: ParseStream) -> syn::Result<Self> {118		if input.is_empty() {119			return Ok(Self { fields: Vec::new() });120		}121		input.parse::<kw::fields>()?;122		let fields;123		parenthesized!(fields in input);124		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;125		Ok(Self {126			fields: p.into_iter().collect(),127		})128	}129}130131enum Optionality {132	Required,133	Optional,134	Default(Expr),135}136137enum ArgInfo {138	Normal {139		ty: Box<Type>,140		optionality: Optionality,141		name: Option<String>,142		cfg_attrs: Vec<Attribute>,143	},144	Lazy {145		is_option: bool,146		name: Option<String>,147	},148	Context,149	Location,150	This,151}152153impl ArgInfo {154	fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {155		let FnArg::Typed(arg) = arg else {156			unreachable!()157		};158		let ident = match &arg.pat as &Pat {159			Pat::Ident(i) => Some(i.ident.clone()),160			_ => None,161		};162		let ty = &arg.ty;163		if type_is_path(ty, "Context").is_some() {164			return Ok(Self::Context);165		} else if type_is_path(ty, "CallLocation").is_some() {166			return Ok(Self::Location);167		} else if type_is_path(ty, "Thunk").is_some() {168			return Ok(Self::Lazy {169				is_option: false,170				name: ident.map(|v| v.to_string()),171			});172		}173174		match ty as &Type {175			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),176			_ => {}177		}178179		let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {180			remove_attr(&mut arg.attrs, "default");181			(Optionality::Default(default), ty.clone())182		} else if let Some(ty) = extract_type_from_option(ty)? {183			if type_is_path(ty, "Thunk").is_some() {184				return Ok(Self::Lazy {185					is_option: true,186					name: ident.map(|v| v.to_string()),187				});188			}189190			(Optionality::Optional, Box::new(ty.clone()))191		} else {192			(Optionality::Required, ty.clone())193		};194195		let cfg_attrs = arg196			.attrs197			.iter()198			.filter(|a| a.path().is_ident("cfg"))199			.cloned()200			.collect();201202		Ok(Self::Normal {203			ty,204			optionality,205			name: ident.map(|v| v.to_string()),206			cfg_attrs,207		})208	}209}210211#[proc_macro_attribute]212pub fn builtin(213	attr: proc_macro::TokenStream,214	item: proc_macro::TokenStream,215) -> proc_macro::TokenStream {216	let attr = parse_macro_input!(attr as BuiltinAttrs);217	let item_fn = parse_macro_input!(item as ItemFn);218219	match builtin_inner(attr, item_fn) {220		Ok(v) => v.into(),221		Err(e) => e.into_compile_error().into(),222	}223}224225#[allow(clippy::too_many_lines)]226fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {227	let ReturnType::Type(_, result) = &fun.sig.output else {228		return Err(Error::new(229			fun.sig.span(),230			"builtin should return something",231		));232	};233234	let name = fun.sig.ident.to_string();235	let args = fun236		.sig237		.inputs238		.iter_mut()239		.map(|arg| ArgInfo::parse(&name, arg))240		.collect::<Result<Vec<_>>>()?;241242	let params_desc = args.iter().filter_map(|a| match a {243		ArgInfo::Normal {244			optionality,245			name,246			cfg_attrs,247			..248		} => {249			let name = name250				.as_ref()251				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});252			let default = match optionality {253				Optionality::Required => quote!(ParamDefault::None),254				Optionality::Optional => quote!(ParamDefault::Exists),255				Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),256			};257			Some(quote! {258				#(#cfg_attrs)*259				BuiltinParam::new(#name, #default),260			})261		}262		ArgInfo::Lazy { is_option, name } => {263			let name = name264				.as_ref()265				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});266			Some(quote! {267				BuiltinParam::new(#name, ParamDefault::exists(#is_option)),268			})269		}270		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,271	});272273	let mut id = 0usize;274	let pass = args275		.iter()276		.map(|a| match a {277			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {278				let cid = id;279				id += 1;280				(quote! {#cid}, a)281			}282			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {283				(quote! {compile_error!("should not use id")}, a)284			}285		})286		.map(|(id, a)| match a {287			ArgInfo::Normal {288				ty,289				optionality,290				name,291				cfg_attrs,292			} => {293				let name = name.as_ref().map_or("<unnamed>", String::as_str);294				let eval = quote! {jrsonnet_evaluator::in_description_frame(295					|| format!("argument <{}> evaluation", #name),296					|| <#ty>::from_untyped(value.evaluate()?),297				)?};298				let value = match optionality {299					Optionality::Required => quote! {{300						let value = parsed[#id].as_ref().expect("args shape is checked");301						#eval302					},},303					Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {304						Some(#eval)305					} else {306						None307					},},308					Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {309						#eval310					} else {311						let v: #ty = #expr;312						v313					},},314				};315				quote! {316					#(#cfg_attrs)*317					#value318				}319			}320			ArgInfo::Lazy { is_option, .. } => {321				if *is_option {322					quote! {if let Some(value) = &parsed[#id] {323						Some(value.clone())324					} else {325						None326					},}327				} else {328					quote! {329						parsed[#id].as_ref().expect("args shape is correct").clone(),330					}331				}332			}333			ArgInfo::Context => quote! {ctx.clone(),},334			ArgInfo::Location => quote! {location,},335			ArgInfo::This => quote! {self,},336		});337338	let fields = attr.fields.iter().map(|field| {339		let attrs = &field.attrs;340		let name = &field.name;341		let ty = &field.ty;342		quote! {343			#(#attrs)*344			pub #name: #ty,345		}346	});347348	let name = &fun.sig.ident;349	let vis = &fun.vis;350	let static_ext = if attr.fields.is_empty() {351		quote! {352			impl #name {353				pub const INST: &'static dyn StaticBuiltin = &#name {};354			}355			impl StaticBuiltin for #name {}356		}357	} else {358		quote! {}359	};360	let static_derive_copy = if attr.fields.is_empty() {361		quote! {, Copy}362	} else {363		quote! {}364	};365366	Ok(quote! {367		#fun368369		#[doc(hidden)]370		#[allow(non_camel_case_types)]371		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]372		#vis struct #name {373			#(#fields)*374		}375		const _: () = {376			use ::jrsonnet_evaluator::{377				State, Val,378				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call},379				Result, Context, typed::Typed,380				parser::Span,381			};382			const PARAMS: &'static [BuiltinParam] = &[383				#(#params_desc)*384			];385386			#static_ext387			impl Builtin for #name388			where389				Self: 'static390			{391				fn name(&self) -> &str {392					stringify!(#name)393				}394				fn params(&self) -> &[BuiltinParam] {395					PARAMS396				}397				#[allow(unused_variables)]398				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {399					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;400401					let result: #result = #name(#(#pass)*);402					<_ as Typed>::into_result(result)403				}404				fn as_any(&self) -> &dyn ::std::any::Any {405					self406				}407			}408		};409	})410}411412#[derive(Default)]413#[allow(clippy::struct_excessive_bools)]414struct TypedAttr {415	rename: Option<String>,416	aliases: Vec<String>,417	flatten: bool,418	/// flatten(ok) strategy for flattened optionals419	/// field would be None in case of any parsing error (as in serde)420	flatten_ok: bool,421	// Should it be `field+:` instead of `field:`422	add: bool,423	// Should it be `field::` instead of `field:`424	hide: bool,425}426impl Parse for TypedAttr {427	fn parse(input: ParseStream) -> syn::Result<Self> {428		let mut out = Self::default();429		loop {430			let lookahead = input.lookahead1();431			if lookahead.peek(kw::rename) {432				input.parse::<kw::rename>()?;433				input.parse::<Token![=]>()?;434				let name = input.parse::<LitStr>()?;435				if out.rename.is_some() {436					return Err(Error::new(437						name.span(),438						"rename attribute may only be specified once",439					));440				}441				out.rename = Some(name.value());442			} else if lookahead.peek(kw::alias) {443				input.parse::<kw::alias>()?;444				input.parse::<Token![=]>()?;445				let alias = input.parse::<LitStr>()?;446				out.aliases.push(alias.value());447			} else if lookahead.peek(kw::flatten) {448				input.parse::<kw::flatten>()?;449				out.flatten = true;450				if input.peek(token::Paren) {451					let content;452					parenthesized!(content in input);453					let lookahead = content.lookahead1();454					if lookahead.peek(kw::ok) {455						content.parse::<kw::ok>()?;456						out.flatten_ok = true;457					} else {458						return Err(lookahead.error());459					}460				}461			} else if lookahead.peek(kw::add) {462				input.parse::<kw::add>()?;463				out.add = true;464			} else if lookahead.peek(kw::hide) {465				input.parse::<kw::hide>()?;466				out.hide = true;467			} else if input.is_empty() {468				break;469			} else {470				return Err(lookahead.error());471			}472			if input.peek(Token![,]) {473				input.parse::<Token![,]>()?;474			} else {475				break;476			}477		}478		Ok(out)479	}480}481482struct TypedField {483	attr: TypedAttr,484	ident: Ident,485	ty: Type,486	is_option: bool,487	is_lazy: bool,488}489impl TypedField {490	fn parse(field: &syn::Field) -> Result<Self> {491		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();492		let Some(ident) = field.ident.clone() else {493			return Err(Error::new(494				field.span(),495				"this field should appear in output object, but it has no visible name",496			));497		};498		let (is_option, ty) = extract_type_from_option(&field.ty)?499			.map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));500		if is_option && attr.flatten {501			if !attr.flatten_ok {502				return Err(Error::new(503					field.span(),504					"strategy should be set when flattening Option",505				));506			}507		} else if attr.flatten_ok {508			return Err(Error::new(509				field.span(),510				"flatten(ok) is only useable on optional fields",511			));512		}513514		let is_lazy = type_is_path(&ty, "Thunk").is_some();515516		Ok(Self {517			attr,518			ident,519			ty,520			is_option,521			is_lazy,522		})523	}524	/// None if this field is flattened in jsonnet output525	fn name(&self) -> Option<String> {526		if self.attr.flatten {527			return None;528		}529		Some(530			self.attr531				.rename532				.clone()533				.unwrap_or_else(|| self.ident.to_string()),534		)535	}536537	fn expand_field(&self) -> Option<TokenStream> {538		if self.is_option {539			return None;540		}541		let name = self.name()?;542		let ty = &self.ty;543		Some(quote! {544			(#name, <#ty as Typed>::TYPE)545		})546	}547548	fn expand_parse(&self) -> TokenStream {549		if self.is_option {550			self.expand_parse_optional()551		} else {552			self.expand_parse_mandatory()553		}554	}555556	fn expand_parse_optional(&self) -> TokenStream {557		let ident = &self.ident;558		let ty = &self.ty;559560		// optional flatten is handled in same way as serde561		if self.attr.flatten {562			return quote! {563				#ident: <#ty as TypedObj>::parse(&obj).ok(),564			};565		}566567		let name = self.name().unwrap();568		let aliases = &self.attr.aliases;569570		quote! {571			#ident: {572				let __value = if let Some(__v) = obj.get(#name.into())? {573					Some(__v)574				} #(else if let Some(__v) = obj.get(#aliases.into())? {575					Some(__v)576				})* else {577					None578				};579580				__value.map(<#ty as Typed>::from_untyped).transpose()?581			},582		}583	}584585	fn expand_parse_mandatory(&self) -> TokenStream {586		let ident = &self.ident;587		let ty = &self.ty;588589		// optional flatten is handled in same way as serde590		if self.attr.flatten {591			return quote! {592				#ident: <#ty as TypedObj>::parse(&obj)?,593			};594		}595596		let name = self.name().unwrap();597		let aliases = &self.attr.aliases;598599		let error_text = if aliases.is_empty() {600			// clippy does not understand name variable usage in quote! macro601			#[allow(clippy::redundant_clone)]602			name.clone()603		} else {604			format!("{name} (alias {})", aliases.join(", "))605		};606607		quote! {608			#ident: {609				let __value = if let Some(__v) = obj.get(#name.into())? {610					__v611				} #(else if let Some(__v) = obj.get(#aliases.into())? {612					__v613				})* else {614					return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into());615				};616617				<#ty as Typed>::from_untyped(__value)?618			},619		}620	}621622	fn expand_serialize(&self) -> TokenStream {623		let ident = &self.ident;624		let ty = &self.ty;625		self.name().map_or_else(626			|| {627				if self.is_option {628					quote! {629						if let Some(value) = self.#ident {630							<#ty as TypedObj>::serialize(value, out)?;631						}632					}633				} else {634					quote! {635						<#ty as TypedObj>::serialize(self.#ident, out)?;636					}637				}638			},639			|name| {640				let hide = if self.attr.hide {641					quote! {.hide()}642				} else {643					quote! {}644				};645				let add = if self.attr.add {646					quote! {.add()}647				} else {648					quote! {}649				};650				let value = if self.is_lazy {651					quote! {652						out.field(#name)653							#hide654							#add655							.try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;656					}657				} else {658					quote! {659						out.field(#name)660							#hide661							#add662							.try_value(<#ty as Typed>::into_untyped(value)?)?;663					}664				};665				if self.is_option {666					quote! {667						if let Some(value) = self.#ident {668							#value669						}670					}671				} else {672					quote! {673						{674							let value = self.#ident;675							#value676						}677					}678				}679			},680		)681	}682}683684#[proc_macro_derive(Typed, attributes(typed))]685pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {686	let input = parse_macro_input!(item as DeriveInput);687688	match derive_typed_inner(input) {689		Ok(v) => v.into(),690		Err(e) => e.to_compile_error().into(),691	}692}693694fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {695	let syn::Data::Struct(data) = &input.data else {696		return Err(Error::new(input.span(), "only structs supported"));697	};698699	let ident = &input.ident;700	let fields = data701		.fields702		.iter()703		.map(TypedField::parse)704		.collect::<Result<Vec<_>>>()?;705706	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();707708	let typed = {709		let fields = fields710			.iter()711			.filter_map(TypedField::expand_field)712			.collect::<Vec<_>>();713		quote! {714			impl #impl_generics Typed for #ident #ty_generics #where_clause {715				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[716					#(#fields,)*717				]);718719				fn from_untyped(value: Val) -> JrResult<Self> {720					let obj = value.as_obj().expect("shape is correct");721					Self::parse(&obj)722				}723724				fn into_untyped(value: Self) -> JrResult<Val> {725					let mut out = ObjValueBuilder::new();726					value.serialize(&mut out)?;727					Ok(Val::Obj(out.build()))728				}729730			}731		}732	};733734	let fields_parse = fields.iter().map(TypedField::expand_parse);735	let fields_serialize = fields736		.iter()737		.map(TypedField::expand_serialize)738		.collect::<Vec<_>>();739740	Ok(quote! {741		const _: () = {742			use ::jrsonnet_evaluator::{743				typed::{ComplexValType, Typed, TypedObj, CheckType},744				Val, State,745				error::{ErrorKind, Result as JrResult},746				ObjValueBuilder, ObjValue,747			};748749			#typed750751			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {752				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {753					#(#fields_serialize)*754755					Ok(())756				}757				fn parse(obj: &ObjValue) -> JrResult<Self> {758					Ok(Self {759						#(#fields_parse)*760					})761				}762			}763		};764	})765}766767struct FormatInput {768	formatting: LitStr,769	arguments: Vec<Expr>,770}771impl Parse for FormatInput {772	fn parse(input: ParseStream) -> Result<Self> {773		let formatting = input.parse()?;774		let mut arguments = Vec::new();775776		while input.peek(Token![,]) {777			input.parse::<Token![,]>()?;778			if input.is_empty() {779				// Trailing comma780				break;781			}782			let expr = input.parse()?;783			arguments.push(expr);784		}785786		if !input.is_empty() {787			return Err(syn::Error::new(input.span(), "unexpected trailing input"));788		}789790		Ok(Self {791			formatting,792			arguments,793		})794	}795}796fn is_format_str(i: &str) -> bool {797	let mut is_plain = true;798	// -1 = {799	// +1 = }800	let mut is_bracket = 0i8;801	for ele in i.chars() {802		match ele {803			'{' if is_bracket == -1 => {804				is_bracket = 0;805			}806			'}' if is_bracket == -1 => {807				is_plain = false;808				break;809			}810			'}' if is_bracket == 1 => {811				is_bracket = 0;812			}813			'{' if is_bracket == 1 => {814				is_plain = false;815				break;816			}817			'{' => {818				is_bracket = -1;819			}820			'}' => {821				is_bracket = 1;822			}823			_ if is_bracket != 0 => {824				is_plain = false;825				break;826			}827			_ => {}828		}829	}830	!is_plain || is_bracket != 0831}832impl FormatInput {833	fn expand(self) -> TokenStream {834		let format = self.formatting;835		if is_format_str(&format.value()) {836			let args = self.arguments;837			quote! {838				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))839			}840		} else {841			if let Some(first) = self.arguments.first() {842				return syn::Error::new(843					first.span(),844					"string has no formatting codes, it should not have the arguments",845				)846				.into_compile_error();847			}848			quote! {849				::jrsonnet_evaluator::IStr::from(#format)850			}851		}852	}853}854855/// `IStr` formatting helper856///857/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`858/// This macro looks for formatting codes in the input string, and uses859/// `format!()` only when necessary860#[proc_macro]861pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {862	let input = parse_macro_input!(input as FormatInput);863	input.expand().into()864}865866/// Create Thunk using closure syntax867#[proc_macro]868#[allow(non_snake_case)]869pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {870	let input = parse_macro_input!(input as ExprClosure);871872	let span = input.inputs.span();873	let move_check = input.capture.is_none().then(|| {874		quote_spanned! {span => {875			compile_error!("Thunk! needs to be called with move closure");876		}}877	});878879	let (env, closure, args) = syn_dissect_closure::split_env(input);880881	let trace_check = args.iter().map(|el| {882		let span = el.span();883		quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}884	});885886	quote! {{887		#move_check888		#(#trace_check)*889		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))890	}}.into()891}
after · crates/jrsonnet-macros/src/lib.rs
1use std::string::String;23use proc_macro2::TokenStream;4use quote::{quote, quote_spanned};5use syn::{6	parenthesized,7	parse::{Parse, ParseStream},8	parse_macro_input,9	punctuated::Punctuated,10	spanned::Spanned,11	token::{self, Comma},12	Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,13	LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18	Ident: PartialEq<I>,19{20	let attrs = attrs21		.iter()22		.filter(|a| a.path().is_ident(&ident))23		.collect::<Vec<_>>();24	if attrs.len() > 1 {25		return Err(Error::new(26			attrs[1].span(),27			"this attribute may be specified only once",28		));29	} else if attrs.is_empty() {30		return Ok(None);31	}32	let attr = attrs[0];33	let attr = attr.parse_args::<A>()?;3435	Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39	Ident: PartialEq<I>,40{41	attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45	path.leading_colon.is_none()46		&& !path.segments.is_empty()47		&& path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51	match ty {52		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53			let args = &path.path.segments.iter().last().unwrap().arguments;54			Some(args)55		}56		_ => None,57	}58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61	let Some(args) = type_is_path(ty, "Option") else {62		return Ok(None);63	};64	// It should have only on angle-bracketed param ("<String>"):65	let PathArguments::AngleBracketed(params) = args else {66		return Err(Error::new(args.span(), "missing option generic"));67	};68	let generic_arg = params.args.iter().next().unwrap();69	// This argument must be a type:70	let GenericArgument::Type(ty) = generic_arg else {71		return Err(Error::new(72			generic_arg.span(),73			"option generic should be a type",74		));75	};76	Ok(Some(ty))77}7879struct Field {80	attrs: Vec<Attribute>,81	name: Ident,82	_colon: Token![:],83	ty: Type,84}85impl Parse for Field {86	fn parse(input: ParseStream) -> syn::Result<Self> {87		Ok(Self {88			attrs: input.call(Attribute::parse_outer)?,89			name: input.parse()?,90			_colon: input.parse()?,91			ty: input.parse()?,92		})93	}94}9596mod kw {97	syn::custom_keyword!(fields);98	syn::custom_keyword!(rename);99	syn::custom_keyword!(alias);100	syn::custom_keyword!(flatten);101	syn::custom_keyword!(add);102	syn::custom_keyword!(hide);103	syn::custom_keyword!(ok);104}105106struct BuiltinAttrs {107	fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110	fn parse(input: ParseStream) -> syn::Result<Self> {111		if input.is_empty() {112			return Ok(Self { fields: Vec::new() });113		}114		input.parse::<kw::fields>()?;115		let fields;116		parenthesized!(fields in input);117		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118		Ok(Self {119			fields: p.into_iter().collect(),120		})121	}122}123124enum Optionality {125	Required,126	Optional,127	Default(Expr),128}129130enum ArgInfo {131	Normal {132		ty: Box<Type>,133		optionality: Optionality,134		name: Option<String>,135		cfg_attrs: Vec<Attribute>,136	},137	Lazy {138		is_option: bool,139		name: Option<String>,140	},141	Context,142	Location,143	This,144}145146impl ArgInfo {147	fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {148		let FnArg::Typed(arg) = arg else {149			unreachable!()150		};151		let ident = match &arg.pat as &Pat {152			Pat::Ident(i) => Some(i.ident.clone()),153			_ => None,154		};155		let ty = &arg.ty;156		if type_is_path(ty, "Context").is_some() {157			return Ok(Self::Context);158		} else if type_is_path(ty, "CallLocation").is_some() {159			return Ok(Self::Location);160		} else if type_is_path(ty, "Thunk").is_some() {161			return Ok(Self::Lazy {162				is_option: false,163				name: ident.map(|v| v.to_string()),164			});165		}166167		match ty as &Type {168			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),169			_ => {}170		}171172		let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {173			remove_attr(&mut arg.attrs, "default");174			(Optionality::Default(default), ty.clone())175		} else if let Some(ty) = extract_type_from_option(ty)? {176			if type_is_path(ty, "Thunk").is_some() {177				return Ok(Self::Lazy {178					is_option: true,179					name: ident.map(|v| v.to_string()),180				});181			}182183			(Optionality::Optional, Box::new(ty.clone()))184		} else {185			(Optionality::Required, ty.clone())186		};187188		let cfg_attrs = arg189			.attrs190			.iter()191			.filter(|a| a.path().is_ident("cfg"))192			.cloned()193			.collect();194195		Ok(Self::Normal {196			ty,197			optionality,198			name: ident.map(|v| v.to_string()),199			cfg_attrs,200		})201	}202}203204#[proc_macro_attribute]205pub fn builtin(206	attr: proc_macro::TokenStream,207	item: proc_macro::TokenStream,208) -> proc_macro::TokenStream {209	let attr = parse_macro_input!(attr as BuiltinAttrs);210	let item_fn = parse_macro_input!(item as ItemFn);211212	match builtin_inner(attr, item_fn) {213		Ok(v) => v.into(),214		Err(e) => e.into_compile_error().into(),215	}216}217218#[allow(clippy::too_many_lines)]219fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {220	let ReturnType::Type(_, result) = &fun.sig.output else {221		return Err(Error::new(222			fun.sig.span(),223			"builtin should return something",224		));225	};226227	let name = fun.sig.ident.to_string();228	let args = fun229		.sig230		.inputs231		.iter_mut()232		.map(|arg| ArgInfo::parse(&name, arg))233		.collect::<Result<Vec<_>>>()?;234235	let params_desc = args.iter().filter_map(|a| match a {236		ArgInfo::Normal {237			optionality,238			name,239			cfg_attrs,240			..241		} => {242			let name = name243				.as_ref()244				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});245			let default = match optionality {246				Optionality::Required => quote!(ParamDefault::None),247				Optionality::Optional => quote!(ParamDefault::Exists),248				Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),249			};250			Some(quote! {251				#(#cfg_attrs)*252				BuiltinParam::new(#name, #default),253			})254		}255		ArgInfo::Lazy { is_option, name } => {256			let name = name257				.as_ref()258				.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});259			Some(quote! {260				BuiltinParam::new(#name, ParamDefault::exists(#is_option)),261			})262		}263		ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,264	});265266	let mut id = 0usize;267	let pass = args268		.iter()269		.map(|a| match a {270			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {271				let cid = id;272				id += 1;273				(quote! {#cid}, a)274			}275			ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {276				(quote! {compile_error!("should not use id")}, a)277			}278		})279		.map(|(id, a)| match a {280			ArgInfo::Normal {281				ty,282				optionality,283				name,284				cfg_attrs,285			} => {286				let name = name.as_ref().map_or("<unnamed>", String::as_str);287				let eval = quote! {jrsonnet_evaluator::in_description_frame(288					|| format!("argument <{}> evaluation", #name),289					|| <#ty>::from_untyped(value.evaluate()?),290				)?};291				let value = match optionality {292					Optionality::Required => quote! {{293						let value = parsed[#id].as_ref().expect("args shape is checked");294						#eval295					},},296					Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {297						Some(#eval)298					} else {299						None300					},},301					Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {302						#eval303					} else {304						let v: #ty = #expr;305						v306					},},307				};308				quote! {309					#(#cfg_attrs)*310					#value311				}312			}313			ArgInfo::Lazy { is_option, .. } => {314				if *is_option {315					quote! {if let Some(value) = &parsed[#id] {316						Some(value.clone())317					} else {318						None319					},}320				} else {321					quote! {322						parsed[#id].as_ref().expect("args shape is correct").clone(),323					}324				}325			}326			ArgInfo::Context => quote! {ctx.clone(),},327			ArgInfo::Location => quote! {location,},328			ArgInfo::This => quote! {self,},329		});330331	let fields = attr.fields.iter().map(|field| {332		let attrs = &field.attrs;333		let name = &field.name;334		let ty = &field.ty;335		quote! {336			#(#attrs)*337			pub #name: #ty,338		}339	});340341	let name = &fun.sig.ident;342	let vis = &fun.vis;343	let static_ext = if attr.fields.is_empty() {344		quote! {345			impl #name {346				pub const INST: &'static dyn StaticBuiltin = &#name {};347			}348			impl StaticBuiltin for #name {}349		}350	} else {351		quote! {}352	};353	let static_derive_copy = if attr.fields.is_empty() {354		quote! {, Copy}355	} else {356		quote! {}357	};358359	Ok(quote! {360		#fun361362		#[doc(hidden)]363		#[allow(non_camel_case_types)]364		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]365		#vis struct #name {366			#(#fields)*367		}368		const _: () = {369			use ::jrsonnet_evaluator::{370				State, Val,371				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call},372				Result, Context, typed::Typed,373				parser::Span,374			};375			const PARAMS: &'static [BuiltinParam] = &[376				#(#params_desc)*377			];378379			#static_ext380			impl Builtin for #name381			where382				Self: 'static383			{384				fn name(&self) -> &str {385					stringify!(#name)386				}387				fn params(&self) -> &[BuiltinParam] {388					PARAMS389				}390				#[allow(unused_variables)]391				fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {392					let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;393394					let result: #result = #name(#(#pass)*);395					<_ as Typed>::into_result(result)396				}397				fn as_any(&self) -> &dyn ::std::any::Any {398					self399				}400			}401		};402	})403}404405#[derive(Default)]406#[allow(clippy::struct_excessive_bools)]407struct TypedAttr {408	rename: Option<String>,409	aliases: Vec<String>,410	flatten: bool,411	/// flatten(ok) strategy for flattened optionals412	/// field would be None in case of any parsing error (as in serde)413	flatten_ok: bool,414	// Should it be `field+:` instead of `field:`415	add: bool,416	// Should it be `field::` instead of `field:`417	hide: bool,418}419impl Parse for TypedAttr {420	fn parse(input: ParseStream) -> syn::Result<Self> {421		let mut out = Self::default();422		loop {423			let lookahead = input.lookahead1();424			if lookahead.peek(kw::rename) {425				input.parse::<kw::rename>()?;426				input.parse::<Token![=]>()?;427				let name = input.parse::<LitStr>()?;428				if out.rename.is_some() {429					return Err(Error::new(430						name.span(),431						"rename attribute may only be specified once",432					));433				}434				out.rename = Some(name.value());435			} else if lookahead.peek(kw::alias) {436				input.parse::<kw::alias>()?;437				input.parse::<Token![=]>()?;438				let alias = input.parse::<LitStr>()?;439				out.aliases.push(alias.value());440			} else if lookahead.peek(kw::flatten) {441				input.parse::<kw::flatten>()?;442				out.flatten = true;443				if input.peek(token::Paren) {444					let content;445					parenthesized!(content in input);446					let lookahead = content.lookahead1();447					if lookahead.peek(kw::ok) {448						content.parse::<kw::ok>()?;449						out.flatten_ok = true;450					} else {451						return Err(lookahead.error());452					}453				}454			} else if lookahead.peek(kw::add) {455				input.parse::<kw::add>()?;456				out.add = true;457			} else if lookahead.peek(kw::hide) {458				input.parse::<kw::hide>()?;459				out.hide = true;460			} else if input.is_empty() {461				break;462			} else {463				return Err(lookahead.error());464			}465			if input.peek(Token![,]) {466				input.parse::<Token![,]>()?;467			} else {468				break;469			}470		}471		Ok(out)472	}473}474475struct TypedField {476	attr: TypedAttr,477	ident: Ident,478	ty: Type,479	is_option: bool,480	is_lazy: bool,481}482impl TypedField {483	fn parse(field: &syn::Field) -> Result<Self> {484		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();485		let Some(ident) = field.ident.clone() else {486			return Err(Error::new(487				field.span(),488				"this field should appear in output object, but it has no visible name",489			));490		};491		let (is_option, ty) = extract_type_from_option(&field.ty)?492			.map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));493		if is_option && attr.flatten {494			if !attr.flatten_ok {495				return Err(Error::new(496					field.span(),497					"strategy should be set when flattening Option",498				));499			}500		} else if attr.flatten_ok {501			return Err(Error::new(502				field.span(),503				"flatten(ok) is only useable on optional fields",504			));505		}506507		let is_lazy = type_is_path(&ty, "Thunk").is_some();508509		Ok(Self {510			attr,511			ident,512			ty,513			is_option,514			is_lazy,515		})516	}517	/// None if this field is flattened in jsonnet output518	fn name(&self) -> Option<String> {519		if self.attr.flatten {520			return None;521		}522		Some(523			self.attr524				.rename525				.clone()526				.unwrap_or_else(|| self.ident.to_string()),527		)528	}529530	fn expand_field(&self) -> Option<TokenStream> {531		if self.is_option {532			return None;533		}534		let name = self.name()?;535		let ty = &self.ty;536		Some(quote! {537			(#name, <#ty as Typed>::TYPE)538		})539	}540541	fn expand_parse(&self) -> TokenStream {542		if self.is_option {543			self.expand_parse_optional()544		} else {545			self.expand_parse_mandatory()546		}547	}548549	fn expand_parse_optional(&self) -> TokenStream {550		let ident = &self.ident;551		let ty = &self.ty;552553		// optional flatten is handled in same way as serde554		if self.attr.flatten {555			return quote! {556				#ident: <#ty as TypedObj>::parse(&obj).ok(),557			};558		}559560		let name = self.name().unwrap();561		let aliases = &self.attr.aliases;562563		quote! {564			#ident: {565				let __value = if let Some(__v) = obj.get(#name.into())? {566					Some(__v)567				} #(else if let Some(__v) = obj.get(#aliases.into())? {568					Some(__v)569				})* else {570					None571				};572573				__value.map(<#ty as Typed>::from_untyped).transpose()?574			},575		}576	}577578	fn expand_parse_mandatory(&self) -> TokenStream {579		let ident = &self.ident;580		let ty = &self.ty;581582		// optional flatten is handled in same way as serde583		if self.attr.flatten {584			return quote! {585				#ident: <#ty as TypedObj>::parse(&obj)?,586			};587		}588589		let name = self.name().unwrap();590		let aliases = &self.attr.aliases;591592		let error_text = if aliases.is_empty() {593			// clippy does not understand name variable usage in quote! macro594			#[allow(clippy::redundant_clone)]595			name.clone()596		} else {597			format!("{name} (alias {})", aliases.join(", "))598		};599600		quote! {601			#ident: {602				let __value = if let Some(__v) = obj.get(#name.into())? {603					__v604				} #(else if let Some(__v) = obj.get(#aliases.into())? {605					__v606				})* else {607					return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into());608				};609610				<#ty as Typed>::from_untyped(__value)?611			},612		}613	}614615	fn expand_serialize(&self) -> TokenStream {616		let ident = &self.ident;617		let ty = &self.ty;618		self.name().map_or_else(619			|| {620				if self.is_option {621					quote! {622						if let Some(value) = self.#ident {623							<#ty as TypedObj>::serialize(value, out)?;624						}625					}626				} else {627					quote! {628						<#ty as TypedObj>::serialize(self.#ident, out)?;629					}630				}631			},632			|name| {633				let hide = if self.attr.hide {634					quote! {.hide()}635				} else {636					quote! {}637				};638				let add = if self.attr.add {639					quote! {.add()}640				} else {641					quote! {}642				};643				let value = if self.is_lazy {644					quote! {645						out.field(#name)646							#hide647							#add648							.try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;649					}650				} else {651					quote! {652						out.field(#name)653							#hide654							#add655							.try_value(<#ty as Typed>::into_untyped(value)?)?;656					}657				};658				if self.is_option {659					quote! {660						if let Some(value) = self.#ident {661							#value662						}663					}664				} else {665					quote! {666						{667							let value = self.#ident;668							#value669						}670					}671				}672			},673		)674	}675}676677#[proc_macro_derive(Typed, attributes(typed))]678pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {679	let input = parse_macro_input!(item as DeriveInput);680681	match derive_typed_inner(input) {682		Ok(v) => v.into(),683		Err(e) => e.to_compile_error().into(),684	}685}686687fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {688	let syn::Data::Struct(data) = &input.data else {689		return Err(Error::new(input.span(), "only structs supported"));690	};691692	let ident = &input.ident;693	let fields = data694		.fields695		.iter()696		.map(TypedField::parse)697		.collect::<Result<Vec<_>>>()?;698699	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();700701	let typed = {702		let fields = fields703			.iter()704			.filter_map(TypedField::expand_field)705			.collect::<Vec<_>>();706		quote! {707			impl #impl_generics Typed for #ident #ty_generics #where_clause {708				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[709					#(#fields,)*710				]);711712				fn from_untyped(value: Val) -> JrResult<Self> {713					let obj = value.as_obj().expect("shape is correct");714					Self::parse(&obj)715				}716717				fn into_untyped(value: Self) -> JrResult<Val> {718					let mut out = ObjValueBuilder::new();719					value.serialize(&mut out)?;720					Ok(Val::Obj(out.build()))721				}722723			}724		}725	};726727	let fields_parse = fields.iter().map(TypedField::expand_parse);728	let fields_serialize = fields729		.iter()730		.map(TypedField::expand_serialize)731		.collect::<Vec<_>>();732733	Ok(quote! {734		const _: () = {735			use ::jrsonnet_evaluator::{736				typed::{ComplexValType, Typed, TypedObj, CheckType},737				Val, State,738				error::{ErrorKind, Result as JrResult},739				ObjValueBuilder, ObjValue,740			};741742			#typed743744			impl #impl_generics TypedObj for #ident #ty_generics #where_clause {745				fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {746					#(#fields_serialize)*747748					Ok(())749				}750				fn parse(obj: &ObjValue) -> JrResult<Self> {751					Ok(Self {752						#(#fields_parse)*753					})754				}755			}756		};757	})758}759760struct FormatInput {761	formatting: LitStr,762	arguments: Vec<Expr>,763}764impl Parse for FormatInput {765	fn parse(input: ParseStream) -> Result<Self> {766		let formatting = input.parse()?;767		let mut arguments = Vec::new();768769		while input.peek(Token![,]) {770			input.parse::<Token![,]>()?;771			if input.is_empty() {772				// Trailing comma773				break;774			}775			let expr = input.parse()?;776			arguments.push(expr);777		}778779		if !input.is_empty() {780			return Err(syn::Error::new(input.span(), "unexpected trailing input"));781		}782783		Ok(Self {784			formatting,785			arguments,786		})787	}788}789fn is_format_str(i: &str) -> bool {790	let mut is_plain = true;791	// -1 = {792	// +1 = }793	let mut is_bracket = 0i8;794	for ele in i.chars() {795		match ele {796			'{' if is_bracket == -1 => {797				is_bracket = 0;798			}799			'}' if is_bracket == -1 => {800				is_plain = false;801				break;802			}803			'}' if is_bracket == 1 => {804				is_bracket = 0;805			}806			'{' if is_bracket == 1 => {807				is_plain = false;808				break;809			}810			'{' => {811				is_bracket = -1;812			}813			'}' => {814				is_bracket = 1;815			}816			_ if is_bracket != 0 => {817				is_plain = false;818				break;819			}820			_ => {}821		}822	}823	!is_plain || is_bracket != 0824}825impl FormatInput {826	fn expand(self) -> TokenStream {827		let format = self.formatting;828		if is_format_str(&format.value()) {829			let args = self.arguments;830			quote! {831				::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))832			}833		} else {834			if let Some(first) = self.arguments.first() {835				return syn::Error::new(836					first.span(),837					"string has no formatting codes, it should not have the arguments",838				)839				.into_compile_error();840			}841			quote! {842				::jrsonnet_evaluator::IStr::from(#format)843			}844		}845	}846}847848/// `IStr` formatting helper849///850/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`851/// This macro looks for formatting codes in the input string, and uses852/// `format!()` only when necessary853#[proc_macro]854pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {855	let input = parse_macro_input!(input as FormatInput);856	input.expand().into()857}858859/// Create Thunk using closure syntax860#[proc_macro]861#[allow(non_snake_case)]862pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {863	let input = parse_macro_input!(input as ExprClosure);864865	let span = input.inputs.span();866	let move_check = input.capture.is_none().then(|| {867		quote_spanned! {span => {868			compile_error!("Thunk! needs to be called with move closure");869		}}870	});871872	let (env, closure, args) = syn_dissect_closure::split_env(input);873874	let trace_check = args.iter().map(|el| {875		let span = el.span();876		quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}877	});878879	quote! {{880		#move_check881		#(#trace_check)*882		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))883	}}.into()884}
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)* }