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

difftreelog

refactor switch to jrsonnet-gc

Yaroslav Bolyukin2021-07-04parent: #c36aa5c.patch.diff
in: master

27 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -100,27 +100,6 @@
 ]
 
 [[package]]
-name = "gc"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752"
-dependencies = [
- "gc_derive",
-]
-
-[[package]]
-name = "gc_derive"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
-[[package]]
 name = "hashbrown"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -190,7 +169,7 @@
  "anyhow",
  "base64",
  "bincode",
- "gc",
+ "jrsonnet-gc",
  "jrsonnet-interner",
  "jrsonnet-parser",
  "jrsonnet-stdlib",
@@ -204,10 +183,31 @@
 ]
 
 [[package]]
+name = "jrsonnet-gc"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68da8bc2f00117b1373bb8877af03b1d391e4c4800e6585d7279e5b99c919dde"
+dependencies = [
+ "jrsonnet-gc-derive",
+]
+
+[[package]]
+name = "jrsonnet-gc-derive"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcba9c387b64b054f06cc4d724905296e21edeeb7506847f3299117a2d92d12"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
 name = "jrsonnet-interner"
 version = "0.3.8"
 dependencies = [
- "gc",
+ "jrsonnet-gc",
  "rustc-hash",
  "serde",
 ]
@@ -216,7 +216,7 @@
 name = "jrsonnet-parser"
 version = "0.3.8"
 dependencies = [
- "gc",
+ "jrsonnet-gc",
  "jrsonnet-interner",
  "jrsonnet-stdlib",
  "peg",
@@ -232,7 +232,7 @@
 name = "jrsonnet-types"
 version = "0.3.8"
 dependencies = [
- "gc",
+ "jrsonnet-gc",
  "peg",
 ]
 
@@ -240,8 +240,8 @@
 name = "jsonnet"
 version = "0.3.8"
 dependencies = [
- "gc",
  "jrsonnet-evaluator",
+ "jrsonnet-gc",
  "jrsonnet-parser",
 ]
 
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -10,7 +10,7 @@
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.3.8" }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.3.8" }
-gc = { version = "0.4.1", features = ["derive"] }
+jrsonnet-gc = { version = "0.4.2", features = ["derive"] }
 
 [lib]
 crate-type = ["cdylib"]
modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -1,9 +1,9 @@
-use gc::{unsafe_empty_trace, Finalize, Gc, Trace};
 use jrsonnet_evaluator::{
 	error::{Error, LocError},
 	native::{NativeCallback, NativeCallbackHandler},
 	EvaluationState, Val,
 };
