1use std::cell::{Cell, OnceCell, RefCell, UnsafeCell};
8
9use std::fmt;
10pub use std::rc::Rc;
11
12use rustc_hash::FxBuildHasher;
13use smallvec::SmallVec;
14pub use smol_str::SmolStr;
15
16use rowan::ast::AstNode;
17
18use sui_intern::Symbol;
19
20pub type FxHashMap<K, V> = im_rc::HashMap<K, V, FxBuildHasher>;
26
27pub type AttrsMap<K, V> = std::collections::HashMap<K, V, FxBuildHasher>;
42
43pub mod census {
61 use std::sync::atomic::{AtomicI64, Ordering::Relaxed};
62 use std::sync::OnceLock;
63
64 pub static ATTRS_LIVE: AtomicI64 = AtomicI64::new(0);
65 pub static ATTRS_MADE: AtomicI64 = AtomicI64::new(0);
66 pub static THUNK_LIVE: AtomicI64 = AtomicI64::new(0);
67 pub static THUNK_MADE: AtomicI64 = AtomicI64::new(0);
68 pub static THUNK_EVALUATED: AtomicI64 = AtomicI64::new(0);
69 pub static ENV_LIVE: AtomicI64 = AtomicI64::new(0);
70 pub static ENV_MADE: AtomicI64 = AtomicI64::new(0);
71 pub static NIXSTR_LIVE: AtomicI64 = AtomicI64::new(0);
72 pub static NIXSTR_MADE: AtomicI64 = AtomicI64::new(0);
73 pub static LIST_LIVE: AtomicI64 = AtomicI64::new(0);
74 pub static LIST_MADE: AtomicI64 = AtomicI64::new(0);
75
76 pub static SCOPE_THUNKS_NARROWED: AtomicI64 = AtomicI64::new(0);
90 pub static SCOPE_THUNKS_PINNED: AtomicI64 = AtomicI64::new(0);
91
92 #[inline(always)]
94 pub fn scope_narrowed() {
95 if enabled() {
96 SCOPE_THUNKS_NARROWED.fetch_add(1, Relaxed);
97 }
98 }
99
100 #[inline(always)]
102 pub fn scope_pinned() {
103 if enabled() {
104 SCOPE_THUNKS_PINNED.fetch_add(1, Relaxed);
105 }
106 }
107
108 #[inline]
110 pub fn enabled() -> bool {
111 static ON: OnceLock<bool> = OnceLock::new();
112 *ON.get_or_init(|| std::env::var("SUI_LIVE_CENSUS").as_deref() == Ok("1"))
113 }
114
115 #[inline(always)]
116 pub fn made(made: &AtomicI64, live: &AtomicI64) {
117 if enabled() {
118 made.fetch_add(1, Relaxed);
119 live.fetch_add(1, Relaxed);
120 }
121 }
122
123 #[inline(always)]
124 pub fn dropped(live: &AtomicI64) {
125 if enabled() {
126 live.fetch_sub(1, Relaxed);
127 }
128 }
129
130 #[inline(always)]
131 pub fn evaluated() {
132 if enabled() {
133 THUNK_EVALUATED.fetch_add(1, Relaxed);
134 }
135 }
136
137 pub fn rss_bytes() -> u64 {
139 #[cfg(target_os = "macos")]
140 unsafe {
141 let mut info: libc::mach_task_basic_info = std::mem::zeroed();
142 let mut count = (std::mem::size_of::<libc::mach_task_basic_info>()
143 / std::mem::size_of::<libc::natural_t>()) as libc::mach_msg_type_number_t;
144 let kr = libc::task_info(
145 libc::mach_task_self(),
146 libc::MACH_TASK_BASIC_INFO,
147 std::ptr::addr_of_mut!(info).cast(),
148 &mut count,
149 );
150 if kr == libc::KERN_SUCCESS {
151 return info.resident_size;
152 }
153 0
154 }
155 #[cfg(not(target_os = "macos"))]
156 {
157 std::fs::read_to_string("/proc/self/statm")
158 .ok()
159 .and_then(|s| s.split_whitespace().nth(1).map(String::from))
160 .and_then(|pages| pages.parse::<u64>().ok())
161 .map(|pages| pages * 4096)
162 .unwrap_or(0)
163 }
164 }
165
166 pub fn dump(tag: &str) {
179 if !enabled() {
180 return;
181 }
182 let rss = rss_bytes();
183 eprintln!(
184 "[census {tag}] rss={rss_mb:.1}MB \
185attrs_live={al} attrs_made={am} \
186thunk_live={tl} thunk_made={tm} thunk_eval={te} \
187env_live={el} env_made={em} \
188nixstr_live={sl} nixstr_made={sm} \
189list_live={ll} list_made={lm} \
190scope_narrowed={sn} scope_pinned={sp}",
191 rss_mb = rss as f64 / (1024.0 * 1024.0),
192 al = ATTRS_LIVE.load(Relaxed),
193 am = ATTRS_MADE.load(Relaxed),
194 tl = THUNK_LIVE.load(Relaxed),
195 tm = THUNK_MADE.load(Relaxed),
196 te = THUNK_EVALUATED.load(Relaxed),
197 el = ENV_LIVE.load(Relaxed),
198 em = ENV_MADE.load(Relaxed),
199 sl = NIXSTR_LIVE.load(Relaxed),
200 sm = NIXSTR_MADE.load(Relaxed),
201 ll = LIST_LIVE.load(Relaxed),
202 lm = LIST_MADE.load(Relaxed),
203 sn = SCOPE_THUNKS_NARROWED.load(Relaxed),
204 sp = SCOPE_THUNKS_PINNED.load(Relaxed),
205 );
206 let (src_files, src_bytes) = crate::pos::source_text_census();
207 eprintln!(
208 "[census {tag}] src_files={src_files} src_bytes={src_mb:.1}MB",
209 src_mb = src_bytes as f64 / (1024.0 * 1024.0),
210 );
211 }
212
213 pub fn spawn_poller() {
217 if !enabled() {
218 return;
219 }
220 std::thread::spawn(|| loop {
221 std::thread::sleep(std::time::Duration::from_millis(2000));
222 dump("periodic");
223 });
224 }
225}
226
227pub fn intern(s: &str) -> Symbol {
240 sui_intern::intern(s)
241}
242
243pub fn resolve(sym: Symbol) -> String {
248 sui_intern::resolve(sym)
249}
250
251pub fn resolve_rc(sym: Symbol) -> std::rc::Rc<str> {
253 sui_intern::resolve_rc(sym)
254}
255
256pub fn with_resolved<F, R>(sym: Symbol, f: F) -> R
258where
259 F: FnOnce(&str) -> R,
260{
261 sui_intern::with_resolved(sym, f)
262}
263
264thread_local! {
275 static SOURCE_GEN: Cell<u32> = const { Cell::new(1) };
285
286 static IDENT_CACHE: RefCell<rustc_hash::FxHashMap<u64, Symbol>> =
288 RefCell::new(rustc_hash::FxHashMap::default());
289}
290
291pub fn next_source_id() -> u32 {
297 SOURCE_GEN.with(|g| {
298 let id = g.get();
299 g.set(id.wrapping_add(1));
300 id
301 })
302}
303
304pub fn intern_cached(name: &str, source_id: u32, text_offset: u32) -> Symbol {
310 intern_cached_with(source_id, text_offset, || intern(name))
311}
312
313pub fn intern_cached_with<F>(source_id: u32, text_offset: u32, cold: F) -> Symbol
321where
322 F: FnOnce() -> Symbol,
323{
324 let key = (u64::from(source_id) << 32) | u64::from(text_offset);
325 IDENT_CACHE.with(|c| {
326 let mut cache = c.borrow_mut();
327 *cache.entry(key).or_insert_with(cold)
328 })
329}
330
331pub fn clear_ident_cache() {
336 IDENT_CACHE.with(|c| c.borrow_mut().clear());
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
343pub enum ContextElement {
344 Plain(SmolStr),
346 Output { drv: SmolStr, output: SmolStr },
348 DrvDeep(SmolStr),
350}
351
352impl fmt::Display for ContextElement {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 match self {
355 ContextElement::Plain(p) => write!(f, "{p}"),
356 ContextElement::Output { drv, output } => write!(f, "{drv}!{output}"),
357 ContextElement::DrvDeep(d) => write!(f, "={d}"),
358 }
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Default)]
370pub struct StringContext(SmallVec<[ContextElement; 2]>);
371
372impl StringContext {
373 pub fn new() -> Self {
375 Self(SmallVec::new())
376 }
377
378 pub fn merge(&mut self, other: &StringContext) {
380 for elem in &other.0 {
381 if !self.0.contains(elem) {
382 self.0.push(elem.clone());
383 }
384 }
385 }
386
387 pub fn add_plain(&mut self, path: impl Into<SmolStr>) {
389 let elem = ContextElement::Plain(path.into());
390 if !self.0.contains(&elem) {
391 self.0.push(elem);
392 }
393 }
394
395 pub fn add_output(&mut self, drv: impl Into<SmolStr>, output: impl Into<SmolStr>) {
397 let elem = ContextElement::Output { drv: drv.into(), output: output.into() };
398 if !self.0.contains(&elem) {
399 self.0.push(elem);
400 }
401 }
402
403 pub fn add_drv_deep(&mut self, drv: impl Into<SmolStr>) {
405 let elem = ContextElement::DrvDeep(drv.into());
406 if !self.0.contains(&elem) {
407 self.0.push(elem);
408 }
409 }
410
411 #[must_use]
413 pub fn is_empty(&self) -> bool {
414 self.0.is_empty()
415 }
416
417 #[must_use]
419 pub fn len(&self) -> usize {
420 self.0.len()
421 }
422
423 pub fn iter(&self) -> impl Iterator<Item = &ContextElement> {
425 self.0.iter()
426 }
427
428 pub fn insert(&mut self, elem: ContextElement) {
430 if !self.0.contains(&elem) {
431 self.0.push(elem);
432 }
433 }
434
435 pub fn elements(&self) -> &[ContextElement] {
437 &self.0
438 }
439}
440
441#[derive(Debug, PartialEq, Eq)]
443pub struct NixString {
444 pub chars: SmolStr,
446 pub context: StringContext,
448}
449
450impl Clone for NixString {
454 fn clone(&self) -> Self {
455 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
456 Self {
457 chars: self.chars.clone(),
458 context: self.context.clone(),
459 }
460 }
461}
462
463impl Drop for NixString {
464 fn drop(&mut self) {
465 census::dropped(&census::NIXSTR_LIVE);
466 }
467}
468
469impl NixString {
470 pub fn plain(s: impl Into<SmolStr>) -> Self {
472 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
473 Self {
474 chars: s.into(),
475 context: StringContext::default(),
476 }
477 }
478
479 pub fn with_context(s: impl Into<SmolStr>, ctx: StringContext) -> Self {
481 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
482 Self {
483 chars: s.into(),
484 context: ctx,
485 }
486 }
487
488 #[must_use]
490 pub fn as_str(&self) -> &str {
491 &self.chars
492 }
493
494 #[must_use]
496 pub fn has_context(&self) -> bool {
497 !self.context.is_empty()
498 }
499}
500
501impl AsRef<str> for NixString {
502 fn as_ref(&self) -> &str {
503 &self.chars
504 }
505}
506
507#[repr(transparent)]
514#[derive(Debug, PartialEq)]
515pub struct NixList(pub Vec<Value>);
516
517impl NixList {
518 #[inline]
519 pub fn new(v: Vec<Value>) -> Self {
520 census::made(&census::LIST_MADE, &census::LIST_LIVE);
521 NixList(v)
522 }
523
524 #[inline]
528 pub fn into_vec(mut self) -> Vec<Value> {
529 std::mem::take(&mut self.0)
530 }
531}
532
533impl From<Vec<Value>> for NixList {
534 #[inline]
535 fn from(v: Vec<Value>) -> Self {
536 NixList::new(v)
537 }
538}
539
540impl<T: AsRef<[Value]>> PartialEq<T> for NixList {
542 #[inline]
543 fn eq(&self, other: &T) -> bool {
544 self.0.as_slice() == other.as_ref()
545 }
546}
547
548impl Clone for NixList {
549 fn clone(&self) -> Self {
550 census::made(&census::LIST_MADE, &census::LIST_LIVE);
551 NixList(self.0.clone())
552 }
553}
554
555impl Drop for NixList {
556 fn drop(&mut self) {
557 census::dropped(&census::LIST_LIVE);
558 }
559}
560
561impl FromIterator<Value> for NixList {
562 #[inline]
563 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
564 NixList::new(iter.into_iter().collect())
565 }
566}
567
568impl std::ops::Deref for NixList {
569 type Target = Vec<Value>;
570 #[inline]
571 fn deref(&self) -> &Vec<Value> {
572 &self.0
573 }
574}
575
576impl std::ops::DerefMut for NixList {
577 #[inline]
578 fn deref_mut(&mut self) -> &mut Vec<Value> {
579 &mut self.0
580 }
581}
582
583impl<'a> IntoIterator for &'a NixList {
584 type Item = &'a Value;
585 type IntoIter = std::slice::Iter<'a, Value>;
586 #[inline]
587 fn into_iter(self) -> Self::IntoIter {
588 self.0.iter()
589 }
590}
591
592impl IntoIterator for NixList {
593 type Item = Value;
594 type IntoIter = std::vec::IntoIter<Value>;
595 #[inline]
596 fn into_iter(mut self) -> Self::IntoIter {
597 std::mem::take(&mut self.0).into_iter()
601 }
602}
603
604impl std::ops::Deref for NixString {
605 type Target = str;
606
607 fn deref(&self) -> &str {
608 &self.chars
609 }
610}
611
612impl fmt::Display for NixString {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 write!(f, "{}", self.chars)
615 }
616}
617
618#[derive(Debug, Clone)]
626#[derive(Default)]
627pub enum Value {
628 #[default]
629 Null,
630 Bool(bool),
631 Int(i64),
632 Float(f64),
633 String(Rc<NixString>),
634 Path(Box<SmolStr>),
635 List(Rc<NixList>),
636 Attrs(Rc<NixAttrs>),
637 Lambda(Rc<Closure>),
638 Builtin(Box<BuiltinFn>),
639 Thunk(Thunk),
641}
642
643#[derive(Debug, Clone)]
659pub enum Concrete {
660 Null,
661 Bool(bool),
662 Int(i64),
663 Float(f64),
664 String(Rc<NixString>),
665 Path(Box<SmolStr>),
666 List(Rc<NixList>), Attrs(Rc<NixAttrs>), Lambda(Rc<Closure>),
669 Builtin(Box<BuiltinFn>),
670 }
672
673impl Concrete {
674 #[inline]
676 pub fn into_value(self) -> Value {
677 match self {
678 Concrete::Null => Value::Null,
679 Concrete::Bool(b) => Value::Bool(b),
680 Concrete::Int(n) => Value::Int(n),
681 Concrete::Float(f) => Value::Float(f),
682 Concrete::String(s) => Value::String(s),
683 Concrete::Path(p) => Value::Path(p),
684 Concrete::List(l) => Value::List(l),
685 Concrete::Attrs(a) => Value::Attrs(a),
686 Concrete::Lambda(c) => Value::Lambda(c),
687 Concrete::Builtin(b) => Value::Builtin(b),
688 }
689 }
690
691 pub fn to_value(&self) -> Value {
694 self.clone().into_value()
695 }
696
697 pub fn as_bool(&self) -> Result<bool, EvalError> {
699 match self {
700 Concrete::Bool(b) => Ok(*b),
701 other => Err(EvalError::TypeMismatch { expected: "bool", got: other.type_name() }),
702 }
703 }
704
705 pub fn as_int(&self) -> Result<i64, EvalError> {
707 match self {
708 Concrete::Int(n) => Ok(*n),
709 other => Err(EvalError::TypeMismatch { expected: "int", got: other.type_name() }),
710 }
711 }
712
713 pub fn as_str(&self) -> Result<&str, EvalError> {
715 match self {
716 Concrete::String(s) => Ok(&s.chars),
717 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
718 }
719 }
720
721 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
723 match self {
724 Concrete::String(s) => Ok(s),
725 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
726 }
727 }
728
729 pub fn as_list(&self) -> Result<&[Value], EvalError> {
732 match self {
733 Concrete::List(l) => Ok(l.as_slice()),
734 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
735 }
736 }
737
738 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
741 match self {
742 Concrete::Attrs(a) => Ok(a),
743 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
744 }
745 }
746
747 pub fn as_float(&self) -> Result<f64, EvalError> {
749 match self {
750 Concrete::Float(f) => Ok(*f),
751 Concrete::Int(n) => Ok(*n as f64),
752 other => Err(EvalError::TypeMismatch { expected: "float", got: other.type_name() }),
753 }
754 }
755
756 pub fn type_name(&self) -> &'static str {
758 match self {
759 Concrete::Null => "null",
760 Concrete::Bool(_) => "bool",
761 Concrete::Int(_) => "int",
762 Concrete::Float(_) => "float",
763 Concrete::String(_) => "string",
764 Concrete::Path(_) => "path",
765 Concrete::List(_) => "list",
766 Concrete::Attrs(_) => "set",
767 Concrete::Lambda(_) | Concrete::Builtin(_) => "lambda",
768 }
769 }
770
771 pub fn as_string(&self) -> Result<&str, EvalError> {
773 self.as_str()
774 }
775
776 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
778 match self {
779 Concrete::Attrs(a) => Ok((**a).clone()),
780 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
781 }
782 }
783
784 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
786 match self {
787 Concrete::List(l) => Ok((**l).0.clone()),
788 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
789 }
790 }
791
792 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
794 match self {
795 Concrete::Path(p) => Ok(p.to_string()),
796 Concrete::String(ns) => Ok(ns.chars.to_string()),
797 Concrete::Attrs(attrs) => {
798 if let Some(out_path) = attrs.get("outPath") {
799 let forced = crate::eval::force_value(out_path)?;
800 forced.coerce_to_path(context)
801 } else {
802 Err(EvalError::type_error(format!(
803 "{context}: expected path or string, got set without outPath"
804 )))
805 }
806 }
807 other => Err(EvalError::type_error(format!(
808 "{context}: expected path or string, got {}", other.type_name()
809 ))),
810 }
811 }
812
813 pub fn to_str(&self) -> Result<String, EvalError> {
815 match self {
816 Concrete::String(s) => Ok(s.chars.to_string()),
817 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
818 }
819 }
820
821 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
823 match self {
824 Concrete::String(s) => Ok((**s).clone()),
825 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
826 }
827 }
828
829 pub fn is_function(&self) -> bool {
831 matches!(self, Concrete::Lambda(_) | Concrete::Builtin(_))
832 }
833}
834
835impl From<Concrete> for Value {
837 fn from(c: Concrete) -> Value {
838 c.into_value()
839 }
840}
841
842impl PartialEq for Concrete {
843 fn eq(&self, other: &Self) -> bool {
844 match (self, other) {
845 (Concrete::Null, Concrete::Null) => true,
846 (Concrete::Bool(a), Concrete::Bool(b)) => a == b,
847 (Concrete::Int(a), Concrete::Int(b)) => a == b,
848 (Concrete::Float(a), Concrete::Float(b)) => a == b,
849 (Concrete::Int(a), Concrete::Float(b)) | (Concrete::Float(b), Concrete::Int(a)) => (*a as f64) == *b,
850 (Concrete::String(a), Concrete::String(b)) => Rc::ptr_eq(a, b) || a.chars == b.chars,
851 (Concrete::Path(a), Concrete::Path(b)) => a == b,
852 (Concrete::List(a), Concrete::List(b)) => Rc::ptr_eq(a, b) || a == b,
853 (Concrete::Attrs(a), Concrete::Attrs(b)) => {
854 if Rc::ptr_eq(a, b) {
855 return true;
856 }
857 if let (Some(pa), Some(pb)) =
868 (derivation_out_path(a), derivation_out_path(b))
869 {
870 return pa == pb;
871 }
872 let (fa, fb) = (a.as_flat(), b.as_flat());
891 if crate::perf::enabled() {
892 crate::perf::inc(crate::perf::Counter::AttrsEqStructuralCalls);
893 crate::perf::add(
896 crate::perf::Counter::AttrsEqEntriesCloneElided,
897 (fa.len() + fb.len()) as u64,
898 );
899 }
900 fa == fb
901 }
902 (Concrete::Lambda(a), Concrete::Lambda(b)) => Rc::ptr_eq(a, b),
903 _ => false,
904 }
905 }
906}
907
908pub fn concat_lists(left: Value, right_elems: &[Value]) -> Result<Value, EvalError> {
924 let mut la = match left {
928 Value::List(rc) => {
929 let reused = Rc::strong_count(&rc) == 1;
930 let vec: Vec<Value> = match Rc::try_unwrap(rc) {
931 Ok(v) => v.into_vec(), Err(rc) => (*rc).0.clone(), };
934 if crate::perf::enabled() {
935 crate::perf::inc(crate::perf::Counter::ListConcatCalls);
936 if reused {
937 crate::perf::add(
939 crate::perf::Counter::ListConcatElemsReused,
940 vec.len() as u64,
941 );
942 } else {
943 crate::perf::add(
945 crate::perf::Counter::ListConcatElemsCopied,
946 vec.len() as u64,
947 );
948 }
949 }
950 vec
951 }
952 other => {
953 return Err(EvalError::TypeMismatch {
954 expected: "list",
955 got: other.type_name(),
956 });
957 }
958 };
959 la.extend_from_slice(right_elems);
961 Ok(Value::list(la))
962}
963
964fn derivation_out_path(attrs: &NixAttrs) -> Option<String> {
970 match attrs.get("type")?.demand().ok()? {
971 Concrete::String(s) if s.chars == "derivation" => {}
972 _ => return None,
973 }
974 match attrs.get("outPath")?.demand().ok()? {
975 Concrete::String(s) => Some(s.chars.to_string()),
976 _ => None,
977 }
978}
979
980fn derivation_drv_and_out(
994 attrs: &NixAttrs,
995) -> Result<Option<(String, String)>, EvalError> {
996 match attrs.get("type") {
998 Some(t) => match crate::eval::force_value(t)? {
999 Value::String(s) if s.chars == "derivation" => {}
1000 _ => return Ok(None),
1001 },
1002 None => return Ok(None),
1003 }
1004 let drv_path = match attrs.get("drvPath") {
1007 Some(d) => crate::eval::force_value(d)?.coerce_to_path("drvPath")?,
1008 None => return Ok(None),
1009 };
1010 let out_path = match attrs.get("outPath") {
1011 Some(o) => crate::eval::force_value(o)?.coerce_to_path("outPath")?,
1012 None => return Ok(None),
1013 };
1014 Ok(Some((drv_path, out_path)))
1015}
1016
1017fn out_path_needs_realize(out_path: &str, ctx: &StringContext) -> Option<String> {
1031 if !out_path.starts_with("/nix/store/") {
1033 return None;
1034 }
1035 for elem in ctx.iter() {
1036 if let ContextElement::Output { drv, output } = elem {
1037 let _ = output; return Some(drv.to_string());
1044 }
1045 }
1046 None
1047}
1048
1049impl Value {
1050 pub(crate) fn demand_unchecked(self) -> Concrete {
1053 match self {
1054 Value::Null => Concrete::Null,
1055 Value::Bool(b) => Concrete::Bool(b),
1056 Value::Int(n) => Concrete::Int(n),
1057 Value::Float(f) => Concrete::Float(f),
1058 Value::String(s) => Concrete::String(s),
1059 Value::Path(p) => Concrete::Path(p),
1060 Value::List(l) => Concrete::List(l),
1061 Value::Attrs(a) => Concrete::Attrs(a),
1062 Value::Lambda(c) => Concrete::Lambda(c),
1063 Value::Builtin(b) => Concrete::Builtin(b),
1064 Value::Thunk(_) => panic!("demand_unchecked called on Thunk"),
1065 }
1066 }
1067}
1068
1069impl Value {
1070 pub fn demand(&self) -> Result<Concrete, EvalError> {
1075 let v = match self {
1076 Value::Thunk(_) => crate::eval::force_value(self)?,
1077 other => other.clone(),
1078 };
1079 match v {
1081 Value::Null => Ok(Concrete::Null),
1082 Value::Bool(b) => Ok(Concrete::Bool(b)),
1083 Value::Int(n) => Ok(Concrete::Int(n)),
1084 Value::Float(f) => Ok(Concrete::Float(f)),
1085 Value::String(s) => Ok(Concrete::String(s)),
1086 Value::Path(p) => Ok(Concrete::Path(p)),
1087 Value::List(l) => Ok(Concrete::List(l)),
1088 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1089 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1090 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1091 Value::Thunk(_) => {
1092 let re_forced = crate::eval::force_value(&v)?;
1096 match re_forced {
1097 Value::Null => Ok(Concrete::Null),
1098 Value::Bool(b) => Ok(Concrete::Bool(b)),
1099 Value::Int(n) => Ok(Concrete::Int(n)),
1100 Value::Float(f) => Ok(Concrete::Float(f)),
1101 Value::String(s) => Ok(Concrete::String(s)),
1102 Value::Path(p) => Ok(Concrete::Path(p)),
1103 Value::List(l) => Ok(Concrete::List(l)),
1104 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1105 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1106 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1107 Value::Thunk(_) => Err(EvalError::InfiniteRecursion(
1108 "demand: thunk chain could not be resolved".to_string(),
1109 )),
1110 }
1111 }
1112 }
1113 }
1114}
1115
1116#[cfg(target_pointer_width = "64")]
1117const _: () = assert!(std::mem::size_of::<Value>() <= 16);
1118
1119const FIXPOINT_PROMOTE_NEST_CAP: u32 = 32;
1136
1137const PROMOTION_RUNAWAY_FORCE_DEPTH: usize = 500;
1147
1148thread_local! {
1149 pub(crate) static IN_PROMISE_EVAL: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
1156
1157 pub(crate) static PROMOTION_OCCURRED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1165}
1166
1167#[inline(always)]
1169pub fn promotion_occurred() -> bool {
1170 PROMOTION_OCCURRED.with(|c| c.get())
1171}
1172
1173#[inline(always)]
1177pub fn in_promise_eval() -> bool {
1178 IN_PROMISE_EVAL.with(|c| c.get() > 0)
1179}
1180
1181pub enum ThunkRepr {
1186 Suspended {
1188 expr: rnix::ast::Expr,
1189 env: Env,
1190 },
1191 InheritSelect {
1207 source_thunk: Thunk,
1208 name: SmolStr,
1209 },
1210 Native(Box<dyn FnOnce() -> Result<Value, EvalError>>),
1215 WithIdent {
1225 name: SmolStr,
1227 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1232 scope_value: Value,
1234 env: Env,
1237 },
1238 Blackhole,
1240 Promise(Rc<RefCell<Value>>),
1253 Failed(EvalError),
1264 Evaluated(Box<Value>),
1268 EvaluatedConcrete,
1279}
1280
1281struct ThunkInner {
1290 cache: OnceCell<Box<Concrete>>,
1294 repr: UnsafeCell<ThunkRepr>,
1296 recursive: bool,
1303}
1304
1305impl Drop for ThunkInner {
1306 fn drop(&mut self) {
1307 census::dropped(&census::THUNK_LIVE);
1308 }
1309}
1310
1311#[derive(Clone)]
1313pub struct Thunk(pub(crate) Rc<ThunkInner>);
1314
1315impl Thunk {
1316 pub fn new_suspended(expr: rnix::ast::Expr, env: Env) -> Self {
1318 crate::trace::inc_thunks_created();
1319 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1320 Self(Rc::new(ThunkInner {
1321 cache: OnceCell::new(),
1322 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1323 recursive: false,
1324 }))
1325 }
1326
1327 pub fn new_suspended_recursive(expr: rnix::ast::Expr, env: Env) -> Self {
1334 crate::trace::inc_thunks_created();
1335 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1336 crate::perf::inc(crate::perf::Counter::ThunkSiteLetForward);
1337 Self(Rc::new(ThunkInner {
1338 cache: OnceCell::new(),
1339 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1340 recursive: true,
1341 }))
1342 }
1343
1344 pub fn new_inherit_select(source_thunk: Thunk, name: impl Into<SmolStr>) -> Self {
1352 crate::trace::inc_thunks_created();
1353 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1354 crate::perf::inc(crate::perf::Counter::ThunkSiteInheritSrc);
1355 Self(Rc::new(ThunkInner {
1356 cache: OnceCell::new(),
1357 repr: UnsafeCell::new(ThunkRepr::InheritSelect {
1358 source_thunk,
1359 name: name.into(),
1360 }),
1361 recursive: false,
1362 }))
1363 }
1364
1365 pub fn new_with_ident(
1369 name: SmolStr,
1370 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1371 scope_value: Value,
1372 env: Env,
1373 ) -> Self {
1374 crate::trace::inc_thunks_created();
1375 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1376 crate::perf::inc(crate::perf::Counter::ThunkSiteOther);
1377 Self(Rc::new(ThunkInner {
1378 cache: OnceCell::new(),
1379 repr: UnsafeCell::new(ThunkRepr::WithIdent {
1380 name,
1381 scope_cache,
1382 scope_value,
1383 env,
1384 }),
1385 recursive: false,
1386 }))
1387 }
1388
1389 pub fn new_native(f: impl FnOnce() -> Result<Value, EvalError> + 'static) -> Self {
1393 crate::trace::inc_thunks_created();
1394 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1395 crate::perf::inc(crate::perf::Counter::ThunkSiteNative);
1396 Self(Rc::new(ThunkInner {
1397 cache: OnceCell::new(),
1398 repr: UnsafeCell::new(ThunkRepr::Native(Box::new(f))),
1399 recursive: false,
1400 }))
1401 }
1402
1403 pub fn new_evaluated(value: Value) -> Self {
1407 crate::trace::inc_thunks_created();
1408 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1409 crate::perf::inc(crate::perf::Counter::ThunkSiteEvaluated);
1410 let cache = OnceCell::new();
1411 let repr = if matches!(value, Value::Thunk(_)) {
1415 ThunkRepr::Evaluated(Box::new(value))
1416 } else {
1417 let _ = cache.set(Box::new(value.demand_unchecked()));
1418 ThunkRepr::EvaluatedConcrete
1419 };
1420 Self(Rc::new(ThunkInner {
1421 cache,
1422 repr: UnsafeCell::new(repr),
1423 recursive: false,
1424 }))
1425 }
1426
1427 pub fn is_evaluated(&self) -> bool {
1430 self.0.cache.get().is_some()
1431 }
1432
1433 pub fn is_native(&self) -> bool {
1439 matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Native(_))
1442 }
1443
1444 pub fn peek(&self) -> Option<&Concrete> {
1450 self.0.cache.get().map(|v| &**v)
1451 }
1452
1453 pub fn update_env(&self, new_env: &Env) {
1458 let repr = unsafe { &mut *self.0.repr.get() };
1461 match repr {
1462 ThunkRepr::Suspended { env, .. } => {
1463 *env = new_env.clone();
1464 }
1465 ThunkRepr::InheritSelect { source_thunk, .. } => {
1466 source_thunk.update_env(new_env);
1467 }
1468 _ => {}
1469 }
1470 }
1471
1472 #[inline]
1495 unsafe fn store_evaluated(&self, value: &Value) {
1496 census::evaluated();
1497 if matches!(value, Value::Thunk(_)) {
1498 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1499 } else {
1500 let _ = self.0.cache.set(Box::new(value.clone().demand_unchecked()));
1501 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1502 }
1503 }
1504
1505 #[inline]
1531 unsafe fn store_evaluated_owned(&self, value: Value) -> Value {
1532 census::evaluated();
1533 let concrete = value.demand_unchecked();
1534 let ret = concrete.clone().into_value();
1535 let _ = self.0.cache.set(Box::new(concrete));
1536 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1537 ret
1538 }
1539
1540 pub fn force(
1549 &self,
1550 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1551 ) -> Result<Value, EvalError> {
1552 if let Some(cached) = self.0.cache.get() {
1556 crate::perf::inc(crate::perf::Counter::ThunkHit);
1557 return Ok((**cached).clone().into_value());
1558 }
1559 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1561 self.force_inner(evaluator)
1562 })
1563 }
1564
1565 fn force_inner(
1568 &self,
1569 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1570 ) -> Result<Value, EvalError> {
1571 if let Some(cached) = self.0.cache.get() {
1580 crate::perf::inc(crate::perf::Counter::ThunkHit);
1581 return Ok((**cached).clone().into_value());
1582 }
1583
1584 let thunk_id = Rc::as_ptr(&self.0) as usize;
1585
1586 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1599 return Ok(cell.borrow().clone());
1600 }
1601
1602 let new_repr_on_force = if self.0.recursive {
1611 ThunkRepr::Promise(Rc::new(RefCell::new(
1612 Value::Attrs(Rc::new(NixAttrs::new())),
1613 )))
1614 } else {
1615 ThunkRepr::Blackhole
1616 };
1617 let is_promise = self.0.recursive;
1618 let repr = std::mem::replace(unsafe { &mut *self.0.repr.get() }, new_repr_on_force);
1619
1620 match repr {
1621 ThunkRepr::Suspended { expr, env } => {
1622 crate::perf::inc(crate::perf::Counter::ThunkForce);
1623 crate::trace::inc_thunks_forced_unique();
1624 let tracing = crate::trace::trace_enabled();
1625 let desc: String = if tracing {
1633 expr.syntax().text().to_string().chars().take(60).collect()
1634 } else {
1635 String::new()
1636 };
1637 crate::trace::push_force(crate::trace::ForceFrame {
1638 defined_in: env.eval_file().cloned(),
1639 description: desc.clone(),
1640 thunk_id,
1641 });
1642 if crate::value::promotion_occurred()
1668 && crate::trace::current_force_depth() as usize
1669 > PROMOTION_RUNAWAY_FORCE_DEPTH
1670 {
1671 crate::trace::pop_force();
1672 *unsafe { &mut *self.0.repr.get() } =
1673 ThunkRepr::Suspended { expr, env };
1674 return Err(EvalError::InfiniteRecursion(
1675 "overlay-fixpoint promotion runaway (force depth exceeded)".into(),
1676 ));
1677 }
1678 if tracing {
1679 crate::trace::trace_force_enter(
1680 env.eval_file().map(|p| p.as_path()),
1681 &desc,
1682 );
1683 if let Err(msg) = crate::trace::check_force_depth() {
1684 crate::trace::dump_trace_on_error();
1685 crate::trace::pop_force();
1686 crate::trace::trace_force_exit();
1687 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended {
1688 expr,
1689 env,
1690 };
1691 return Err(EvalError::InfiniteRecursion(msg));
1692 }
1693 }
1694 let _file_guard = env.eval_file().cloned().map(crate::eval::push_eval_file);
1700 let _srcid_guard = crate::eval::push_source_id(env.source_id());
1709 if is_promise {
1715 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
1716 }
1717 let result = evaluator(&expr, &env);
1718 if is_promise {
1719 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1720 }
1721 let became_promise = !is_promise
1731 && matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Promise(_));
1732 if became_promise {
1733 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1734 }
1735 match result {
1736 Ok(mut value) => {
1737 crate::perf::inc(crate::perf::Counter::ThunkStoreWrites);
1738 if is_promise || became_promise {
1745 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1746 *cell.borrow_mut() = value.clone();
1747 }
1748 }
1749 let was_thunk_before_loop = matches!(value, Value::Thunk(_));
1771 if !was_thunk_before_loop {
1772 crate::perf::inc(crate::perf::Counter::ThunkStoreRedundant);
1777 let ret = unsafe { self.store_evaluated_owned(value) };
1778 crate::trace::pop_force();
1779 if tracing { crate::trace::trace_force_exit(); }
1780 return Ok(ret);
1781 }
1782 unsafe { self.store_evaluated(&value) };
1784 while let Value::Thunk(ref inner) = value {
1789 match inner.peek() {
1790 Some(cached) => value = cached.clone().into_value(),
1791 None => break,
1792 }
1793 }
1794 if !matches!(value, Value::Thunk(_)) {
1795 crate::perf::inc(crate::perf::Counter::ThunkStoreLoopMutated);
1796 }
1797 unsafe { self.store_evaluated(&value) };
1798 crate::trace::pop_force();
1799 if tracing { crate::trace::trace_force_exit(); }
1800 Ok(value)
1801 }
1802 Err(e) => {
1803 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended { expr, env };
1804 if tracing { crate::trace::dump_trace_on_error(); }
1805 crate::trace::pop_force();
1806 if tracing { crate::trace::trace_force_exit(); }
1807 Err(e)
1808 }
1809 }
1810 }
1811 ThunkRepr::InheritSelect { source_thunk, name } => {
1812 let tracing = crate::trace::trace_enabled();
1813 let desc = if tracing { format!("inherit (..) {name}") } else { String::new() };
1814 crate::trace::push_force(crate::trace::ForceFrame {
1815 defined_in: None,
1816 description: desc.clone(),
1817 thunk_id,
1818 });
1819 if tracing {
1820 crate::trace::trace_force_enter(None, &desc);
1821 }
1822 crate::trace::inc_thunks_forced_unique();
1823 if tracing {
1824 if let Err(msg) = crate::trace::check_force_depth() {
1825 crate::trace::dump_trace_on_error();
1826 crate::trace::pop_force();
1827 crate::trace::trace_force_exit();
1828 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect {
1829 source_thunk,
1830 name,
1831 };
1832 return Err(EvalError::InfiniteRecursion(msg));
1833 }
1834 }
1835 let attempt = (|| -> Result<Value, EvalError> {
1836 let mut forced = source_thunk.force(evaluator)?;
1837 while let Value::Thunk(inner) = forced {
1838 forced = inner.force(evaluator)?;
1839 }
1840 let attrs = match &forced {
1841 Value::Attrs(a) => a,
1842 _ => {
1843 return Err(EvalError::TypeError(format!(
1844 "inherit (source) {name}: source is {}, not a set",
1845 forced.type_name()
1846 )))
1847 }
1848 };
1849 attrs
1850 .get(&name)
1851 .cloned()
1852 .ok_or_else(|| EvalError::AttrNotFound(name.to_string()))
1853 })();
1854 match attempt {
1855 Ok(mut value) => {
1856 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1857 while let Value::Thunk(ref inner) = value {
1858 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1859 }
1860 unsafe { self.store_evaluated(&value) };
1861 crate::trace::pop_force();
1862 if tracing { crate::trace::trace_force_exit(); }
1863 Ok(value)
1864 }
1865 Err(e) => {
1866 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect { source_thunk, name };
1867 if tracing { crate::trace::dump_trace_on_error(); }
1868 crate::trace::pop_force();
1869 if tracing { crate::trace::trace_force_exit(); }
1870 Err(e)
1871 }
1872 }
1873 }
1874 ThunkRepr::Native(f) => {
1875 let tracing = crate::trace::trace_enabled();
1876 crate::trace::push_force(crate::trace::ForceFrame {
1877 defined_in: None,
1878 description: if tracing { "<native-thunk>".into() } else { String::new() },
1879 thunk_id,
1880 });
1881 if tracing {
1882 crate::trace::trace_force_enter(None, "<native-thunk>");
1883 }
1884 crate::trace::inc_thunks_forced_unique();
1885 match f() {
1890 Ok(mut value) => {
1891 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1892 while let Value::Thunk(ref inner) = value {
1893 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1894 }
1895 unsafe { self.store_evaluated(&value) };
1896 crate::trace::pop_force();
1897 if tracing { crate::trace::trace_force_exit(); }
1898 Ok(value)
1899 }
1900 Err(e) => {
1901 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e.clone());
1915 if tracing { crate::trace::dump_trace_on_error(); }
1916 crate::trace::pop_force();
1917 if tracing { crate::trace::trace_force_exit(); }
1918 Err(e)
1919 }
1920 }
1921 }
1922 ThunkRepr::WithIdent { name, scope_cache, scope_value, env } => {
1923 crate::perf::inc(crate::perf::Counter::ThunkForce);
1924 crate::trace::inc_thunks_forced_unique();
1925 {
1930 let cache = scope_cache.borrow();
1931 if let Some(ref attrs) = *cache {
1932 if let Some(v) = attrs.get(&name) {
1933 let value = v.clone();
1934 unsafe { self.store_evaluated(&value) };
1935 return Ok(value);
1936 }
1937 }
1939 }
1940 if let Ok(forced) = crate::eval::force_value(&scope_value) {
1942 if let Value::Attrs(ref attrs) = forced {
1943 *scope_cache.borrow_mut() = Some((**attrs).clone());
1944 if let Some(v) = attrs.get(&name) {
1945 let value = v.clone();
1946 unsafe { self.store_evaluated(&value) };
1947 return Ok(value);
1948 }
1949 }
1950 }
1951 let result = match env.lookup(&name) {
1981 Some(v) => v,
1982 None => match env.lookup_fresh(&name) {
1983 Some(v) => v,
1984 None if in_promise_eval() => Value::Null,
1985 None => return Err(EvalError::UndefinedVar(format!("'{name}'"))),
1986 },
1987 };
1988 unsafe { self.store_evaluated(&result) };
1989 Ok(result)
1990 }
1991 ThunkRepr::Blackhole => {
1992 if std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some() {
2016 return Ok(Value::Null);
2017 }
2018 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_LIST").is_some() {
2019 return Ok(Value::List(Rc::new(NixList::new(Vec::new()))));
2020 }
2021 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some() {
2022 return Ok(Value::Attrs(Rc::new(NixAttrs::new())));
2023 }
2024 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2025 let same = crate::trace::force_stack_contains(thunk_id);
2026 eprintln!(
2027 "[SUI_DEBUG_CYCLE] blackhole re-entry thunk_id={thunk_id:#x} same_thunk_on_stack={same} recursive_flag={}",
2028 self.0.recursive
2029 );
2030 crate::trace::dump_force_stack_ids();
2031 }
2032 if crate::trace::force_stack_contains(thunk_id)
2071 && IN_PROMISE_EVAL.with(|c| c.get()) < FIXPOINT_PROMOTE_NEST_CAP
2072 {
2073 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2074 let chain = crate::trace::capture_cycle(thunk_id);
2075 let nest = IN_PROMISE_EVAL.with(|c| c.get());
2076 let fdepth = crate::trace::current_force_depth();
2077 eprintln!("[SUI_PROMOTE] thunk_id={thunk_id:#x} cycle_len={} nest={nest} fdepth={fdepth}", chain.0.len());
2078 }
2079 let cell = Rc::new(RefCell::new(
2080 Value::Attrs(Rc::new(NixAttrs::new())),
2081 ));
2082 *unsafe { &mut *self.0.repr.get() } =
2085 ThunkRepr::Promise(cell.clone());
2086 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
2090 PROMOTION_OCCURRED.with(|c| c.set(true));
2092 return Ok(cell.borrow().clone());
2093 }
2094 let chain = crate::trace::capture_cycle(thunk_id);
2095 crate::trace::dump_trace_on_error();
2096 Err(EvalError::InfiniteRecursion(chain.to_string()))
2097 }
2098 ThunkRepr::Promise(cell) => {
2099 Ok(cell.borrow().clone())
2108 }
2109 ThunkRepr::Evaluated(v) => {
2110 crate::perf::inc(crate::perf::Counter::ThunkHit);
2114 let cloned = (*v).clone();
2115 if !matches!(cloned, Value::Thunk(_)) {
2116 if !matches!(cloned, Value::Thunk(_)) { let _ = self.0.cache.set(Box::new(cloned.clone().demand_unchecked())); }
2117 }
2118 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(v);
2119 Ok(cloned)
2120 }
2121 ThunkRepr::EvaluatedConcrete => {
2122 crate::perf::inc(crate::perf::Counter::ThunkHit);
2132 let value = self
2133 .0
2134 .cache
2135 .get()
2136 .expect("EvaluatedConcrete implies a populated cache")
2137 .as_ref()
2138 .clone()
2139 .into_value();
2140 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
2141 Ok(value)
2142 }
2143 ThunkRepr::Failed(e) => {
2144 let err = e.clone();
2149 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e);
2150 Err(err)
2151 }
2152 }
2153 }
2154}
2155
2156impl fmt::Debug for Thunk {
2157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2158 match unsafe { &*self.0.repr.get() } {
2160 ThunkRepr::Suspended { .. } => write!(f, "<thunk>"),
2161 ThunkRepr::InheritSelect { name, .. } => write!(f, "<inherit-select {name}>"),
2162 ThunkRepr::Native(_) => write!(f, "<native-thunk>"),
2163 ThunkRepr::WithIdent { name, .. } => write!(f, "<with-ident {name}>"),
2164 ThunkRepr::Blackhole => write!(f, "<blackhole>"),
2165 ThunkRepr::Promise(_) => write!(f, "<promise>"),
2166 ThunkRepr::Failed(e) => write!(f, "<failed-thunk: {e}>"),
2167 ThunkRepr::Evaluated(v) => write!(f, "{v:?}"),
2168 ThunkRepr::EvaluatedConcrete => match self.0.cache.get() {
2169 Some(c) => write!(f, "{:?}", c.as_ref().clone().into_value()),
2170 None => write!(f, "<evaluated-concrete>"),
2171 },
2172 }
2173 }
2174}
2175
2176pub struct NixAttrs(AttrsInner, Option<Rc<crate::pos::AttrPositions>>);
2190
2191impl Clone for NixAttrs {
2196 fn clone(&self) -> Self {
2197 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2198 NixAttrs(self.0.clone(), self.1.clone())
2199 }
2200}
2201
2202impl Drop for NixAttrs {
2203 fn drop(&mut self) {
2204 census::dropped(&census::ATTRS_LIVE);
2205 }
2206}
2207
2208#[derive(Clone)]
2210enum AttrsInner {
2211 Flat(AttrsMap<Symbol, Value>),
2213 Overlay {
2224 left: RefCell<Rc<NixAttrs>>,
2225 right: RefCell<Rc<NixAttrs>>,
2226 cache: Rc<OnceCell<AttrsMap<Symbol, Value>>>,
2227 },
2228}
2229
2230impl fmt::Debug for NixAttrs {
2231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2232 write!(f, "NixAttrs({})", self.len())
2233 }
2234}
2235
2236impl Default for NixAttrs {
2237 fn default() -> Self {
2238 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2239 Self(AttrsInner::Flat(AttrsMap::default()), None)
2240 }
2241}
2242
2243impl NixAttrs {
2244 pub fn new() -> Self {
2245 Self::default()
2246 }
2247
2248 pub fn with_capacity(_capacity: usize) -> Self {
2249 Self::default()
2250 }
2251
2252 pub fn set_positions(&mut self, pos: Rc<crate::pos::AttrPositions>) {
2256 self.1 = Some(pos);
2257 }
2258
2259 #[must_use]
2263 pub fn positions(&self) -> Option<&Rc<crate::pos::AttrPositions>> {
2264 self.1.as_ref()
2265 }
2266
2267 #[must_use]
2272 pub fn pos_for(&self, key: &str) -> Option<crate::pos::ResolvedPos> {
2273 let sym = intern(key);
2274 let (file, offset) = self.pos_entry(sym)?;
2275 crate::pos::resolve(file.as_deref(), offset)
2276 }
2277
2278 fn pos_entry(&self, sym: Symbol) -> Option<(Option<std::path::PathBuf>, u32)> {
2298 if let Some(table) = self.1.as_ref() {
2299 if let Some(offset) = table.keys.get(&sym) {
2300 return Some((table.file.clone(), *offset));
2301 }
2302 }
2303 match &self.0 {
2304 AttrsInner::Overlay { left, right, .. } => {
2305 let r = right.borrow().pos_entry(sym);
2306 if r.is_some() {
2307 return r;
2308 }
2309 let l = left.borrow().pos_entry(sym);
2310 l
2311 }
2312 _ => None,
2313 }
2314 }
2315
2316 #[must_use]
2318 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2319 self.as_flat().clone()
2320 }
2321
2322 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2324 match &self.0 {
2325 AttrsInner::Flat(m) => m,
2326 AttrsInner::Overlay { left, right, cache } => {
2327 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2328 let flat = cache.get_or_init(|| {
2329 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2332 let timed = crate::perf::enabled();
2333 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2334 let mut result = left.borrow().as_flat().clone();
2335 for (k, v) in right.borrow().as_flat().iter() {
2336 result.insert(*k, v.clone());
2337 }
2338 crate::perf::add(
2339 crate::perf::Counter::OverlayFlattenEntries,
2340 result.len() as u64,
2341 );
2342 if let Some(t0) = t0 {
2343 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2344 }
2345 result
2346 });
2347 {
2367 let mut l = left.borrow_mut();
2368 if !l.is_empty() { *l = Rc::new(l.position_husk()); }
2369 }
2370 {
2371 let mut r = right.borrow_mut();
2372 if !r.is_empty() { *r = Rc::new(r.position_husk()); }
2373 }
2374 flat
2375 }
2376 }
2377 }
2378
2379 fn position_husk(&self) -> NixAttrs {
2389 match &self.0 {
2390 AttrsInner::Overlay { left, right, .. } => {
2391 let (l, r) = (left.borrow().position_husk(), right.borrow().position_husk());
2392 if l.1.is_none() && r.1.is_none() && !matches!(l.0, AttrsInner::Overlay { .. })
2393 && !matches!(r.0, AttrsInner::Overlay { .. })
2394 {
2395 return NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone());
2398 }
2399 NixAttrs(
2400 AttrsInner::Overlay {
2401 left: RefCell::new(Rc::new(l)),
2402 right: RefCell::new(Rc::new(r)),
2403 cache: Rc::new(OnceCell::new()),
2404 },
2405 self.1.clone(),
2406 )
2407 }
2408 AttrsInner::Flat(_) => NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone()),
2409 }
2410 }
2411
2412 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2413 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2414 let m = self.as_flat();
2415 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2416 let timed = crate::perf::enabled();
2417 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2418 let mut pairs: Vec<(String, &Value)> = m.iter()
2419 .map(|(sym, v)| (resolve(*sym), v))
2420 .collect();
2421 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2422 if let Some(t0) = t0 {
2423 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2424 }
2425 pairs
2426 }
2427
2428 #[must_use]
2430 pub fn get(&self, key: &str) -> Option<&Value> {
2431 let sym = intern(key);
2432 self.get_sym(&sym)
2433 }
2434
2435 #[must_use]
2449 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2450 match &self.0 {
2451 AttrsInner::Flat(m) => m.get(sym),
2452 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2458 }
2459 }
2460
2461 pub fn insert(&mut self, key: String, value: Value) {
2463 self.ensure_flat();
2464 if let AttrsInner::Flat(ref mut m) = self.0 {
2465 m.insert(intern(&key), value);
2466 }
2467 }
2468
2469 fn ensure_flat(&mut self) {
2471 if matches!(self.0, AttrsInner::Overlay { .. }) {
2472 self.0 = AttrsInner::Flat(self.as_flat().clone());
2473 }
2474 }
2475
2476 #[must_use]
2477 pub fn contains_key(&self, key: &str) -> bool {
2478 let sym = intern(key);
2479 self.contains_key_sym(&sym)
2480 }
2481
2482 #[must_use]
2483 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2484 match &self.0 {
2485 AttrsInner::Flat(m) => m.contains_key(sym),
2486 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2488 }
2489 }
2490
2491 pub fn keys(&self) -> impl Iterator<Item = String> {
2492 self.sorted_entries().into_iter().map(|(k, _)| k)
2493 }
2494
2495 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2496 self.sorted_entries().into_iter()
2497 }
2498
2499 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2500 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2501 }
2502
2503 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2521 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2522 }
2523
2524 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2527 self.ensure_flat();
2528 if let AttrsInner::Flat(ref mut m) = self.0 {
2529 m.insert(sym, value);
2530 }
2531 }
2532
2533 pub fn values(&self) -> impl Iterator<Item = &Value> {
2534 self.sorted_entries().into_iter().map(|(_, v)| v)
2535 }
2536
2537
2538 pub fn remove(&mut self, key: &str) -> Option<Value> {
2539 self.ensure_flat();
2540 if let AttrsInner::Flat(ref mut m) = self.0 {
2541 m.remove(&intern(key))
2542 } else {
2543 None
2544 }
2545 }
2546
2547 #[must_use]
2548 pub fn len(&self) -> usize {
2549 match &self.0 {
2550 AttrsInner::Flat(m) => m.len(),
2551 AttrsInner::Overlay { .. } => {
2552 self.as_flat().len()
2556 }
2557 }
2558 }
2559
2560 #[must_use]
2561 pub fn is_empty(&self) -> bool {
2562 match &self.0 {
2563 AttrsInner::Flat(m) => m.is_empty(),
2564 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2568 }
2569 }
2570
2571 #[must_use]
2573 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2574 if other.is_empty() { return self; }
2575 if self.is_empty() { return other; }
2576 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2577 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2578 NixAttrs(AttrsInner::Overlay {
2579 left: RefCell::new(Rc::new(self)),
2580 right: RefCell::new(Rc::new(other)),
2581 cache: Rc::new(OnceCell::new()),
2582 }, None)
2583 }
2584
2585 #[must_use]
2587 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2588 match (&self.0, &other.0) {
2589 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2590 let mut result = l.clone();
2591 for (k, v) in r.iter() {
2592 result.insert(*k, v.clone());
2593 }
2594 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2595 NixAttrs(AttrsInner::Flat(result), None)
2596 }
2597 _ => {
2598 let mut result = self.as_flat().clone();
2600 let other_flat = other.as_flat();
2601 for (k, v) in other_flat.iter() {
2602 result.insert(*k, v.clone());
2603 }
2604 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2605 NixAttrs(AttrsInner::Flat(result), None)
2606 }
2607 }
2608 }
2609}
2610
2611impl FromIterator<(String, Value)> for NixAttrs {
2612 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2613 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2614 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2615 }
2616}
2617
2618impl IntoIterator for NixAttrs {
2619 type Item = (String, Value);
2620 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2621
2622 fn into_iter(self) -> Self::IntoIter {
2623 let flat = self.as_flat().clone();
2624 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2625 }
2626}
2627
2628#[derive(Debug, Clone)]
2636pub struct Closure {
2637 pub param: rnix::ast::Param,
2638 pub body: rnix::ast::Expr,
2639 pub env: Env,
2640}
2641
2642pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2644
2645#[derive(Clone)]
2650pub struct BuiltinFn {
2651 pub name: &'static str,
2653 pub func: Rc<BuiltinFunc>,
2655}
2656
2657impl fmt::Debug for BuiltinFn {
2658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2659 write!(f, "<builtin {}>", self.name)
2660 }
2661}
2662
2663#[derive(Clone)]
2673struct WithScope {
2674 value: Value,
2675 cached: Rc<RefCell<Option<NixAttrs>>>,
2678}
2679
2680impl fmt::Debug for WithScope {
2681 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2682 f.debug_struct("WithScope")
2683 .field("value", &self.value)
2684 .field("cached", &self.cached.borrow().is_some())
2685 .finish()
2686 }
2687}
2688
2689#[derive(Debug, Clone, Default)]
2699struct EnvInner {
2700 bindings: FxHashMap<Symbol, Value>,
2701 with_scopes: Vec<WithScope>,
2703 eval_file: Option<std::path::PathBuf>,
2707 source_id: u32,
2714}
2715
2716#[derive(Clone, Default)]
2724pub struct Env(Rc<EnvInner>);
2725
2726impl fmt::Debug for Env {
2727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2728 self.0.fmt(f)
2729 }
2730}
2731
2732impl Drop for EnvInner {
2741 fn drop(&mut self) {
2742 census::dropped(&census::ENV_LIVE);
2743 }
2744}
2745
2746impl Env {
2747 #[must_use]
2749 pub fn new() -> Self {
2750 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2751 Self(Rc::new(EnvInner {
2752 bindings: FxHashMap::default(),
2753 with_scopes: Vec::new(),
2754 eval_file: None,
2755 source_id: 0,
2756 }))
2757 }
2758
2759 #[must_use]
2764 pub fn child(&self) -> Self {
2765 crate::perf::inc(crate::perf::Counter::EnvClone);
2766 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2772 Self(Rc::new(EnvInner {
2773 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2775 eval_file: self.0.eval_file.clone(),
2779 source_id: self.0.source_id,
2783 }))
2784 }
2785
2786 #[must_use]
2794 pub fn with_scope(mut self, value: Value) -> Self {
2795 let pre_cached = match &value {
2797 Value::Attrs(attrs) => Some((**attrs).clone()),
2798 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2799 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2800 }),
2801 _ => None,
2802 };
2803 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2804 value,
2805 cached: Rc::new(RefCell::new(pre_cached)),
2806 });
2807 self
2808 }
2809
2810 pub fn bind(&mut self, name: String, value: Value) {
2815 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2816 }
2817
2818 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2828 let inner = Rc::make_mut(&mut self.0);
2829 for (name, value) in pairs {
2830 inner.bindings.insert(intern(&name), value);
2831 }
2832 }
2833
2834 #[must_use]
2836 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2837 self.0.eval_file.as_ref()
2838 }
2839
2840 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2842 Rc::make_mut(&mut self.0).eval_file = file;
2843 }
2844
2845 #[must_use]
2847 pub fn source_id(&self) -> u32 {
2848 self.0.source_id
2849 }
2850
2851 pub fn set_source_id(&mut self, id: u32) {
2854 Rc::make_mut(&mut self.0).source_id = id;
2855 }
2856
2857 #[must_use]
2859 pub fn binding_count(&self) -> usize {
2860 self.0.bindings.len()
2861 }
2862
2863 #[must_use]
2865 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2866 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2867 }
2868
2869 #[must_use]
2871 pub fn with_scope_count(&self) -> usize {
2872 self.0.with_scopes.len()
2873 }
2874
2875 #[must_use]
2879 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2880 let sym = intern(name);
2881 self.0.bindings.get(&sym).cloned()
2882 }
2883
2884 #[must_use]
2895 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
2896 self.0.bindings.get(&sym).cloned()
2897 }
2898
2899 #[must_use]
2903 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
2904 for scope in self.0.with_scopes.iter().rev() {
2905 let cache = scope.cached.borrow();
2906 if let Some(ref attrs) = *cache {
2907 if let Some(v) = attrs.get(name) {
2908 return Some(v.clone());
2909 }
2910 }
2911 drop(cache);
2913 if let Value::Thunk(ref thunk) = scope.value {
2914 if let Some(cached_val) = thunk.peek() {
2915 if let Concrete::Attrs(ref attrs) = *cached_val {
2916 *scope.cached.borrow_mut() = Some((**attrs).clone());
2918 if let Some(v) = attrs.get(name) {
2919 return Some(v.clone());
2920 }
2921 }
2922 }
2923 } else if let Value::Attrs(ref attrs) = scope.value {
2924 *scope.cached.borrow_mut() = Some((**attrs).clone());
2925 if let Some(v) = attrs.get(name) {
2926 return Some(v.clone());
2927 }
2928 }
2929 }
2930 None
2931 }
2932
2933 #[must_use]
2936 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
2937 self.0.with_scopes.last().map(|scope| {
2938 (scope.cached.clone(), scope.value.clone())
2939 })
2940 }
2941
2942 #[must_use]
2951 pub fn lookup(&self, name: &str) -> Option<Value> {
2952 self.lookup_fast(intern(name), name)
2953 }
2954
2955 #[must_use]
2967 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
2968 let sym = intern(name);
2969 if let Some(v) = self.0.bindings.get(&sym) {
2970 return Some(v.clone());
2971 }
2972 for scope in self.0.with_scopes.iter().rev() {
2973 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
2974 if let Some(v) = attrs.get_sym(&sym) {
2975 *scope.cached.borrow_mut() = Some((*attrs).clone());
2978 return Some(v.clone());
2979 }
2980 }
2981 }
2982 None
2983 }
2984
2985 #[must_use]
2987 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
2988 crate::perf::inc(crate::perf::Counter::EnvLookup);
2989 if let Some(v) = self.0.bindings.get(&sym) {
2990 return Some(v.clone());
2991 }
2992 for scope in self.0.with_scopes.iter().rev() {
2994 {
2996 let cache = scope.cached.borrow();
2997 if let Some(ref attrs) = *cache {
2998 if let Some(v) = attrs.get_sym(&sym) {
2999 return Some(v.clone());
3000 }
3001 continue;
3002 }
3003 }
3004 let resolved = match &scope.value {
3009 Value::Attrs(attrs) => {
3010 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3012 *scope.cached.borrow_mut() = Some((**attrs).clone());
3013 Some((**attrs).clone())
3014 }
3015 Value::Thunk(thunk) => {
3016 if let Some(cached_val) = thunk.peek() {
3019 if let Concrete::Attrs(ref attrs) = *cached_val {
3020 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3021 *scope.cached.borrow_mut() = Some((**attrs).clone());
3022 Some((**attrs).clone())
3023 } else {
3024 None
3025 }
3026 } else {
3027 match crate::eval::force_value(&scope.value) {
3038 Ok(forced) => {
3039 if let Value::Attrs(ref attrs) = forced {
3040 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3041 *scope.cached.borrow_mut() = Some((**attrs).clone());
3042 Some((**attrs).clone())
3043 } else {
3044 None
3045 }
3046 }
3047 Err(_) => None, }
3049 }
3050 }
3051 _ => {
3052 match crate::eval::force_value(&scope.value) {
3054 Ok(forced) => {
3055 if let Value::Attrs(ref attrs) = forced {
3056 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3057 *scope.cached.borrow_mut() = Some((**attrs).clone());
3058 Some((**attrs).clone())
3059 } else {
3060 None
3061 }
3062 }
3063 Err(_) => None,
3064 }
3065 }
3066 };
3067 if let Some(ref attrs) = resolved {
3068 if let Some(v) = attrs.get(name) {
3069 return Some(v.clone());
3070 }
3071 }
3072 }
3074 None
3075 }
3076
3077 #[must_use]
3083 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
3084 crate::perf::inc(crate::perf::Counter::EnvLookup);
3085 if let Some(v) = self.0.bindings.get(&sym) {
3087 return Some(v.clone());
3088 }
3089 for scope in self.0.with_scopes.iter().rev() {
3091 {
3093 let cache = scope.cached.borrow();
3094 if let Some(ref attrs) = *cache {
3095 if let Some(v) = attrs.get_sym(&sym) {
3096 return Some(v.clone());
3097 }
3098 continue;
3099 }
3100 }
3101 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
3103 if let Value::Attrs(ref attrs) = forced {
3104 let result = attrs.get_sym(&sym).cloned();
3105 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3106 *scope.cached.borrow_mut() = Some((**attrs).clone());
3107 if result.is_some() {
3108 return result;
3109 }
3110 }
3111 }
3112 }
3114 None
3115 }
3116}
3117
3118#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
3120#[non_exhaustive]
3121pub enum EvalError {
3122 #[error("undefined variable: {0}")]
3124 UndefinedVar(String),
3125 #[error("type error: {0}")]
3127 TypeError(String),
3128 #[error("attribute not found: {0}")]
3130 AttrNotFound(String),
3131 #[error("type error: expected {expected}, got {got}")]
3133 TypeMismatch {
3134 expected: &'static str,
3135 got: &'static str,
3136 },
3137 #[error("assertion failed{0}")]
3139 AssertionFailed(String),
3140 #[error("division by zero")]
3142 DivisionByZero,
3143 #[error("infinite recursion ({0})")]
3145 InfiniteRecursion(String),
3146 #[error("I/O error: {context}: {message}")]
3148 IoError { context: String, message: String },
3149 #[error("{0}")]
3151 Throw(String),
3152 #[error("{0}")]
3156 Abort(String),
3157 #[error("not yet implemented: {0}")]
3159 NotImplemented(String),
3160 #[error("parse error: {0}")]
3162 ParseError(String),
3163 #[error("recursion limit: {0}")]
3165 RecursionLimit(String),
3166}
3167
3168impl EvalError {
3169 #[must_use]
3171 pub fn type_error(msg: impl Into<String>) -> Self {
3172 EvalError::TypeError(msg.into())
3173 }
3174
3175 #[must_use]
3177 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3178 EvalError::TypeMismatch { expected, got }
3179 }
3180
3181 #[must_use]
3183 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3184 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3185 }
3186
3187 #[must_use]
3208 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3209 EvalError::TypeError(format!(
3210 "cannot {op} {lhs} and {rhs}{}",
3211 crate::eval::eval_file_ctx()
3212 ))
3213 }
3214
3215 #[must_use]
3217 pub fn is_throw(&self) -> bool {
3218 matches!(self, EvalError::Throw(_))
3219 }
3220
3221 #[must_use]
3223 pub fn is_infinite_recursion(&self) -> bool {
3224 matches!(self, EvalError::InfiniteRecursion(_))
3225 }
3226}
3227
3228impl Value {
3229 #[must_use]
3231 pub fn string(s: impl Into<SmolStr>) -> Self {
3232 Value::String(Rc::new(NixString::plain(s)))
3233 }
3234
3235 #[must_use]
3238 pub fn list(items: Vec<Value>) -> Self {
3239 Value::List(Rc::new(NixList::new(items)))
3240 }
3241
3242 #[must_use]
3245 pub fn is_uniquely_owned_list(&self) -> bool {
3246 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3247 }
3248
3249 #[must_use]
3251 pub fn to_json(&self) -> serde_json::Value {
3252 match self {
3253 Value::Null => serde_json::Value::Null,
3254 Value::Bool(b) => serde_json::Value::Bool(*b),
3255 Value::Int(n) => serde_json::json!(n),
3256 Value::Float(f) => serde_json::json!(f),
3257 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3258 Value::Path(p) => serde_json::Value::String(p.to_string()),
3259 Value::List(items) => {
3260 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3261 }
3262 Value::Attrs(attrs) => {
3263 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3270 if let Ok((s, _ctx)) = self.coerce_to_string() {
3271 return serde_json::Value::String(s);
3272 }
3273 }
3274 let map: serde_json::Map<String, serde_json::Value> = attrs
3275 .iter()
3276 .map(|(k, v)| (k.clone(), v.to_json()))
3277 .collect();
3278 serde_json::Value::Object(map)
3279 }
3280 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3281 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3282 Value::Thunk(thunk) => {
3283 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3285 Ok(v) => v.to_json(),
3286 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3287 }
3288 }
3289 }
3290 }
3291
3292 pub fn to_json_with_context(
3299 &self,
3300 ctx: &mut StringContext,
3301 ) -> Result<serde_json::Value, EvalError> {
3302 Ok(match self {
3303 Value::Null => serde_json::Value::Null,
3304 Value::Bool(b) => serde_json::Value::Bool(*b),
3305 Value::Int(n) => serde_json::json!(n),
3306 Value::Float(f) => serde_json::json!(f),
3307 Value::String(s) => {
3308 ctx.merge(&s.context);
3309 serde_json::Value::String(s.chars.to_string())
3310 }
3311 Value::Path(_) => {
3312 let (str, c) = self.coerce_to_string_copy_to_store()?;
3313 ctx.merge(&c);
3314 serde_json::Value::String(str)
3315 }
3316 Value::List(items) => {
3317 let mut arr = Vec::with_capacity(items.len());
3318 for v in items.iter() {
3319 let fv = crate::eval::force_value(v)?;
3320 arr.push(fv.to_json_with_context(ctx)?);
3321 }
3322 serde_json::Value::Array(arr)
3323 }
3324 Value::Attrs(attrs) => {
3325 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3328 let (s, c) = self.coerce_to_string_copy_to_store()?;
3329 ctx.merge(&c);
3330 return Ok(serde_json::Value::String(s));
3331 }
3332 let mut map = serde_json::Map::new();
3333 for (k, v) in attrs.iter() {
3334 let fv = crate::eval::force_value(v)?;
3335 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3336 }
3337 serde_json::Value::Object(map)
3338 }
3339 Value::Thunk(_) => {
3340 let forced = crate::eval::force_value(self)?;
3341 forced.to_json_with_context(ctx)?
3342 }
3343 other => {
3344 return Err(EvalError::TypeError(format!(
3345 "cannot serialize {} to JSON (__structuredAttrs)",
3346 other.type_name()
3347 )));
3348 }
3349 })
3350 }
3351
3352 #[must_use]
3354 pub fn type_name(&self) -> &'static str {
3355 match self {
3356 Value::Null => "null",
3357 Value::Bool(_) => "bool",
3358 Value::Int(_) => "int",
3359 Value::Float(_) => "float",
3360 Value::String(_) => "string",
3361 Value::Path(_) => "path",
3362 Value::List(_) => "list",
3363 Value::Attrs(_) => "set",
3364 Value::Lambda(_) => "lambda",
3365 Value::Builtin(_) => "lambda",
3366 Value::Thunk(thunk) => {
3367 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3369 Ok(v) => v.type_name(),
3370 Err(_) => "thunk",
3371 }
3372 }
3373 }
3374 }
3375
3376 pub fn as_bool(&self) -> Result<bool, EvalError> {
3397 match self {
3398 Value::Bool(b) => Ok(*b),
3399 Value::Thunk(thunk) => {
3400 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3401 }
3402 _ if in_promise_eval() => Ok(false),
3406 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3407 }
3408 }
3409
3410 pub fn as_int(&self) -> Result<i64, EvalError> {
3412 match self {
3413 Value::Int(n) => Ok(*n),
3414 Value::Thunk(thunk) => {
3415 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3416 }
3417 _ if in_promise_eval() => Ok(0),
3420 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3421 }
3422 }
3423
3424 pub fn as_string(&self) -> Result<&str, EvalError> {
3426 match self {
3427 Value::String(s) => Ok(&s.chars),
3428 Value::Thunk(_) => Err(EvalError::TypeError(
3429 "thunk in as_string: force first via force_value()".into(),
3430 )),
3431 _ if in_promise_eval() => Ok(""),
3432 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3433 }
3434 }
3435
3436 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3438 match self {
3439 Value::String(ns) => Ok(ns),
3440 Value::Thunk(_) => Err(EvalError::TypeError(
3441 "thunk in as_nix_string: force first via force_value()".into(),
3442 )),
3443 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3444 }
3445 }
3446
3447 pub fn to_str(&self) -> Result<String, EvalError> {
3451 match self {
3452 Value::String(s) => Ok(s.chars.to_string()),
3453 Value::Thunk(thunk) => {
3454 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3455 forced.to_str()
3456 }
3457 _ if in_promise_eval() => Ok(String::new()),
3458 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3459 }
3460 }
3461
3462 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3465 match self {
3466 Value::String(s) => Ok((**s).clone()),
3467 Value::Thunk(thunk) => {
3468 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3469 forced.to_nix_string()
3470 }
3471 _ if in_promise_eval() => Ok(NixString::plain("")),
3472 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3473 }
3474 }
3475
3476 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3485 match self {
3486 Value::Attrs(a) => Ok(a),
3487 Value::Thunk(_) => Err(EvalError::TypeError(
3488 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3489 )),
3490 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3491 }
3492 }
3493
3494 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3496 match self {
3497 Value::List(l) => Ok(l.as_slice()),
3498 Value::Thunk(_) => Err(EvalError::TypeError(
3499 "thunk in as_list: force first via force_value()".into(),
3500 )),
3501 _ => Err(crate::eval::attach_trace(
3502 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3503 )),
3504 }
3505 }
3506
3507 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3509 match self {
3510 Value::Attrs(a) => Ok((**a).clone()),
3511 Value::Thunk(thunk) => {
3512 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3513 forced.to_attrs()
3514 }
3515 _ if in_promise_eval() => Ok(NixAttrs::new()),
3521 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3522 }
3523 }
3524
3525 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3527 match self {
3528 Value::List(l) => Ok((**l).0.clone()),
3529 Value::Thunk(thunk) => {
3530 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3531 forced.to_list()
3532 }
3533 _ if in_promise_eval() => Ok(Vec::new()),
3536 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3537 }
3538 }
3539
3540 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3546 match self {
3547 Value::Path(p) => Ok(p.to_string()),
3548 Value::String(ns) => Ok(ns.chars.to_string()),
3549 Value::Attrs(attrs) => {
3550 if let Some(out_path) = attrs.get("outPath") {
3551 let forced = crate::eval::force_value(out_path)?;
3552 forced.coerce_to_path(context)
3553 } else {
3554 Err(EvalError::TypeError(format!(
3555 "{context}: expected path or string, got set without outPath"
3556 )))
3557 }
3558 }
3559 _ => Err(EvalError::TypeError(format!(
3560 "{context}: expected path or string, got {}",
3561 self.type_name()
3562 ))),
3563 }
3564 }
3565
3566 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3590 match self {
3591 Value::Attrs(attrs) => {
3594 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3595 self.realize_if_absent(&drv_path, &out_path, context)?;
3596 return Ok(out_path);
3597 }
3598 }
3599 Value::String(ns) => {
3606 let out_path = ns.chars.to_string();
3607 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3608 self.realize_if_absent(&drv_path, &out_path, context)?;
3609 }
3610 return Ok(out_path);
3611 }
3612 _ => {}
3613 }
3614 self.coerce_to_path(context)
3615 }
3616
3617 fn realize_if_absent(
3622 &self,
3623 drv_path: &str,
3624 out_path: &str,
3625 context: &str,
3626 ) -> Result<(), EvalError> {
3627 let read_path = crate::path::materialize_str(out_path);
3630 if std::path::Path::new(&read_path).exists() {
3631 return Ok(());
3632 }
3633 match crate::realize::realize_output(drv_path, out_path) {
3634 Ok(true) | Ok(false) => Ok(()),
3635 Err(msg) => Err(EvalError::IoError {
3636 context: context.to_string(),
3637 message: format!(
3638 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3639 ),
3640 }),
3641 }
3642 }
3643
3644 pub fn to_float(&self) -> Result<f64, EvalError> {
3646 match self {
3647 Value::Float(f) => Ok(*f),
3648 Value::Int(n) => Ok(*n as f64),
3649 Value::Thunk(thunk) => {
3650 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3651 }
3652 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3653 }
3654 }
3655
3656 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3674 self.coerce_to_string_impl(false)
3675 }
3676
3677 pub fn coerce_to_string_copy_to_store(
3687 &self,
3688 ) -> Result<(String, StringContext), EvalError> {
3689 self.coerce_to_string_impl(true)
3690 }
3691
3692 fn coerce_to_string_impl(
3693 &self,
3694 copy_to_store: bool,
3695 ) -> Result<(String, StringContext), EvalError> {
3696 let mut ctx = StringContext::new();
3697 let s = match self {
3698 Value::String(ns) => {
3699 ctx.merge(&ns.context);
3700 ns.chars.to_string()
3701 }
3702 Value::Path(p) => {
3703 let raw: &str = &**p;
3704 if copy_to_store {
3705 let pb = std::path::Path::new(raw);
3725 let abs = if pb.is_absolute() {
3726 pb.to_path_buf()
3727 } else if let Some(dir) = crate::eval::current_eval_dir() {
3728 dir.join(pb)
3729 } else {
3730 std::env::current_dir()
3731 .map_err(|e| EvalError::IoError {
3732 context: format!("copy-to-store coercion of {raw}"),
3733 message: e.to_string(),
3734 })?
3735 .join(pb)
3736 };
3737 let read_abs = crate::path::materialize(&abs);
3744 let canon = read_abs.canonicalize().map_err(|_| {
3745 EvalError::TypeError(format!(
3746 "path '{}' does not exist",
3747 abs.display()
3748 ))
3749 })?;
3750 let name = crate::path::source_name_for_read_dir(&canon)
3767 .or_else(|| {
3768 canon
3769 .file_name()
3770 .map(|n| sui_compat::source::strip_store_hash_prefix(
3771 &n.to_string_lossy()).to_string())
3772 })
3773 .unwrap_or_else(|| "source".to_string());
3774 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3775 .map_err(|e| {
3776 EvalError::TypeError(format!(
3777 "copy-to-store coercion of '{}': {e}",
3778 canon.display()
3779 ))
3780 })?;
3781 ctx.add_plain(src.store_path.clone());
3782 src.store_path
3783 } else {
3784 ctx.add_plain(raw.to_string());
3785 raw.to_string()
3786 }
3787 }
3788 Value::Int(n) => n.to_string(),
3789 Value::Float(f) => format!("{f:.6}"),
3795 Value::Bool(true) => "1".to_string(),
3796 Value::Bool(false) => String::new(),
3797 Value::Null => String::new(),
3798 Value::Attrs(attrs) => {
3799 if let Some(to_str) = attrs.get("__toString") {
3800 let result =
3801 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3802 let forced = crate::eval::force_value(&result)?;
3803 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3804 ctx.merge(&c);
3805 s
3806 } else if let Some(out_path) = attrs.get("outPath") {
3807 let forced = crate::eval::force_value(out_path)?;
3808 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3809 ctx.merge(&c);
3810 s
3811 } else {
3812 return Err(EvalError::TypeError(
3813 "cannot coerce set to string (no __toString or outPath)".into(),
3814 ));
3815 }
3816 }
3817 Value::List(items) => {
3818 let mut parts = Vec::new();
3819 for item in items.iter() {
3820 let forced = crate::eval::force_value(item)?;
3821 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3822 ctx.merge(&c);
3823 parts.push(s);
3824 }
3825 parts.join(" ")
3826 }
3827 Value::Thunk(_) => {
3828 let forced = crate::eval::force_value(self)?;
3830 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3831 ctx.merge(&c);
3832 s
3833 }
3834 other => {
3835 return Err(EvalError::TypeError(format!(
3836 "cannot coerce {} to string",
3837 other.type_name()
3838 )));
3839 }
3840 };
3841 Ok((s, ctx))
3842 }
3843}
3844
3845impl From<&serde_json::Value> for Value {
3848 fn from(json: &serde_json::Value) -> Self {
3849 match json {
3850 serde_json::Value::Null => Value::Null,
3851 serde_json::Value::Bool(b) => Value::Bool(*b),
3852 serde_json::Value::Number(n) => {
3853 if let Some(i) = n.as_i64() {
3854 Value::Int(i)
3855 } else {
3856 Value::Float(n.as_f64().unwrap_or(0.0))
3857 }
3858 }
3859 serde_json::Value::String(s) => Value::string(s.clone()),
3860 serde_json::Value::Array(arr) => {
3861 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3862 }
3863 serde_json::Value::Object(obj) => {
3864 let mut attrs = NixAttrs::new();
3865 for (k, v) in obj {
3866 attrs.insert(k.clone(), Value::from(v));
3867 }
3868 Value::Attrs(Rc::new(attrs))
3869 }
3870 }
3871 }
3872}
3873
3874impl From<&toml::Value> for Value {
3875 fn from(v: &toml::Value) -> Self {
3876 match v {
3877 toml::Value::String(s) => Value::string(s.clone()),
3878 toml::Value::Integer(n) => Value::Int(*n),
3879 toml::Value::Float(f) => Value::Float(*f),
3880 toml::Value::Boolean(b) => Value::Bool(*b),
3881 toml::Value::Array(arr) => {
3882 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3883 }
3884 toml::Value::Table(t) => {
3885 let mut attrs = NixAttrs::new();
3886 for (k, val) in t {
3887 attrs.insert(k.clone(), Value::from(val));
3888 }
3889 Value::Attrs(Rc::new(attrs))
3890 }
3891 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
3892 }
3893 }
3894}
3895
3896
3897impl From<bool> for Value {
3900 fn from(b: bool) -> Self {
3901 Value::Bool(b)
3902 }
3903}
3904
3905impl From<i64> for Value {
3906 fn from(n: i64) -> Self {
3907 Value::Int(n)
3908 }
3909}
3910
3911impl From<f64> for Value {
3912 fn from(f: f64) -> Self {
3913 Value::Float(f)
3914 }
3915}
3916
3917impl From<NixString> for Value {
3918 fn from(s: NixString) -> Self {
3919 Value::String(Rc::new(s))
3920 }
3921}
3922
3923impl From<NixAttrs> for Value {
3924 fn from(attrs: NixAttrs) -> Self {
3925 Value::Attrs(Rc::new(attrs))
3926 }
3927}
3928
3929impl From<Vec<Value>> for Value {
3930 fn from(list: Vec<Value>) -> Self {
3931 Value::List(Rc::new(NixList::new(list)))
3932 }
3933}
3934
3935impl PartialEq for Value {
3936 fn eq(&self, other: &Self) -> bool {
3937 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
3939 if Rc::ptr_eq(&a.0, &b.0) { return true; }
3940 }
3941 let l = self.demand().unwrap_or(Concrete::Null);
3944 let r = other.demand().unwrap_or(Concrete::Null);
3945 l == r
3946 }
3947}
3948
3949impl fmt::Display for Value {
3950 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3951 match self {
3952 Value::Null => write!(f, "null"),
3953 Value::Bool(b) => write!(f, "{b}"),
3954 Value::Int(n) => write!(f, "{n}"),
3955 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
3956 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
3957 Value::Path(p) => write!(f, "{p}"),
3958 Value::List(items) => {
3959 write!(f, "[ ")?;
3960 for item in items.iter() {
3961 write!(f, "{item} ")?;
3962 }
3963 write!(f, "]")
3964 }
3965 Value::Attrs(attrs) => {
3966 write!(f, "{{ ")?;
3967 for (k, v) in attrs.iter() {
3968 write!(f, "{k} = {v}; ")?;
3969 }
3970 write!(f, "}}")
3971 }
3972 Value::Lambda(_) => write!(f, "<<lambda>>"),
3973 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
3974 Value::Thunk(thunk) => {
3975 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
3976 Ok(v) => write!(f, "{v}"),
3977 Err(_) => write!(f, "<<thunk:error>>"),
3978 }
3979 }
3980 }
3981 }
3982}
3983
3984#[cfg(test)]
3985mod tests {
3986 use super::*;
3987 use std::rc::Rc;
3988
3989 #[test]
3995 #[ignore = "measurement, not a gate: run with --ignored --nocapture"]
3996 fn measure_hamt_vs_flat_attrset_cost() {
3997 use crate::value::census::rss_bytes;
3998 const N: usize = 300_000;
3999 const ENTRIES: usize = 4; let syms: Vec<Symbol> = (0..ENTRIES).map(|i| intern(&format!("k{i}"))).collect();
4002
4003 let base = rss_bytes();
4004 let mut hamts: Vec<FxHashMap<Symbol, Value>> = Vec::with_capacity(N);
4005 for _ in 0..N {
4006 let mut m = FxHashMap::default();
4007 for s in &syms { m.insert(*s, Value::Int(1)); }
4008 hamts.push(m);
4009 }
4010 let after_hamt = rss_bytes();
4011
4012 let mut flats: Vec<std::collections::HashMap<Symbol, Value>> = Vec::with_capacity(N);
4013 for _ in 0..N {
4014 let mut m = std::collections::HashMap::with_capacity(ENTRIES);
4015 for s in &syms { m.insert(*s, Value::Int(1)); }
4016 flats.push(m);
4017 }
4018 let after_flat = rss_bytes();
4019
4020 let hamt_cost = after_hamt.saturating_sub(base);
4021 let flat_cost = after_flat.saturating_sub(after_hamt);
4022 eprintln!("N={N} entries={ENTRIES}");
4023 eprintln!(" im_rc HAMT : {} B total, {} B/map", hamt_cost, hamt_cost / N as u64);
4024 eprintln!(" std flat : {} B total, {} B/map", flat_cost, flat_cost / N as u64);
4025 if flat_cost > 0 {
4026 eprintln!(" ratio : {:.2}x", hamt_cost as f64 / flat_cost as f64);
4027 }
4028 std::hint::black_box((&hamts, &flats));
4029 }
4030
4031 #[test]
4032 fn value_is_16_bytes() {
4033 assert_eq!(std::mem::size_of::<Value>(), 16);
4034 }
4035
4036 #[test]
4049 fn overlay_carries_attr_positions_from_both_sides() {
4050 let tbl = |file: &str, key: &str, off: u32| {
4051 let mut t = crate::pos::AttrPositions::new(Some(std::path::PathBuf::from(file)));
4052 t.insert(intern(key), off);
4053 Rc::new(t)
4054 };
4055 let mk = |file: &str, key: &str, off: u32| {
4059 let mut a = NixAttrs::new();
4060 a.insert(key.to_string(), Value::Int(1));
4061 a.set_positions(tbl(file, key, off));
4062 a
4063 };
4064
4065 let left_only = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "other", 22));
4068 assert_eq!(
4069 left_only.pos_entry(intern("modules")),
4070 Some((Some(std::path::PathBuf::from("/l.nix")), 11)),
4071 );
4072
4073 let both = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "modules", 22));
4075 assert_eq!(
4076 both.pos_entry(intern("modules")),
4077 Some((Some(std::path::PathBuf::from("/r.nix")), 22)),
4078 );
4079
4080 assert_eq!(both.pos_entry(intern("nope")), None);
4082 }
4083
4084 #[test]
4087 fn to_json_null() {
4088 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
4089 }
4090
4091 #[test]
4092 fn to_json_bool() {
4093 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
4094 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
4095 }
4096
4097 #[test]
4098 fn to_json_int() {
4099 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
4100 }
4101
4102 #[test]
4103 fn to_json_float() {
4104 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
4105 }
4106
4107 #[test]
4108 fn to_json_string() {
4109 assert_eq!(
4110 Value::string("hello").to_json(),
4111 serde_json::Value::String("hello".to_string()),
4112 );
4113 }
4114
4115 #[test]
4116 fn to_json_path() {
4117 assert_eq!(
4118 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
4119 serde_json::Value::String("/nix/store".to_string()),
4120 );
4121 }
4122
4123 #[test]
4124 fn to_json_list() {
4125 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
4126 assert_eq!(v.to_json(), serde_json::json!([1, true]));
4127 }
4128
4129 #[test]
4130 fn to_json_attrs() {
4131 let mut attrs = NixAttrs::new();
4132 attrs.insert("a".to_string(), Value::Int(1));
4133 let v = Value::Attrs(Rc::new(attrs));
4134 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
4135 }
4136
4137 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
4140 let mut a = NixAttrs::new();
4141 a.insert("type".to_string(), Value::string("derivation"));
4142 a.insert("outPath".to_string(), Value::string(out_path));
4143 a.insert(extra_key.to_string(), Value::Int(extra_val));
4144 Value::Attrs(Rc::new(a))
4145 }
4146
4147 #[test]
4148 fn derivations_same_outpath_differing_attrs_are_equal() {
4149 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4156 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
4157 assert!(a == b, "same-outPath derivations must compare equal");
4158 assert!(!(a != b));
4159 }
4160
4161 #[test]
4162 fn derivations_differing_outpath_are_unequal() {
4163 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4164 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
4165 assert!(a != b, "different-outPath derivations must compare unequal");
4166 }
4167
4168 #[test]
4169 fn non_derivation_attrs_with_outpath_use_structural_eq() {
4170 let mut a = NixAttrs::new();
4173 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
4174 a.insert("foo".to_string(), Value::Int(1));
4175 let mut b = NixAttrs::new();
4176 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
4177 b.insert("foo".to_string(), Value::Int(2));
4178 assert!(
4179 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
4180 "non-derivation attrs with equal outPath but differing foo must be unequal",
4181 );
4182 }
4183
4184 #[test]
4190 fn attrs_eq_borrow_result_matches_multi_key() {
4191 let mk = || {
4194 let mut inner = NixAttrs::new();
4195 inner.insert("n".to_string(), Value::Int(7));
4196 let mut a = NixAttrs::new();
4197 a.insert("a".to_string(), Value::Int(1));
4198 a.insert("b".to_string(), Value::string("two"));
4199 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
4200 Value::Attrs(Rc::new(a))
4201 };
4202 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
4203
4204 let mut b = NixAttrs::new();
4206 b.insert("a".to_string(), Value::Int(1));
4207 b.insert("b".to_string(), Value::string("TWO"));
4208 let mut a2 = NixAttrs::new();
4209 a2.insert("a".to_string(), Value::Int(1));
4210 a2.insert("b".to_string(), Value::string("two"));
4211 assert!(
4212 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
4213 "attrsets differing in one value must be unequal (borrow path)",
4214 );
4215
4216 let mut a3 = NixAttrs::new();
4218 a3.insert("a".to_string(), Value::Int(1));
4219 let mut b3 = NixAttrs::new();
4220 b3.insert("a".to_string(), Value::Int(1));
4221 b3.insert("extra".to_string(), Value::Int(9));
4222 assert!(
4223 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
4224 "attrsets differing in key set must be unequal (borrow path)",
4225 );
4226 }
4227
4228 #[test]
4229 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
4230 let boom = Value::Thunk(Thunk::new_native(|| {
4241 Err(EvalError::Throw("kaboom".to_string()))
4242 }));
4243 let mut a = NixAttrs::new();
4244 a.insert("x".to_string(), Value::Int(1));
4245 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4247 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4249 let va = Value::Attrs(Rc::new(a));
4253 let vb = Value::Attrs(Rc::new(b));
4254 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4255 }
4256
4257 #[test]
4258 fn attrs_eq_borrow_overlay_still_compares() {
4259 let mut base = NixAttrs::new();
4263 base.insert("a".to_string(), Value::Int(1));
4264 let mut over = NixAttrs::new();
4265 over.insert("b".to_string(), Value::Int(2));
4266 let merged = base.overlay(over);
4269 let mut flat = NixAttrs::new();
4270 flat.insert("a".to_string(), Value::Int(1));
4271 flat.insert("b".to_string(), Value::Int(2));
4272 assert!(
4273 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4274 "overlay and equivalent flat attrset must compare equal (borrow path)",
4275 );
4276 }
4277
4278 #[test]
4279 fn to_json_lambda() {
4280 let root = rnix::Root::parse("x: x");
4282 let expr = root.tree().expr().unwrap();
4283 let lambda = match expr {
4284 rnix::ast::Expr::Lambda(l) => l,
4285 _ => panic!("expected lambda"),
4286 };
4287 let closure = Closure {
4288 param: lambda.param().unwrap(),
4289 body: lambda.body().unwrap(),
4290 env: Env::new(),
4291 };
4292 assert_eq!(
4293 Value::Lambda(Rc::new(closure)).to_json(),
4294 serde_json::Value::String("<lambda>".to_string()),
4295 );
4296 }
4297
4298 #[test]
4299 fn to_json_builtin() {
4300 let b = BuiltinFn {
4301 name: "test",
4302 func: Rc::new(|_| Ok(Value::Null)),
4303 };
4304 assert_eq!(
4305 Value::Builtin(Box::new(b)).to_json(),
4306 serde_json::Value::String("<builtin test>".to_string()),
4307 );
4308 }
4309
4310 #[test]
4313 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4314
4315 #[test]
4316 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4317
4318 #[test]
4319 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4320
4321 #[test]
4322 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4323
4324 #[test]
4325 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4326
4327 #[test]
4328 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4329
4330 #[test]
4331 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4332
4333 #[test]
4334 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4335
4336 #[test]
4337 fn type_name_lambda() {
4338 let root = rnix::Root::parse("x: x");
4339 let expr = root.tree().expr().unwrap();
4340 let lambda = match expr {
4341 rnix::ast::Expr::Lambda(l) => l,
4342 _ => panic!("expected lambda"),
4343 };
4344 let closure = Closure {
4345 param: lambda.param().unwrap(),
4346 body: lambda.body().unwrap(),
4347 env: Env::new(),
4348 };
4349 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4350 }
4351
4352 #[test]
4353 fn type_name_builtin() {
4354 let b = BuiltinFn {
4355 name: "t",
4356 func: Rc::new(|_| Ok(Value::Null)),
4357 };
4358 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4359 }
4360
4361 #[test]
4364 fn as_bool_error_on_non_bool() {
4365 assert!(Value::Int(1).as_bool().is_err());
4366 assert!(Value::string("true").as_bool().is_err());
4367 }
4368
4369 #[test]
4370 fn as_int_error_on_non_int() {
4371 assert!(Value::Bool(true).as_int().is_err());
4372 assert!(Value::Float(1.0).as_int().is_err());
4373 }
4374
4375 #[test]
4376 fn as_string_error_on_non_string() {
4377 assert!(Value::Int(42).as_string().is_err());
4378 assert!(Value::Null.as_string().is_err());
4379 }
4380
4381 #[test]
4382 fn as_attrs_error_on_non_attrs() {
4383 assert!(Value::Int(1).as_attrs().is_err());
4384 assert!(Value::list(vec![]).as_attrs().is_err());
4385 }
4386
4387 #[test]
4388 fn as_list_error_on_non_list() {
4389 assert!(Value::Int(1).as_list().is_err());
4390 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4391 }
4392
4393 #[test]
4396 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4397 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4399 assert!(left.is_uniquely_owned_list());
4400 let right = [Value::Int(3), Value::Int(4)];
4401 let out = super::concat_lists(left, &right).unwrap();
4402 assert_eq!(
4403 out.as_list().unwrap(),
4404 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4405 );
4406 }
4407
4408 #[test]
4409 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4410 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4413 let left = Value::List(Rc::clone(&shared));
4414 assert!(!left.is_uniquely_owned_list());
4415 let right = [Value::Int(3)];
4416 let out = super::concat_lists(left, &right).unwrap();
4417 assert_eq!(
4418 out.as_list().unwrap(),
4419 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4420 );
4421 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4423 }
4424
4425 #[test]
4426 fn concat_lists_empty_operands() {
4427 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4428 assert!(out.as_list().unwrap().is_empty());
4429 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4430 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4431 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4432 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4433 }
4434
4435 #[test]
4436 fn concat_lists_non_list_left_errors() {
4437 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4438 }
4439
4440 #[test]
4441 fn concat_lists_preserves_element_identity() {
4442 let inner = Rc::new(NixString::plain("x"));
4444 let a = Value::String(Rc::clone(&inner));
4445 let left = Value::list(vec![a]);
4446 let out = super::concat_lists(left, &[]).unwrap();
4447 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4448 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4449 } else {
4450 panic!("expected string element");
4451 }
4452 }
4453
4454 #[test]
4457 fn to_float_coerces_int() {
4458 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4459 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4460 assert!(Value::string("x").to_float().is_err());
4461 }
4462
4463 #[test]
4466 fn partial_eq_int_float_cross() {
4467 assert_eq!(Value::Int(3), Value::Float(3.0));
4468 assert_eq!(Value::Float(3.0), Value::Int(3));
4469 assert_ne!(Value::Int(3), Value::Float(3.5));
4470 }
4471
4472 #[test]
4473 fn partial_eq_different_types_not_equal() {
4474 assert_ne!(Value::Int(1), Value::string("1"));
4475 assert_ne!(Value::Bool(true), Value::Int(1));
4476 assert_ne!(Value::Null, Value::Bool(false));
4477 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4478 }
4479
4480 #[test]
4483 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4484
4485 #[test]
4486 fn display_bool() {
4487 assert_eq!(format!("{}", Value::Bool(true)), "true");
4488 assert_eq!(format!("{}", Value::Bool(false)), "false");
4489 }
4490
4491 #[test]
4492 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4493
4494 #[test]
4495 fn display_float() {
4496 let s = format!("{}", Value::Float(3.14));
4497 assert!(s.contains("3.14"));
4498 }
4499
4500 #[test]
4501 fn display_string() {
4502 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4503 }
4504
4505 #[test]
4506 fn display_string_with_escapes() {
4507 let v = Value::string("a\"b\\c");
4508 let s = format!("{v}");
4509 assert!(s.contains("\\\""));
4510 assert!(s.contains("\\\\"));
4511 }
4512
4513 #[test]
4514 fn display_path() {
4515 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4516 }
4517
4518 #[test]
4519 fn display_list() {
4520 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4521 assert_eq!(format!("{v}"), "[ 1 2 ]");
4522 }
4523
4524 #[test]
4525 fn display_attrs() {
4526 let mut attrs = NixAttrs::new();
4527 attrs.insert("x".to_string(), Value::Int(1));
4528 let v = Value::Attrs(Rc::new(attrs));
4529 assert_eq!(format!("{v}"), "{ x = 1; }");
4530 }
4531
4532 #[test]
4533 fn display_lambda() {
4534 let root = rnix::Root::parse("x: x");
4535 let expr = root.tree().expr().unwrap();
4536 let lambda = match expr {
4537 rnix::ast::Expr::Lambda(l) => l,
4538 _ => panic!("expected lambda"),
4539 };
4540 let closure = Closure {
4541 param: lambda.param().unwrap(),
4542 body: lambda.body().unwrap(),
4543 env: Env::new(),
4544 };
4545 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4546 }
4547
4548 #[test]
4549 fn display_builtin() {
4550 let b = BuiltinFn {
4551 name: "add",
4552 func: Rc::new(|_| Ok(Value::Null)),
4553 };
4554 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4555 }
4556
4557 #[test]
4560 fn nixattrs_update_merging() {
4561 let mut a = NixAttrs::new();
4562 a.insert("x".to_string(), Value::Int(1));
4563 a.insert("y".to_string(), Value::Int(2));
4564 let mut b = NixAttrs::new();
4565 b.insert("y".to_string(), Value::Int(99));
4566 b.insert("z".to_string(), Value::Int(3));
4567 let merged = a.update(&b);
4568 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4569 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4570 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4571 assert_eq!(merged.len(), 3);
4572 }
4573
4574 #[test]
4575 fn nixattrs_contains_key() {
4576 let mut a = NixAttrs::new();
4577 a.insert("foo".to_string(), Value::Null);
4578 assert!(a.contains_key("foo"));
4579 assert!(!a.contains_key("bar"));
4580 }
4581
4582 #[test]
4585 fn env_lookup_through_parent_chain() {
4586 let mut root = Env::new();
4587 root.bind("a".to_string(), Value::Int(1));
4588 let mut child = root.child();
4589 child.bind("b".to_string(), Value::Int(2));
4590 let grandchild = child.child();
4591 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4593 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4594 assert_eq!(grandchild.lookup("c"), None);
4595 }
4596
4597 #[test]
4598 fn env_with_scope_lookup() {
4599 let mut attrs = NixAttrs::new();
4600 attrs.insert("x".to_string(), Value::Int(42));
4601 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4602 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4603 assert_eq!(env.lookup("y"), None);
4604 }
4605
4606 #[test]
4607 fn env_local_shadows_with_scope() {
4608 let mut attrs = NixAttrs::new();
4609 attrs.insert("x".to_string(), Value::Int(1));
4610 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4611 env.bind("x".to_string(), Value::Int(99));
4612 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4613 }
4614
4615 #[test]
4618 fn string_context_merge_combines_elements() {
4619 let mut ctx_a = StringContext::new();
4620 ctx_a.add_plain("/nix/store/aaa".to_string());
4621 let mut ctx_b = StringContext::new();
4622 ctx_b.add_plain("/nix/store/bbb".to_string());
4623 ctx_a.merge(&ctx_b);
4624 assert_eq!(ctx_a.len(), 2);
4625 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4626 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4627 }
4628
4629 #[test]
4630 fn string_context_merge_deduplicates() {
4631 let mut ctx = StringContext::new();
4632 ctx.add_plain("/nix/store/same".to_string());
4633 ctx.add_plain("/nix/store/same".to_string());
4634 assert_eq!(ctx.len(), 1);
4635 }
4636
4637 #[test]
4638 fn string_context_mixed_element_types() {
4639 let mut ctx = StringContext::new();
4640 ctx.add_plain("/nix/store/foo".to_string());
4641 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4642 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4643 assert_eq!(ctx.len(), 3);
4644 assert!(!ctx.is_empty());
4645 }
4646
4647 #[test]
4648 fn string_context_new_is_empty() {
4649 let ctx = StringContext::new();
4650 assert!(ctx.is_empty());
4651 assert_eq!(ctx.len(), 0);
4652 }
4653
4654 #[test]
4655 fn string_context_merge_zero_elements() {
4656 let mut ctx_a = StringContext::new();
4657 let ctx_b = StringContext::new();
4658 ctx_a.merge(&ctx_b);
4659 assert!(ctx_a.is_empty());
4660 }
4661
4662 #[test]
4663 fn string_context_merge_one_element() {
4664 let mut ctx = StringContext::new();
4665 let mut other = StringContext::new();
4666 other.add_plain("/nix/store/only".to_string());
4667 ctx.merge(&other);
4668 assert_eq!(ctx.len(), 1);
4669 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4670 }
4671
4672 #[test]
4673 fn string_context_merge_two_elements() {
4674 let mut ctx = StringContext::new();
4675 ctx.add_plain("/nix/store/a".to_string());
4676 let mut other = StringContext::new();
4677 other.add_plain("/nix/store/b".to_string());
4678 ctx.merge(&other);
4679 assert_eq!(ctx.len(), 2);
4680 }
4681
4682 #[test]
4683 fn string_context_merge_five_elements() {
4684 let mut ctx = StringContext::new();
4685 for i in 0..5 {
4686 ctx.add_plain(format!("/nix/store/path-{i}"));
4687 }
4688 assert_eq!(ctx.len(), 5);
4689 for i in 0..5 {
4690 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4691 }
4692 }
4693
4694 #[test]
4695 fn string_context_insert_deduplicates() {
4696 let mut ctx = StringContext::new();
4697 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4698 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4699 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4700 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4701 assert_eq!(ctx.len(), 2);
4702 }
4703
4704 #[test]
4705 fn nix_string_plain_has_no_context() {
4706 let s = NixString::plain("hello");
4707 assert!(!s.has_context());
4708 assert_eq!(s.as_str(), "hello");
4709 }
4710
4711 #[test]
4712 fn nix_string_with_context_reports_context() {
4713 let mut ctx = StringContext::new();
4714 ctx.add_plain("/nix/store/xyz".to_string());
4715 let s = NixString::with_context("hello", ctx);
4716 assert!(s.has_context());
4717 assert_eq!(s.as_str(), "hello");
4718 }
4719
4720 #[test]
4721 fn nix_string_display_shows_chars_only() {
4722 let mut ctx = StringContext::new();
4723 ctx.add_plain("/nix/store/abc".to_string());
4724 let s = NixString::with_context("visible", ctx);
4725 assert_eq!(format!("{s}"), "visible");
4726 }
4727
4728 #[test]
4729 fn nix_string_struct_eq_includes_context() {
4730 let plain = NixString::plain("hello");
4731 let mut ctx = StringContext::new();
4732 ctx.add_plain("/nix/store/xxx".to_string());
4733 let with_ctx = NixString::with_context("hello", ctx);
4734 assert_ne!(plain, with_ctx);
4736 }
4737
4738 #[test]
4739 fn value_string_eq_ignores_context() {
4740 let plain = Value::String(Rc::new(NixString::plain("hello")));
4741 let mut ctx = StringContext::new();
4742 ctx.add_plain("/nix/store/xxx".to_string());
4743 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4744 assert_eq!(plain, with_ctx);
4746 }
4747
4748 #[test]
4751 fn env_nested_with_inner_wins() {
4752 let mut outer_attrs = NixAttrs::new();
4753 outer_attrs.insert("x".to_string(), Value::Int(1));
4754 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4755 let mut inner_attrs = NixAttrs::new();
4756 inner_attrs.insert("x".to_string(), Value::Int(2));
4757 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4758 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4759 }
4760
4761 #[test]
4762 fn env_nested_with_fallback_to_outer() {
4763 let mut outer_attrs = NixAttrs::new();
4764 outer_attrs.insert("x".to_string(), Value::Int(1));
4765 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4766 let mut inner_attrs = NixAttrs::new();
4767 inner_attrs.insert("y".to_string(), Value::Int(2));
4768 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4769 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4770 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4771 }
4772
4773 #[test]
4774 fn env_lexical_binding_wins_over_all_with_scopes() {
4775 let mut outer_attrs = NixAttrs::new();
4776 outer_attrs.insert("x".to_string(), Value::Int(1));
4777 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4778 let mut inner_attrs = NixAttrs::new();
4779 inner_attrs.insert("x".to_string(), Value::Int(2));
4780 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4781 inner.bind("x".to_string(), Value::Int(99));
4782 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4783 }
4784
4785 #[test]
4786 fn env_parent_lexical_wins_over_child_with_scope() {
4787 let mut root = Env::new();
4788 root.bind("x".to_string(), Value::Int(10));
4789 let mut child_attrs = NixAttrs::new();
4790 child_attrs.insert("x".to_string(), Value::Int(20));
4791 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4792 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4793 }
4794
4795 #[test]
4796 fn env_deeply_nested_with_scopes_three_levels() {
4797 let mut a = NixAttrs::new();
4798 a.insert("x".to_string(), Value::Int(1));
4799 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4800
4801 let mut b = NixAttrs::new();
4802 b.insert("y".to_string(), Value::Int(2));
4803 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4804
4805 let mut c = NixAttrs::new();
4806 c.insert("z".to_string(), Value::Int(3));
4807 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4808
4809 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4810 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4811 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4812 assert_eq!(env3.lookup("w"), None);
4813 }
4814
4815 #[test]
4816 fn env_with_scope_does_not_pollute_bindings() {
4817 let mut attrs = NixAttrs::new();
4820 attrs.insert("x".to_string(), Value::Int(42));
4821 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4822 assert!(env.0.bindings.get(&intern("x")).is_none());
4824 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4826 }
4827
4828 #[test]
4829 fn env_lexical_binding_not_in_with_scopes() {
4830 let mut env = Env::new();
4832 env.bind("x".to_string(), Value::Int(42));
4833 assert!(env.0.with_scopes.is_empty());
4835 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4837 }
4838
4839 #[test]
4840 fn env_child_inherits_eval_file() {
4841 let mut env = Env::new();
4842 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4843 let child = env.child();
4844 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4845 }
4846
4847 #[test]
4848 fn env_new_has_no_parent_no_with() {
4849 let env = Env::new();
4850 assert_eq!(env.lookup("anything"), None);
4851 assert!(env.eval_file().is_none());
4852 }
4853
4854 #[test]
4857 fn thunk_new_suspended_is_not_evaluated() {
4858 let root = rnix::Root::parse("42");
4859 let expr = root.tree().expr().unwrap();
4860 let thunk = Thunk::new_suspended(expr, Env::new());
4861 assert!(!thunk.is_evaluated());
4862 }
4863
4864 #[test]
4865 fn thunk_new_evaluated_is_evaluated() {
4866 let thunk = Thunk::new_evaluated(Value::Int(42));
4867 assert!(thunk.is_evaluated());
4868 }
4869
4870 #[test]
4871 fn thunk_force_evaluates_suspended() {
4872 let root = rnix::Root::parse("42");
4873 let expr = root.tree().expr().unwrap();
4874 let thunk = Thunk::new_suspended(expr, Env::new());
4875 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4876 assert!(result.is_ok());
4877 assert_eq!(result.unwrap(), Value::Int(42));
4878 assert!(thunk.is_evaluated());
4879 }
4880
4881 #[test]
4882 fn thunk_force_memoizes_result() {
4883 let root = rnix::Root::parse("1 + 2");
4884 let expr = root.tree().expr().unwrap();
4885 let thunk = Thunk::new_suspended(expr, Env::new());
4886 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4887 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4888 assert_eq!(r1, Value::Int(3));
4889 assert_eq!(r2, Value::Int(3));
4890 }
4891
4892 #[test]
4893 fn thunk_force_already_evaluated_returns_value() {
4894 let thunk = Thunk::new_evaluated(Value::Bool(true));
4895 let result = thunk.force(&|_, _| panic!("should not be called"));
4896 assert_eq!(result.unwrap(), Value::Bool(true));
4897 }
4898
4899 #[test]
4908 fn thunk_force_concrete_skips_redundant_store_but_caches() {
4909 let root = rnix::Root::parse("1 + 2");
4912 let expr = root.tree().expr().unwrap();
4913 let thunk = Thunk::new_suspended(expr, Env::new());
4914
4915 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4916 assert_eq!(r1, Value::Int(3));
4917 assert!(thunk.is_evaluated());
4918
4919 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
4922
4923 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
4925 assert_eq!(r2, Value::Int(3));
4926 }
4927
4928 #[test]
4929 fn thunk_blackhole_detects_infinite_recursion() {
4930 let root = rnix::Root::parse("42");
4931 let expr = root.tree().expr().unwrap();
4932 let thunk = Thunk::new_suspended(expr, Env::new());
4933
4934 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
4937
4938 let result = thunk.force(&|_, _| Ok(Value::Null));
4939 assert!(result.is_err());
4940 let err_msg = format!("{}", result.unwrap_err());
4941 assert!(err_msg.contains("infinite recursion"));
4942 }
4943
4944 #[test]
4945 fn thunk_update_env_replaces_suspended_env() {
4946 let root = rnix::Root::parse("x");
4947 let expr = root.tree().expr().unwrap();
4948 let thunk = Thunk::new_suspended(expr, Env::new());
4949
4950 let mut new_env = Env::new();
4951 new_env.bind("x".to_string(), Value::Int(99));
4952 thunk.update_env(&new_env);
4953
4954 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4955 assert_eq!(result.unwrap(), Value::Int(99));
4956 }
4957
4958 #[test]
4959 fn thunk_update_env_noop_when_evaluated() {
4960 let thunk = Thunk::new_evaluated(Value::Int(1));
4961 let mut new_env = Env::new();
4962 new_env.bind("x".to_string(), Value::Int(99));
4963 thunk.update_env(&new_env);
4964 assert_eq!(
4965 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
4966 Value::Int(1),
4967 );
4968 }
4969
4970 #[test]
4971 fn thunk_debug_suspended() {
4972 let root = rnix::Root::parse("42");
4973 let expr = root.tree().expr().unwrap();
4974 let thunk = Thunk::new_suspended(expr, Env::new());
4975 assert_eq!(format!("{thunk:?}"), "<thunk>");
4976 }
4977
4978 #[test]
4979 fn thunk_debug_evaluated() {
4980 let thunk = Thunk::new_evaluated(Value::Int(42));
4981 let dbg = format!("{thunk:?}");
4982 assert!(dbg.contains("42"));
4983 }
4984
4985 #[test]
4986 fn thunk_error_restores_suspended_state() {
4987 let root = rnix::Root::parse("nonexistent_var");
4988 let expr = root.tree().expr().unwrap();
4989 let thunk = Thunk::new_suspended(expr, Env::new());
4990
4991 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4992 assert!(result.is_err());
4993 assert!(!thunk.is_evaluated());
4995 let dbg = format!("{thunk:?}");
4996 assert_eq!(dbg, "<thunk>");
4997 }
4998
4999 #[test]
5000 fn thunk_inherit_select_forces_and_selects() {
5001 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5002 let expr = root.tree().expr().unwrap();
5003 let source = Thunk::new_suspended(expr, Env::new());
5004 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5005 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5006 assert_eq!(result.unwrap(), Value::Int(42));
5007 assert!(thunk.is_evaluated());
5008 }
5009
5010 #[test]
5011 fn thunk_inherit_select_missing_attr_errors() {
5012 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5013 let expr = root.tree().expr().unwrap();
5014 let source = Thunk::new_suspended(expr, Env::new());
5015 let thunk = Thunk::new_inherit_select(source, "y".to_string());
5016 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5017 assert!(result.is_err());
5018 assert!(!thunk.is_evaluated());
5020 }
5021
5022 #[test]
5023 fn thunk_inherit_select_non_attrs_source_errors() {
5024 let root = rnix::Root::parse("42");
5025 let expr = root.tree().expr().unwrap();
5026 let source = Thunk::new_suspended(expr, Env::new());
5027 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5028 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5029 assert!(result.is_err());
5030 let msg = format!("{}", result.unwrap_err());
5031 assert!(msg.contains("not a set"));
5032 }
5033
5034 #[test]
5035 fn thunk_inherit_select_shares_source_thunk() {
5036 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
5040 let expr = root.tree().expr().unwrap();
5041 let source = Thunk::new_suspended(expr, Env::new());
5042 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
5043 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
5044 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
5045 assert_eq!(result_a.unwrap(), Value::Int(1));
5046 assert!(source.is_evaluated());
5048 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
5050 assert_eq!(result_b.unwrap(), Value::Int(2));
5051 }
5052
5053 #[test]
5056 fn nixattrs_empty_operations() {
5057 let a = NixAttrs::new();
5058 assert!(a.is_empty());
5059 assert_eq!(a.len(), 0);
5060 assert_eq!(a.get("x"), None);
5061 assert!(!a.contains_key("x"));
5062 assert_eq!(a.keys().count(), 0);
5063 assert_eq!(a.iter().count(), 0);
5064 }
5065
5066 #[test]
5067 fn nixattrs_update_with_empty() {
5068 let mut a = NixAttrs::new();
5069 a.insert("x".to_string(), Value::Int(1));
5070 let b = NixAttrs::new();
5071 let merged = a.update(&b);
5072 assert_eq!(merged.len(), 1);
5073 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5074 }
5075
5076 #[test]
5077 fn nixattrs_update_empty_with_nonempty() {
5078 let a = NixAttrs::new();
5079 let mut b = NixAttrs::new();
5080 b.insert("x".to_string(), Value::Int(1));
5081 let merged = a.update(&b);
5082 assert_eq!(merged.len(), 1);
5083 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5084 }
5085
5086 #[test]
5087 fn nixattrs_keys_sorted_order() {
5088 let mut a = NixAttrs::new();
5089 a.insert("c".to_string(), Value::Int(3));
5090 a.insert("a".to_string(), Value::Int(1));
5091 a.insert("b".to_string(), Value::Int(2));
5092 let keys: Vec<String> = a.keys().collect();
5093 assert_eq!(keys, vec!["a", "b", "c"]);
5094 }
5095
5096 #[test]
5099 fn value_to_str_forces_thunks() {
5100 let root = rnix::Root::parse(r#""hello""#);
5101 let expr = root.tree().expr().unwrap();
5102 let thunk = Thunk::new_suspended(expr, Env::new());
5103 let val = Value::Thunk(thunk);
5104 assert_eq!(val.to_str().unwrap(), "hello");
5105 }
5106
5107 #[test]
5108 fn value_to_nix_string_forces_thunks() {
5109 let root = rnix::Root::parse(r#""world""#);
5110 let expr = root.tree().expr().unwrap();
5111 let thunk = Thunk::new_suspended(expr, Env::new());
5112 let val = Value::Thunk(thunk);
5113 let ns = val.to_nix_string().unwrap();
5114 assert_eq!(ns.as_str(), "world");
5115 assert!(!ns.has_context());
5116 }
5117
5118 #[test]
5119 fn value_to_attrs_forces_thunks() {
5120 let root = rnix::Root::parse("{ x = 1; }");
5121 let expr = root.tree().expr().unwrap();
5122 let thunk = Thunk::new_suspended(expr, Env::new());
5123 let val = Value::Thunk(thunk);
5124 let attrs = val.to_attrs().unwrap();
5125 assert_eq!(attrs.len(), 1);
5126 }
5127
5128 #[test]
5129 fn value_to_list_forces_thunks() {
5130 let root = rnix::Root::parse("[1 2 3]");
5131 let expr = root.tree().expr().unwrap();
5132 let thunk = Thunk::new_suspended(expr, Env::new());
5133 let val = Value::Thunk(thunk);
5134 let list = val.to_list().unwrap();
5135 assert_eq!(list.len(), 3);
5136 }
5137
5138 #[test]
5139 fn value_to_float_on_thunk() {
5140 let root = rnix::Root::parse("3.14");
5141 let expr = root.tree().expr().unwrap();
5142 let thunk = Thunk::new_suspended(expr, Env::new());
5143 let val = Value::Thunk(thunk);
5144 let f = val.to_float().unwrap();
5145 assert!((f - 3.14).abs() < f64::EPSILON);
5146 }
5147
5148 #[test]
5149 fn value_as_bool_on_thunk() {
5150 let root = rnix::Root::parse("true");
5151 let expr = root.tree().expr().unwrap();
5152 let thunk = Thunk::new_suspended(expr, Env::new());
5153 let val = Value::Thunk(thunk);
5154 assert!(val.as_bool().unwrap());
5155 }
5156
5157 #[test]
5158 fn value_as_int_on_thunk() {
5159 let root = rnix::Root::parse("42");
5160 let expr = root.tree().expr().unwrap();
5161 let thunk = Thunk::new_suspended(expr, Env::new());
5162 let val = Value::Thunk(thunk);
5163 assert_eq!(val.as_int().unwrap(), 42);
5164 }
5165
5166 #[test]
5167 fn value_string_constructor() {
5168 let v = Value::string("test");
5169 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
5170 }
5171
5172 #[test]
5173 fn value_partial_eq_null_null() {
5174 assert_eq!(Value::Null, Value::Null);
5175 }
5176
5177 #[test]
5178 fn value_partial_eq_lists_deep() {
5179 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5180 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5181 assert_eq!(a, b);
5182 }
5183
5184 #[test]
5185 fn value_partial_eq_attrs_deep() {
5186 let mut a = NixAttrs::new();
5187 a.insert("x".to_string(), Value::Int(1));
5188 let mut b = NixAttrs::new();
5189 b.insert("x".to_string(), Value::Int(1));
5190 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
5191 }
5192
5193 #[test]
5196 fn eval_error_type_error_constructor() {
5197 let e = EvalError::type_error("oops");
5198 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
5199 }
5200
5201 #[test]
5202 fn eval_error_type_mismatch_constructor() {
5203 let e = EvalError::type_mismatch("int", "string");
5204 match e {
5205 EvalError::TypeMismatch { expected, got } => {
5206 assert_eq!(expected, "int");
5207 assert_eq!(got, "string");
5208 }
5209 _ => panic!("expected TypeMismatch"),
5210 }
5211 }
5212
5213 #[test]
5214 fn eval_error_is_throw_yes_no() {
5215 assert!(EvalError::Throw("oops".into()).is_throw());
5216 assert!(!EvalError::TypeError("oops".into()).is_throw());
5217 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
5218 }
5219
5220 #[test]
5221 fn eval_error_is_infinite_recursion_yes_no() {
5222 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
5223 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
5224 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
5225 }
5226
5227 #[test]
5228 fn eval_error_display_undefined_var() {
5229 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
5230 assert!(s.contains("undefined variable"));
5231 assert!(s.contains("foo"));
5232 }
5233
5234 #[test]
5235 fn eval_error_display_type_error() {
5236 let s = format!("{}", EvalError::TypeError("bad".into()));
5237 assert!(s.contains("type error"));
5238 assert!(s.contains("bad"));
5239 }
5240
5241 #[test]
5242 fn eval_error_display_attr_not_found() {
5243 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5244 assert!(s.contains("attribute not found"));
5245 assert!(s.contains("x"));
5246 }
5247
5248 #[test]
5249 fn eval_error_display_type_mismatch() {
5250 let s = format!(
5251 "{}",
5252 EvalError::TypeMismatch { expected: "int", got: "string" }
5253 );
5254 assert!(s.contains("expected int"));
5255 assert!(s.contains("got string"));
5256 }
5257
5258 #[test]
5259 fn eval_error_display_assertion_failed() {
5260 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5261 assert!(s.contains("assertion"));
5262 }
5263
5264 #[test]
5265 fn eval_error_display_division_by_zero() {
5266 let s = format!("{}", EvalError::DivisionByZero);
5267 assert!(s.contains("division by zero"));
5268 }
5269
5270 #[test]
5271 fn eval_error_display_infinite_recursion() {
5272 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5273 assert!(s.contains("infinite recursion"));
5274 assert!(s.contains("loop"));
5275 }
5276
5277 #[test]
5278 fn eval_error_display_io_error() {
5279 let s = format!(
5280 "{}",
5281 EvalError::IoError {
5282 context: "ctx".into(),
5283 message: "no such file".into(),
5284 }
5285 );
5286 assert!(s.contains("I/O"));
5287 assert!(s.contains("ctx"));
5288 assert!(s.contains("no such file"));
5289 }
5290
5291 #[test]
5292 fn eval_error_display_throw() {
5293 let s = format!("{}", EvalError::Throw("boom".into()));
5294 assert_eq!(s, "boom");
5295 }
5296
5297 #[test]
5298 fn eval_error_display_not_implemented() {
5299 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5300 assert!(s.contains("not yet implemented"));
5301 assert!(s.contains("frob"));
5302 }
5303
5304 #[test]
5305 fn eval_error_display_parse_error() {
5306 let s = format!("{}", EvalError::ParseError("syntax".into()));
5307 assert!(s.contains("parse error"));
5308 assert!(s.contains("syntax"));
5309 }
5310
5311 #[test]
5312 fn eval_error_display_recursion_limit() {
5313 let s = format!(
5314 "{}",
5315 EvalError::RecursionLimit("max depth exceeded".into())
5316 );
5317 assert!(s.contains("recursion limit"));
5318 assert!(s.contains("max depth exceeded"));
5319 }
5320
5321 #[test]
5322 fn eval_error_partial_eq_same_variant() {
5323 assert_eq!(
5324 EvalError::UndefinedVar("x".into()),
5325 EvalError::UndefinedVar("x".into()),
5326 );
5327 assert_ne!(
5328 EvalError::UndefinedVar("x".into()),
5329 EvalError::UndefinedVar("y".into()),
5330 );
5331 assert_ne!(
5332 EvalError::UndefinedVar("x".into()),
5333 EvalError::AttrNotFound("x".into()),
5334 );
5335 }
5336
5337 #[test]
5340 fn context_element_display_plain() {
5341 let e = ContextElement::Plain("/nix/store/xyz".into());
5342 assert_eq!(format!("{e}"), "/nix/store/xyz");
5343 }
5344
5345 #[test]
5346 fn context_element_display_output() {
5347 let e = ContextElement::Output {
5348 drv: "/nix/store/abc.drv".into(),
5349 output: "out".into(),
5350 };
5351 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5352 }
5353
5354 #[test]
5355 fn context_element_display_drv_deep() {
5356 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5357 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5358 }
5359
5360 #[test]
5363 fn string_context_iter_yields_all() {
5364 let mut ctx = StringContext::new();
5365 ctx.add_plain("/nix/store/aaa");
5366 ctx.add_plain("/nix/store/bbb");
5367 let count = ctx.iter().count();
5368 assert_eq!(count, 2);
5369 }
5370
5371 #[test]
5372 fn string_context_len_matches_set_size() {
5373 let mut ctx = StringContext::new();
5374 assert_eq!(ctx.len(), 0);
5375 ctx.add_plain("/nix/store/x");
5376 assert_eq!(ctx.len(), 1);
5377 ctx.add_output("/nix/store/y.drv", "out");
5378 assert_eq!(ctx.len(), 2);
5379 }
5380
5381 #[test]
5382 fn string_context_insert_raw_element() {
5383 let mut ctx = StringContext::new();
5384 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5385 assert_eq!(ctx.len(), 1);
5386 }
5387
5388 #[test]
5389 fn string_context_default_is_empty() {
5390 let ctx = StringContext::default();
5391 assert!(ctx.is_empty());
5392 }
5393
5394 #[test]
5397 fn nix_string_as_ref_str() {
5398 let s = NixString::plain("hello");
5399 let r: &str = s.as_ref();
5400 assert_eq!(r, "hello");
5401 }
5402
5403 #[test]
5404 fn nix_string_deref_to_str_methods() {
5405 let s = NixString::plain("Hello World");
5406 assert_eq!(s.len(), 11);
5407 assert!(s.starts_with("Hello"));
5408 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5410 }
5411
5412 #[test]
5415 fn nixattrs_remove_returns_value() {
5416 let mut a = NixAttrs::new();
5417 a.insert("x".into(), Value::Int(1));
5418 let removed = a.remove("x");
5419 assert_eq!(removed, Some(Value::Int(1)));
5420 assert!(!a.contains_key("x"));
5421 assert_eq!(a.remove("y"), None);
5422 }
5423
5424 #[test]
5425 fn nixattrs_values_iter() {
5426 let mut a = NixAttrs::new();
5427 a.insert("a".into(), Value::Int(1));
5428 a.insert("b".into(), Value::Int(2));
5429 let mut vs: Vec<&Value> = a.values().collect();
5430 vs.sort_by_key(|v| match v {
5431 Value::Int(n) => *n,
5432 _ => 0,
5433 });
5434 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5435 }
5436
5437 #[test]
5438 fn nixattrs_iter_returns_sorted_pairs() {
5439 let mut a = NixAttrs::new();
5440 a.insert("zeta".into(), Value::Int(3));
5441 a.insert("alpha".into(), Value::Int(1));
5442 a.insert("mu".into(), Value::Int(2));
5443 let pairs: Vec<(String, &Value)> = a.iter().collect();
5444 assert_eq!(pairs[0].0, "alpha");
5445 assert_eq!(pairs[1].0, "mu");
5446 assert_eq!(pairs[2].0, "zeta");
5447 }
5448
5449 #[test]
5450 fn nixattrs_from_iterator() {
5451 let pairs = vec![
5452 ("a".to_string(), Value::Int(1)),
5453 ("b".to_string(), Value::Int(2)),
5454 ];
5455 let attrs: NixAttrs = pairs.into_iter().collect();
5456 assert_eq!(attrs.len(), 2);
5457 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5458 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5459 }
5460
5461 #[test]
5462 fn nixattrs_into_iterator_yields_owned() {
5463 let mut a = NixAttrs::new();
5464 a.insert("x".into(), Value::Int(42));
5465 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5466 assert_eq!(pairs.len(), 1);
5467 assert_eq!(pairs[0].0, "x");
5468 assert_eq!(pairs[0].1, Value::Int(42));
5469 }
5470
5471 #[test]
5472 fn nixattrs_default_is_empty() {
5473 let a = NixAttrs::default();
5474 assert!(a.is_empty());
5475 }
5476
5477 #[test]
5480 fn value_from_bool() {
5481 assert_eq!(Value::from(true), Value::Bool(true));
5482 assert_eq!(Value::from(false), Value::Bool(false));
5483 }
5484
5485 #[test]
5486 fn value_from_i64() {
5487 assert_eq!(Value::from(42_i64), Value::Int(42));
5488 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5489 }
5490
5491 #[test]
5492 fn value_from_f64() {
5493 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5494 }
5495
5496 #[test]
5497 fn value_from_nix_string() {
5498 let v: Value = NixString::plain("hi").into();
5499 assert_eq!(v, Value::string("hi"));
5500 }
5501
5502 #[test]
5503 fn value_from_nix_attrs() {
5504 let mut a = NixAttrs::new();
5505 a.insert("x".into(), Value::Int(1));
5506 let v: Value = a.into();
5507 match v {
5508 Value::Attrs(_) => {}
5509 _ => panic!("expected Attrs"),
5510 }
5511 }
5512
5513 #[test]
5514 fn value_from_vec() {
5515 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5516 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5517 }
5518
5519 #[test]
5520 fn value_default_is_null() {
5521 let v: Value = Value::default();
5522 assert_eq!(v, Value::Null);
5523 }
5524
5525 #[test]
5528 fn value_from_json_null() {
5529 let v = Value::from(&serde_json::Value::Null);
5530 assert_eq!(v, Value::Null);
5531 }
5532
5533 #[test]
5534 fn value_from_json_bool() {
5535 let v = Value::from(&serde_json::Value::Bool(true));
5536 assert_eq!(v, Value::Bool(true));
5537 }
5538
5539 #[test]
5540 fn value_from_json_int() {
5541 let v = Value::from(&serde_json::json!(42));
5542 assert_eq!(v, Value::Int(42));
5543 }
5544
5545 #[test]
5546 fn value_from_json_float() {
5547 let v = Value::from(&serde_json::json!(3.14));
5548 match v {
5549 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5550 _ => panic!("expected Float"),
5551 }
5552 }
5553
5554 #[test]
5555 fn value_from_json_string() {
5556 let v = Value::from(&serde_json::Value::String("hi".into()));
5557 assert_eq!(v, Value::string("hi"));
5558 }
5559
5560 #[test]
5561 fn value_from_json_array() {
5562 let v = Value::from(&serde_json::json!([1, true, "x"]));
5563 match v {
5564 Value::List(items) => {
5565 assert_eq!(items.len(), 3);
5566 assert_eq!(items[0], Value::Int(1));
5567 assert_eq!(items[1], Value::Bool(true));
5568 assert_eq!(items[2], Value::string("x"));
5569 }
5570 _ => panic!("expected List"),
5571 }
5572 }
5573
5574 #[test]
5575 fn value_from_json_object() {
5576 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5577 match v {
5578 Value::Attrs(attrs) => {
5579 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5580 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5581 }
5582 _ => panic!("expected Attrs"),
5583 }
5584 }
5585
5586 #[test]
5587 fn value_from_json_nested() {
5588 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5589 let json_back = v.to_json();
5590 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5591 }
5592
5593 #[test]
5596 fn value_from_toml_string() {
5597 let t = toml::Value::String("hi".into());
5598 assert_eq!(Value::from(&t), Value::string("hi"));
5599 }
5600
5601 #[test]
5602 fn value_from_toml_int() {
5603 let t = toml::Value::Integer(42);
5604 assert_eq!(Value::from(&t), Value::Int(42));
5605 }
5606
5607 #[test]
5608 fn value_from_toml_float() {
5609 let t = toml::Value::Float(3.14);
5610 match Value::from(&t) {
5611 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5612 _ => panic!("expected Float"),
5613 }
5614 }
5615
5616 #[test]
5617 fn value_from_toml_bool() {
5618 let t = toml::Value::Boolean(true);
5619 assert_eq!(Value::from(&t), Value::Bool(true));
5620 }
5621
5622 #[test]
5623 fn value_from_toml_array() {
5624 let t = toml::Value::Array(vec![
5625 toml::Value::Integer(1),
5626 toml::Value::Integer(2),
5627 ]);
5628 assert_eq!(
5629 Value::from(&t),
5630 Value::list(vec![Value::Int(1), Value::Int(2)]),
5631 );
5632 }
5633
5634 #[test]
5635 fn value_from_toml_table() {
5636 let mut tbl = toml::map::Map::new();
5637 tbl.insert("k".into(), toml::Value::Integer(7));
5638 let t = toml::Value::Table(tbl);
5639 match Value::from(&t) {
5640 Value::Attrs(attrs) => {
5641 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5642 }
5643 _ => panic!("expected Attrs"),
5644 }
5645 }
5646
5647 #[test]
5648 fn value_from_toml_datetime_becomes_string() {
5649 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5651 let t = toml::Value::Datetime(dt);
5652 match Value::from(&t) {
5653 Value::String(_) => {}
5654 other => panic!("expected String, got {other:?}"),
5655 }
5656 }
5657
5658 #[test]
5661 fn coerce_to_path_from_path() {
5662 let v = Value::Path(Box::new("/foo".into()));
5663 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5664 }
5665
5666 #[test]
5667 fn coerce_to_path_from_string() {
5668 let v = Value::string("/bar");
5669 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5670 }
5671
5672 #[test]
5680 fn out_path_needs_realize_matches_output_context() {
5681 let mut ctx = StringContext::new();
5684 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5685 assert_eq!(
5686 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5687 Some("/nix/store/aaa-thing.drv".to_string()),
5688 );
5689 }
5690
5691 #[test]
5692 fn out_path_needs_realize_ignores_plain_context() {
5693 let mut ctx = StringContext::new();
5696 ctx.add_plain("/nix/store/ccc-plain");
5697 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5698 }
5699
5700 #[test]
5701 fn out_path_needs_realize_ignores_non_store_path() {
5702 let mut ctx = StringContext::new();
5705 ctx.add_output("/nix/store/ddd.drv", "out");
5706 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5707 }
5708
5709 #[test]
5710 fn out_path_needs_realize_empty_context_is_none() {
5711 let ctx = StringContext::new();
5713 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5714 }
5715
5716 #[test]
5717 fn coerce_to_realized_path_present_output_is_passthrough() {
5718 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5722 std::fs::create_dir_all(&dir).unwrap();
5723 let file = dir.join("out");
5724 std::fs::write(&file, b"present").unwrap();
5725 let present = file.to_string_lossy().to_string();
5726
5727 let mut ctx = StringContext::new();
5728 ctx.add_plain(&present);
5733 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5734 present.as_str(),
5735 ctx,
5736 )));
5737 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5738 }
5739
5740 #[test]
5741 fn coerce_to_realized_path_absent_output_invokes_hook() {
5742 use std::sync::{Arc, Mutex};
5747 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5748 let seen2 = seen.clone();
5749 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5750 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5751 Ok(())
5752 }));
5753
5754 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5757 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5758 let mut ctx = StringContext::new();
5759 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5760 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5761
5762 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5764 let s = seen.lock().unwrap();
5765 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5766 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5767 assert_eq!(s[0].1, out);
5768 }
5769
5770 #[test]
5771 fn coerce_to_path_errors_on_int() {
5772 let v = Value::Int(1);
5773 let e = v.coerce_to_path("readFile").unwrap_err();
5774 match e {
5775 EvalError::TypeError(ref msg) => {
5776 assert!(msg.contains("readFile"));
5777 assert!(msg.contains("path or string"));
5778 assert!(msg.contains("int"));
5779 }
5780 _ => panic!("expected TypeError"),
5781 }
5782 }
5783
5784 #[test]
5785 fn coerce_to_path_errors_on_null() {
5786 let v = Value::Null;
5787 assert!(v.coerce_to_path("ctx").is_err());
5788 }
5789
5790 #[test]
5791 fn coerce_to_path_attrs_with_outpath() {
5792 let mut attrs = NixAttrs::new();
5793 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5794 let val = Value::Attrs(Rc::new(attrs));
5795 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5796 }
5797
5798 #[test]
5799 fn coerce_to_path_attrs_without_outpath_fails() {
5800 let attrs = NixAttrs::new();
5801 let val = Value::Attrs(Rc::new(attrs));
5802 assert!(val.coerce_to_path("test").is_err());
5803 }
5804
5805 #[test]
5808 fn coerce_to_string_string() {
5809 let v = Value::string("hello");
5810 let (s, _ctx) = v.coerce_to_string().unwrap();
5811 assert_eq!(s, "hello");
5812 }
5813
5814 #[test]
5815 fn coerce_to_string_path() {
5816 let v = Value::Path(Box::new("/foo".into()));
5817 let (s, ctx) = v.coerce_to_string().unwrap();
5818 assert_eq!(s, "/foo");
5819 assert!(!ctx.is_empty()); }
5821
5822 #[test]
5823 fn coerce_to_string_int() {
5824 let v = Value::Int(42);
5825 let (s, _ctx) = v.coerce_to_string().unwrap();
5826 assert_eq!(s, "42");
5827 }
5828
5829 #[test]
5830 fn coerce_to_string_float() {
5831 let v = Value::Float(3.14);
5833 let (s, _ctx) = v.coerce_to_string().unwrap();
5834 assert_eq!(s, "3.140000");
5835 }
5836
5837 #[test]
5838 fn coerce_to_string_bool_true() {
5839 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5840 assert_eq!(s, "1");
5841 }
5842
5843 #[test]
5844 fn coerce_to_string_bool_false() {
5845 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5846 assert_eq!(s, "");
5847 }
5848
5849 #[test]
5850 fn coerce_to_string_null() {
5851 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5852 assert_eq!(s, "");
5853 }
5854
5855 #[test]
5856 fn coerce_to_string_attrs_with_outpath() {
5857 let mut attrs = NixAttrs::new();
5858 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5859 let val = Value::Attrs(Rc::new(attrs));
5860 let (s, _ctx) = val.coerce_to_string().unwrap();
5861 assert_eq!(s, "/nix/store/abc");
5862 }
5863
5864 #[test]
5865 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5866 let attrs = NixAttrs::new();
5867 let val = Value::Attrs(Rc::new(attrs));
5868 assert!(val.coerce_to_string().is_err());
5869 }
5870
5871 #[test]
5872 fn coerce_to_string_lambda_fails() {
5873 let root = rnix::Root::parse("x: x");
5874 let expr = root.tree().expr().unwrap();
5875 let closure = Closure {
5876 param: match expr {
5877 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
5878 _ => panic!("expected lambda"),
5879 },
5880 body: match expr {
5881 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
5882 _ => panic!("expected lambda"),
5883 },
5884 env: Env::new(),
5885 };
5886 let val = Value::Lambda(Rc::new(closure));
5887 assert!(val.coerce_to_string().is_err());
5888 }
5889
5890 #[test]
5893 fn builtin_fn_debug_includes_name() {
5894 let b = BuiltinFn {
5895 name: "myFunc",
5896 func: Rc::new(|_| Ok(Value::Null)),
5897 };
5898 let s = format!("{b:?}");
5899 assert!(s.contains("myFunc"));
5900 assert!(s.contains("builtin"));
5901 }
5902
5903 #[test]
5906 fn thunk_force_chains_through_inner_thunks() {
5907 let inner_root = rnix::Root::parse("99");
5909 let inner_expr = inner_root.tree().expr().unwrap();
5910 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
5911 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
5912 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
5913 match result.unwrap() {
5918 Value::Thunk(_) | Value::Int(99) => {}
5919 other => panic!("unexpected: {other:?}"),
5920 }
5921 }
5922
5923 #[test]
5924 fn thunk_inherit_select_debug_format() {
5925 let root = rnix::Root::parse("{ x = 1; }");
5926 let expr = root.tree().expr().unwrap();
5927 let source = Thunk::new_suspended(expr, Env::new());
5928 let thunk = Thunk::new_inherit_select(source, "x");
5929 let s = format!("{thunk:?}");
5930 assert!(s.contains("inherit-select"));
5931 assert!(s.contains("x"));
5932 }
5933
5934 #[test]
5935 fn thunk_blackhole_debug_format() {
5936 let root = rnix::Root::parse("1");
5937 let expr = root.tree().expr().unwrap();
5938 let thunk = Thunk::new_suspended(expr, Env::new());
5939 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5941 assert_eq!(format!("{thunk:?}"), "<blackhole>");
5942 }
5943
5944 #[test]
5947 fn value_display_thunk_evaluates() {
5948 let root = rnix::Root::parse("42");
5949 let expr = root.tree().expr().unwrap();
5950 let thunk = Thunk::new_suspended(expr, Env::new());
5951 let val = Value::Thunk(thunk);
5952 assert_eq!(format!("{val}"), "42");
5953 }
5954
5955 #[test]
5956 fn value_to_json_thunk_forces() {
5957 let root = rnix::Root::parse(r#""world""#);
5958 let expr = root.tree().expr().unwrap();
5959 let thunk = Thunk::new_suspended(expr, Env::new());
5960 let val = Value::Thunk(thunk);
5961 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
5962 }
5963
5964 #[test]
5965 fn value_type_name_thunk_forces() {
5966 let root = rnix::Root::parse("42");
5967 let expr = root.tree().expr().unwrap();
5968 let thunk = Thunk::new_suspended(expr, Env::new());
5969 let val = Value::Thunk(thunk);
5970 assert_eq!(val.type_name(), "int");
5971 }
5972
5973 #[test]
5976 fn as_string_errors_on_thunk() {
5977 let root = rnix::Root::parse(r#""x""#);
5978 let expr = root.tree().expr().unwrap();
5979 let thunk = Thunk::new_suspended(expr, Env::new());
5980 let val = Value::Thunk(thunk);
5981 let err = val.as_string().unwrap_err();
5982 match err {
5983 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
5984 _ => panic!("expected TypeError"),
5985 }
5986 }
5987
5988 #[test]
5989 fn as_nix_string_errors_on_thunk() {
5990 let root = rnix::Root::parse(r#""x""#);
5991 let expr = root.tree().expr().unwrap();
5992 let thunk = Thunk::new_suspended(expr, Env::new());
5993 let val = Value::Thunk(thunk);
5994 assert!(val.as_nix_string().is_err());
5995 }
5996
5997 #[test]
5998 fn as_attrs_errors_on_thunk() {
5999 let root = rnix::Root::parse("{}");
6000 let expr = root.tree().expr().unwrap();
6001 let thunk = Thunk::new_suspended(expr, Env::new());
6002 let val = Value::Thunk(thunk);
6003 assert!(val.as_attrs().is_err());
6004 }
6005
6006 #[test]
6007 fn as_list_errors_on_thunk() {
6008 let root = rnix::Root::parse("[]");
6009 let expr = root.tree().expr().unwrap();
6010 let thunk = Thunk::new_suspended(expr, Env::new());
6011 let val = Value::Thunk(thunk);
6012 assert!(val.as_list().is_err());
6013 }
6014
6015 #[test]
6018 fn as_nix_string_ok_on_string() {
6019 let v = Value::string("hi");
6020 let ns = v.as_nix_string().unwrap();
6021 assert_eq!(ns.as_str(), "hi");
6022 }
6023
6024 #[test]
6025 fn as_nix_string_errors_on_int() {
6026 let v = Value::Int(1);
6027 match v.as_nix_string() {
6028 Err(EvalError::TypeMismatch { expected, got }) => {
6029 assert_eq!(expected, "string");
6030 assert_eq!(got, "int");
6031 }
6032 _ => panic!("expected TypeMismatch"),
6033 }
6034 }
6035
6036 #[test]
6041 fn oncecell_cache_populated_after_force() {
6042 let root = rnix::Root::parse("42");
6043 let expr = root.tree().expr().unwrap();
6044 let thunk = Thunk::new_suspended(expr, Env::new());
6045 assert!(thunk.0.cache.get().is_none());
6047 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6048 assert!(thunk.0.cache.get().is_some());
6050 }
6051
6052 #[test]
6053 fn oncecell_cache_matches_force_result() {
6054 let root = rnix::Root::parse("1 + 2");
6055 let expr = root.tree().expr().unwrap();
6056 let thunk = Thunk::new_suspended(expr, Env::new());
6057 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6058 let cached = thunk.0.cache.get().unwrap();
6059 assert_eq!((**cached).clone().into_value(), forced);
6062 }
6063
6064 #[test]
6065 fn oncecell_new_evaluated_prepopulates_cache() {
6066 let thunk = Thunk::new_evaluated(Value::Int(77));
6067 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
6069 assert_eq!(**cached, Concrete::Int(77));
6070 }
6071
6072 #[test]
6073 fn oncecell_is_evaluated_uses_cache() {
6074 let thunk = Thunk::new_evaluated(Value::Bool(false));
6075 assert!(thunk.is_evaluated());
6077 assert!(thunk.0.cache.get().is_some());
6078 }
6079
6080 #[test]
6081 fn oncecell_already_evaluated_returns_cached_without_repr() {
6082 let thunk = Thunk::new_evaluated(Value::Int(55));
6086 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
6087 assert_eq!(result.unwrap(), Value::Int(55));
6088 }
6089
6090 #[test]
6095 fn with_scope_created_with_empty_cache() {
6096 let thunk = Thunk::new_suspended(
6098 rnix::Root::parse("{}").tree().expr().unwrap(),
6099 Env::new(),
6100 );
6101 let env = Env::new().with_scope(Value::Thunk(thunk));
6102 let scope = &env.0.with_scopes[0];
6103 assert!(scope.cached.borrow().is_none());
6104 }
6105
6106 #[test]
6107 fn with_scope_concrete_pre_populates_cache() {
6108 let mut attrs = NixAttrs::new();
6110 attrs.insert("x".to_string(), Value::Int(1));
6111 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6112 let scope = &env.0.with_scopes[0];
6113 assert!(scope.cached.borrow().is_some());
6114 }
6115
6116 #[test]
6117 fn with_scope_first_lookup_populates_cache() {
6118 let mut attrs = NixAttrs::new();
6119 attrs.insert("x".to_string(), Value::Int(42));
6120 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6121 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6123 let _ = env.lookup("x");
6125 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6126 }
6127
6128 #[test]
6129 fn with_scope_second_lookup_uses_cache() {
6130 let mut attrs = NixAttrs::new();
6131 attrs.insert("x".to_string(), Value::Int(10));
6132 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6133 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6135 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6136 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6138 }
6139
6140 #[test]
6141 fn with_scope_child_shares_cache_via_rc() {
6142 let mut attrs = NixAttrs::new();
6143 attrs.insert("shared".to_string(), Value::Int(7));
6144 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6145 let child = parent.child();
6146 let _ = parent.lookup("shared");
6148 assert!(child.0.with_scopes[0].cached.borrow().is_some());
6151 }
6152
6153 #[test]
6154 fn with_scope_innermost_checked_first() {
6155 let mut outer = NixAttrs::new();
6156 outer.insert("x".to_string(), Value::Int(1));
6157 outer.insert("y".to_string(), Value::Int(100));
6158 let mut inner = NixAttrs::new();
6159 inner.insert("x".to_string(), Value::Int(2));
6160 let env = Env::new()
6161 .with_scope(Value::Attrs(Rc::new(outer)))
6162 .with_scope(Value::Attrs(Rc::new(inner)));
6163 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
6165 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
6167 }
6168
6169 #[test]
6174 fn fxhashmap_nixattrs_new_creates_empty() {
6175 let a = NixAttrs::new();
6176 assert!(a.is_empty());
6177 assert_eq!(a.len(), 0);
6178 assert!(a.inner().is_empty());
6180 }
6181
6182 #[test]
6183 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
6184 let mut a = NixAttrs::new();
6185 a.insert("mykey".to_string(), Value::Int(42));
6186 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
6187 }
6188
6189 #[test]
6190 fn fxhashmap_contains_key_with_interned_keys() {
6191 let mut a = NixAttrs::new();
6192 a.insert("alpha".to_string(), Value::Int(1));
6193 let sym = intern("alpha");
6194 assert!(a.inner().contains_key(&sym));
6195 let missing_sym = intern("beta");
6196 assert!(!a.inner().contains_key(&missing_sym));
6197 }
6198
6199 #[test]
6200 fn fxhashmap_remove_returns_value() {
6201 let mut a = NixAttrs::new();
6202 a.insert("key".to_string(), Value::Int(99));
6203 let removed = a.remove("key");
6204 assert_eq!(removed, Some(Value::Int(99)));
6205 assert!(a.is_empty());
6206 }
6207
6208 #[test]
6209 fn fxhashmap_keys_returns_sorted_strings() {
6210 let mut a = NixAttrs::new();
6211 a.insert("zulu".to_string(), Value::Int(1));
6212 a.insert("alpha".to_string(), Value::Int(2));
6213 a.insert("mike".to_string(), Value::Int(3));
6214 let keys: Vec<String> = a.keys().collect();
6215 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
6216 }
6217
6218 #[test]
6219 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
6220 let mut a = NixAttrs::new();
6221 a.insert("b".to_string(), Value::Int(2));
6222 a.insert("a".to_string(), Value::Int(1));
6223 let pairs: Vec<(String, &Value)> = a.iter().collect();
6224 assert_eq!(pairs.len(), 2);
6225 assert_eq!(pairs[0].0, "a");
6226 assert_eq!(*pairs[0].1, Value::Int(1));
6227 assert_eq!(pairs[1].0, "b");
6228 assert_eq!(*pairs[1].1, Value::Int(2));
6229 }
6230
6231 #[test]
6232 fn fxhashmap_update_merges_correctly() {
6233 let mut left = NixAttrs::new();
6234 left.insert("a".to_string(), Value::Int(1));
6235 left.insert("b".to_string(), Value::Int(2));
6236 let mut right = NixAttrs::new();
6237 right.insert("b".to_string(), Value::Int(20));
6238 right.insert("c".to_string(), Value::Int(3));
6239 let merged = left.update(&right);
6240 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6241 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6243 assert_eq!(merged.len(), 3);
6244 }
6245
6246 #[test]
6247 fn fxhashmap_from_iterator_collects_with_interning() {
6248 let pairs = vec![
6249 ("x".to_string(), Value::Int(10)),
6250 ("y".to_string(), Value::Int(20)),
6251 ("z".to_string(), Value::Int(30)),
6252 ];
6253 let attrs: NixAttrs = pairs.into_iter().collect();
6254 assert_eq!(attrs.len(), 3);
6255 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6256 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6257 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6258 let sym_x = intern("x");
6260 assert!(attrs.inner().contains_key(&sym_x));
6261 }
6262
6263 #[test]
6268 fn smallvec_context_empty() {
6269 let ctx = StringContext::new();
6270 assert!(ctx.is_empty());
6271 assert_eq!(ctx.len(), 0);
6272 assert_eq!(ctx.elements().len(), 0);
6273 }
6274
6275 #[test]
6276 fn smallvec_context_single_element_inline() {
6277 let mut ctx = StringContext::new();
6278 ctx.add_plain("/nix/store/single");
6279 assert_eq!(ctx.len(), 1);
6280 assert!(!ctx.is_empty());
6282 }
6283
6284 #[test]
6285 fn smallvec_context_two_elements_still_inline() {
6286 let mut ctx = StringContext::new();
6287 ctx.add_plain("/nix/store/one");
6288 ctx.add_output("/nix/store/two.drv", "out");
6289 assert_eq!(ctx.len(), 2);
6290 }
6291
6292 #[test]
6293 fn smallvec_context_three_plus_spills_to_heap() {
6294 let mut ctx = StringContext::new();
6295 ctx.add_plain("/nix/store/a");
6296 ctx.add_plain("/nix/store/b");
6297 ctx.add_drv_deep("/nix/store/c.drv");
6298 assert_eq!(ctx.len(), 3);
6299 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6301 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6302 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6303 }
6304
6305 #[test]
6306 fn smallvec_context_merge_deduplicates() {
6307 let mut ctx1 = StringContext::new();
6308 ctx1.add_plain("/nix/store/dup");
6309 ctx1.add_output("/nix/store/x.drv", "out");
6310 let mut ctx2 = StringContext::new();
6311 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6314 assert_eq!(ctx1.len(), 3); }
6316
6317 #[test]
6318 fn smallvec_context_add_plain_output_drv_deep() {
6319 let mut ctx = StringContext::new();
6320 ctx.add_plain("/nix/store/plain");
6321 assert_eq!(ctx.len(), 1);
6322 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6323
6324 ctx.add_output("/nix/store/out.drv", "lib");
6325 assert_eq!(ctx.len(), 2);
6326 assert!(ctx.elements().contains(&ContextElement::Output {
6327 drv: SmolStr::from("/nix/store/out.drv"),
6328 output: SmolStr::from("lib"),
6329 }));
6330
6331 ctx.add_drv_deep("/nix/store/deep.drv");
6332 assert_eq!(ctx.len(), 3);
6333 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6334 }
6335
6336 #[test]
6341 fn rc_list_constructor_wraps_in_rc() {
6342 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6343 match &v {
6344 Value::List(rc) => {
6345 assert_eq!(rc.len(), 2);
6346 assert_eq!(Rc::strong_count(rc), 1);
6347 }
6348 _ => panic!("expected List"),
6349 }
6350 }
6351
6352 #[test]
6353 fn rc_list_clone_is_refcount_bump() {
6354 let v = Value::list(vec![Value::Int(10)]);
6355 let rc1 = match &v {
6356 Value::List(rc) => rc.clone(),
6357 _ => panic!("expected List"),
6358 };
6359 let v2 = v.clone();
6360 let rc2 = match &v2 {
6361 Value::List(rc) => rc.clone(),
6362 _ => panic!("expected List"),
6363 };
6364 assert!(Rc::ptr_eq(&rc1, &rc2));
6366 assert!(Rc::strong_count(&rc1) >= 2);
6369 }
6370
6371 #[test]
6372 fn rc_list_as_list_returns_slice() {
6373 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6374 let slice = v.as_list().unwrap();
6375 assert_eq!(slice.len(), 3);
6376 assert_eq!(slice[0], Value::Int(1));
6377 assert_eq!(slice[1], Value::Int(2));
6378 assert_eq!(slice[2], Value::Int(3));
6379 }
6380
6381 #[test]
6382 fn rc_list_from_vec_wraps_in_rc() {
6383 let items = vec![Value::Bool(true), Value::Bool(false)];
6384 let v: Value = items.into();
6385 match &v {
6386 Value::List(rc) => {
6387 assert_eq!(rc.len(), 2);
6388 assert_eq!(Rc::strong_count(rc), 1);
6389 }
6390 _ => panic!("expected List"),
6391 }
6392 }
6393
6394 #[test]
6399 fn intern_same_string_returns_same_symbol() {
6400 let s1 = intern("hello_intern_test");
6401 let s2 = intern("hello_intern_test");
6402 assert_eq!(s1, s2);
6403 }
6404
6405 #[test]
6406 fn intern_different_strings_returns_different_symbols() {
6407 let s1 = intern("unique_str_a_9182");
6408 let s2 = intern("unique_str_b_9182");
6409 assert_ne!(s1, s2);
6410 }
6411
6412 #[test]
6413 fn resolve_roundtrips_correctly() {
6414 let sym = intern("roundtrip_test_str");
6415 let resolved = resolve(sym);
6416 assert_eq!(resolved, "roundtrip_test_str");
6417 }
6418
6419 #[test]
6420 fn intern_cached_same_offset_returns_cached_symbol() {
6421 let sid = next_source_id();
6422 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6423 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6424 assert_eq!(sym1, sym2);
6425 }
6426
6427 #[test]
6428 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6429 let sid = next_source_id();
6432 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6433 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6434 assert_eq!(sym1, sym2);
6436 }
6437
6438 #[test]
6439 fn clear_ident_cache_clears() {
6440 let sid = next_source_id();
6441 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6442 clear_ident_cache();
6443 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6447 let resolved = resolve(sym2);
6448 assert_eq!(resolved, "to_be_cleared_99");
6449 }
6450
6451 #[test]
6452 fn next_source_id_increments_monotonically() {
6453 let id1 = next_source_id();
6454 let id2 = next_source_id();
6455 let id3 = next_source_id();
6456 assert_eq!(id2, id1 + 1);
6457 assert_eq!(id3, id2 + 1);
6458 }
6459
6460 #[test]
6465 fn env_new_creates_empty_bindings() {
6466 let env = Env::new();
6467 assert!(env.0.bindings.is_empty());
6468 assert!(env.0.with_scopes.is_empty());
6469 assert!(env.eval_file().is_none());
6470 }
6471
6472 #[test]
6473 fn env_bind_lookup_roundtrip() {
6474 let mut env = Env::new();
6475 env.bind("foo".to_string(), Value::Int(42));
6476 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6477 assert_eq!(env.lookup("bar"), None);
6478 }
6479
6480 #[test]
6481 fn env_child_inherits_parent_bindings_flattened() {
6482 let mut parent = Env::new();
6483 parent.bind("a".to_string(), Value::Int(1));
6484 parent.bind("b".to_string(), Value::Int(2));
6485 let child = parent.child();
6486 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6488 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6489 let sym_a = intern("a");
6491 assert!(child.0.bindings.contains_key(&sym_a));
6492 }
6493
6494 #[test]
6495 fn env_child_inherits_with_scopes() {
6496 let mut attrs = NixAttrs::new();
6497 attrs.insert("ws".to_string(), Value::Int(10));
6498 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6499 let child = parent.child();
6500 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6502 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6503 }
6504
6505 #[test]
6506 fn env_lookup_sym_fast_path_matches_lookup() {
6507 let mut env = Env::new();
6508 env.bind("target".to_string(), Value::Int(88));
6509 let sym = intern("target");
6510 let via_lookup = env.lookup("target");
6511 let via_sym = env.lookup_sym(sym);
6512 assert_eq!(via_lookup, via_sym);
6513 assert_eq!(via_sym, Some(Value::Int(88)));
6514 }
6515
6516 #[test]
6517 fn env_lookup_sym_with_scope_fallback() {
6518 let mut attrs = NixAttrs::new();
6519 attrs.insert("sym_ws".to_string(), Value::Int(33));
6520 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6521 let sym = intern("sym_ws");
6522 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6523 }
6524
6525 #[test]
6526 fn env_with_scope_ordering_multiple_innermost_wins() {
6527 let mut a1 = NixAttrs::new();
6528 a1.insert("x".to_string(), Value::Int(1));
6529 let mut a2 = NixAttrs::new();
6530 a2.insert("x".to_string(), Value::Int(2));
6531 let mut a3 = NixAttrs::new();
6532 a3.insert("x".to_string(), Value::Int(3));
6533 let env = Env::new()
6534 .with_scope(Value::Attrs(Rc::new(a1)))
6535 .with_scope(Value::Attrs(Rc::new(a2)))
6536 .with_scope(Value::Attrs(Rc::new(a3)));
6537 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6539 }
6540
6541 #[test]
6542 fn env_lookup_sym_not_found_returns_none() {
6543 let env = Env::new();
6544 let sym = intern("nonexistent_sym_99");
6545 assert_eq!(env.lookup_sym(sym), None);
6546 }
6547
6548 #[test]
6549 fn env_lookup_sym_lexical_wins_over_with_scope() {
6550 let mut attrs = NixAttrs::new();
6551 attrs.insert("priority".to_string(), Value::Int(1));
6552 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6553 env.bind("priority".to_string(), Value::Int(99));
6554 let sym = intern("priority");
6555 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6556 }
6557}