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

difftreelog

refactor nix-eval macro-support

loprwqwnYaroslav Bolyukin2026-03-12parent: #493bea4.patch.diff
in: trunk

3 files changed

modifiedcmds/repl-plugin-unstable/src/lib.rsdiffbeforeafterboth
--- a/cmds/repl-plugin-unstable/src/lib.rs
+++ b/cmds/repl-plugin-unstable/src/lib.rs
@@ -1,5 +1,6 @@
 use fleet_base::primops::init_primops;
 
+/// SAFETY: expected plugin dynamic library entry point
 #[unsafe(no_mangle)]
 fn nix_plugin_entry() {
 	init_primops();
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
before · crates/nix-eval/src/lib.rs
1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::ptr::{null, null_mut};5use std::sync::{Arc, LazyLock, OnceLock};6use std::{array, fmt, slice};7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use itertools::Itertools;11use serde::Serialize;12use serde::de::DeserializeOwned;13use std::mem::transmute;1415pub use anyhow::Result;16use tracing::{Instrument, info, instrument, warn};1718use self::logging::{ErrorInfoBuilder, nix_logging_cxx};19use self::nix_cxx::set_fetcher_setting;20use self::nix_raw::{21	BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,22	GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,23	GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,24	PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,25	bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,26	clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,27	err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,28	eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,29	expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,30	flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,31	flake_reference_and_fragment_from_string, flake_reference_parse_flags,32	flake_reference_parse_flags_free, flake_reference_parse_flags_new,33	flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,34	flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,35	get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,36	init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,37	list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,38	make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,39	realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,40	realised_string_get_store_path, realised_string_get_store_path_count, register_primop,41	set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,42	store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,43};4445// Contains macros helpers46pub mod logging;47#[doc(hidden)]48pub mod macros;49pub mod util;5051#[allow(52	non_upper_case_globals,53	non_camel_case_types,54	non_snake_case,55	dead_code56)]57mod nix_raw {58	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));59}60#[cxx::bridge]61pub mod nix_cxx {62	unsafe extern "C++" {63		type nix_fetchers_settings;64		include!("nix-eval/src/lib.hh");6566		#[allow(clippy::missing_safety_doc)]67		unsafe fn set_fetcher_setting(68			settings: *mut nix_fetchers_settings,69			setting: *const c_char,70			value: *const c_char,71		);72	}73}7475#[derive(Debug, PartialEq, Eq)]76pub enum NixType {77	Thunk,78	Int,79	Float,80	Bool,81	String,82	Path,83	Null,84	Attrs,85	List,86	Function,87	External,88}89impl NixType {90	fn from_int(c: c_uint) -> Self {91		match c {92			0 => Self::Thunk,93			1 => Self::Int,94			2 => Self::Float,95			3 => Self::Bool,96			4 => Self::String,97			5 => Self::Path,98			6 => Self::Null,99			7 => Self::Attrs,100			8 => Self::List,101			9 => Self::Function,102			10 => Self::External,103			_ => unreachable!("unknown nix type: {c}"),104		}105	}106}107108enum FunctorKind {109	Function,110	Functor,111}112113#[derive(Debug)]114#[repr(i32)]115pub enum NixErrorKind {116	Unknown = err_NIX_ERR_UNKNOWN,117	Overflow = err_NIX_ERR_OVERFLOW,118	Key = err_NIX_ERR_KEY,119	Generic = err_NIX_ERR_NIX_ERROR,120}121impl NixErrorKind {122	fn from_int(v: c_int) -> Option<Self> {123		Some(match v {124			0 => return None,125			nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,126			nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,127			nix_raw::err_NIX_ERR_KEY => Self::Key,128			nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,129			_ => {130				debug_assert!(false, "unexpected nix error kind: {v}");131				Self::Unknown132			}133		})134	}135}136137pub fn gc_now() {138	unsafe { gc_now_raw() };139}140141pub fn gc_register_my_thread() {142	assert_eq!(unsafe { GC_thread_is_registered() }, 0);143144	let mut sb = GC_stack_base {145		mem_base: null_mut(),146	};147	let r = unsafe { GC_get_stack_base(&mut sb) };148	if r as u32 != GC_SUCCESS {149		panic!("failed to get thread stack base");150	}151	unsafe { GC_register_my_thread(&sb) };152}153pub fn gc_unregister_my_thread() {154	assert_eq!(unsafe { GC_thread_is_registered() }, 1);155156	unsafe { GC_unregister_my_thread() };157}158159pub struct ThreadRegisterGuard {}160impl ThreadRegisterGuard {161	#[allow(clippy::new_without_default)]162	pub fn new() -> Self {163		gc_register_my_thread();164		Self {}165	}166}167impl Drop for ThreadRegisterGuard {168	fn drop(&mut self) {169		gc_unregister_my_thread();170	}171}172173#[repr(transparent)]174pub struct NixContext(*mut c_context);175impl NixContext {176	pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {177		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };178	}179	pub fn set_err(&mut self, err: anyhow::Error) {180		let mut fmt = format!("{err:?}").replace("\0", "\\0");181		self.set_err_raw(182			NixErrorKind::Generic,183			&CString::new(fmt).expect("NUL bytes were just replaced"),184		);185	}186	pub fn new() -> Self {187		let ctx = unsafe { c_context_create() };188		Self(ctx)189	}190	fn error_kind(&self) -> Option<NixErrorKind> {191		let code = unsafe { err_code(self.0) };192		NixErrorKind::from_int(code)193	}194	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {195		if let NixErrorKind::Generic = self.error_kind()? {196			let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };197			let mut err_out = String::new();198			unsafe {199				err_info_msg(200					null_mut(),201					self.0,202					Some(copy_nix_str),203					(&raw mut err_out).cast(),204				)205			};206			return Some((Cow::Owned(err_out), Some(ei)));207		};208209		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,210		// but it looks ugly211		let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };212		Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))213	}214	fn clean_err(&mut self) {215		unsafe {216			clear_err(self.0);217		}218	}219220	fn bail_if_error(&self) -> Result<()> {221		if let Some((err, stack)) = self.error() {222			let mut e = Err(anyhow!("{err}"));223			if let Some(stack) = stack {224				for ele in stack.stack_frames {225					e = e.with_context(|| {226						if ele.pos.is_empty() {227							ele.msg228						} else {229							format!("{} at {}", ele.msg, ele.pos)230						}231					})232				}233			}234			return e.context("<nix frames>");235		};236		Ok(())237	}238239	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {240		self.clean_err();241		let o = f(self.0);242		self.bail_if_error()?;243		self.clean_err();244		Ok(o)245	}246}247248impl Default for NixContext {249	fn default() -> Self {250		Self::new()251	}252}253impl Drop for NixContext {254	fn drop(&mut self) {255		unsafe {256			c_context_free(self.0);257		}258	}259}260struct GlobalState {261	// Store should be valid as long as EvalState is valid262	#[allow(dead_code)]263	store: Store,264	state: EvalState,265}266impl GlobalState {267	fn new() -> Result<Self> {268		let mut ctx = NixContext::new();269		let store = ctx270			.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })271			.map(Store)?;272273		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;274		ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;275		ctx.run_in_context(|c| unsafe {276			eval_state_builder_set_eval_setting(277				c,278				builder,279				c"lazy-trees".as_ptr(),280				c"true".as_ptr(),281			)282		})?;283		ctx.run_in_context(|c| unsafe {284			eval_state_builder_set_eval_setting(285				c,286				builder,287				c"lazy-locks".as_ptr(),288				c"true".as_ptr(),289			)290		})?;291		let state = ctx292			.run_in_context(|c| unsafe { eval_state_build(c, builder) })293			.map(EvalState)?;294295		Ok(Self { store, state })296	}297}298299struct ThreadState {300	ctx: NixContext,301}302impl ThreadState {303	fn new() -> Result<Self> {304		let ctx = NixContext::new();305306		Ok(Self { ctx })307	}308}309310static GLOBAL_STATE: LazyLock<GlobalState> =311	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));312313thread_local! {314	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));315}316fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {317	let global = &GLOBAL_STATE.state;318	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));319	let mut ctx = NixContext(ctx);320	let v = ctx.run_in_context(|c| f(c, state));321	// It is reused for thread322	std::mem::forget(ctx);323	v324}325326pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {327	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())328}329330pub struct FetchSettings(*mut fetchers_settings);331impl FetchSettings {332	pub fn new() -> Self {333		Self::try_new().expect("allocation should not fail")334	}335	fn try_new() -> Result<Self> {336		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)337	}338	pub fn set(&mut self, setting: &CStr, value: &CStr) {339		unsafe {340			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());341		};342	}343}344unsafe impl Send for FetchSettings {}345unsafe impl Sync for FetchSettings {}346347impl Default for FetchSettings {348	fn default() -> Self {349		Self::new()350	}351}352353impl Drop for FetchSettings {354	fn drop(&mut self) {355		unsafe { fetchers_settings_free(self.0) };356	}357}358pub struct FlakeSettings(*mut flake_settings);359impl FlakeSettings {360	pub fn new() -> Result<Self> {361		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)362	}363}364unsafe impl Send for FlakeSettings {}365unsafe impl Sync for FlakeSettings {}366impl Drop for FlakeSettings {367	fn drop(&mut self) {368		unsafe {369			flake_settings_free(self.0);370		}371	}372}373374pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);375impl FlakeReferenceParseFlags {376	pub fn new(settings: &FlakeSettings) -> Result<Self> {377		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })378			.map(Self)379	}380	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {381		with_default_context(|c, _| {382			unsafe {383				flake_reference_parse_flags_set_base_directory(384					c,385					self.0,386					dir.as_ptr().cast(),387					dir.len(),388				)389			};390		})391	}392}393impl Drop for FlakeReferenceParseFlags {394	fn drop(&mut self) {395		unsafe {396			flake_reference_parse_flags_free(self.0);397		}398	}399}400pub struct FlakeLockFlags(*mut flake_lock_flags);401impl FlakeLockFlags {402	pub fn new(settings: &FlakeSettings) -> Result<Self> {403		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })404			.map(Self)?;405		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;406407		Ok(o)408	}409}410impl Drop for FlakeLockFlags {411	fn drop(&mut self) {412		unsafe {413			flake_lock_flags_free(self.0);414		}415	}416}417418unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {419	let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };420	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");421	unsafe { *user_data.cast::<String>() = s.to_owned() };422}423424struct Store(*mut c_store);425unsafe impl Send for Store {}426unsafe impl Sync for Store {}427428impl Store {429	fn parse_path(&self, path: &CStr) -> Result<StorePath> {430		with_default_context(|c, _| {431			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })432		})433	}434}435436#[repr(transparent)]437pub struct EvalState(*mut c_eval_state);438unsafe impl Send for EvalState {}439unsafe impl Sync for EvalState {}440441impl Drop for EvalState {442	fn drop(&mut self) {443		unsafe {444			state_free(self.0);445		}446	}447}448449pub struct FlakeReference(*mut flake_reference);450impl FlakeReference {451	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]452	pub fn new(453		s: &str,454		flake: &FlakeSettings,455		parse: &FlakeReferenceParseFlags,456		fetch: &FetchSettings,457	) -> Result<(Self, String)> {458		let mut out = null_mut();459		let mut fragment = String::new();460		// let fetch_settings = fetcher_settings;461		with_default_context(|c, _| unsafe {462			flake_reference_and_fragment_from_string(463				c,464				fetch.0,465				flake.0,466				parse.0,467				s.as_ptr().cast(),468				s.len(),469				&mut out,470				Some(copy_nix_str),471				(&raw mut fragment).cast(),472			)473		})?;474		assert!(!out.is_null());475476		Ok((Self(out), fragment))477	}478	#[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]479	pub fn lock(480		&mut self,481		fetch: &FetchSettings,482		flake: &FlakeSettings,483		lock: &FlakeLockFlags,484	) -> Result<LockedFlake> {485		with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })486			.map(LockedFlake)487	}488}489unsafe impl Send for FlakeReference {}490unsafe impl Sync for FlakeReference {}491492pub struct LockedFlake(*mut locked_flake);493impl LockedFlake {494	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {495		with_default_context(|c, es| unsafe {496			locked_flake_get_output_attrs(c, settings.0, es, self.0)497		})498		.map(Value)499	}500}501unsafe impl Send for LockedFlake {}502unsafe impl Sync for LockedFlake {}503impl Drop for LockedFlake {504	fn drop(&mut self) {505		unsafe {506			locked_flake_free(self.0);507		};508	}509}510511type FieldName = [u8; 64];512fn init_field_name(v: &str) -> FieldName {513	let mut f = [0; 64];514	assert!(v.len() < 64, "max field name is 63 chars");515	assert!(516		v.bytes().all(|v| v != 0),517		"nul bytes are unsupported in field name"518	);519	f[0..v.len()].copy_from_slice(v.as_bytes());520	f521}522523pub struct RealisedString(*mut realised_string);524impl fmt::Debug for RealisedString {525	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {526		self.as_str().fmt(f)527	}528}529530impl RealisedString {531	pub fn as_str(&self) -> &str {532		let len = unsafe { realised_string_get_buffer_size(self.0) };533		let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();534		let data = unsafe { slice::from_raw_parts(data, len) };535		std::str::from_utf8(data).expect("non-utf8 strings not supported")536	}537	pub fn path_count(&self) -> usize {538		unsafe { realised_string_get_store_path_count(self.0) }539	}540	pub fn path(&self, i: usize) -> String {541		assert!(i < self.path_count());542		let path = unsafe { realised_string_get_store_path(self.0, i) };543		let mut err_out = String::new();544		unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };545		err_out546	}547}548549unsafe impl Send for RealisedString {}550impl Drop for RealisedString {551	fn drop(&mut self) {552		unsafe { realised_string_free(self.0) }553	}554}555556#[repr(transparent)]557pub struct Value(*mut value);558559unsafe impl Send for Value {}560unsafe impl Sync for Value {}561562pub trait AsFieldName {563	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;564	fn to_field_name(&self) -> Result<String>;565}566impl AsFieldName for Value {567	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {568		let f = self.to_string()?;569		v(init_field_name(&f))570	}571	fn to_field_name(&self) -> Result<String> {572		self.to_string()573	}574}575impl<E> AsFieldName for E576where577	E: AsRef<str>,578{579	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {580		let f = self.as_ref();581		v(init_field_name(f))582	}583	fn to_field_name(&self) -> Result<String> {584		Ok(self.as_ref().to_owned())585	}586}587588struct AttrsBuilder(*mut c_bindings_builder);589impl AttrsBuilder {590	fn new(capacity: usize) -> Self {591		with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })592			.map(Self)593			.expect("alloc should not fail")594	}595	fn insert(&mut self, k: &impl AsFieldName, v: Value) {596		k.as_field_name(|name| {597			with_default_context(|c, _| unsafe {598				bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);599				// bindings_builder_insert doesn't do incref600			})601		})602		.expect("builder insert shouldn't fail");603	}604}605impl Drop for AttrsBuilder {606	fn drop(&mut self) {607		unsafe { bindings_builder_free(self.0) };608	}609}610611struct ListBuilder(*mut c_list_builder, c_uint);612impl ListBuilder {613	fn new(capacity: usize) -> Self {614		with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })615			.map(|l| Self(l, 0))616			.expect("alloc should not fail")617	}618}619impl ListBuilder {620	fn push(&mut self, v: Value) {621		with_default_context(|c, _| unsafe {622			list_builder_insert(623				c,624				self.0,625				{626					let v = self.1;627					self.1 += 1;628					v629				},630				v.0,631			)632		})633		.expect("list insert shouldn't fail");634	}635}636impl Drop for ListBuilder {637	fn drop(&mut self) {638		unsafe { list_builder_free(self.0) };639	}640}641642impl Value {643	pub fn new_primop(v: NativeFn) -> Self {644		let out = Self::new_uninit();645		with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })646			.expect("primop initialization should not fail");647		out648	}649	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {650		let out = Self::new_uninit();651		let mut b = AttrsBuilder::new(v.len());652		for (k, v) in v {653			b.insert(&k, v);654		}655		with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })656			.expect("attrs initialization should not fail");657658		out659	}660	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {661		let out = Self::new_uninit();662		let mut b = ListBuilder::new(v.len());663		for v in v {664			b.push(v.into());665		}666		with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })667			.expect("list initialization should not fail");668669		out670	}671	fn new_uninit() -> Self {672		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })673			.expect("value allocation should not fail");674		Self(out)675	}676	pub fn new_str(v: &str) -> Self {677		let s = CString::new(v).expect("string should not contain NULs");678		let out = Self::new_uninit();679		// String is copied, `s` is free to be dropped680		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })681			.expect("string initialization should not fail");682		out683	}684	pub fn new_int(i: i64) -> Self {685		let out = Self::new_uninit();686		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })687			.expect("int initialization should not fail");688		out689	}690	pub fn new_bool(v: bool) -> Self {691		let out = Self::new_uninit();692		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })693			.expect("bool initialization should not fail");694		out695	}696	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless697	// fn force(&mut self, st: &mut EvalState) -> Result<()> {698	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;699	// 	Ok(())700	// }701	pub fn type_of(&self) -> NixType {702		let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })703			.expect("get_type should not fail");704		NixType::from_int(ty)705	}706	fn builtin_to_string(&self) -> Result<Self> {707		let builtin = Self::eval("builtins.toString")?;708		builtin.call(self.clone())709	}710	fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {711		with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;712		Ok(())713	}714	pub fn to_string(&self) -> Result<String> {715		let ty = self.type_of();716		if !matches!(ty, NixType::String) {717			bail!("unexpected type: {ty:?}, expected string");718		}719		let mut str_out = String::new();720		with_default_context(|c, _| unsafe {721			get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())722		})?;723724		Ok(str_out)725	}726	pub fn to_realised_string(&self) -> Result<RealisedString> {727		with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })728			.map(RealisedString)729730		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };731		// for i in 0..store_paths {732		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };733		// 	nix_raw::store_path_name(store_path, callback, user_data);734		// }735		// dbg!(store_paths);736		// todo!();737	}738739	pub fn has_field(&self, field: &str) -> Result<bool> {740		if !matches!(self.type_of(), NixType::Attrs) {741			bail!("invalid type: expected attrs");742		}743744		let f = init_field_name(field);745		with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })746	}747	// pub fn derivation_path(&self) {748	// 	nix_raw::real749	// }750	pub fn list_fields(&self) -> Result<Vec<String>> {751		if !matches!(self.type_of(), NixType::Attrs) {752			bail!("invalid type: expected attrs");753		}754755		let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;756		let mut out = Vec::with_capacity(len as usize);757758		for i in 0..len {759			let name =760				with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;761			let c = unsafe { CStr::from_ptr(name) };762			out.push(c.to_str().expect("nix field names are utf-8").to_owned());763		}764		Ok(out)765	}766	pub fn get_elem(&self, v: usize) -> Result<Self> {767		if !matches!(self.type_of(), NixType::List) {768			bail!("invalid type: expected list");769		}770		let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;771		if v >= len {772			bail!("oob list get: {v} >= {len}");773		}774775		with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)776	}777	pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {778		let attrs_update_fn = Self::eval("a: b: a // b")?;779780		attrs_update_fn781			.call(self)?782			.call(other)783			.context("attrs update")784	}785	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {786		if !matches!(self.type_of(), NixType::Attrs) {787			bail!("invalid type: expected attrs");788		}789790		name.as_field_name(|name| {791			with_default_context(|c, es| unsafe {792				get_attr_byname(c, self.0, es, name.as_ptr().cast())793			})794			.map(Self)795		})796		.with_context(|| format!("getting field {:?}", name.to_field_name()))797	}798	pub fn call(&self, v: Value) -> Result<Self> {799		let kind = self800			.functor_kind()801			.ok_or_else(|| anyhow!("can only call function or functor"))?;802803		let function = match kind {804			FunctorKind::Function => self.clone(),805			FunctorKind::Functor => {806				let f = self807					.get_field("__functor")808					.context("getting functor value")?;809				assert_eq!(810					f.type_of(),811					NixType::Function,812					"invalid functor encountered"813				);814				f815			}816		};817818		let out = Value::new_uninit();819		with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;820821		Ok(out)822	}823	pub fn eval(v: &str) -> Result<Self> {824		let s = CString::new(v).expect("expression shouldn't have internal NULs");825		let out = Self::new_uninit();826		with_default_context(|c, es| unsafe {827			expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)828		})?;829		Ok(out)830	}831	pub fn build(&self, output: &str) -> Result<PathBuf> {832		if !self.is_derivation() {833			bail!("expected derivation to build")834		}835		let output_name = self836			.get_field("outputName")837			.context("getting output name field")?838			.to_string()?;839		let v = if output_name != output {840			let out = self.get_field(output).context("getting target output")?;841			if !out.is_derivation() {842				bail!("unknown output: {output}");843			}844			out845		} else {846			self.clone()847		};848		// to_string here blocks until the path is built849		let s = v.builtin_to_string()?;850		let rs = s.to_realised_string()?;851		let drv_path = rs.as_str().to_owned();852		Ok(PathBuf::from(drv_path))853	}854	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {855		let to_json = Self::eval("builtins.toJSON")?;856		let s = to_json.call(self.clone())?.to_string()?;857		Ok(serde_json::from_str(&s)?)858	}859	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {860		Self::eval(&nixlike::serialize(v)?)861	}862863	// Convert to string/evaluate derivations/etc864	// fn to_string_weak(&self) -> Result<String> {865	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()866	// 	self.to_string()867	// }868869	fn is_derivation(&self) -> bool {870		if !matches!(self.type_of(), NixType::Attrs) {871			return false;872		}873		let Some(ty) = self.get_field("type").ok() else {874			return false;875		};876		matches!(ty.to_string().as_deref(), Ok("derivation"))877	}878	fn functor_kind(&self) -> Option<FunctorKind> {879		match self.type_of() {880			NixType::Attrs => self881				.has_field("__functor")882				.expect("has_field shouldn't fail for attrs")883				.then_some(FunctorKind::Functor),884			NixType::Function => Some(FunctorKind::Function),885			_ => None,886		}887	}888	pub fn is_function(&self) -> bool {889		self.functor_kind().is_some()890	}891	pub fn is_null(&self) -> bool {892		matches!(self.type_of(), NixType::Null)893	}894	pub fn is_string(&self) -> bool {895		matches!(self.type_of(), NixType::String)896	}897	pub fn is_attrs(&self) -> bool {898		matches!(self.type_of(), NixType::Attrs)899	}900}901902impl From<String> for Value {903	fn from(value: String) -> Self {904		Value::new_str(&value)905	}906}907impl From<bool> for Value {908	fn from(value: bool) -> Self {909		Value::new_bool(value)910	}911}912impl From<&str> for Value {913	fn from(value: &str) -> Self {914		Value::new_str(value)915	}916}917impl<T> From<Vec<T>> for Value918where919	T: Into<Value>,920{921	fn from(value: Vec<T>) -> Self {922		Value::new_list(value)923	}924}925926impl Clone for Value {927	fn clone(&self) -> Self {928		with_default_context(|c, _| unsafe { value_incref(c, self.0) })929			.expect("value incref should not fail");930		Self(self.0)931	}932}933impl Drop for Value {934	fn drop(&mut self) {935		with_default_context(|c, _| unsafe { value_decref(c, self.0) })936			.expect("value drop should not fail");937	}938}939940static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();941942pub fn init_libraries() {943	unsafe { GC_allow_register_threads() };944945	let mut ctx = NixContext::new();946	ctx.run_in_context(|c| unsafe { libutil_init(c) })947		.expect("util init should not fail");948	ctx.run_in_context(|c| unsafe { libstore_init(c) })949		.expect("store init should not fail");950	ctx.run_in_context(|c| unsafe { libexpr_init(c) })951		.expect("expr init should not fail");952953	nix_logging_cxx::apply_tracing_logger();954}955956pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {957	TOKIO_FOR_NIX958		.set(tokio)959		.expect("tokio for nix should only be initialized once");960}961962pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {963	// It should be possible to do Handle::current(), but some of the planned features don't work well with that964	let runtime = TOKIO_FOR_NIX965		.get()966		.expect("init_tokio_for_nix was not called");967	std::thread::spawn(move || runtime.block_on(f))968		.join()969		.expect("await_in_nix inner thread panicked")970}971972unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(973	user_data: *mut c_void,974	mut context: *mut c_context,975	state: *mut nix_raw::EvalState,976	args: *mut *mut value,977	ret: *mut value,978) {979	let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };980	let args: [&Value; N] = array::from_fn(|i| {981		let v: &mut Value = unsafe { &mut *args.add(i).cast() };982		v as &Value983	});984	let ctx: &mut NixContext = unsafe { transmute(&mut context) };985986	let state: &EvalState = unsafe { std::mem::transmute(&state) };987988	match user_closure(state, args) {989		Ok(v) => {990			unsafe { copy_value(context, ret, v.0) };991		}992		Err(e) => {993			ctx.set_err(e);994		}995	}996}997998type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;9991000pub struct NativeFn(*mut PrimOp);1001impl NativeFn {1002	pub fn new<const N: usize>(1003		name: &'static CStr,1004		doc: &'static CStr,1005		args: [&'static CStr; N],1006		f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1007	) -> Self {1008		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1009		let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1010		let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1011		let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1012		args.push(null());1013		let args = args.as_mut_ptr();1014		let primop = unsafe {1015			alloc_primop(1016				null_mut(),1017				f,1018				N as i32,1019				name.as_ptr(),1020				args,1021				doc.as_ptr(),1022				Box::into_raw(closure).cast(),1023			)1024		};10251026		assert!(!primop.is_null(), "primop allocation should not fail");10271028		Self(primop)1029	}1030	pub fn register(self) {1031		unsafe { register_primop(null_mut(), self.0) };1032	}1033}10341035struct StorePath(*mut c_store_path);1036impl StorePath {}10371038impl Drop for StorePath {1039	fn drop(&mut self) {1040		unsafe { store_path_free(self.0) }1041	}1042}10431044#[test_log::test]1045fn test_native() -> Result<()> {1046	init_libraries();1047	NativeFn::new(1048		c"__uppercaseSuffix2",1049		c"make string uppercase and add suffix",1050		[c"str", c"suffix"],1051		|_, [str, suffix]: [&Value; 2]| {1052			let str = str.to_string()?;1053			let suffix = suffix.to_string()?;1054			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1055		},1056	)1057	.register();10581059	let mut fetch_settings = FetchSettings::new();1060	fetch_settings.set(c"warn-dirty", c"false");10611062	let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1063	let flake = FlakeSettings::new()?;1064	let parse = FlakeReferenceParseFlags::new(&flake)?;1065	let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1066	let lock = FlakeLockFlags::new(&flake)?;1067	let locked = r.lock(&fetch_settings, &flake, &lock)?;1068	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;10691070	let builtins = Value::eval("builtins")?;1071	assert_eq!(builtins.type_of(), NixType::Attrs);10721073	assert_eq!(attrs.type_of(), NixType::Attrs);1074	let test_data = nix_go!(attrs.testData);10751076	let test_string: String = nix_go_json!(test_data.testString);1077	assert_eq!(test_string, "hello");10781079	let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1080	let s = CString::new(s.to_string()?).expect("path str is cstring");10811082	let uppercase_suffix = Value::new_primop(NativeFn::new(1083		c"uppercase_suffix",1084		c"make string uppercase and add suffix",1085		[c"str", c"suffix"],1086		|es, [str, suffix]: [&Value; 2]| {1087			let str = str.to_string()?;1088			let suffix = suffix.to_string()?;1089			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1090		},1091	));10921093	let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1094	assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1095	let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1096	assert_eq!(test_result, "TESTsuffix");10971098	let nix_ctx = NixContext::new();1099	let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;11001101	// nix_raw::store_get_fs_closure(1);11021103	Ok(())1104}11051106// pub struct GcAlloc;1107// unsafe impl GlobalAlloc for GcAlloc {1108// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1109// 		let ptr = unsafe { GC_malloc(l.size()) };1110// 		ptr.cast()1111// 	}1112// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1113// 		// unsafe { GC_free(ptr.cast()) };1114// 	}1115//1116// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1117// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1118// 		ptr.cast()1119// 	}1120// }1121//1122// #[global_allocator]1123// static GC: GcAlloc = GcAlloc;
modifiedcrates/nix-eval/src/macros.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/macros.rs
+++ b/crates/nix-eval/src/macros.rs
@@ -18,12 +18,12 @@
 	(@obj($o:ident)) => {{}};
 	(Obj { }) => {{
 		use $crate::{nix_expr_inner};
-		let out = std::collections::hash_map::HashMap::new();
+		let out = $crate::__macro_support::HashMap::new();
 		Value::new_attrs(out)
 	}};
 	(Obj { $($tt:tt)+ }) => {{
 		use $crate::{nix_expr_inner};
-		let mut out = std::collections::hash_map::HashMap::new();
+		let mut out = $crate::__macro_support::HashMap::new();
 		nix_expr_inner!(@obj(out) $($tt)*);
 		Value::new_attrs(out)
 	}};
@@ -68,25 +68,25 @@
 #[macro_export]
 macro_rules! nix_go {
 	(@o($o:expr, $path:expr) . $var:ident $($tt:tt)*) => {{
-		nix_go!(@o(tokio::task::block_in_place(|| $o.get_field(stringify!($var))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+		nix_go!(@o($crate::__macro_support::block_in_place(|| $o.get_field(stringify!($var))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
 	}};
 	(@o($o:expr, $path:expr) [ $v:expr ] $($tt:tt)*) => {{
-		nix_go!(@o(tokio::task::block_in_place(|| $o.get_field($v)).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+		nix_go!(@o($crate::__macro_support::block_in_place(|| $o.get_field($v)).context(concat!("getting nested ", $path))?, $path) $($tt)*)
 	}};
 	(@o($o:expr, $path:expr) ($($var:tt)*) $($tt:tt)*) => {
-		nix_go!(@o(tokio::task::block_in_place(|| $o.call($crate::nix_expr_inner!($($var)+))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+		nix_go!(@o($crate::__macro_support::block_in_place(|| $o.call($crate::nix_expr_inner!($($var)+))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
 	};
 	(@o($o:expr, $path:expr)) => {$o};
 	($field:ident $($tt:tt)+) => {{
 		use $crate::nix_go;
-		use ::anyhow::Context;
+		use $crate::__macro_support::Context;
 		let out = $field.clone();
-		nix_go!(@o(out, ::std::stringify!($($tt)*)) $($tt)*)
+		nix_go!(@o(out, stringify!($($tt)*)) $($tt)*)
 	}}
 }
 #[macro_export]
 macro_rules! nix_go_json {
 	($($tt:tt)*) => {{
-		tokio::task::block_in_place(|| $crate::nix_go!($($tt)*).as_json())?
+		$crate::__macro_support::block_in_place(|| $crate::nix_go!($($tt)*).as_json())?
 	}};
 }