+use jrsonnet_gc::{unsafe_empty_trace, Finalize, Gc, Trace};
 use jrsonnet_parser::{Param, ParamsDesc};
 use std::{
 	ffi::{c_void, CStr},
modifiedbindings/jsonnet/src/val_make.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_make.rs
+++ b/bindings/jsonnet/src/val_make.rs
@@ -1,7 +1,7 @@
 //! Create values in VM
 
-use gc::Gc;
 use jrsonnet_evaluator::{ArrValue, EvaluationState, ObjValue, Val};
+use jrsonnet_gc::Gc;
 use std::{
 	ffi::CStr,
 	os::raw::{c_char, c_double, c_int},
modifiedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -2,8 +2,8 @@
 //! Only tested with variables, which haven't altered by code before appearing here
 //! In jrsonnet every value is immutable, and this code is probally broken
 
-use gc::Gc;
 use jrsonnet_evaluator::{ArrValue, EvaluationState, LazyBinding, LazyVal, ObjMember, Val};
+use jrsonnet_gc::Gc;
 use jrsonnet_parser::Visibility;
 use std::{ffi::CStr, os::raw::c_char};
 
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -35,7 +35,7 @@
 rustc-hash = "1.1.0"
 
 thiserror = "1.0"
-gc = { version = "0.4.1", features = ["derive"] }
+jrsonnet-gc = { version = "0.4.2", features = ["derive"] }
 
 [dependencies.anyhow]
 version = "1.0"
modifiedcrates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/builtin/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val};5use gc::{Finalize, Trace};6use jrsonnet_interner::IStr;7use jrsonnet_types::ValType;8use thiserror::Error;910#[derive(Debug, Clone, Error, Trace, Finalize)]11pub enum FormatError {12	#[error("truncated format code")]13	TruncatedFormatCode,14	#[error("unrecognized conversion type: {0}")]15	UnrecognizedConversionType(char),1617	#[error("not enough values")]18	NotEnoughValues,1920	#[error("cannot use * width with object")]21	CannotUseStarWidthWithObject,22	#[error("mapping keys required")]23	MappingKeysRequired,24	#[error("no such format field: {0}")]25	NoSuchFormatField(IStr),26}2728impl From<FormatError> for LocError {29	fn from(e: FormatError) -> Self {30		Self::new(Format(e))31	}32}3334use FormatError::*;3536type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;3738pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {39	if str.is_empty() {40		return Err(TruncatedFormatCode);41	}42	let bytes = str.as_bytes();43	if bytes[0] == b'(' {44		let mut i = 1;45		while i < bytes.len() {46			if bytes[i] == b')' {47				return Ok((&str[1..i as usize], &str[i as usize + 1..]));48			}49			i += 1;50		}51		Err(TruncatedFormatCode)52	} else {53		Ok(("", str))54	}55}5657#[cfg(test)]58pub mod tests_key {59	use super::*;6061	#[test]62	fn parse_key() {63		assert_eq!(64			try_parse_mapping_key("(hello ) world").unwrap(),65			("hello ", " world")66		);67		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));68		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));69		assert_eq!(70			try_parse_mapping_key(" () world").unwrap(),71			("", " () world")72		);73	}7475	#[test]76	#[should_panic]77	fn parse_key_missing_start() {78		try_parse_mapping_key("").unwrap();79	}8081	#[test]82	#[should_panic]83	fn parse_key_missing_end() {84		try_parse_mapping_key("(   ").unwrap();85	}86}8788#[derive(Default, Debug)]89pub struct CFlags {90	pub alt: bool,91	pub zero: bool,92	pub left: bool,93	pub blank: bool,94	pub sign: bool,95}9697pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {98	if str.is_empty() {99		return Err(TruncatedFormatCode);100	}101	let bytes = str.as_bytes();102	let mut i = 0;103	let mut out = CFlags::default();104	loop {105		if bytes.len() == i {106			return Err(TruncatedFormatCode);107		}108		match bytes[i] {109			b'#' => out.alt = true,110			b'0' => out.zero = true,111			b'-' => out.left = true,112			b' ' => out.blank = true,113			b'+' => out.sign = true,114			_ => break,115		}116		i += 1;117	}118	Ok((out, &str[i..]))119}120121#[derive(Debug, PartialEq)]122pub enum Width {123	Star,124	Fixed(usize),125}126pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {127	if str.is_empty() {128		return Err(TruncatedFormatCode);129	}130	let bytes = str.as_bytes();131	if bytes[0] == b'*' {132		return Ok((Width::Star, &str[1..]));133	}134	let mut out: usize = 0;135	let mut digits = 0;136	while let Some(digit) = (bytes[digits] as char).to_digit(10) {137		out *= 10;138		out += digit as usize;139		digits += 1;140		if digits == bytes.len() {141			return Err(TruncatedFormatCode);142		}143	}144	Ok((Width::Fixed(out), &str[digits..]))145}146147pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {148	if str.is_empty() {149		return Err(TruncatedFormatCode);150	}151	let bytes = str.as_bytes();152	if bytes[0] == b'.' {153		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))154	} else {155		Ok((None, str))156	}157}158159// Only skips160pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {161	if str.is_empty() {162		return Err(TruncatedFormatCode);163	}164	let bytes = str.as_bytes();165	let mut idx = 0;166	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {167		idx += 1;168		if bytes.len() == idx {169			return Err(TruncatedFormatCode);170		}171	}172	Ok(((), &str[idx..]))173}174175#[derive(Debug, PartialEq)]176pub enum ConvTypeV {177	Decimal,178	Octal,179	Hexadecimal,180	Scientific,181	Float,182	Shorter,183	Char,184	String,185	Percent,186}187pub struct ConvType {188	v: ConvTypeV,189	caps: bool,190}191192pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {193	if str.is_empty() {194		return Err(TruncatedFormatCode);195	}196197	let code = str.as_bytes()[0];198	let v: (ConvTypeV, bool) = match code {199		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),200		b'o' => (ConvTypeV::Octal, false),201		b'x' => (ConvTypeV::Hexadecimal, false),202		b'X' => (ConvTypeV::Hexadecimal, true),203		b'e' => (ConvTypeV::Scientific, false),204		b'E' => (ConvTypeV::Scientific, true),205		b'f' => (ConvTypeV::Float, false),206		b'F' => (ConvTypeV::Float, true),207		b'g' => (ConvTypeV::Shorter, false),208		b'G' => (ConvTypeV::Shorter, true),209		b'c' => (ConvTypeV::Char, false),210		b's' => (ConvTypeV::String, false),211		b'%' => (ConvTypeV::Percent, false),212		c => return Err(UnrecognizedConversionType(c as char)),213	};214215	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))216}217218#[derive(Debug)]219pub struct Code<'s> {220	mkey: &'s str,221	cflags: CFlags,222	width: Width,223	precision: Option<Width>,224	convtype: ConvTypeV,225	caps: bool,226}227pub fn parse_code(str: &str) -> ParseResult<Code> {228	if str.is_empty() {229		return Err(TruncatedFormatCode);230	}231	let (mkey, str) = try_parse_mapping_key(str)?;232	let (cflags, str) = try_parse_cflags(str)?;233	let (width, str) = try_parse_field_width(str)?;234	let (precision, str) = try_parse_precision(str)?;235	let (_, str) = try_parse_length_modifier(str)?;236	let (convtype, str) = parse_conversion_type(str)?;237238	Ok((239		Code {240			mkey,241			cflags,242			width,243			precision,244			convtype: convtype.v,245			caps: convtype.caps,246		},247		str,248	))249}250251#[derive(Debug)]252pub enum Element<'s> {253	String(&'s str),254	Code(Code<'s>),255}256pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {257	let mut bytes = str.as_bytes();258	let mut out = vec![];259	let mut offset = 0;260261	loop {262		while offset != bytes.len() && bytes[offset] != b'%' {263			offset += 1;264		}265		if offset != 0 {266			out.push(Element::String(&str[0..offset]));267		}268		if offset == bytes.len() {269			return Ok(out);270		}271		str = &str[offset + 1..];272		let (code, nstr) = parse_code(str)?;273		str = nstr;274		bytes = str.as_bytes();275		offset = 0;276277		out.push(Element::Code(code))278	}279}280281const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";282283#[inline]284pub fn render_integer(285	out: &mut String,286	iv: i64,287	padding: usize,288	precision: usize,289	blank: bool,290	sign: bool,291	radix: i64,292	prefix: &str,293	caps: bool,294) {295	// Digit char indexes in reverse order, i.e296	// for radix = 16 and n = 12f: [15, 2, 1]297	let digits = if iv == 0 {298		vec![0u8]299	} else {300		let mut v = iv.abs();301		let mut nums = Vec::with_capacity(1);302		while v > 0 {303			nums.push((v % radix) as u8);304			v /= radix;305		}306		nums307	};308	let neg = iv < 0;309	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });310	let zp2 = zp311		.max(precision)312		.saturating_sub(prefix.len() + digits.len());313314	if neg {315		out.push('-')316	} else if sign {317		out.push('+');318	} else if blank {319		out.push(' ');320	}321322	out.reserve(zp2);323	for _ in 0..zp2 {324		out.push('0');325	}326	out.push_str(prefix);327328	for digit in digits.into_iter().rev() {329		let ch = NUMBERS[digit as usize] as char;330		out.push(if caps { ch.to_ascii_uppercase() } else { ch });331	}332}333334pub fn render_decimal(335	out: &mut String,336	iv: i64,337	padding: usize,338	precision: usize,339	blank: bool,340	sign: bool,341) {342	render_integer(out, iv, padding, precision, blank, sign, 10, "", false)343}344pub fn render_octal(345	out: &mut String,346	iv: i64,347	padding: usize,348	precision: usize,349	alt: bool,350	blank: bool,351	sign: bool,352) {353	render_integer(354		out,355		iv,356		padding,357		precision,358		blank,359		sign,360		8,361		if alt && iv != 0 { "0" } else { "" },362		false,363	)364}365pub fn render_hexadecimal(366	out: &mut String,367	iv: i64,368	padding: usize,369	precision: usize,370	alt: bool,371	blank: bool,372	sign: bool,373	caps: bool,374) {375	render_integer(376		out,377		iv,378		padding,379		precision,380		blank,381		sign,382		16,383		match (alt, caps) {384			(true, true) => "0X",385			(true, false) => "0x",386			(false, _) => "",387		},388		caps,389	)390}391392pub fn render_float(393	out: &mut String,394	n: f64,395	mut padding: usize,396	precision: usize,397	blank: bool,398	sign: bool,399	ensure_pt: bool,400	trailing: bool,401) {402	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };403	padding = padding.saturating_sub(dot_size + precision);404	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);405	if precision == 0 {406		if ensure_pt {407			out.push('.');408		}409		return;410	}411	let frac = n412		.fract()413		.mul_add(10.0_f64.powf(precision as f64), 0.5)414		.floor();415	if trailing || frac > 0.0 {416		out.push('.');417		let mut frac_str = String::new();418		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);419		let mut trim = frac_str.len();420		if !trailing {421			for b in frac_str.as_bytes().iter().rev() {422				if *b == b'0' {423					trim -= 1;424				}425			}426		}427		out.push_str(&frac_str[..trim]);428	} else if ensure_pt {429		out.push('.');430	}431}432433pub fn render_float_sci(434	out: &mut String,435	n: f64,436	mut padding: usize,437	precision: usize,438	blank: bool,439	sign: bool,440	ensure_pt: bool,441	trailing: bool,442	caps: bool,443) {444	let exponent = n.log10().floor();445	let mantissa = if exponent as i16 == -324 {446		n * 10.0 / 10.0_f64.powf(exponent + 1.0)447	} else {448		n / 10.0_f64.powf(exponent)449	};450	let mut exponent_str = String::new();451	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);452453	// +1 for e454	padding = padding.saturating_sub(exponent_str.len() + 1);455456	render_float(457		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,458	);459	out.push(if caps { 'E' } else { 'e' });460	out.push_str(&exponent_str);461}462463pub fn format_code(464	out: &mut String,465	value: &Val,466	code: &Code,467	width: usize,468	precision: Option<usize>,469) -> Result<()> {470	let clfags = &code.cflags;471	let (fpprec, iprec) = match precision {472		Some(v) => (v, v),473		None => (6, 0),474	};475	let padding = if clfags.zero && !clfags.left {476		width477	} else {478		0479	};480481	// TODO: If left padded, can optimize by writing directly to out482	let mut tmp_out = String::new();483484	match code.convtype {485		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),486		ConvTypeV::Decimal => {487			let value = value.clone().try_cast_num("%d/%u/%i requires number")?;488			render_decimal(489				&mut tmp_out,490				value as i64,491				padding,492				iprec,493				clfags.blank,494				clfags.sign,495			);496		}497		ConvTypeV::Octal => {498			let value = value.clone().try_cast_num("%o requires number")?;499			render_octal(500				&mut tmp_out,501				value as i64,502				padding,503				iprec,504				clfags.alt,505				clfags.blank,506				clfags.sign,507			);508		}509		ConvTypeV::Hexadecimal => {510			let value = value.clone().try_cast_num("%x/%X requires number")?;511			render_hexadecimal(512				&mut tmp_out,513				value as i64,514				padding,515				iprec,516				clfags.alt,517				clfags.blank,518				clfags.sign,519				code.caps,520			);521		}522		ConvTypeV::Scientific => {523			let value = value.clone().try_cast_num("%e/%E requires number")?;524			render_float_sci(525				&mut tmp_out,526				value,527				padding,528				fpprec,529				clfags.blank,530				clfags.sign,531				clfags.alt,532				true,533				code.caps,534			);535		}536		ConvTypeV::Float => {537			let value = value.clone().try_cast_num("%e/%E requires number")?;538			render_float(539				&mut tmp_out,540				value,541				padding,542				fpprec,543				clfags.blank,544				clfags.sign,545				clfags.alt,546				true,547			);548		}549		ConvTypeV::Shorter => {550			let value = value.clone().try_cast_num("%g/%G requires number")?;551			let exponent = value.log10().floor();552			if exponent < -4.0 || exponent >= fpprec as f64 {553				render_float_sci(554					&mut tmp_out,555					value,556					padding,557					fpprec - 1,558					clfags.blank,559					clfags.sign,560					clfags.alt,561					clfags.alt,562					code.caps,563				);564			} else {565				let digits_before_pt = 1.max(exponent as usize + 1);566				render_float(567					&mut tmp_out,568					value,569					padding,570					fpprec - digits_before_pt,571					clfags.blank,572					clfags.sign,573					clfags.alt,574					clfags.alt,575				);576			}577		}578		ConvTypeV::Char => match value.clone() {579			Val::Num(n) => tmp_out.push(580				std::char::from_u32(n as u32)581					.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,582			),583			Val::Str(s) => {584				if s.chars().count() != 1 {585					throw!(RuntimeError(586						format!("%c expected 1 char string, got {}", s.chars().count()).into(),587					));588				}589				tmp_out.push_str(&s);590			}591			_ => {592				throw!(TypeMismatch(593					"%c requires number/string",594					vec![ValType::Num, ValType::Str],595					value.value_type(),596				));597			}598		},599		ConvTypeV::Percent => tmp_out.push('%'),600	};601602	let padding = width.saturating_sub(tmp_out.len());603604	if !clfags.left {605		for _ in 0..padding {606			out.push(' ');607		}608	}609	out.push_str(&tmp_out);610	if clfags.left {611		for _ in 0..padding {612			out.push(' ');613		}614	}615616	Ok(())617}618619pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {620	let codes = parse_codes(str)?;621	let mut out = String::new();622623	for code in codes {624		match code {625			Element::String(s) => {626				out.push_str(s);627			}628			Element::Code(c) => {629				let width = match c.width {630					Width::Star => {631						if values.is_empty() {632							throw!(NotEnoughValues);633						}634						let value = &values[0];635						values = &values[1..];636						value.clone().try_cast_num("field width")? as usize637					}638					Width::Fixed(n) => n,639				};640				let precision = match c.precision {641					Some(Width::Star) => {642						if values.is_empty() {643							throw!(NotEnoughValues);644						}645						let value = &values[0];646						values = &values[1..];647						Some(value.clone().try_cast_num("field precision")? as usize)648					}649					Some(Width::Fixed(n)) => Some(n),650					None => None,651				};652653				// %% should not consume a value654				let value = if c.convtype == ConvTypeV::Percent {655					&Val::Null656				} else {657					if values.is_empty() {658						throw!(NotEnoughValues);659					}660					let value = &values[0];661					values = &values[1..];662					value663				};664665				format_code(&mut out, value, &c, width, precision)?;666			}667		}668	}669670	Ok(out)671}672673pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {674	let codes = parse_codes(str)?;675	let mut out = String::new();676677	for code in codes {678		match code {679			Element::String(s) => {680				out.push_str(s);681			}682			Element::Code(c) => {683				// TODO: Operate on ref684				let f: IStr = c.mkey.into();685				let width = match c.width {686					Width::Star => {687						throw!(CannotUseStarWidthWithObject);688					}689					Width::Fixed(n) => n,690				};691				let precision = match c.precision {692					Some(Width::Star) => {693						throw!(CannotUseStarWidthWithObject);694					}695					Some(Width::Fixed(n)) => Some(n),696					None => None,697				};698699				let value = if c.convtype == ConvTypeV::Percent {700					Val::Null701				} else {702					if f.is_empty() {703						throw!(MappingKeysRequired);704					}705					if let Some(v) = values.get(f.clone())? {706						v707					} else {708						throw!(NoSuchFormatField(f));709					}710				};711712				format_code(&mut out, &value, &c, width, precision)?;713			}714		}715	}716717	Ok(out)718}719720#[cfg(test)]721pub mod test_format {722	use super::*;723724	#[test]725	fn parse() {726		assert_eq!(727			parse_codes(728				"How much error budget is left looking at our %.3f%% availability gurantees?"729			)730			.unwrap()731			.len(),732			4733		);734	}735736	#[test]737	fn octals() {738		assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");739		assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");740		assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), "  10");741		assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");742		assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");743		assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");744		assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10  ");745		assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");746		assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");747	}748749	#[test]750	fn percent_doesnt_consumes_values() {751		assert_eq!(752			format_arr(753				"How much error budget is left looking at our %.3f%% availability gurantees?",754				&[Val::Num(4.0)]755			)756			.unwrap(),757			"How much error budget is left looking at our 4.000% availability gurantees?"758		);759	}760}
after · crates/jrsonnet-evaluator/src/builtin/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val};5use jrsonnet_gc::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_types::ValType;8use thiserror::Error;910#[derive(Debug, Clone, Error, Trace)]11#[trivially_drop]12pub enum FormatError {13	#[error("truncated format code")]14	TruncatedFormatCode,15	#[error("unrecognized conversion type: {0}")]16	UnrecognizedConversionType(char),1718	#[error("not enough values")]19	NotEnoughValues,2021	#[error("cannot use * width with object")]22	CannotUseStarWidthWithObject,23	#[error("mapping keys required")]24	MappingKeysRequired,25	#[error("no such format field: {0}")]26	NoSuchFormatField(IStr),27}2829impl From<FormatError> for LocError {30	fn from(e: FormatError) -> Self {31		Self::new(Format(e))32	}33}3435use FormatError::*;3637type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;3839pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {40	if str.is_empty() {41		return Err(TruncatedFormatCode);42	}43	let bytes = str.as_bytes();44	if bytes[0] == b'(' {45		let mut i = 1;46		while i < bytes.len() {47			if bytes[i] == b')' {48				return Ok((&str[1..i as usize], &str[i as usize + 1..]));49			}50			i += 1;51		}52		Err(TruncatedFormatCode)53	} else {54		Ok(("", str))55	}56}5758#[cfg(test)]59pub mod tests_key {60	use super::*;6162	#[test]63	fn parse_key() {64		assert_eq!(65			try_parse_mapping_key("(hello ) world").unwrap(),66			("hello ", " world")67		);68		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));69		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));70		assert_eq!(71			try_parse_mapping_key(" () world").unwrap(),72			("", " () world")73		);74	}7576	#[test]77	#[should_panic]78	fn parse_key_missing_start() {79		try_parse_mapping_key("").unwrap();80	}8182	#[test]83	#[should_panic]84	fn parse_key_missing_end() {85		try_parse_mapping_key("(   ").unwrap();86	}87}8889#[derive(Default, Debug)]90pub struct CFlags {91	pub alt: bool,92	pub zero: bool,93	pub left: bool,94	pub blank: bool,95	pub sign: bool,96}9798pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {99	if str.is_empty() {100		return Err(TruncatedFormatCode);101	}102	let bytes = str.as_bytes();103	let mut i = 0;104	let mut out = CFlags::default();105	loop {106		if bytes.len() == i {107			return Err(TruncatedFormatCode);108		}109		match bytes[i] {110			b'#' => out.alt = true,111			b'0' => out.zero = true,112			b'-' => out.left = true,113			b' ' => out.blank = true,114			b'+' => out.sign = true,115			_ => break,116		}117		i += 1;118	}119	Ok((out, &str[i..]))120}121122#[derive(Debug, PartialEq)]123pub enum Width {124	Star,125	Fixed(usize),126}127pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {128	if str.is_empty() {129		return Err(TruncatedFormatCode);130	}131	let bytes = str.as_bytes();132	if bytes[0] == b'*' {133		return Ok((Width::Star, &str[1..]));134	}135	let mut out: usize = 0;136	let mut digits = 0;137	while let Some(digit) = (bytes[digits] as char).to_digit(10) {138		out *= 10;139		out += digit as usize;140		digits += 1;141		if digits == bytes.len() {142			return Err(TruncatedFormatCode);143		}144	}145	Ok((Width::Fixed(out), &str[digits..]))146}147148pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {149	if str.is_empty() {150		return Err(TruncatedFormatCode);151	}152	let bytes = str.as_bytes();153	if bytes[0] == b'.' {154		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))155	} else {156		Ok((None, str))157	}158}159160// Only skips161pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {162	if str.is_empty() {163		return Err(TruncatedFormatCode);164	}165	let bytes = str.as_bytes();166	let mut idx = 0;167	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {168		idx += 1;169		if bytes.len() == idx {170			return Err(TruncatedFormatCode);171		}172	}173	Ok(((), &str[idx..]))174}175176#[derive(Debug, PartialEq)]177pub enum ConvTypeV {178	Decimal,179	Octal,180	Hexadecimal,181	Scientific,182	Float,183	Shorter,184	Char,185	String,186	Percent,187}188pub struct ConvType {189	v: ConvTypeV,190	caps: bool,191}192193pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {194	if str.is_empty() {195		return Err(TruncatedFormatCode);196	}197198	let code = str.as_bytes()[0];199	let v: (ConvTypeV, bool) = match code {200		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),201		b'o' => (ConvTypeV::Octal, false),202		b'x' => (ConvTypeV::Hexadecimal, false),203		b'X' => (ConvTypeV::Hexadecimal, true),204		b'e' => (ConvTypeV::Scientific, false),205		b'E' => (ConvTypeV::Scientific, true),206		b'f' => (ConvTypeV::Float, false),207		b'F' => (ConvTypeV::Float, true),208		b'g' => (ConvTypeV::Shorter, false),209		b'G' => (ConvTypeV::Shorter, true),210		b'c' => (ConvTypeV::Char, false),211		b's' => (ConvTypeV::String, false),212		b'%' => (ConvTypeV::Percent, false),213		c => return Err(UnrecognizedConversionType(c as char)),214	};215216	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))217}218219#[derive(Debug)]220pub struct Code<'s> {221	mkey: &'s str,222	cflags: CFlags,223	width: Width,224	precision: Option<Width>,225	convtype: ConvTypeV,226	caps: bool,227}228pub fn parse_code(str: &str) -> ParseResult<Code> {229	if str.is_empty() {230		return Err(TruncatedFormatCode);231	}232	let (mkey, str) = try_parse_mapping_key(str)?;233	let (cflags, str) = try_parse_cflags(str)?;234	let (width, str) = try_parse_field_width(str)?;235	let (precision, str) = try_parse_precision(str)?;236	let (_, str) = try_parse_length_modifier(str)?;237	let (convtype, str) = parse_conversion_type(str)?;238239	Ok((240		Code {241			mkey,242			cflags,243			width,244			precision,245			convtype: convtype.v,246			caps: convtype.caps,247		},248		str,249	))250}251252#[derive(Debug)]253pub enum Element<'s> {254	String(&'s str),255	Code(Code<'s>),256}257pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {258	let mut bytes = str.as_bytes();259	let mut out = vec![];260	let mut offset = 0;261262	loop {263		while offset != bytes.len() && bytes[offset] != b'%' {264			offset += 1;265		}266		if offset != 0 {267			out.push(Element::String(&str[0..offset]));268		}269		if offset == bytes.len() {270			return Ok(out);271		}272		str = &str[offset + 1..];273		let (code, nstr) = parse_code(str)?;274		str = nstr;275		bytes = str.as_bytes();276		offset = 0;277278		out.push(Element::Code(code))279	}280}281282const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";283284#[inline]285pub fn render_integer(286	out: &mut String,287	iv: i64,288	padding: usize,289	precision: usize,290	blank: bool,291	sign: bool,292	radix: i64,293	prefix: &str,294	caps: bool,295) {296	// Digit char indexes in reverse order, i.e297	// for radix = 16 and n = 12f: [15, 2, 1]298	let digits = if iv == 0 {299		vec![0u8]300	} else {301		let mut v = iv.abs();302		let mut nums = Vec::with_capacity(1);303		while v > 0 {304			nums.push((v % radix) as u8);305			v /= radix;306		}307		nums308	};309	let neg = iv < 0;310	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });311	let zp2 = zp312		.max(precision)313		.saturating_sub(prefix.len() + digits.len());314315	if neg {316		out.push('-')317	} else if sign {318		out.push('+');319	} else if blank {320		out.push(' ');321	}322323	out.reserve(zp2);324	for _ in 0..zp2 {325		out.push('0');326	}327	out.push_str(prefix);328329	for digit in digits.into_iter().rev() {330		let ch = NUMBERS[digit as usize] as char;331		out.push(if caps { ch.to_ascii_uppercase() } else { ch });332	}333}334335pub fn render_decimal(336	out: &mut String,337	iv: i64,338	padding: usize,339	precision: usize,340	blank: bool,341	sign: bool,342) {343	render_integer(out, iv, padding, precision, blank, sign, 10, "", false)344}345pub fn render_octal(346	out: &mut String,347	iv: i64,348	padding: usize,349	precision: usize,350	alt: bool,351	blank: bool,352	sign: bool,353) {354	render_integer(355		out,356		iv,357		padding,358		precision,359		blank,360		sign,361		8,362		if alt && iv != 0 { "0" } else { "" },363		false,364	)365}366pub fn render_hexadecimal(367	out: &mut String,368	iv: i64,369	padding: usize,370	precision: usize,371	alt: bool,372	blank: bool,373	sign: bool,374	caps: bool,375) {376	render_integer(377		out,378		iv,379		padding,380		precision,381		blank,382		sign,383		16,384		match (alt, caps) {385			(true, true) => "0X",386			(true, false) => "0x",387			(false, _) => "",388		},389		caps,390	)391}392393pub fn render_float(394	out: &mut String,395	n: f64,396	mut padding: usize,397	precision: usize,398	blank: bool,399	sign: bool,400	ensure_pt: bool,401	trailing: bool,402) {403	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };404	padding = padding.saturating_sub(dot_size + precision);405	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);406	if precision == 0 {407		if ensure_pt {408			out.push('.');409		}410		return;411	}412	let frac = n413		.fract()414		.mul_add(10.0_f64.powf(precision as f64), 0.5)415		.floor();416	if trailing || frac > 0.0 {417		out.push('.');418		let mut frac_str = String::new();419		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);420		let mut trim = frac_str.len();421		if !trailing {422			for b in frac_str.as_bytes().iter().rev() {423				if *b == b'0' {424					trim -= 1;425				}426			}427		}428		out.push_str(&frac_str[..trim]);429	} else if ensure_pt {430		out.push('.');431	}432}433434pub fn render_float_sci(435	out: &mut String,436	n: f64,437	mut padding: usize,438	precision: usize,439	blank: bool,440	sign: bool,441	ensure_pt: bool,442	trailing: bool,443	caps: bool,444) {445	let exponent = n.log10().floor();446	let mantissa = if exponent as i16 == -324 {447		n * 10.0 / 10.0_f64.powf(exponent + 1.0)448	} else {449		n / 10.0_f64.powf(exponent)450	};451	let mut exponent_str = String::new();452	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);453454	// +1 for e455	padding = padding.saturating_sub(exponent_str.len() + 1);456457	render_float(458		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,459	);460	out.push(if caps { 'E' } else { 'e' });461	out.push_str(&exponent_str);462}463464pub fn format_code(465	out: &mut String,466	value: &Val,467	code: &Code,468	width: usize,469	precision: Option<usize>,470) -> Result<()> {471	let clfags = &code.cflags;472	let (fpprec, iprec) = match precision {473		Some(v) => (v, v),474		None => (6, 0),475	};476	let padding = if clfags.zero && !clfags.left {477		width478	} else {479		0480	};481482	// TODO: If left padded, can optimize by writing directly to out483	let mut tmp_out = String::new();484485	match code.convtype {486		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),487		ConvTypeV::Decimal => {488			let value = value.clone().try_cast_num("%d/%u/%i requires number")?;489			render_decimal(490				&mut tmp_out,491				value as i64,492				padding,493				iprec,494				clfags.blank,495				clfags.sign,496			);497		}498		ConvTypeV::Octal => {499			let value = value.clone().try_cast_num("%o requires number")?;500			render_octal(501				&mut tmp_out,502				value as i64,503				padding,504				iprec,505				clfags.alt,506				clfags.blank,507				clfags.sign,508			);509		}510		ConvTypeV::Hexadecimal => {511			let value = value.clone().try_cast_num("%x/%X requires number")?;512			render_hexadecimal(513				&mut tmp_out,514				value as i64,515				padding,516				iprec,517				clfags.alt,518				clfags.blank,519				clfags.sign,520				code.caps,521			);522		}523		ConvTypeV::Scientific => {524			let value = value.clone().try_cast_num("%e/%E requires number")?;525			render_float_sci(526				&mut tmp_out,527				value,528				padding,529				fpprec,530				clfags.blank,531				clfags.sign,532				clfags.alt,533				true,534				code.caps,535			);536		}537		ConvTypeV::Float => {538			let value = value.clone().try_cast_num("%e/%E requires number")?;539			render_float(540				&mut tmp_out,541				value,542				padding,543				fpprec,544				clfags.blank,545				clfags.sign,546				clfags.alt,547				true,548			);549		}550		ConvTypeV::Shorter => {551			let value = value.clone().try_cast_num("%g/%G requires number")?;552			let exponent = value.log10().floor();553			if exponent < -4.0 || exponent >= fpprec as f64 {554				render_float_sci(555					&mut tmp_out,556					value,557					padding,558					fpprec - 1,559					clfags.blank,560					clfags.sign,561					clfags.alt,562					clfags.alt,563					code.caps,564				);565			} else {566				let digits_before_pt = 1.max(exponent as usize + 1);567				render_float(568					&mut tmp_out,569					value,570					padding,571					fpprec - digits_before_pt,572					clfags.blank,573					clfags.sign,574					clfags.alt,575					clfags.alt,576				);577			}578		}579		ConvTypeV::Char => match value.clone() {580			Val::Num(n) => tmp_out.push(581				std::char::from_u32(n as u32)582					.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,583			),584			Val::Str(s) => {585				if s.chars().count() != 1 {586					throw!(RuntimeError(587						format!("%c expected 1 char string, got {}", s.chars().count()).into(),588					));589				}590				tmp_out.push_str(&s);591			}592			_ => {593				throw!(TypeMismatch(594					"%c requires number/string",595					vec![ValType::Num, ValType::Str],596					value.value_type(),597				));598			}599		},600		ConvTypeV::Percent => tmp_out.push('%'),601	};602603	let padding = width.saturating_sub(tmp_out.len());604605	if !clfags.left {606		for _ in 0..padding {607			out.push(' ');608		}609	}610	out.push_str(&tmp_out);611	if clfags.left {612		for _ in 0..padding {613			out.push(' ');614		}615	}616617	Ok(())618}619620pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {621	let codes = parse_codes(str)?;622	let mut out = String::new();623624	for code in codes {625		match code {626			Element::String(s) => {627				out.push_str(s);628			}629			Element::Code(c) => {630				let width = match c.width {631					Width::Star => {632						if values.is_empty() {633							throw!(NotEnoughValues);634						}635						let value = &values[0];636						values = &values[1..];637						value.clone().try_cast_num("field width")? as usize638					}639					Width::Fixed(n) => n,640				};641				let precision = match c.precision {642					Some(Width::Star) => {643						if values.is_empty() {644							throw!(NotEnoughValues);645						}646						let value = &values[0];647						values = &values[1..];648						Some(value.clone().try_cast_num("field precision")? as usize)649					}650					Some(Width::Fixed(n)) => Some(n),651					None => None,652				};653654				// %% should not consume a value655				let value = if c.convtype == ConvTypeV::Percent {656					&Val::Null657				} else {658					if values.is_empty() {659						throw!(NotEnoughValues);660					}661					let value = &values[0];662					values = &values[1..];663					value664				};665666				format_code(&mut out, value, &c, width, precision)?;667			}668		}669	}670671	Ok(out)672}673674pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {675	let codes = parse_codes(str)?;676	let mut out = String::new();677678	for code in codes {679		match code {680			Element::String(s) => {681				out.push_str(s);682			}683			Element::Code(c) => {684				// TODO: Operate on ref685				let f: IStr = c.mkey.into();686				let width = match c.width {687					Width::Star => {688						throw!(CannotUseStarWidthWithObject);689					}690					Width::Fixed(n) => n,691				};692				let precision = match c.precision {693					Some(Width::Star) => {694						throw!(CannotUseStarWidthWithObject);695					}696					Some(Width::Fixed(n)) => Some(n),697					None => None,698				};699700				let value = if c.convtype == ConvTypeV::Percent {701					Val::Null702				} else {703					if f.is_empty() {704						throw!(MappingKeysRequired);705					}706					if let Some(v) = values.get(f.clone())? {707						v708					} else {709						throw!(NoSuchFormatField(f));710					}711				};712713				format_code(&mut out, &value, &c, width, precision)?;714			}715		}716	}717718	Ok(out)719}720721#[cfg(test)]722pub mod test_format {723	use super::*;724725	#[test]726	fn parse() {727		assert_eq!(728			parse_codes(729				"How much error budget is left looking at our %.3f%% availability gurantees?"730			)731			.unwrap()732			.len(),733			4734		);735	}736737	#[test]738	fn octals() {739		assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");740		assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");741		assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), "  10");742		assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");743		assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");744		assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");745		assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10  ");746		assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");747		assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");748	}749750	#[test]751	fn percent_doesnt_consumes_values() {752		assert_eq!(753			format_arr(754				"How much error budget is left looking at our %.3f%% availability gurantees?",755				&[Val::Num(4.0)]756			)757			.unwrap(),758			"How much error budget is left looking at our 4.000% availability gurantees?"759		);760	}761}
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -5,7 +5,7 @@
 	EvaluationState, FuncVal, LazyVal, Val,
 };
 use format::{format_arr, format_obj};
-use gc::Gc;
+use jrsonnet_gc::Gc;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ArgsDesc, BinaryOpType, ExprLocation};
 use jrsonnet_types::ty;
@@ -454,7 +454,7 @@
 		0, rest: ty!(any);
 	], {
 		println!("GC start");
-		gc::force_collect();
+		jrsonnet_gc::force_collect();
 		println!("GC done");
 
 		Ok(rest)
modifiedcrates/jrsonnet-evaluator/src/builtin/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/sort.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs
@@ -2,7 +2,7 @@
 	error::{Error, LocError, Result},
 	throw, Context, FuncVal, Val,
 };
-use gc::{Finalize, Gc, Trace};
+use jrsonnet_gc::{Finalize, Gc, Trace};
 
 #[derive(Debug, Clone, thiserror::Error, Trace, Finalize)]
 pub enum SortError {
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -2,13 +2,14 @@
 	error::Error::*, map::LayeredHashMap, FutureWrapper, LazyBinding, LazyVal, ObjValue, Result,
 	Val,
 };
-use gc::{Finalize, Gc, Trace};
+use jrsonnet_gc::{Gc, Trace};
 use jrsonnet_interner::IStr;
 use rustc_hash::FxHashMap;
 use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 
-#[derive(Clone, Trace, Finalize)]
+#[derive(Clone, Trace)]
+#[trivially_drop]
 pub struct ContextCreator(pub Context, pub FutureWrapper<FxHashMap<IStr, LazyBinding>>);
 impl ContextCreator {
 	pub fn create(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<Context> {
@@ -21,12 +22,13 @@
 	}
 }
 
-#[derive(Trace, Finalize)]
+#[derive(Trace)]
+#[trivially_drop]
 struct ContextInternals {
 	dollar: Option<ObjValue>,
 	this: Option<ObjValue>,
 	super_obj: Option<ObjValue>,
-	bindings: LayeredHashMap<LazyVal>,
+	bindings: LayeredHashMap,
 }
 impl Debug for ContextInternals {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -34,7 +36,8 @@
 	}
 }
 
-#[derive(Debug, Clone, Trace, Finalize)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub struct Context(Gc<ContextInternals>);
 impl Context {
 	pub fn new_future() -> FutureWrapper<Self> {
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -1,6 +1,7 @@
-use gc::{Finalize, Gc, GcCell, Trace};
+use jrsonnet_gc::{Gc, GcCell, Trace};
 
-#[derive(Clone, Trace, Finalize)]
+#[derive(Clone, Trace)]
+#[trivially_drop]
 pub struct FutureWrapper<V: Trace + 'static>(pub Gc<GcCell<Option<V>>>);
 impl<T: Trace + 'static> FutureWrapper<T> {
 	pub fn new() -> Self {
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -2,7 +2,7 @@
 	builtin::{format::FormatError, sort::SortError},
 	typed::TypeLocError,
 };
-use gc::{Finalize, Trace};
+use jrsonnet_gc::Trace;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType};
 use jrsonnet_types::ValType;
@@ -12,7 +12,8 @@
 };
 use thiserror::Error;
 
-#[derive(Error, Debug, Clone, Trace, Finalize)]
+#[derive(Error, Debug, Clone, Trace)]
+#[trivially_drop]
 pub enum Error {
 	#[error("intrinsic not found: {0}")]
 	IntrinsicNotFound(IStr),
@@ -149,15 +150,18 @@
 	}
 }
 
-#[derive(Clone, Debug, Trace, Finalize)]
+#[derive(Clone, Debug, Trace)]
+#[trivially_drop]
 pub struct StackTraceElement {
 	pub location: Option<ExprLocation>,
 	pub desc: String,
 }
-#[derive(Debug, Clone, Trace, Finalize)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub struct StackTrace(pub Vec<StackTraceElement>);
 
-#[derive(Debug, Clone, Trace, Finalize)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub struct LocError(Box<(Error, StackTrace)>);
 impl LocError {
 	pub fn new(e: Error) -> Self {
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -3,7 +3,7 @@
 	FuncDesc, FuncVal, FutureWrapper, LazyBinding, LazyVal, LazyValValue, ObjMember, ObjValue,
 	ObjectAssertion, Result, Val,
 };
-use gc::{custom_trace, Finalize, Gc, Trace};
+use jrsonnet_gc::{Gc, Trace};
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{
 	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember,
@@ -22,21 +22,14 @@
 	if let Some(params) = &b.params {
 		let params = params.clone();
 
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct LazyMethodBinding {
 			context_creator: FutureWrapper<Context>,
 			name: IStr,
 			params: ParamsDesc,
 			value: LocExpr,
 		}
-		impl Finalize for LazyMethodBinding {}
-		unsafe impl Trace for LazyMethodBinding {
-			custom_trace!(this, {
-				mark(&this.context_creator);
-				mark(&this.name);
-				mark(&this.params);
-				mark(&this.value);
-			});
-		}
 		impl LazyValValue for LazyMethodBinding {
 			fn get(self: Box<Self>) -> Result<Val> {
 				Ok(evaluate_method(
@@ -55,19 +48,13 @@
 			value: b.value.clone(),
 		}))
 	} else {
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct LazyNamedBinding {
 			context_creator: FutureWrapper<Context>,
 			name: IStr,
 			value: LocExpr,
 		}
-		impl Finalize for LazyNamedBinding {}
-		unsafe impl Trace for LazyNamedBinding {
-			custom_trace!(this, {
-				mark(&this.context_creator);
-				mark(&this.name);
-				mark(&this.value);
-			});
-		}
 		impl LazyValValue for LazyNamedBinding {
 			fn get(self: Box<Self>) -> Result<Val> {
 				evaluate_named(self.context_creator.unwrap(), &self.value, self.name)
@@ -86,6 +73,8 @@
 	if let Some(params) = &b.params {
 		let params = params.clone();
 
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct BindableMethodLazyVal {
 			this: Option<ObjValue>,
 			super_obj: Option<ObjValue>,
@@ -94,17 +83,6 @@
 			name: IStr,
 			params: ParamsDesc,
 			value: LocExpr,
-		}
-		impl Finalize for BindableMethodLazyVal {}
-		unsafe impl Trace for BindableMethodLazyVal {
-			custom_trace!(this, {
-				mark(&this.this);
-				mark(&this.super_obj);
-				mark(&this.context_creator);
-				mark(&this.name);
-				mark(&this.params);
-				mark(&this.value);
-			});
 		}
 		impl LazyValValue for BindableMethodLazyVal {
 			fn get(self: Box<Self>) -> Result<Val> {
@@ -117,7 +95,8 @@
 			}
 		}
 
-		#[derive(Trace, Finalize)]
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct BindableMethod {
 			context_creator: ContextCreator,
 			name: IStr,
@@ -148,6 +127,8 @@
 			}))),
 		)
 	} else {
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct BindableNamedLazyVal {
 			this: Option<ObjValue>,
 			super_obj: Option<ObjValue>,
@@ -156,16 +137,6 @@
 			name: IStr,
 			value: LocExpr,
 		}
-		impl Finalize for BindableNamedLazyVal {}
-		unsafe impl Trace for BindableNamedLazyVal {
-			custom_trace!(this, {
-				mark(&this.this);
-				mark(&this.super_obj);
-				mark(&this.context_creator);
-				mark(&this.name);
-				mark(&this.value);
-			});
-		}
 		impl LazyValValue for BindableNamedLazyVal {
 			fn get(self: Box<Self>) -> Result<Val> {
 				evaluate_named(
@@ -176,7 +147,8 @@
 			}
 		}
 
-		#[derive(Trace, Finalize)]
+		#[derive(Trace)]
+		#[trivially_drop]
 		struct BindableNamed {
 			context_creator: ContextCreator,
 			name: IStr,
@@ -414,7 +386,8 @@
 				}
 				let name = name.unwrap();
 
-				#[derive(Trace, Finalize)]
+				#[derive(Trace)]
+				#[trivially_drop]
 				struct ObjMemberBinding {
 					context_creator: ContextCreator,
 					value: LocExpr,
@@ -458,7 +431,8 @@
 					continue;
 				}
 				let name = name.unwrap();
-				#[derive(Trace, Finalize)]
+				#[derive(Trace)]
+				#[trivially_drop]
 				struct ObjMemberBinding {
 					context_creator: ContextCreator,
 					value: LocExpr,
@@ -496,16 +470,11 @@
 			}
 			Member::BindStmt(_) => {}
 			Member::AssertStmt(stmt) => {
+				#[derive(Trace)]
+				#[trivially_drop]
 				struct ObjectAssert {
 					context_creator: ContextCreator,
 					assert: AssertStmt,
-				}
-				impl Finalize for ObjectAssert {}
-				unsafe impl Trace for ObjectAssert {
-					custom_trace!(this, {
-						mark(&this.context_creator);
-						mark(&this.assert);
-					});
 				}
 				impl ObjectAssertion for ObjectAssert {
 					fn run(
@@ -558,7 +527,8 @@
 				match key {
 					Val::Null => {}
 					Val::Str(n) => {
-						#[derive(Trace, Finalize)]
+						#[derive(Trace)]
+						#[trivially_drop]
 						struct ObjCompBinding {
 							context: Context,
 							value: LocExpr,
@@ -768,16 +738,11 @@
 			let mut out = Vec::with_capacity(items.len());
 			for item in items {
 				// TODO: Implement ArrValue::Lazy with same context for every element?
+				#[derive(Trace)]
+				#[trivially_drop]
 				struct ArrayElement {
 					context: Context,
 					item: LocExpr,
-				}
-				impl Finalize for ArrayElement {}
-				unsafe impl Trace for ArrayElement {
-					custom_trace!(this, {
-						mark(&this.context);
-						mark(&this.item);
-					});
 				}
 				impl LazyValValue for ArrayElement {
 					fn get(self: Box<Self>) -> Result<Val> {
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -1,5 +1,5 @@
 use crate::{error::Error::*, evaluate, throw, Context, LazyVal, LazyValValue, Result, Val};
-use gc::{custom_trace, Finalize, Trace};
+use jrsonnet_gc::Trace;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};
 use rustc_hash::FxHashMap;
@@ -55,16 +55,11 @@
 		let val = if tailstrict {
 			LazyVal::new_resolved(evaluate(ctx, expr)?)
 		} else {
+			#[derive(Trace)]
+			#[trivially_drop]
 			struct EvaluateLazyVal {
 				context: Context,
 				expr: LocExpr,
-			}
-			impl Finalize for EvaluateLazyVal {}
-			unsafe impl Trace for EvaluateLazyVal {
-				custom_trace!(this, {
-					mark(&this.context);
-					mark(&this.expr);
-				});
 			}
 			impl LazyValValue for EvaluateLazyVal {
 				fn get(self: Box<Self>) -> Result<Val> {
@@ -119,7 +114,8 @@
 			} else {
 				let body_ctx = body_ctx.clone();
 				let default = default.clone();
-				#[derive(Trace, Finalize)]
+				#[derive(Trace)]
+				#[trivially_drop]
 				struct EvaluateLazyVal {
 					body_ctx: Option<Context>,
 					default: LocExpr,
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -2,7 +2,7 @@
 	error::{Error::*, LocError, Result},
 	throw, LazyBinding, LazyVal, ObjMember, ObjValue, Val,
 };
-use gc::Gc;
+use jrsonnet_gc::Gc;
 use jrsonnet_parser::Visibility;
 use rustc_hash::FxHasher;
 use serde_json::{Map, Number, Value};
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -25,8 +25,8 @@
 use error::{Error::*, LocError, Result, StackTraceElement};
 pub use evaluate::*;
 pub use function::parse_function_call;
-use gc::{Finalize, Gc, Trace};
 pub use import::*;
+use jrsonnet_gc::{Finalize, Gc, Trace};
 pub use jrsonnet_interner::IStr;
 use jrsonnet_parser::*;
 use native::NativeCallback;
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -1,32 +1,29 @@
-use gc::{Finalize, Gc, Trace};
+use jrsonnet_gc::{Gc, Trace};
 use jrsonnet_interner::IStr;
 use rustc_hash::FxHashMap;
 
-pub struct LayeredHashMapInternals<V: Trace + Finalize + 'static> {
-	parent: Option<LayeredHashMap<V>>,
-	current: FxHashMap<IStr, V>,
-}
+use crate::LazyVal;
 
-unsafe impl<V: Trace + Finalize + 'static> Trace for LayeredHashMapInternals<V> {
-	gc::custom_trace!(this, {
-		mark(&this.parent);
-		mark(&this.current);
-	});
+#[derive(Trace)]
+#[trivially_drop]
+pub struct LayeredHashMapInternals {
+	parent: Option<LayeredHashMap>,
+	current: FxHashMap<IStr, LazyVal>,
 }
-impl<V: Trace + Finalize + 'static> Finalize for LayeredHashMapInternals<V> {}
 
-#[derive(Trace, Finalize)]
-pub struct LayeredHashMap<V: Trace + Finalize + 'static>(Gc<LayeredHashMapInternals<V>>);
+#[derive(Trace)]
+#[trivially_drop]
+pub struct LayeredHashMap(Gc<LayeredHashMapInternals>);
 
-impl<V: Trace + 'static> LayeredHashMap<V> {
-	pub fn extend(self, new_layer: FxHashMap<IStr, V>) -> Self {
+impl LayeredHashMap {
+	pub fn extend(self, new_layer: FxHashMap<IStr, LazyVal>) -> Self {
 		Self(Gc::new(LayeredHashMapInternals {
 			parent: Some(self),
 			current: new_layer,
 		}))
 	}
 
-	pub fn get(&self, key: &IStr) -> Option<&V> {
+	pub fn get(&self, key: &IStr) -> Option<&LazyVal> {
 		(self.0)
 			.current
 			.get(key)
@@ -34,13 +31,13 @@
 	}
 }
 
-impl<V: Trace + 'static> Clone for LayeredHashMap<V> {
+impl Clone for LayeredHashMap {
 	fn clone(&self) -> Self {
 		Self(self.0.clone())
 	}
 }
 
-impl<V: Trace + 'static> Default for LayeredHashMap<V> {
+impl Default for LayeredHashMap {
 	fn default() -> Self {
 		Self(Gc::new(LayeredHashMapInternals {
 			parent: None,
modifiedcrates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/native.rs
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -1,7 +1,7 @@
 #![allow(clippy::type_complexity)]
 
 use crate::{error::Result, Val};
-use gc::{Finalize, Trace};
+use jrsonnet_gc::Trace;
 use jrsonnet_parser::ParamsDesc;
 use std::fmt::Debug;
 use std::path::Path;
@@ -11,7 +11,8 @@
 	fn call(&self, from: Option<Rc<Path>>, args: &[Val]) -> Result<Val>;
 }
 
-#[derive(Trace, Finalize)]
+#[derive(Trace)]
+#[trivially_drop]
 pub struct NativeCallback {
 	pub params: ParamsDesc,
 	handler: Box<dyn NativeCallbackHandler>,
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -1,12 +1,13 @@
 use crate::{evaluate_add_op, LazyBinding, Result, Val};
-use gc::{Finalize, Gc, GcCell, Trace};
+use jrsonnet_gc::{Gc, GcCell, Trace};
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{ExprLocation, Visibility};
 use rustc_hash::{FxHashMap, FxHashSet};
 use std::hash::{Hash, Hasher};
 use std::{fmt::Debug, hash::BuildHasherDefault};
 
-#[derive(Debug, Trace, Finalize)]
+#[derive(Debug, Trace)]
+#[trivially_drop]
 pub struct ObjMember {
 	pub add: bool,
 	pub visibility: Visibility,
@@ -20,7 +21,8 @@
 
 // Field => This
 type CacheKey = (IStr, ObjValue);
-#[derive(Trace, Finalize)]
+#[derive(Trace)]
+#[trivially_drop]
 pub struct ObjValueInternals {
 	super_obj: Option<ObjValue>,
 	assertions: Gc<Vec<Box<dyn ObjectAssertion>>>,
@@ -30,7 +32,8 @@
 	value_cache: GcCell<FxHashMap<CacheKey, Option<Val>>>,
 }
 
-#[derive(Clone, Trace, Finalize)]
+#[derive(Clone, Trace)]
+#[trivially_drop]
 pub struct ObjValue(pub(crate) Gc<ObjValueInternals>);
 impl Debug for ObjValue {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
modifiedcrates/jrsonnet-evaluator/src/typed.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed.rs
+++ b/crates/jrsonnet-evaluator/src/typed.rs
@@ -4,7 +4,7 @@
 	error::{Error, LocError, Result},
 	push, Val,
 };
-use gc::{Finalize, Trace};
+use jrsonnet_gc::Trace;
 use jrsonnet_parser::ExprLocation;
 use jrsonnet_types::{ComplexValType, ValType};
 use thiserror::Error;
@@ -21,7 +21,8 @@
 	}};
 }
 
-#[derive(Debug, Error, Clone, Trace, Finalize)]
+#[derive(Debug, Error, Clone, Trace)]
+#[trivially_drop]
 pub enum TypeError {
 	#[error("expected {0}, got {1}")]
 	ExpectedGot(ComplexValType, ValType),
@@ -38,7 +39,8 @@
 	}
 }
 
-#[derive(Debug, Clone, Trace, Finalize)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub struct TypeLocError(Box<TypeError>, ValuePathStack);
 impl From<TypeError> for TypeLocError {
 	fn from(e: TypeError) -> Self {
@@ -60,7 +62,8 @@
 	}
 }
 
-#[derive(Debug, Clone, Trace, Finalize)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub struct TypeLocErrorList(Vec<TypeLocError>);
 impl Display for TypeLocErrorList {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -123,7 +126,8 @@
 	}
 }
 
-#[derive(Clone, Debug, Trace, Finalize)]
+#[derive(Clone, Debug, Trace)]
+#[trivially_drop]
 enum ValuePathItem {
 	Field(Rc<str>),
 	Index(u64),
@@ -138,7 +142,8 @@
 	}
 }
 
-#[derive(Clone, Debug, Trace, Finalize)]
+#[derive(Clone, Debug, Trace)]
+#[trivially_drop]
 struct ValuePathStack(Vec<ValuePathItem>);
 impl Display for ValuePathStack {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -9,7 +9,7 @@
 	native::NativeCallback,
 	throw, with_state, Context, ObjValue, Result,
 };
-use gc::{custom_trace, Finalize, Gc, GcCell, Trace};
+use jrsonnet_gc::{Finalize, Gc, GcCell, Trace};
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LiteralType, LocExpr, ParamsDesc};
 use jrsonnet_types::ValType;
@@ -19,25 +19,17 @@
 	fn get(self: Box<Self>) -> Result<Val>;
 }
 
+#[derive(Trace)]
+#[trivially_drop]
 enum LazyValInternals {
 	Computed(Val),
 	Errored(LocError),
 	Waiting(Box<dyn LazyValValue>),
 	Pending,
-}
-impl Finalize for LazyValInternals {}
-unsafe impl Trace for LazyValInternals {
-	custom_trace!(this, {
-		match &this {
-			LazyValInternals::Computed(v) => mark(v),
-			LazyValInternals::Errored(e) => mark(e),
-			LazyValInternals::Waiting(w) => mark(w),
-			LazyValInternals::Pending => {}
-		}
-	});
 }
 
-#[derive(Clone, Trace, Finalize)]
+#[derive(Clone, Trace)]
+#[trivially_drop]
 pub struct LazyVal(Gc<GcCell<LazyValInternals>>);
 impl LazyVal {
 	pub fn new(f: Box<dyn LazyValValue>) -> Self {
@@ -83,7 +75,8 @@
 	}
 }
 
-#[derive(Debug, PartialEq, Trace, Finalize)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct FuncDesc {
 	pub name: IStr,
 	pub ctx: Context,
@@ -91,7 +84,8 @@
 	pub body: LocExpr,
 }
 
-#[derive(Debug, Trace, Finalize)]
+#[derive(Debug, Trace)]
+#[trivially_drop]
 pub enum FuncVal {
 	/// Plain function implemented in jsonnet
 	Normal(FuncDesc),
@@ -195,22 +189,13 @@
 	String,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub enum ArrValue {
 	Lazy(Gc<Vec<LazyVal>>),
 	Eager(Gc<Vec<Val>>),
 	Extended(Box<(Self, Self)>),
 }
-impl Finalize for ArrValue {}
-unsafe impl Trace for ArrValue {
-	custom_trace!(this, {
-		match &this {
-			ArrValue::Lazy(l) => mark(l),
-			ArrValue::Eager(e) => mark(e),
-			ArrValue::Extended(x) => mark(x),
-		}
-	});
-}
 impl ArrValue {
 	pub fn new_eager() -> Self {
 		Self::Eager(Gc::new(Vec::new()))
@@ -419,7 +404,8 @@
 	}
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Trace)]
+#[trivially_drop]
 pub enum Val {
 	Bool(bool),
 	Null,
@@ -429,21 +415,6 @@
 	Obj(ObjValue),
 	Func(Gc<FuncVal>),
 	DebugGcTraceValue(DebugGcTraceValue),
-}
-impl Finalize for Val {}
-unsafe impl Trace for Val {
-	custom_trace!(this, {
-		match &this {
-			Val::Bool(_) => {}
-			Val::Null => {}
-			Val::Str(_) => {}
-			Val::Num(_) => {}
-			Val::Arr(a) => mark(a),
-			Val::Obj(o) => mark(o),
-			Val::Func(f) => mark(f),
-			Val::DebugGcTraceValue(v) => mark(v),
-		}
-	});
 }
 
 macro_rules! matches_unwrap {
modifiedcrates/jrsonnet-interner/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-interner/Cargo.toml
+++ b/crates/jrsonnet-interner/Cargo.toml
@@ -9,4 +9,4 @@
 [dependencies]
 serde = { version = "1.0" }
 rustc-hash = "1.1.0"
-gc = { version = "0.4.1", features = ["derive"] }
+jrsonnet-gc = { version = "0.4.2", features = ["derive"] }
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -1,4 +1,4 @@
-use gc::{unsafe_empty_trace, Finalize, Trace};
+use jrsonnet_gc::{unsafe_empty_trace, Finalize, Trace};
 use rustc_hash::FxHashMap;
 use serde::{Deserialize, Serialize};
 use std::{
modifiedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -18,7 +18,7 @@
 unescape = "0.1.0"
 
 serde = { version = "1.0", features = ["derive", "rc"], optional = true }
-gc = { version = "0.4.1", features = ["derive"] }
+jrsonnet-gc = { version = "0.4.2", features = ["derive"] }
 
 [dev-dependencies]
 jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.3.8" }
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -1,4 +1,4 @@
-use gc::{unsafe_empty_trace, Finalize, Trace};
+use jrsonnet_gc::{unsafe_empty_trace, Finalize, Trace};
 use jrsonnet_interner::IStr;
 #[cfg(feature = "deserialize")]
 use serde::Deserialize;
@@ -13,21 +13,19 @@
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub enum FieldName {
 	/// {fixed: 2}
 	Fixed(IStr),
 	/// {["dyn"+"amic"]: 3}
 	Dyn(LocExpr),
 }
-impl Finalize for FieldName {}
-unsafe impl Trace for FieldName {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Trace)]
+#[trivially_drop]
 pub enum Visibility {
 	/// :
 	Normal,
@@ -36,10 +34,6 @@
 	/// :::
 	Unhide,
 }
-impl Finalize for Visibility {}
-unsafe impl Trace for Visibility {
-	unsafe_empty_trace!();
-}
 
 impl Visibility {
 	pub fn is_visible(&self) -> bool {
@@ -49,16 +43,14 @@
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
-impl Finalize for AssertStmt {}
-unsafe impl Trace for AssertStmt {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct FieldMember {
 	pub name: FieldName,
 	pub plus: bool,
@@ -66,36 +58,26 @@
 	pub visibility: Visibility,
 	pub value: LocExpr,
 }
-impl Finalize for FieldMember {}
-unsafe impl Trace for FieldMember {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub enum Member {
 	Field(FieldMember),
 	BindStmt(BindSpec),
 	AssertStmt(AssertStmt),
 }
-impl Finalize for Member {}
-unsafe impl Trace for Member {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Trace)]
+#[trivially_drop]
 pub enum UnaryOpType {
 	Plus,
 	Minus,
 	BitNot,
 	Not,
-}
-impl Finalize for UnaryOpType {}
-unsafe impl Trace for UnaryOpType {
-	unsafe_empty_trace!();
 }
 
 impl Display for UnaryOpType {
@@ -116,7 +98,8 @@
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Trace)]
+#[trivially_drop]
 pub enum BinaryOpType {
 	Mul,
 	Div,
@@ -145,10 +128,6 @@
 	And,
 	Or,
 }
-impl Finalize for BinaryOpType {}
-unsafe impl Trace for BinaryOpType {
-	unsafe_empty_trace!();
-}
 
 impl Display for BinaryOpType {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -183,22 +162,22 @@
 /// name, default value
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct Param(pub IStr, pub Option<LocExpr>);
-impl Finalize for Param {}
-unsafe impl Trace for Param {
-	unsafe_empty_trace!();
-}
 
 /// Defined function parameters
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Debug, Clone, PartialEq)]
 pub struct ParamsDesc(pub Rc<Vec<Param>>);
-impl Finalize for ParamsDesc {}
+
+/// Safety:
+/// AST is acyclic, and there should be no gc pointers
 unsafe impl Trace for ParamsDesc {
 	unsafe_empty_trace!();
 }
+impl Finalize for ParamsDesc {}
 
 impl Deref for ParamsDesc {
 	type Target = Vec<Param>;
@@ -209,21 +188,15 @@
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct Arg(pub Option<String>, pub LocExpr);
-impl Finalize for Arg {}
-unsafe impl Trace for Arg {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct ArgsDesc(pub Vec<Arg>);
-impl Finalize for ArgsDesc {}
-unsafe impl Trace for ArgsDesc {
-	unsafe_empty_trace!();
-}
 
 impl Deref for ArgsDesc {
 	type Target = Vec<Arg>;
@@ -234,50 +207,39 @@
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Trace)]
+#[trivially_drop]
 pub struct BindSpec {
 	pub name: IStr,
 	pub params: Option<ParamsDesc>,
 	pub value: LocExpr,
-}
-impl Finalize for BindSpec {}
-unsafe impl Trace for BindSpec {
-	unsafe_empty_trace!();
 }
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct IfSpecData(pub LocExpr);
-impl Finalize for IfSpecData {}
-unsafe impl Trace for IfSpecData {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct ForSpecData(pub IStr, pub LocExpr);
-impl Finalize for ForSpecData {}
-unsafe impl Trace for ForSpecData {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub enum CompSpec {
 	IfSpec(IfSpecData),
 	ForSpec(ForSpecData),
-}
-impl Finalize for CompSpec {}
-unsafe impl Trace for CompSpec {
-	unsafe_empty_trace!();
 }
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct ObjComp {
 	pub pre_locals: Vec<BindSpec>,
 	pub key: LocExpr,
@@ -285,26 +247,20 @@
 	pub post_locals: Vec<BindSpec>,
 	pub compspecs: Vec<CompSpec>,
 }
-impl Finalize for ObjComp {}
-unsafe impl Trace for ObjComp {
-	unsafe_empty_trace!();
-}
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub enum ObjBody {
 	MemberList(Vec<Member>),
 	ObjComp(ObjComp),
-}
-impl Finalize for ObjBody {}
-unsafe impl Trace for ObjBody {
-	unsafe_empty_trace!();
 }
 
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq, Clone, Copy)]
+#[derive(Debug, PartialEq, Clone, Copy, Trace)]
+#[trivially_drop]
 pub enum LiteralType {
 	This,
 	Super,
@@ -313,26 +269,20 @@
 	True,
 	False,
 }
-impl Finalize for LiteralType {}
-unsafe impl Trace for LiteralType {
-	unsafe_empty_trace!();
-}
 
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub struct SliceDesc {
 	pub start: Option<LocExpr>,
 	pub end: Option<LocExpr>,
 	pub step: Option<LocExpr>,
-}
-impl Finalize for SliceDesc {}
-unsafe impl Trace for SliceDesc {
-	unsafe_empty_trace!();
 }
 
 /// Syntax base
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Trace)]
+#[trivially_drop]
 pub enum Expr {
 	Literal(LiteralType),
 
@@ -396,20 +346,13 @@
 		cond_else: Option<LocExpr>,
 	},
 }
-impl Finalize for Expr {}
-unsafe impl Trace for Expr {
-	unsafe_empty_trace!();
-}
 
 /// file, begin offset, end offset
 #[cfg_attr(feature = "serialize", derive(Serialize))]
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Trace)]
+#[trivially_drop]
 pub struct ExprLocation(pub Rc<Path>, pub usize, pub usize);
-impl Finalize for ExprLocation {}
-unsafe impl Trace for ExprLocation {
-	unsafe_empty_trace!();
-}
 
 impl Debug for ExprLocation {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -422,10 +365,12 @@
 #[cfg_attr(feature = "deserialize", derive(Deserialize))]
 #[derive(Clone, PartialEq)]
 pub struct LocExpr(pub Rc<Expr>, pub Option<ExprLocation>);
-impl Finalize for LocExpr {}
+/// Safety:
+/// AST is acyclic, and there should be no gc pointers
 unsafe impl Trace for LocExpr {
 	unsafe_empty_trace!();
 }
+impl Finalize for LocExpr {}
 
 impl Debug for LocExpr {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
modifiedcrates/jrsonnet-types/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-types/Cargo.toml
+++ b/crates/jrsonnet-types/Cargo.toml
@@ -8,4 +8,4 @@
 
 [dependencies]
 peg = "0.7.0"
-gc = { version = "0.4.1", features = ["derive"] }
+jrsonnet-gc = { version = "0.4.2", features = ["derive"] }
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -1,6 +1,6 @@
 #![allow(clippy::redundant_closure_call)]
 
-use gc::{unsafe_empty_trace, Finalize, Trace};
+use jrsonnet_gc::Trace;
 use std::fmt::Display;
 
 #[macro_export]
@@ -78,7 +78,8 @@
 	);
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
+#[trivially_drop]
 pub enum ValType {
 	Bool,
 	Null,
@@ -87,10 +88,6 @@
 	Arr,
 	Obj,
 	Func,
-}
-impl Finalize for ValType {}
-unsafe impl Trace for ValType {
-	unsafe_empty_trace!();
 }
 
 impl ValType {
@@ -114,7 +111,8 @@
 	}
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Trace)]
+#[trivially_drop]
 pub enum ComplexValType {
 	Any,
 	Char,
@@ -127,10 +125,6 @@
 	UnionRef(&'static [ComplexValType]),
 	Sum(Vec<ComplexValType>),
 	SumRef(&'static [ComplexValType]),
-}
-impl Finalize for ComplexValType {}
-unsafe impl Trace for ComplexValType {
-	unsafe_empty_trace!();
 }
 
 impl From<ValType> for ComplexValType {