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 #[inline]
78 pub fn enabled() -> bool {
79 static ON: OnceLock<bool> = OnceLock::new();
80 *ON.get_or_init(|| std::env::var("SUI_LIVE_CENSUS").as_deref() == Ok("1"))
81 }
82
83 #[inline(always)]
84 pub fn made(made: &AtomicI64, live: &AtomicI64) {
85 if enabled() {
86 made.fetch_add(1, Relaxed);
87 live.fetch_add(1, Relaxed);
88 }
89 }
90
91 #[inline(always)]
92 pub fn dropped(live: &AtomicI64) {
93 if enabled() {
94 live.fetch_sub(1, Relaxed);
95 }
96 }
97
98 #[inline(always)]
99 pub fn evaluated() {
100 if enabled() {
101 THUNK_EVALUATED.fetch_add(1, Relaxed);
102 }
103 }
104
105 pub fn rss_bytes() -> u64 {
107 #[cfg(target_os = "macos")]
108 unsafe {
109 let mut info: libc::mach_task_basic_info = std::mem::zeroed();
110 let mut count = (std::mem::size_of::<libc::mach_task_basic_info>()
111 / std::mem::size_of::<libc::natural_t>()) as libc::mach_msg_type_number_t;
112 let kr = libc::task_info(
113 libc::mach_task_self(),
114 libc::MACH_TASK_BASIC_INFO,
115 std::ptr::addr_of_mut!(info).cast(),
116 &mut count,
117 );
118 if kr == libc::KERN_SUCCESS {
119 return info.resident_size;
120 }
121 0
122 }
123 #[cfg(not(target_os = "macos"))]
124 {
125 std::fs::read_to_string("/proc/self/statm")
126 .ok()
127 .and_then(|s| s.split_whitespace().nth(1).map(String::from))
128 .and_then(|pages| pages.parse::<u64>().ok())
129 .map(|pages| pages * 4096)
130 .unwrap_or(0)
131 }
132 }
133
134 pub fn dump(tag: &str) {
147 if !enabled() {
148 return;
149 }
150 let rss = rss_bytes();
151 eprintln!(
152 "[census {tag}] rss={rss_mb:.1}MB \
153attrs_live={al} attrs_made={am} \
154thunk_live={tl} thunk_made={tm} thunk_eval={te} \
155env_live={el} env_made={em} \
156nixstr_live={sl} nixstr_made={sm} \
157list_live={ll} list_made={lm}",
158 rss_mb = rss as f64 / (1024.0 * 1024.0),
159 al = ATTRS_LIVE.load(Relaxed),
160 am = ATTRS_MADE.load(Relaxed),
161 tl = THUNK_LIVE.load(Relaxed),
162 tm = THUNK_MADE.load(Relaxed),
163 te = THUNK_EVALUATED.load(Relaxed),
164 el = ENV_LIVE.load(Relaxed),
165 em = ENV_MADE.load(Relaxed),
166 sl = NIXSTR_LIVE.load(Relaxed),
167 sm = NIXSTR_MADE.load(Relaxed),
168 ll = LIST_LIVE.load(Relaxed),
169 lm = LIST_MADE.load(Relaxed),
170 );
171 let (src_files, src_bytes) = crate::pos::source_text_census();
172 eprintln!(
173 "[census {tag}] src_files={src_files} src_bytes={src_mb:.1}MB",
174 src_mb = src_bytes as f64 / (1024.0 * 1024.0),
175 );
176 }
177
178 pub fn spawn_poller() {
182 if !enabled() {
183 return;
184 }
185 std::thread::spawn(|| loop {
186 std::thread::sleep(std::time::Duration::from_millis(2000));
187 dump("periodic");
188 });
189 }
190}
191
192pub fn intern(s: &str) -> Symbol {
205 sui_intern::intern(s)
206}
207
208pub fn resolve(sym: Symbol) -> String {
213 sui_intern::resolve(sym)
214}
215
216pub fn resolve_rc(sym: Symbol) -> std::rc::Rc<str> {
218 sui_intern::resolve_rc(sym)
219}
220
221pub fn with_resolved<F, R>(sym: Symbol, f: F) -> R
223where
224 F: FnOnce(&str) -> R,
225{
226 sui_intern::with_resolved(sym, f)
227}
228
229thread_local! {
240 static SOURCE_GEN: Cell<u32> = const { Cell::new(1) };
250
251 static IDENT_CACHE: RefCell<rustc_hash::FxHashMap<u64, Symbol>> =
253 RefCell::new(rustc_hash::FxHashMap::default());
254}
255
256pub fn next_source_id() -> u32 {
262 SOURCE_GEN.with(|g| {
263 let id = g.get();
264 g.set(id.wrapping_add(1));
265 id
266 })
267}
268
269pub fn intern_cached(name: &str, source_id: u32, text_offset: u32) -> Symbol {
275 intern_cached_with(source_id, text_offset, || intern(name))
276}
277
278pub fn intern_cached_with<F>(source_id: u32, text_offset: u32, cold: F) -> Symbol
286where
287 F: FnOnce() -> Symbol,
288{
289 let key = (u64::from(source_id) << 32) | u64::from(text_offset);
290 IDENT_CACHE.with(|c| {
291 let mut cache = c.borrow_mut();
292 *cache.entry(key).or_insert_with(cold)
293 })
294}
295
296pub fn clear_ident_cache() {
301 IDENT_CACHE.with(|c| c.borrow_mut().clear());
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
308pub enum ContextElement {
309 Plain(SmolStr),
311 Output { drv: SmolStr, output: SmolStr },
313 DrvDeep(SmolStr),
315}
316
317impl fmt::Display for ContextElement {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match self {
320 ContextElement::Plain(p) => write!(f, "{p}"),
321 ContextElement::Output { drv, output } => write!(f, "{drv}!{output}"),
322 ContextElement::DrvDeep(d) => write!(f, "={d}"),
323 }
324 }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Default)]
335pub struct StringContext(SmallVec<[ContextElement; 2]>);
336
337impl StringContext {
338 pub fn new() -> Self {
340 Self(SmallVec::new())
341 }
342
343 pub fn merge(&mut self, other: &StringContext) {
345 for elem in &other.0 {
346 if !self.0.contains(elem) {
347 self.0.push(elem.clone());
348 }
349 }
350 }
351
352 pub fn add_plain(&mut self, path: impl Into<SmolStr>) {
354 let elem = ContextElement::Plain(path.into());
355 if !self.0.contains(&elem) {
356 self.0.push(elem);
357 }
358 }
359
360 pub fn add_output(&mut self, drv: impl Into<SmolStr>, output: impl Into<SmolStr>) {
362 let elem = ContextElement::Output { drv: drv.into(), output: output.into() };
363 if !self.0.contains(&elem) {
364 self.0.push(elem);
365 }
366 }
367
368 pub fn add_drv_deep(&mut self, drv: impl Into<SmolStr>) {
370 let elem = ContextElement::DrvDeep(drv.into());
371 if !self.0.contains(&elem) {
372 self.0.push(elem);
373 }
374 }
375
376 #[must_use]
378 pub fn is_empty(&self) -> bool {
379 self.0.is_empty()
380 }
381
382 #[must_use]
384 pub fn len(&self) -> usize {
385 self.0.len()
386 }
387
388 pub fn iter(&self) -> impl Iterator<Item = &ContextElement> {
390 self.0.iter()
391 }
392
393 pub fn insert(&mut self, elem: ContextElement) {
395 if !self.0.contains(&elem) {
396 self.0.push(elem);
397 }
398 }
399
400 pub fn elements(&self) -> &[ContextElement] {
402 &self.0
403 }
404}
405
406#[derive(Debug, PartialEq, Eq)]
408pub struct NixString {
409 pub chars: SmolStr,
411 pub context: StringContext,
413}
414
415impl Clone for NixString {
419 fn clone(&self) -> Self {
420 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
421 Self {
422 chars: self.chars.clone(),
423 context: self.context.clone(),
424 }
425 }
426}
427
428impl Drop for NixString {
429 fn drop(&mut self) {
430 census::dropped(&census::NIXSTR_LIVE);
431 }
432}
433
434impl NixString {
435 pub fn plain(s: impl Into<SmolStr>) -> Self {
437 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
438 Self {
439 chars: s.into(),
440 context: StringContext::default(),
441 }
442 }
443
444 pub fn with_context(s: impl Into<SmolStr>, ctx: StringContext) -> Self {
446 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
447 Self {
448 chars: s.into(),
449 context: ctx,
450 }
451 }
452
453 #[must_use]
455 pub fn as_str(&self) -> &str {
456 &self.chars
457 }
458
459 #[must_use]
461 pub fn has_context(&self) -> bool {
462 !self.context.is_empty()
463 }
464}
465
466impl AsRef<str> for NixString {
467 fn as_ref(&self) -> &str {
468 &self.chars
469 }
470}
471
472#[repr(transparent)]
479#[derive(Debug, PartialEq)]
480pub struct NixList(pub Vec<Value>);
481
482impl NixList {
483 #[inline]
484 pub fn new(v: Vec<Value>) -> Self {
485 census::made(&census::LIST_MADE, &census::LIST_LIVE);
486 NixList(v)
487 }
488
489 #[inline]
493 pub fn into_vec(mut self) -> Vec<Value> {
494 std::mem::take(&mut self.0)
495 }
496}
497
498impl From<Vec<Value>> for NixList {
499 #[inline]
500 fn from(v: Vec<Value>) -> Self {
501 NixList::new(v)
502 }
503}
504
505impl<T: AsRef<[Value]>> PartialEq<T> for NixList {
507 #[inline]
508 fn eq(&self, other: &T) -> bool {
509 self.0.as_slice() == other.as_ref()
510 }
511}
512
513impl Clone for NixList {
514 fn clone(&self) -> Self {
515 census::made(&census::LIST_MADE, &census::LIST_LIVE);
516 NixList(self.0.clone())
517 }
518}
519
520impl Drop for NixList {
521 fn drop(&mut self) {
522 census::dropped(&census::LIST_LIVE);
523 }
524}
525
526impl FromIterator<Value> for NixList {
527 #[inline]
528 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
529 NixList::new(iter.into_iter().collect())
530 }
531}
532
533impl std::ops::Deref for NixList {
534 type Target = Vec<Value>;
535 #[inline]
536 fn deref(&self) -> &Vec<Value> {
537 &self.0
538 }
539}
540
541impl std::ops::DerefMut for NixList {
542 #[inline]
543 fn deref_mut(&mut self) -> &mut Vec<Value> {
544 &mut self.0
545 }
546}
547
548impl<'a> IntoIterator for &'a NixList {
549 type Item = &'a Value;
550 type IntoIter = std::slice::Iter<'a, Value>;
551 #[inline]
552 fn into_iter(self) -> Self::IntoIter {
553 self.0.iter()
554 }
555}
556
557impl IntoIterator for NixList {
558 type Item = Value;
559 type IntoIter = std::vec::IntoIter<Value>;
560 #[inline]
561 fn into_iter(mut self) -> Self::IntoIter {
562 std::mem::take(&mut self.0).into_iter()
566 }
567}
568
569impl std::ops::Deref for NixString {
570 type Target = str;
571
572 fn deref(&self) -> &str {
573 &self.chars
574 }
575}
576
577impl fmt::Display for NixString {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 write!(f, "{}", self.chars)
580 }
581}
582
583#[derive(Debug, Clone)]
591#[derive(Default)]
592pub enum Value {
593 #[default]
594 Null,
595 Bool(bool),
596 Int(i64),
597 Float(f64),
598 String(Rc<NixString>),
599 Path(Box<SmolStr>),
600 List(Rc<NixList>),
601 Attrs(Rc<NixAttrs>),
602 Lambda(Rc<Closure>),
603 Builtin(Box<BuiltinFn>),
604 Thunk(Thunk),
606}
607
608#[derive(Debug, Clone)]
624pub enum Concrete {
625 Null,
626 Bool(bool),
627 Int(i64),
628 Float(f64),
629 String(Rc<NixString>),
630 Path(Box<SmolStr>),
631 List(Rc<NixList>), Attrs(Rc<NixAttrs>), Lambda(Rc<Closure>),
634 Builtin(Box<BuiltinFn>),
635 }
637
638impl Concrete {
639 #[inline]
641 pub fn into_value(self) -> Value {
642 match self {
643 Concrete::Null => Value::Null,
644 Concrete::Bool(b) => Value::Bool(b),
645 Concrete::Int(n) => Value::Int(n),
646 Concrete::Float(f) => Value::Float(f),
647 Concrete::String(s) => Value::String(s),
648 Concrete::Path(p) => Value::Path(p),
649 Concrete::List(l) => Value::List(l),
650 Concrete::Attrs(a) => Value::Attrs(a),
651 Concrete::Lambda(c) => Value::Lambda(c),
652 Concrete::Builtin(b) => Value::Builtin(b),
653 }
654 }
655
656 pub fn to_value(&self) -> Value {
659 self.clone().into_value()
660 }
661
662 pub fn as_bool(&self) -> Result<bool, EvalError> {
664 match self {
665 Concrete::Bool(b) => Ok(*b),
666 other => Err(EvalError::TypeMismatch { expected: "bool", got: other.type_name() }),
667 }
668 }
669
670 pub fn as_int(&self) -> Result<i64, EvalError> {
672 match self {
673 Concrete::Int(n) => Ok(*n),
674 other => Err(EvalError::TypeMismatch { expected: "int", got: other.type_name() }),
675 }
676 }
677
678 pub fn as_str(&self) -> Result<&str, EvalError> {
680 match self {
681 Concrete::String(s) => Ok(&s.chars),
682 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
683 }
684 }
685
686 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
688 match self {
689 Concrete::String(s) => Ok(s),
690 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
691 }
692 }
693
694 pub fn as_list(&self) -> Result<&[Value], EvalError> {
697 match self {
698 Concrete::List(l) => Ok(l.as_slice()),
699 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
700 }
701 }
702
703 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
706 match self {
707 Concrete::Attrs(a) => Ok(a),
708 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
709 }
710 }
711
712 pub fn as_float(&self) -> Result<f64, EvalError> {
714 match self {
715 Concrete::Float(f) => Ok(*f),
716 Concrete::Int(n) => Ok(*n as f64),
717 other => Err(EvalError::TypeMismatch { expected: "float", got: other.type_name() }),
718 }
719 }
720
721 pub fn type_name(&self) -> &'static str {
723 match self {
724 Concrete::Null => "null",
725 Concrete::Bool(_) => "bool",
726 Concrete::Int(_) => "int",
727 Concrete::Float(_) => "float",
728 Concrete::String(_) => "string",
729 Concrete::Path(_) => "path",
730 Concrete::List(_) => "list",
731 Concrete::Attrs(_) => "set",
732 Concrete::Lambda(_) | Concrete::Builtin(_) => "lambda",
733 }
734 }
735
736 pub fn as_string(&self) -> Result<&str, EvalError> {
738 self.as_str()
739 }
740
741 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
743 match self {
744 Concrete::Attrs(a) => Ok((**a).clone()),
745 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
746 }
747 }
748
749 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
751 match self {
752 Concrete::List(l) => Ok((**l).0.clone()),
753 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
754 }
755 }
756
757 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
759 match self {
760 Concrete::Path(p) => Ok(p.to_string()),
761 Concrete::String(ns) => Ok(ns.chars.to_string()),
762 Concrete::Attrs(attrs) => {
763 if let Some(out_path) = attrs.get("outPath") {
764 let forced = crate::eval::force_value(out_path)?;
765 forced.coerce_to_path(context)
766 } else {
767 Err(EvalError::type_error(format!(
768 "{context}: expected path or string, got set without outPath"
769 )))
770 }
771 }
772 other => Err(EvalError::type_error(format!(
773 "{context}: expected path or string, got {}", other.type_name()
774 ))),
775 }
776 }
777
778 pub fn to_str(&self) -> Result<String, EvalError> {
780 match self {
781 Concrete::String(s) => Ok(s.chars.to_string()),
782 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
783 }
784 }
785
786 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
788 match self {
789 Concrete::String(s) => Ok((**s).clone()),
790 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
791 }
792 }
793
794 pub fn is_function(&self) -> bool {
796 matches!(self, Concrete::Lambda(_) | Concrete::Builtin(_))
797 }
798}
799
800impl From<Concrete> for Value {
802 fn from(c: Concrete) -> Value {
803 c.into_value()
804 }
805}
806
807impl PartialEq for Concrete {
808 fn eq(&self, other: &Self) -> bool {
809 match (self, other) {
810 (Concrete::Null, Concrete::Null) => true,
811 (Concrete::Bool(a), Concrete::Bool(b)) => a == b,
812 (Concrete::Int(a), Concrete::Int(b)) => a == b,
813 (Concrete::Float(a), Concrete::Float(b)) => a == b,
814 (Concrete::Int(a), Concrete::Float(b)) | (Concrete::Float(b), Concrete::Int(a)) => (*a as f64) == *b,
815 (Concrete::String(a), Concrete::String(b)) => Rc::ptr_eq(a, b) || a.chars == b.chars,
816 (Concrete::Path(a), Concrete::Path(b)) => a == b,
817 (Concrete::List(a), Concrete::List(b)) => Rc::ptr_eq(a, b) || a == b,
818 (Concrete::Attrs(a), Concrete::Attrs(b)) => {
819 if Rc::ptr_eq(a, b) {
820 return true;
821 }
822 if let (Some(pa), Some(pb)) =
833 (derivation_out_path(a), derivation_out_path(b))
834 {
835 return pa == pb;
836 }
837 let (fa, fb) = (a.as_flat(), b.as_flat());
856 if crate::perf::enabled() {
857 crate::perf::inc(crate::perf::Counter::AttrsEqStructuralCalls);
858 crate::perf::add(
861 crate::perf::Counter::AttrsEqEntriesCloneElided,
862 (fa.len() + fb.len()) as u64,
863 );
864 }
865 fa == fb
866 }
867 (Concrete::Lambda(a), Concrete::Lambda(b)) => Rc::ptr_eq(a, b),
868 _ => false,
869 }
870 }
871}
872
873pub fn concat_lists(left: Value, right_elems: &[Value]) -> Result<Value, EvalError> {
889 let mut la = match left {
893 Value::List(rc) => {
894 let reused = Rc::strong_count(&rc) == 1;
895 let vec: Vec<Value> = match Rc::try_unwrap(rc) {
896 Ok(v) => v.into_vec(), Err(rc) => (*rc).0.clone(), };
899 if crate::perf::enabled() {
900 crate::perf::inc(crate::perf::Counter::ListConcatCalls);
901 if reused {
902 crate::perf::add(
904 crate::perf::Counter::ListConcatElemsReused,
905 vec.len() as u64,
906 );
907 } else {
908 crate::perf::add(
910 crate::perf::Counter::ListConcatElemsCopied,
911 vec.len() as u64,
912 );
913 }
914 }
915 vec
916 }
917 other => {
918 return Err(EvalError::TypeMismatch {
919 expected: "list",
920 got: other.type_name(),
921 });
922 }
923 };
924 la.extend_from_slice(right_elems);
926 Ok(Value::list(la))
927}
928
929fn derivation_out_path(attrs: &NixAttrs) -> Option<String> {
935 match attrs.get("type")?.demand().ok()? {
936 Concrete::String(s) if s.chars == "derivation" => {}
937 _ => return None,
938 }
939 match attrs.get("outPath")?.demand().ok()? {
940 Concrete::String(s) => Some(s.chars.to_string()),
941 _ => None,
942 }
943}
944
945fn derivation_drv_and_out(
959 attrs: &NixAttrs,
960) -> Result<Option<(String, String)>, EvalError> {
961 match attrs.get("type") {
963 Some(t) => match crate::eval::force_value(t)? {
964 Value::String(s) if s.chars == "derivation" => {}
965 _ => return Ok(None),
966 },
967 None => return Ok(None),
968 }
969 let drv_path = match attrs.get("drvPath") {
972 Some(d) => crate::eval::force_value(d)?.coerce_to_path("drvPath")?,
973 None => return Ok(None),
974 };
975 let out_path = match attrs.get("outPath") {
976 Some(o) => crate::eval::force_value(o)?.coerce_to_path("outPath")?,
977 None => return Ok(None),
978 };
979 Ok(Some((drv_path, out_path)))
980}
981
982fn out_path_needs_realize(out_path: &str, ctx: &StringContext) -> Option<String> {
996 if !out_path.starts_with("/nix/store/") {
998 return None;
999 }
1000 for elem in ctx.iter() {
1001 if let ContextElement::Output { drv, output } = elem {
1002 let _ = output; return Some(drv.to_string());
1009 }
1010 }
1011 None
1012}
1013
1014impl Value {
1015 pub(crate) fn demand_unchecked(self) -> Concrete {
1018 match self {
1019 Value::Null => Concrete::Null,
1020 Value::Bool(b) => Concrete::Bool(b),
1021 Value::Int(n) => Concrete::Int(n),
1022 Value::Float(f) => Concrete::Float(f),
1023 Value::String(s) => Concrete::String(s),
1024 Value::Path(p) => Concrete::Path(p),
1025 Value::List(l) => Concrete::List(l),
1026 Value::Attrs(a) => Concrete::Attrs(a),
1027 Value::Lambda(c) => Concrete::Lambda(c),
1028 Value::Builtin(b) => Concrete::Builtin(b),
1029 Value::Thunk(_) => panic!("demand_unchecked called on Thunk"),
1030 }
1031 }
1032}
1033
1034impl Value {
1035 pub fn demand(&self) -> Result<Concrete, EvalError> {
1040 let v = match self {
1041 Value::Thunk(_) => crate::eval::force_value(self)?,
1042 other => other.clone(),
1043 };
1044 match v {
1046 Value::Null => Ok(Concrete::Null),
1047 Value::Bool(b) => Ok(Concrete::Bool(b)),
1048 Value::Int(n) => Ok(Concrete::Int(n)),
1049 Value::Float(f) => Ok(Concrete::Float(f)),
1050 Value::String(s) => Ok(Concrete::String(s)),
1051 Value::Path(p) => Ok(Concrete::Path(p)),
1052 Value::List(l) => Ok(Concrete::List(l)),
1053 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1054 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1055 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1056 Value::Thunk(_) => {
1057 let re_forced = crate::eval::force_value(&v)?;
1061 match re_forced {
1062 Value::Null => Ok(Concrete::Null),
1063 Value::Bool(b) => Ok(Concrete::Bool(b)),
1064 Value::Int(n) => Ok(Concrete::Int(n)),
1065 Value::Float(f) => Ok(Concrete::Float(f)),
1066 Value::String(s) => Ok(Concrete::String(s)),
1067 Value::Path(p) => Ok(Concrete::Path(p)),
1068 Value::List(l) => Ok(Concrete::List(l)),
1069 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1070 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1071 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1072 Value::Thunk(_) => Err(EvalError::InfiniteRecursion(
1073 "demand: thunk chain could not be resolved".to_string(),
1074 )),
1075 }
1076 }
1077 }
1078 }
1079}
1080
1081#[cfg(target_pointer_width = "64")]
1082const _: () = assert!(std::mem::size_of::<Value>() <= 16);
1083
1084const FIXPOINT_PROMOTE_NEST_CAP: u32 = 32;
1101
1102const PROMOTION_RUNAWAY_FORCE_DEPTH: usize = 500;
1112
1113thread_local! {
1114 pub(crate) static IN_PROMISE_EVAL: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
1121
1122 pub(crate) static PROMOTION_OCCURRED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1130}
1131
1132#[inline(always)]
1134pub fn promotion_occurred() -> bool {
1135 PROMOTION_OCCURRED.with(|c| c.get())
1136}
1137
1138#[inline(always)]
1142pub fn in_promise_eval() -> bool {
1143 IN_PROMISE_EVAL.with(|c| c.get() > 0)
1144}
1145
1146pub enum ThunkRepr {
1151 Suspended {
1153 expr: rnix::ast::Expr,
1154 env: Env,
1155 },
1156 InheritSelect {
1172 source_thunk: Thunk,
1173 name: SmolStr,
1174 },
1175 Native(Box<dyn FnOnce() -> Result<Value, EvalError>>),
1180 WithIdent {
1190 name: SmolStr,
1192 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1197 scope_value: Value,
1199 env: Env,
1202 },
1203 Blackhole,
1205 Promise(Rc<RefCell<Value>>),
1218 Failed(EvalError),
1229 Evaluated(Box<Value>),
1233 EvaluatedConcrete,
1244}
1245
1246struct ThunkInner {
1255 cache: OnceCell<Box<Concrete>>,
1259 repr: UnsafeCell<ThunkRepr>,
1261 recursive: bool,
1268}
1269
1270impl Drop for ThunkInner {
1271 fn drop(&mut self) {
1272 census::dropped(&census::THUNK_LIVE);
1273 }
1274}
1275
1276#[derive(Clone)]
1278pub struct Thunk(pub(crate) Rc<ThunkInner>);
1279
1280impl Thunk {
1281 pub fn new_suspended(expr: rnix::ast::Expr, env: Env) -> Self {
1283 crate::trace::inc_thunks_created();
1284 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1285 Self(Rc::new(ThunkInner {
1286 cache: OnceCell::new(),
1287 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1288 recursive: false,
1289 }))
1290 }
1291
1292 pub fn new_suspended_recursive(expr: rnix::ast::Expr, env: Env) -> Self {
1299 crate::trace::inc_thunks_created();
1300 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1301 crate::perf::inc(crate::perf::Counter::ThunkSiteLetForward);
1302 Self(Rc::new(ThunkInner {
1303 cache: OnceCell::new(),
1304 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1305 recursive: true,
1306 }))
1307 }
1308
1309 pub fn new_inherit_select(source_thunk: Thunk, name: impl Into<SmolStr>) -> Self {
1317 crate::trace::inc_thunks_created();
1318 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1319 crate::perf::inc(crate::perf::Counter::ThunkSiteInheritSrc);
1320 Self(Rc::new(ThunkInner {
1321 cache: OnceCell::new(),
1322 repr: UnsafeCell::new(ThunkRepr::InheritSelect {
1323 source_thunk,
1324 name: name.into(),
1325 }),
1326 recursive: false,
1327 }))
1328 }
1329
1330 pub fn new_with_ident(
1334 name: SmolStr,
1335 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1336 scope_value: Value,
1337 env: Env,
1338 ) -> Self {
1339 crate::trace::inc_thunks_created();
1340 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1341 crate::perf::inc(crate::perf::Counter::ThunkSiteOther);
1342 Self(Rc::new(ThunkInner {
1343 cache: OnceCell::new(),
1344 repr: UnsafeCell::new(ThunkRepr::WithIdent {
1345 name,
1346 scope_cache,
1347 scope_value,
1348 env,
1349 }),
1350 recursive: false,
1351 }))
1352 }
1353
1354 pub fn new_native(f: impl FnOnce() -> Result<Value, EvalError> + 'static) -> Self {
1358 crate::trace::inc_thunks_created();
1359 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1360 crate::perf::inc(crate::perf::Counter::ThunkSiteNative);
1361 Self(Rc::new(ThunkInner {
1362 cache: OnceCell::new(),
1363 repr: UnsafeCell::new(ThunkRepr::Native(Box::new(f))),
1364 recursive: false,
1365 }))
1366 }
1367
1368 pub fn new_evaluated(value: Value) -> Self {
1372 crate::trace::inc_thunks_created();
1373 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1374 crate::perf::inc(crate::perf::Counter::ThunkSiteEvaluated);
1375 let cache = OnceCell::new();
1376 let repr = if matches!(value, Value::Thunk(_)) {
1380 ThunkRepr::Evaluated(Box::new(value))
1381 } else {
1382 let _ = cache.set(Box::new(value.demand_unchecked()));
1383 ThunkRepr::EvaluatedConcrete
1384 };
1385 Self(Rc::new(ThunkInner {
1386 cache,
1387 repr: UnsafeCell::new(repr),
1388 recursive: false,
1389 }))
1390 }
1391
1392 pub fn is_evaluated(&self) -> bool {
1395 self.0.cache.get().is_some()
1396 }
1397
1398 pub fn is_native(&self) -> bool {
1404 matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Native(_))
1407 }
1408
1409 pub fn peek(&self) -> Option<&Concrete> {
1415 self.0.cache.get().map(|v| &**v)
1416 }
1417
1418 pub fn update_env(&self, new_env: &Env) {
1423 let repr = unsafe { &mut *self.0.repr.get() };
1426 match repr {
1427 ThunkRepr::Suspended { env, .. } => {
1428 *env = new_env.clone();
1429 }
1430 ThunkRepr::InheritSelect { source_thunk, .. } => {
1431 source_thunk.update_env(new_env);
1432 }
1433 _ => {}
1434 }
1435 }
1436
1437 #[inline]
1460 unsafe fn store_evaluated(&self, value: &Value) {
1461 census::evaluated();
1462 if matches!(value, Value::Thunk(_)) {
1463 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1464 } else {
1465 let _ = self.0.cache.set(Box::new(value.clone().demand_unchecked()));
1466 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1467 }
1468 }
1469
1470 #[inline]
1496 unsafe fn store_evaluated_owned(&self, value: Value) -> Value {
1497 census::evaluated();
1498 let concrete = value.demand_unchecked();
1499 let ret = concrete.clone().into_value();
1500 let _ = self.0.cache.set(Box::new(concrete));
1501 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1502 ret
1503 }
1504
1505 pub fn force(
1514 &self,
1515 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1516 ) -> Result<Value, EvalError> {
1517 if let Some(cached) = self.0.cache.get() {
1521 crate::perf::inc(crate::perf::Counter::ThunkHit);
1522 return Ok((**cached).clone().into_value());
1523 }
1524 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1526 self.force_inner(evaluator)
1527 })
1528 }
1529
1530 fn force_inner(
1533 &self,
1534 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1535 ) -> Result<Value, EvalError> {
1536 if let Some(cached) = self.0.cache.get() {
1545 crate::perf::inc(crate::perf::Counter::ThunkHit);
1546 return Ok((**cached).clone().into_value());
1547 }
1548
1549 let thunk_id = Rc::as_ptr(&self.0) as usize;
1550
1551 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1564 return Ok(cell.borrow().clone());
1565 }
1566
1567 let new_repr_on_force = if self.0.recursive {
1576 ThunkRepr::Promise(Rc::new(RefCell::new(
1577 Value::Attrs(Rc::new(NixAttrs::new())),
1578 )))
1579 } else {
1580 ThunkRepr::Blackhole
1581 };
1582 let is_promise = self.0.recursive;
1583 let repr = std::mem::replace(unsafe { &mut *self.0.repr.get() }, new_repr_on_force);
1584
1585 match repr {
1586 ThunkRepr::Suspended { expr, env } => {
1587 crate::perf::inc(crate::perf::Counter::ThunkForce);
1588 crate::trace::inc_thunks_forced_unique();
1589 let tracing = crate::trace::trace_enabled();
1590 let desc: String = if tracing {
1598 expr.syntax().text().to_string().chars().take(60).collect()
1599 } else {
1600 String::new()
1601 };
1602 crate::trace::push_force(crate::trace::ForceFrame {
1603 defined_in: env.eval_file().cloned(),
1604 description: desc.clone(),
1605 thunk_id,
1606 });
1607 if crate::value::promotion_occurred()
1633 && crate::trace::current_force_depth() as usize
1634 > PROMOTION_RUNAWAY_FORCE_DEPTH
1635 {
1636 crate::trace::pop_force();
1637 *unsafe { &mut *self.0.repr.get() } =
1638 ThunkRepr::Suspended { expr, env };
1639 return Err(EvalError::InfiniteRecursion(
1640 "overlay-fixpoint promotion runaway (force depth exceeded)".into(),
1641 ));
1642 }
1643 if tracing {
1644 crate::trace::trace_force_enter(
1645 env.eval_file().map(|p| p.as_path()),
1646 &desc,
1647 );
1648 if let Err(msg) = crate::trace::check_force_depth() {
1649 crate::trace::dump_trace_on_error();
1650 crate::trace::pop_force();
1651 crate::trace::trace_force_exit();
1652 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended {
1653 expr,
1654 env,
1655 };
1656 return Err(EvalError::InfiniteRecursion(msg));
1657 }
1658 }
1659 let _file_guard = env.eval_file().cloned().map(crate::eval::push_eval_file);
1665 let _srcid_guard = crate::eval::push_source_id(env.source_id());
1674 if is_promise {
1680 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
1681 }
1682 let result = evaluator(&expr, &env);
1683 if is_promise {
1684 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1685 }
1686 let became_promise = !is_promise
1696 && matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Promise(_));
1697 if became_promise {
1698 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1699 }
1700 match result {
1701 Ok(mut value) => {
1702 crate::perf::inc(crate::perf::Counter::ThunkStoreWrites);
1703 if is_promise || became_promise {
1710 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1711 *cell.borrow_mut() = value.clone();
1712 }
1713 }
1714 let was_thunk_before_loop = matches!(value, Value::Thunk(_));
1736 if !was_thunk_before_loop {
1737 crate::perf::inc(crate::perf::Counter::ThunkStoreRedundant);
1742 let ret = unsafe { self.store_evaluated_owned(value) };
1743 crate::trace::pop_force();
1744 if tracing { crate::trace::trace_force_exit(); }
1745 return Ok(ret);
1746 }
1747 unsafe { self.store_evaluated(&value) };
1749 while let Value::Thunk(ref inner) = value {
1754 match inner.peek() {
1755 Some(cached) => value = cached.clone().into_value(),
1756 None => break,
1757 }
1758 }
1759 if !matches!(value, Value::Thunk(_)) {
1760 crate::perf::inc(crate::perf::Counter::ThunkStoreLoopMutated);
1761 }
1762 unsafe { self.store_evaluated(&value) };
1763 crate::trace::pop_force();
1764 if tracing { crate::trace::trace_force_exit(); }
1765 Ok(value)
1766 }
1767 Err(e) => {
1768 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended { expr, env };
1769 if tracing { crate::trace::dump_trace_on_error(); }
1770 crate::trace::pop_force();
1771 if tracing { crate::trace::trace_force_exit(); }
1772 Err(e)
1773 }
1774 }
1775 }
1776 ThunkRepr::InheritSelect { source_thunk, name } => {
1777 let tracing = crate::trace::trace_enabled();
1778 let desc = if tracing { format!("inherit (..) {name}") } else { String::new() };
1779 crate::trace::push_force(crate::trace::ForceFrame {
1780 defined_in: None,
1781 description: desc.clone(),
1782 thunk_id,
1783 });
1784 if tracing {
1785 crate::trace::trace_force_enter(None, &desc);
1786 }
1787 crate::trace::inc_thunks_forced_unique();
1788 if tracing {
1789 if let Err(msg) = crate::trace::check_force_depth() {
1790 crate::trace::dump_trace_on_error();
1791 crate::trace::pop_force();
1792 crate::trace::trace_force_exit();
1793 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect {
1794 source_thunk,
1795 name,
1796 };
1797 return Err(EvalError::InfiniteRecursion(msg));
1798 }
1799 }
1800 let attempt = (|| -> Result<Value, EvalError> {
1801 let mut forced = source_thunk.force(evaluator)?;
1802 while let Value::Thunk(inner) = forced {
1803 forced = inner.force(evaluator)?;
1804 }
1805 let attrs = match &forced {
1806 Value::Attrs(a) => a,
1807 _ => {
1808 return Err(EvalError::TypeError(format!(
1809 "inherit (source) {name}: source is {}, not a set",
1810 forced.type_name()
1811 )))
1812 }
1813 };
1814 attrs
1815 .get(&name)
1816 .cloned()
1817 .ok_or_else(|| EvalError::AttrNotFound(name.to_string()))
1818 })();
1819 match attempt {
1820 Ok(mut value) => {
1821 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1822 while let Value::Thunk(ref inner) = value {
1823 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1824 }
1825 unsafe { self.store_evaluated(&value) };
1826 crate::trace::pop_force();
1827 if tracing { crate::trace::trace_force_exit(); }
1828 Ok(value)
1829 }
1830 Err(e) => {
1831 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect { source_thunk, name };
1832 if tracing { crate::trace::dump_trace_on_error(); }
1833 crate::trace::pop_force();
1834 if tracing { crate::trace::trace_force_exit(); }
1835 Err(e)
1836 }
1837 }
1838 }
1839 ThunkRepr::Native(f) => {
1840 let tracing = crate::trace::trace_enabled();
1841 crate::trace::push_force(crate::trace::ForceFrame {
1842 defined_in: None,
1843 description: if tracing { "<native-thunk>".into() } else { String::new() },
1844 thunk_id,
1845 });
1846 if tracing {
1847 crate::trace::trace_force_enter(None, "<native-thunk>");
1848 }
1849 crate::trace::inc_thunks_forced_unique();
1850 match f() {
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::Failed(e.clone());
1880 if tracing { crate::trace::dump_trace_on_error(); }
1881 crate::trace::pop_force();
1882 if tracing { crate::trace::trace_force_exit(); }
1883 Err(e)
1884 }
1885 }
1886 }
1887 ThunkRepr::WithIdent { name, scope_cache, scope_value, env } => {
1888 crate::perf::inc(crate::perf::Counter::ThunkForce);
1889 crate::trace::inc_thunks_forced_unique();
1890 {
1895 let cache = scope_cache.borrow();
1896 if let Some(ref attrs) = *cache {
1897 if let Some(v) = attrs.get(&name) {
1898 let value = v.clone();
1899 unsafe { self.store_evaluated(&value) };
1900 return Ok(value);
1901 }
1902 }
1904 }
1905 if let Ok(forced) = crate::eval::force_value(&scope_value) {
1907 if let Value::Attrs(ref attrs) = forced {
1908 *scope_cache.borrow_mut() = Some((**attrs).clone());
1909 if let Some(v) = attrs.get(&name) {
1910 let value = v.clone();
1911 unsafe { self.store_evaluated(&value) };
1912 return Ok(value);
1913 }
1914 }
1915 }
1916 let result = match env.lookup(&name) {
1946 Some(v) => v,
1947 None => match env.lookup_fresh(&name) {
1948 Some(v) => v,
1949 None if in_promise_eval() => Value::Null,
1950 None => return Err(EvalError::UndefinedVar(format!("'{name}'"))),
1951 },
1952 };
1953 unsafe { self.store_evaluated(&result) };
1954 Ok(result)
1955 }
1956 ThunkRepr::Blackhole => {
1957 if std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some() {
1981 return Ok(Value::Null);
1982 }
1983 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_LIST").is_some() {
1984 return Ok(Value::List(Rc::new(NixList::new(Vec::new()))));
1985 }
1986 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some() {
1987 return Ok(Value::Attrs(Rc::new(NixAttrs::new())));
1988 }
1989 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
1990 let same = crate::trace::force_stack_contains(thunk_id);
1991 eprintln!(
1992 "[SUI_DEBUG_CYCLE] blackhole re-entry thunk_id={thunk_id:#x} same_thunk_on_stack={same} recursive_flag={}",
1993 self.0.recursive
1994 );
1995 crate::trace::dump_force_stack_ids();
1996 }
1997 if crate::trace::force_stack_contains(thunk_id)
2036 && IN_PROMISE_EVAL.with(|c| c.get()) < FIXPOINT_PROMOTE_NEST_CAP
2037 {
2038 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2039 let chain = crate::trace::capture_cycle(thunk_id);
2040 let nest = IN_PROMISE_EVAL.with(|c| c.get());
2041 let fdepth = crate::trace::current_force_depth();
2042 eprintln!("[SUI_PROMOTE] thunk_id={thunk_id:#x} cycle_len={} nest={nest} fdepth={fdepth}", chain.0.len());
2043 }
2044 let cell = Rc::new(RefCell::new(
2045 Value::Attrs(Rc::new(NixAttrs::new())),
2046 ));
2047 *unsafe { &mut *self.0.repr.get() } =
2050 ThunkRepr::Promise(cell.clone());
2051 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
2055 PROMOTION_OCCURRED.with(|c| c.set(true));
2057 return Ok(cell.borrow().clone());
2058 }
2059 let chain = crate::trace::capture_cycle(thunk_id);
2060 crate::trace::dump_trace_on_error();
2061 Err(EvalError::InfiniteRecursion(chain.to_string()))
2062 }
2063 ThunkRepr::Promise(cell) => {
2064 Ok(cell.borrow().clone())
2073 }
2074 ThunkRepr::Evaluated(v) => {
2075 crate::perf::inc(crate::perf::Counter::ThunkHit);
2079 let cloned = (*v).clone();
2080 if !matches!(cloned, Value::Thunk(_)) {
2081 if !matches!(cloned, Value::Thunk(_)) { let _ = self.0.cache.set(Box::new(cloned.clone().demand_unchecked())); }
2082 }
2083 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(v);
2084 Ok(cloned)
2085 }
2086 ThunkRepr::EvaluatedConcrete => {
2087 crate::perf::inc(crate::perf::Counter::ThunkHit);
2097 let value = self
2098 .0
2099 .cache
2100 .get()
2101 .expect("EvaluatedConcrete implies a populated cache")
2102 .as_ref()
2103 .clone()
2104 .into_value();
2105 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
2106 Ok(value)
2107 }
2108 ThunkRepr::Failed(e) => {
2109 let err = e.clone();
2114 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e);
2115 Err(err)
2116 }
2117 }
2118 }
2119}
2120
2121impl fmt::Debug for Thunk {
2122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2123 match unsafe { &*self.0.repr.get() } {
2125 ThunkRepr::Suspended { .. } => write!(f, "<thunk>"),
2126 ThunkRepr::InheritSelect { name, .. } => write!(f, "<inherit-select {name}>"),
2127 ThunkRepr::Native(_) => write!(f, "<native-thunk>"),
2128 ThunkRepr::WithIdent { name, .. } => write!(f, "<with-ident {name}>"),
2129 ThunkRepr::Blackhole => write!(f, "<blackhole>"),
2130 ThunkRepr::Promise(_) => write!(f, "<promise>"),
2131 ThunkRepr::Failed(e) => write!(f, "<failed-thunk: {e}>"),
2132 ThunkRepr::Evaluated(v) => write!(f, "{v:?}"),
2133 ThunkRepr::EvaluatedConcrete => match self.0.cache.get() {
2134 Some(c) => write!(f, "{:?}", c.as_ref().clone().into_value()),
2135 None => write!(f, "<evaluated-concrete>"),
2136 },
2137 }
2138 }
2139}
2140
2141pub struct NixAttrs(AttrsInner, Option<Rc<crate::pos::AttrPositions>>);
2155
2156impl Clone for NixAttrs {
2161 fn clone(&self) -> Self {
2162 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2163 NixAttrs(self.0.clone(), self.1.clone())
2164 }
2165}
2166
2167impl Drop for NixAttrs {
2168 fn drop(&mut self) {
2169 census::dropped(&census::ATTRS_LIVE);
2170 }
2171}
2172
2173#[derive(Clone)]
2175enum AttrsInner {
2176 Flat(AttrsMap<Symbol, Value>),
2178 Overlay {
2189 left: RefCell<Rc<NixAttrs>>,
2190 right: RefCell<Rc<NixAttrs>>,
2191 cache: Rc<OnceCell<AttrsMap<Symbol, Value>>>,
2192 },
2193}
2194
2195impl fmt::Debug for NixAttrs {
2196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2197 write!(f, "NixAttrs({})", self.len())
2198 }
2199}
2200
2201impl Default for NixAttrs {
2202 fn default() -> Self {
2203 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2204 Self(AttrsInner::Flat(AttrsMap::default()), None)
2205 }
2206}
2207
2208impl NixAttrs {
2209 pub fn new() -> Self {
2210 Self::default()
2211 }
2212
2213 pub fn with_capacity(_capacity: usize) -> Self {
2214 Self::default()
2215 }
2216
2217 pub fn set_positions(&mut self, pos: Rc<crate::pos::AttrPositions>) {
2221 self.1 = Some(pos);
2222 }
2223
2224 #[must_use]
2228 pub fn positions(&self) -> Option<&Rc<crate::pos::AttrPositions>> {
2229 self.1.as_ref()
2230 }
2231
2232 #[must_use]
2237 pub fn pos_for(&self, key: &str) -> Option<crate::pos::ResolvedPos> {
2238 let sym = intern(key);
2239 let (file, offset) = self.pos_entry(sym)?;
2240 crate::pos::resolve(file.as_deref(), offset)
2241 }
2242
2243 fn pos_entry(&self, sym: Symbol) -> Option<(Option<std::path::PathBuf>, u32)> {
2263 if let Some(table) = self.1.as_ref() {
2264 if let Some(offset) = table.keys.get(&sym) {
2265 return Some((table.file.clone(), *offset));
2266 }
2267 }
2268 match &self.0 {
2269 AttrsInner::Overlay { left, right, .. } => {
2270 let r = right.borrow().pos_entry(sym);
2271 if r.is_some() {
2272 return r;
2273 }
2274 let l = left.borrow().pos_entry(sym);
2275 l
2276 }
2277 _ => None,
2278 }
2279 }
2280
2281 #[must_use]
2283 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2284 self.as_flat().clone()
2285 }
2286
2287 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2289 match &self.0 {
2290 AttrsInner::Flat(m) => m,
2291 AttrsInner::Overlay { left, right, cache } => {
2292 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2293 let flat = cache.get_or_init(|| {
2294 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2297 let timed = crate::perf::enabled();
2298 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2299 let mut result = left.borrow().as_flat().clone();
2300 for (k, v) in right.borrow().as_flat().iter() {
2301 result.insert(*k, v.clone());
2302 }
2303 crate::perf::add(
2304 crate::perf::Counter::OverlayFlattenEntries,
2305 result.len() as u64,
2306 );
2307 if let Some(t0) = t0 {
2308 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2309 }
2310 result
2311 });
2312 {
2332 let mut l = left.borrow_mut();
2333 if !l.is_empty() { *l = Rc::new(l.position_husk()); }
2334 }
2335 {
2336 let mut r = right.borrow_mut();
2337 if !r.is_empty() { *r = Rc::new(r.position_husk()); }
2338 }
2339 flat
2340 }
2341 }
2342 }
2343
2344 fn position_husk(&self) -> NixAttrs {
2354 match &self.0 {
2355 AttrsInner::Overlay { left, right, .. } => {
2356 let (l, r) = (left.borrow().position_husk(), right.borrow().position_husk());
2357 if l.1.is_none() && r.1.is_none() && !matches!(l.0, AttrsInner::Overlay { .. })
2358 && !matches!(r.0, AttrsInner::Overlay { .. })
2359 {
2360 return NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone());
2363 }
2364 NixAttrs(
2365 AttrsInner::Overlay {
2366 left: RefCell::new(Rc::new(l)),
2367 right: RefCell::new(Rc::new(r)),
2368 cache: Rc::new(OnceCell::new()),
2369 },
2370 self.1.clone(),
2371 )
2372 }
2373 AttrsInner::Flat(_) => NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone()),
2374 }
2375 }
2376
2377 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2378 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2379 let m = self.as_flat();
2380 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2381 let timed = crate::perf::enabled();
2382 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2383 let mut pairs: Vec<(String, &Value)> = m.iter()
2384 .map(|(sym, v)| (resolve(*sym), v))
2385 .collect();
2386 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2387 if let Some(t0) = t0 {
2388 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2389 }
2390 pairs
2391 }
2392
2393 #[must_use]
2395 pub fn get(&self, key: &str) -> Option<&Value> {
2396 let sym = intern(key);
2397 self.get_sym(&sym)
2398 }
2399
2400 #[must_use]
2414 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2415 match &self.0 {
2416 AttrsInner::Flat(m) => m.get(sym),
2417 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2423 }
2424 }
2425
2426 pub fn insert(&mut self, key: String, value: Value) {
2428 self.ensure_flat();
2429 if let AttrsInner::Flat(ref mut m) = self.0 {
2430 m.insert(intern(&key), value);
2431 }
2432 }
2433
2434 fn ensure_flat(&mut self) {
2436 if matches!(self.0, AttrsInner::Overlay { .. }) {
2437 self.0 = AttrsInner::Flat(self.as_flat().clone());
2438 }
2439 }
2440
2441 #[must_use]
2442 pub fn contains_key(&self, key: &str) -> bool {
2443 let sym = intern(key);
2444 self.contains_key_sym(&sym)
2445 }
2446
2447 #[must_use]
2448 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2449 match &self.0 {
2450 AttrsInner::Flat(m) => m.contains_key(sym),
2451 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2453 }
2454 }
2455
2456 pub fn keys(&self) -> impl Iterator<Item = String> {
2457 self.sorted_entries().into_iter().map(|(k, _)| k)
2458 }
2459
2460 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2461 self.sorted_entries().into_iter()
2462 }
2463
2464 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2465 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2466 }
2467
2468 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2486 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2487 }
2488
2489 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2492 self.ensure_flat();
2493 if let AttrsInner::Flat(ref mut m) = self.0 {
2494 m.insert(sym, value);
2495 }
2496 }
2497
2498 pub fn values(&self) -> impl Iterator<Item = &Value> {
2499 self.sorted_entries().into_iter().map(|(_, v)| v)
2500 }
2501
2502
2503 pub fn remove(&mut self, key: &str) -> Option<Value> {
2504 self.ensure_flat();
2505 if let AttrsInner::Flat(ref mut m) = self.0 {
2506 m.remove(&intern(key))
2507 } else {
2508 None
2509 }
2510 }
2511
2512 #[must_use]
2513 pub fn len(&self) -> usize {
2514 match &self.0 {
2515 AttrsInner::Flat(m) => m.len(),
2516 AttrsInner::Overlay { .. } => {
2517 self.as_flat().len()
2521 }
2522 }
2523 }
2524
2525 #[must_use]
2526 pub fn is_empty(&self) -> bool {
2527 match &self.0 {
2528 AttrsInner::Flat(m) => m.is_empty(),
2529 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2533 }
2534 }
2535
2536 #[must_use]
2538 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2539 if other.is_empty() { return self; }
2540 if self.is_empty() { return other; }
2541 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2542 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2543 NixAttrs(AttrsInner::Overlay {
2544 left: RefCell::new(Rc::new(self)),
2545 right: RefCell::new(Rc::new(other)),
2546 cache: Rc::new(OnceCell::new()),
2547 }, None)
2548 }
2549
2550 #[must_use]
2552 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2553 match (&self.0, &other.0) {
2554 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2555 let mut result = l.clone();
2556 for (k, v) in r.iter() {
2557 result.insert(*k, v.clone());
2558 }
2559 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2560 NixAttrs(AttrsInner::Flat(result), None)
2561 }
2562 _ => {
2563 let mut result = self.as_flat().clone();
2565 let other_flat = other.as_flat();
2566 for (k, v) in other_flat.iter() {
2567 result.insert(*k, v.clone());
2568 }
2569 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2570 NixAttrs(AttrsInner::Flat(result), None)
2571 }
2572 }
2573 }
2574}
2575
2576impl FromIterator<(String, Value)> for NixAttrs {
2577 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2578 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2579 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2580 }
2581}
2582
2583impl IntoIterator for NixAttrs {
2584 type Item = (String, Value);
2585 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2586
2587 fn into_iter(self) -> Self::IntoIter {
2588 let flat = self.as_flat().clone();
2589 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2590 }
2591}
2592
2593#[derive(Debug, Clone)]
2601pub struct Closure {
2602 pub param: rnix::ast::Param,
2603 pub body: rnix::ast::Expr,
2604 pub env: Env,
2605}
2606
2607pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2609
2610#[derive(Clone)]
2615pub struct BuiltinFn {
2616 pub name: &'static str,
2618 pub func: Rc<BuiltinFunc>,
2620}
2621
2622impl fmt::Debug for BuiltinFn {
2623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2624 write!(f, "<builtin {}>", self.name)
2625 }
2626}
2627
2628#[derive(Clone)]
2638struct WithScope {
2639 value: Value,
2640 cached: Rc<RefCell<Option<NixAttrs>>>,
2643}
2644
2645impl fmt::Debug for WithScope {
2646 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2647 f.debug_struct("WithScope")
2648 .field("value", &self.value)
2649 .field("cached", &self.cached.borrow().is_some())
2650 .finish()
2651 }
2652}
2653
2654#[derive(Debug, Clone, Default)]
2664struct EnvInner {
2665 bindings: FxHashMap<Symbol, Value>,
2666 with_scopes: Vec<WithScope>,
2668 eval_file: Option<std::path::PathBuf>,
2672 source_id: u32,
2679}
2680
2681#[derive(Clone, Default)]
2689pub struct Env(Rc<EnvInner>);
2690
2691impl fmt::Debug for Env {
2692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2693 self.0.fmt(f)
2694 }
2695}
2696
2697impl Drop for EnvInner {
2706 fn drop(&mut self) {
2707 census::dropped(&census::ENV_LIVE);
2708 }
2709}
2710
2711impl Env {
2712 #[must_use]
2714 pub fn new() -> Self {
2715 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2716 Self(Rc::new(EnvInner {
2717 bindings: FxHashMap::default(),
2718 with_scopes: Vec::new(),
2719 eval_file: None,
2720 source_id: 0,
2721 }))
2722 }
2723
2724 #[must_use]
2729 pub fn child(&self) -> Self {
2730 crate::perf::inc(crate::perf::Counter::EnvClone);
2731 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2737 Self(Rc::new(EnvInner {
2738 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2740 eval_file: self.0.eval_file.clone(),
2744 source_id: self.0.source_id,
2748 }))
2749 }
2750
2751 #[must_use]
2759 pub fn with_scope(mut self, value: Value) -> Self {
2760 let pre_cached = match &value {
2762 Value::Attrs(attrs) => Some((**attrs).clone()),
2763 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2764 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2765 }),
2766 _ => None,
2767 };
2768 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2769 value,
2770 cached: Rc::new(RefCell::new(pre_cached)),
2771 });
2772 self
2773 }
2774
2775 pub fn bind(&mut self, name: String, value: Value) {
2780 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2781 }
2782
2783 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2793 let inner = Rc::make_mut(&mut self.0);
2794 for (name, value) in pairs {
2795 inner.bindings.insert(intern(&name), value);
2796 }
2797 }
2798
2799 #[must_use]
2801 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2802 self.0.eval_file.as_ref()
2803 }
2804
2805 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2807 Rc::make_mut(&mut self.0).eval_file = file;
2808 }
2809
2810 #[must_use]
2812 pub fn source_id(&self) -> u32 {
2813 self.0.source_id
2814 }
2815
2816 pub fn set_source_id(&mut self, id: u32) {
2819 Rc::make_mut(&mut self.0).source_id = id;
2820 }
2821
2822 #[must_use]
2824 pub fn binding_count(&self) -> usize {
2825 self.0.bindings.len()
2826 }
2827
2828 #[must_use]
2830 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2831 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2832 }
2833
2834 #[must_use]
2836 pub fn with_scope_count(&self) -> usize {
2837 self.0.with_scopes.len()
2838 }
2839
2840 #[must_use]
2844 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2845 let sym = intern(name);
2846 self.0.bindings.get(&sym).cloned()
2847 }
2848
2849 #[must_use]
2860 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
2861 self.0.bindings.get(&sym).cloned()
2862 }
2863
2864 #[must_use]
2868 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
2869 for scope in self.0.with_scopes.iter().rev() {
2870 let cache = scope.cached.borrow();
2871 if let Some(ref attrs) = *cache {
2872 if let Some(v) = attrs.get(name) {
2873 return Some(v.clone());
2874 }
2875 }
2876 drop(cache);
2878 if let Value::Thunk(ref thunk) = scope.value {
2879 if let Some(cached_val) = thunk.peek() {
2880 if let Concrete::Attrs(ref attrs) = *cached_val {
2881 *scope.cached.borrow_mut() = Some((**attrs).clone());
2883 if let Some(v) = attrs.get(name) {
2884 return Some(v.clone());
2885 }
2886 }
2887 }
2888 } else if let Value::Attrs(ref attrs) = scope.value {
2889 *scope.cached.borrow_mut() = Some((**attrs).clone());
2890 if let Some(v) = attrs.get(name) {
2891 return Some(v.clone());
2892 }
2893 }
2894 }
2895 None
2896 }
2897
2898 #[must_use]
2901 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
2902 self.0.with_scopes.last().map(|scope| {
2903 (scope.cached.clone(), scope.value.clone())
2904 })
2905 }
2906
2907 #[must_use]
2916 pub fn lookup(&self, name: &str) -> Option<Value> {
2917 self.lookup_fast(intern(name), name)
2918 }
2919
2920 #[must_use]
2932 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
2933 let sym = intern(name);
2934 if let Some(v) = self.0.bindings.get(&sym) {
2935 return Some(v.clone());
2936 }
2937 for scope in self.0.with_scopes.iter().rev() {
2938 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
2939 if let Some(v) = attrs.get_sym(&sym) {
2940 *scope.cached.borrow_mut() = Some((*attrs).clone());
2943 return Some(v.clone());
2944 }
2945 }
2946 }
2947 None
2948 }
2949
2950 #[must_use]
2952 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
2953 crate::perf::inc(crate::perf::Counter::EnvLookup);
2954 if let Some(v) = self.0.bindings.get(&sym) {
2955 return Some(v.clone());
2956 }
2957 for scope in self.0.with_scopes.iter().rev() {
2959 {
2961 let cache = scope.cached.borrow();
2962 if let Some(ref attrs) = *cache {
2963 if let Some(v) = attrs.get_sym(&sym) {
2964 return Some(v.clone());
2965 }
2966 continue;
2967 }
2968 }
2969 let resolved = match &scope.value {
2974 Value::Attrs(attrs) => {
2975 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2977 *scope.cached.borrow_mut() = Some((**attrs).clone());
2978 Some((**attrs).clone())
2979 }
2980 Value::Thunk(thunk) => {
2981 if let Some(cached_val) = thunk.peek() {
2984 if let Concrete::Attrs(ref attrs) = *cached_val {
2985 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2986 *scope.cached.borrow_mut() = Some((**attrs).clone());
2987 Some((**attrs).clone())
2988 } else {
2989 None
2990 }
2991 } else {
2992 match crate::eval::force_value(&scope.value) {
3003 Ok(forced) => {
3004 if let Value::Attrs(ref attrs) = forced {
3005 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3006 *scope.cached.borrow_mut() = Some((**attrs).clone());
3007 Some((**attrs).clone())
3008 } else {
3009 None
3010 }
3011 }
3012 Err(_) => None, }
3014 }
3015 }
3016 _ => {
3017 match crate::eval::force_value(&scope.value) {
3019 Ok(forced) => {
3020 if let Value::Attrs(ref attrs) = forced {
3021 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3022 *scope.cached.borrow_mut() = Some((**attrs).clone());
3023 Some((**attrs).clone())
3024 } else {
3025 None
3026 }
3027 }
3028 Err(_) => None,
3029 }
3030 }
3031 };
3032 if let Some(ref attrs) = resolved {
3033 if let Some(v) = attrs.get(name) {
3034 return Some(v.clone());
3035 }
3036 }
3037 }
3039 None
3040 }
3041
3042 #[must_use]
3048 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
3049 crate::perf::inc(crate::perf::Counter::EnvLookup);
3050 if let Some(v) = self.0.bindings.get(&sym) {
3052 return Some(v.clone());
3053 }
3054 for scope in self.0.with_scopes.iter().rev() {
3056 {
3058 let cache = scope.cached.borrow();
3059 if let Some(ref attrs) = *cache {
3060 if let Some(v) = attrs.get_sym(&sym) {
3061 return Some(v.clone());
3062 }
3063 continue;
3064 }
3065 }
3066 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
3068 if let Value::Attrs(ref attrs) = forced {
3069 let result = attrs.get_sym(&sym).cloned();
3070 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3071 *scope.cached.borrow_mut() = Some((**attrs).clone());
3072 if result.is_some() {
3073 return result;
3074 }
3075 }
3076 }
3077 }
3079 None
3080 }
3081}
3082
3083#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
3085#[non_exhaustive]
3086pub enum EvalError {
3087 #[error("undefined variable: {0}")]
3089 UndefinedVar(String),
3090 #[error("type error: {0}")]
3092 TypeError(String),
3093 #[error("attribute not found: {0}")]
3095 AttrNotFound(String),
3096 #[error("type error: expected {expected}, got {got}")]
3098 TypeMismatch {
3099 expected: &'static str,
3100 got: &'static str,
3101 },
3102 #[error("assertion failed{0}")]
3104 AssertionFailed(String),
3105 #[error("division by zero")]
3107 DivisionByZero,
3108 #[error("infinite recursion ({0})")]
3110 InfiniteRecursion(String),
3111 #[error("I/O error: {context}: {message}")]
3113 IoError { context: String, message: String },
3114 #[error("{0}")]
3116 Throw(String),
3117 #[error("{0}")]
3121 Abort(String),
3122 #[error("not yet implemented: {0}")]
3124 NotImplemented(String),
3125 #[error("parse error: {0}")]
3127 ParseError(String),
3128 #[error("recursion limit: {0}")]
3130 RecursionLimit(String),
3131}
3132
3133impl EvalError {
3134 #[must_use]
3136 pub fn type_error(msg: impl Into<String>) -> Self {
3137 EvalError::TypeError(msg.into())
3138 }
3139
3140 #[must_use]
3142 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3143 EvalError::TypeMismatch { expected, got }
3144 }
3145
3146 #[must_use]
3148 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3149 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3150 }
3151
3152 #[must_use]
3173 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3174 EvalError::TypeError(format!(
3175 "cannot {op} {lhs} and {rhs}{}",
3176 crate::eval::eval_file_ctx()
3177 ))
3178 }
3179
3180 #[must_use]
3182 pub fn is_throw(&self) -> bool {
3183 matches!(self, EvalError::Throw(_))
3184 }
3185
3186 #[must_use]
3188 pub fn is_infinite_recursion(&self) -> bool {
3189 matches!(self, EvalError::InfiniteRecursion(_))
3190 }
3191}
3192
3193impl Value {
3194 #[must_use]
3196 pub fn string(s: impl Into<SmolStr>) -> Self {
3197 Value::String(Rc::new(NixString::plain(s)))
3198 }
3199
3200 #[must_use]
3203 pub fn list(items: Vec<Value>) -> Self {
3204 Value::List(Rc::new(NixList::new(items)))
3205 }
3206
3207 #[must_use]
3210 pub fn is_uniquely_owned_list(&self) -> bool {
3211 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3212 }
3213
3214 #[must_use]
3216 pub fn to_json(&self) -> serde_json::Value {
3217 match self {
3218 Value::Null => serde_json::Value::Null,
3219 Value::Bool(b) => serde_json::Value::Bool(*b),
3220 Value::Int(n) => serde_json::json!(n),
3221 Value::Float(f) => serde_json::json!(f),
3222 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3223 Value::Path(p) => serde_json::Value::String(p.to_string()),
3224 Value::List(items) => {
3225 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3226 }
3227 Value::Attrs(attrs) => {
3228 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3235 if let Ok((s, _ctx)) = self.coerce_to_string() {
3236 return serde_json::Value::String(s);
3237 }
3238 }
3239 let map: serde_json::Map<String, serde_json::Value> = attrs
3240 .iter()
3241 .map(|(k, v)| (k.clone(), v.to_json()))
3242 .collect();
3243 serde_json::Value::Object(map)
3244 }
3245 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3246 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3247 Value::Thunk(thunk) => {
3248 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3250 Ok(v) => v.to_json(),
3251 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3252 }
3253 }
3254 }
3255 }
3256
3257 pub fn to_json_with_context(
3264 &self,
3265 ctx: &mut StringContext,
3266 ) -> Result<serde_json::Value, EvalError> {
3267 Ok(match self {
3268 Value::Null => serde_json::Value::Null,
3269 Value::Bool(b) => serde_json::Value::Bool(*b),
3270 Value::Int(n) => serde_json::json!(n),
3271 Value::Float(f) => serde_json::json!(f),
3272 Value::String(s) => {
3273 ctx.merge(&s.context);
3274 serde_json::Value::String(s.chars.to_string())
3275 }
3276 Value::Path(_) => {
3277 let (str, c) = self.coerce_to_string_copy_to_store()?;
3278 ctx.merge(&c);
3279 serde_json::Value::String(str)
3280 }
3281 Value::List(items) => {
3282 let mut arr = Vec::with_capacity(items.len());
3283 for v in items.iter() {
3284 let fv = crate::eval::force_value(v)?;
3285 arr.push(fv.to_json_with_context(ctx)?);
3286 }
3287 serde_json::Value::Array(arr)
3288 }
3289 Value::Attrs(attrs) => {
3290 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3293 let (s, c) = self.coerce_to_string_copy_to_store()?;
3294 ctx.merge(&c);
3295 return Ok(serde_json::Value::String(s));
3296 }
3297 let mut map = serde_json::Map::new();
3298 for (k, v) in attrs.iter() {
3299 let fv = crate::eval::force_value(v)?;
3300 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3301 }
3302 serde_json::Value::Object(map)
3303 }
3304 Value::Thunk(_) => {
3305 let forced = crate::eval::force_value(self)?;
3306 forced.to_json_with_context(ctx)?
3307 }
3308 other => {
3309 return Err(EvalError::TypeError(format!(
3310 "cannot serialize {} to JSON (__structuredAttrs)",
3311 other.type_name()
3312 )));
3313 }
3314 })
3315 }
3316
3317 #[must_use]
3319 pub fn type_name(&self) -> &'static str {
3320 match self {
3321 Value::Null => "null",
3322 Value::Bool(_) => "bool",
3323 Value::Int(_) => "int",
3324 Value::Float(_) => "float",
3325 Value::String(_) => "string",
3326 Value::Path(_) => "path",
3327 Value::List(_) => "list",
3328 Value::Attrs(_) => "set",
3329 Value::Lambda(_) => "lambda",
3330 Value::Builtin(_) => "lambda",
3331 Value::Thunk(thunk) => {
3332 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3334 Ok(v) => v.type_name(),
3335 Err(_) => "thunk",
3336 }
3337 }
3338 }
3339 }
3340
3341 pub fn as_bool(&self) -> Result<bool, EvalError> {
3362 match self {
3363 Value::Bool(b) => Ok(*b),
3364 Value::Thunk(thunk) => {
3365 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3366 }
3367 _ if in_promise_eval() => Ok(false),
3371 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3372 }
3373 }
3374
3375 pub fn as_int(&self) -> Result<i64, EvalError> {
3377 match self {
3378 Value::Int(n) => Ok(*n),
3379 Value::Thunk(thunk) => {
3380 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3381 }
3382 _ if in_promise_eval() => Ok(0),
3385 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3386 }
3387 }
3388
3389 pub fn as_string(&self) -> Result<&str, EvalError> {
3391 match self {
3392 Value::String(s) => Ok(&s.chars),
3393 Value::Thunk(_) => Err(EvalError::TypeError(
3394 "thunk in as_string: force first via force_value()".into(),
3395 )),
3396 _ if in_promise_eval() => Ok(""),
3397 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3398 }
3399 }
3400
3401 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3403 match self {
3404 Value::String(ns) => Ok(ns),
3405 Value::Thunk(_) => Err(EvalError::TypeError(
3406 "thunk in as_nix_string: force first via force_value()".into(),
3407 )),
3408 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3409 }
3410 }
3411
3412 pub fn to_str(&self) -> Result<String, EvalError> {
3416 match self {
3417 Value::String(s) => Ok(s.chars.to_string()),
3418 Value::Thunk(thunk) => {
3419 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3420 forced.to_str()
3421 }
3422 _ if in_promise_eval() => Ok(String::new()),
3423 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3424 }
3425 }
3426
3427 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3430 match self {
3431 Value::String(s) => Ok((**s).clone()),
3432 Value::Thunk(thunk) => {
3433 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3434 forced.to_nix_string()
3435 }
3436 _ if in_promise_eval() => Ok(NixString::plain("")),
3437 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3438 }
3439 }
3440
3441 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3450 match self {
3451 Value::Attrs(a) => Ok(a),
3452 Value::Thunk(_) => Err(EvalError::TypeError(
3453 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3454 )),
3455 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3456 }
3457 }
3458
3459 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3461 match self {
3462 Value::List(l) => Ok(l.as_slice()),
3463 Value::Thunk(_) => Err(EvalError::TypeError(
3464 "thunk in as_list: force first via force_value()".into(),
3465 )),
3466 _ => Err(crate::eval::attach_trace(
3467 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3468 )),
3469 }
3470 }
3471
3472 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3474 match self {
3475 Value::Attrs(a) => Ok((**a).clone()),
3476 Value::Thunk(thunk) => {
3477 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3478 forced.to_attrs()
3479 }
3480 _ if in_promise_eval() => Ok(NixAttrs::new()),
3486 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3487 }
3488 }
3489
3490 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3492 match self {
3493 Value::List(l) => Ok((**l).0.clone()),
3494 Value::Thunk(thunk) => {
3495 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3496 forced.to_list()
3497 }
3498 _ if in_promise_eval() => Ok(Vec::new()),
3501 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3502 }
3503 }
3504
3505 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3511 match self {
3512 Value::Path(p) => Ok(p.to_string()),
3513 Value::String(ns) => Ok(ns.chars.to_string()),
3514 Value::Attrs(attrs) => {
3515 if let Some(out_path) = attrs.get("outPath") {
3516 let forced = crate::eval::force_value(out_path)?;
3517 forced.coerce_to_path(context)
3518 } else {
3519 Err(EvalError::TypeError(format!(
3520 "{context}: expected path or string, got set without outPath"
3521 )))
3522 }
3523 }
3524 _ => Err(EvalError::TypeError(format!(
3525 "{context}: expected path or string, got {}",
3526 self.type_name()
3527 ))),
3528 }
3529 }
3530
3531 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3555 match self {
3556 Value::Attrs(attrs) => {
3559 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3560 self.realize_if_absent(&drv_path, &out_path, context)?;
3561 return Ok(out_path);
3562 }
3563 }
3564 Value::String(ns) => {
3571 let out_path = ns.chars.to_string();
3572 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3573 self.realize_if_absent(&drv_path, &out_path, context)?;
3574 }
3575 return Ok(out_path);
3576 }
3577 _ => {}
3578 }
3579 self.coerce_to_path(context)
3580 }
3581
3582 fn realize_if_absent(
3587 &self,
3588 drv_path: &str,
3589 out_path: &str,
3590 context: &str,
3591 ) -> Result<(), EvalError> {
3592 let read_path = crate::path::materialize_str(out_path);
3595 if std::path::Path::new(&read_path).exists() {
3596 return Ok(());
3597 }
3598 match crate::realize::realize_output(drv_path, out_path) {
3599 Ok(true) | Ok(false) => Ok(()),
3600 Err(msg) => Err(EvalError::IoError {
3601 context: context.to_string(),
3602 message: format!(
3603 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3604 ),
3605 }),
3606 }
3607 }
3608
3609 pub fn to_float(&self) -> Result<f64, EvalError> {
3611 match self {
3612 Value::Float(f) => Ok(*f),
3613 Value::Int(n) => Ok(*n as f64),
3614 Value::Thunk(thunk) => {
3615 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3616 }
3617 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3618 }
3619 }
3620
3621 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3639 self.coerce_to_string_impl(false)
3640 }
3641
3642 pub fn coerce_to_string_copy_to_store(
3652 &self,
3653 ) -> Result<(String, StringContext), EvalError> {
3654 self.coerce_to_string_impl(true)
3655 }
3656
3657 fn coerce_to_string_impl(
3658 &self,
3659 copy_to_store: bool,
3660 ) -> Result<(String, StringContext), EvalError> {
3661 let mut ctx = StringContext::new();
3662 let s = match self {
3663 Value::String(ns) => {
3664 ctx.merge(&ns.context);
3665 ns.chars.to_string()
3666 }
3667 Value::Path(p) => {
3668 let raw: &str = &**p;
3669 if copy_to_store {
3670 let pb = std::path::Path::new(raw);
3690 let abs = if pb.is_absolute() {
3691 pb.to_path_buf()
3692 } else if let Some(dir) = crate::eval::current_eval_dir() {
3693 dir.join(pb)
3694 } else {
3695 std::env::current_dir()
3696 .map_err(|e| EvalError::IoError {
3697 context: format!("copy-to-store coercion of {raw}"),
3698 message: e.to_string(),
3699 })?
3700 .join(pb)
3701 };
3702 let read_abs = crate::path::materialize(&abs);
3709 let canon = read_abs.canonicalize().map_err(|_| {
3710 EvalError::TypeError(format!(
3711 "path '{}' does not exist",
3712 abs.display()
3713 ))
3714 })?;
3715 let name = crate::path::source_name_for_read_dir(&canon)
3732 .or_else(|| {
3733 canon
3734 .file_name()
3735 .map(|n| sui_compat::source::strip_store_hash_prefix(
3736 &n.to_string_lossy()).to_string())
3737 })
3738 .unwrap_or_else(|| "source".to_string());
3739 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3740 .map_err(|e| {
3741 EvalError::TypeError(format!(
3742 "copy-to-store coercion of '{}': {e}",
3743 canon.display()
3744 ))
3745 })?;
3746 ctx.add_plain(src.store_path.clone());
3747 src.store_path
3748 } else {
3749 ctx.add_plain(raw.to_string());
3750 raw.to_string()
3751 }
3752 }
3753 Value::Int(n) => n.to_string(),
3754 Value::Float(f) => format!("{f:.6}"),
3760 Value::Bool(true) => "1".to_string(),
3761 Value::Bool(false) => String::new(),
3762 Value::Null => String::new(),
3763 Value::Attrs(attrs) => {
3764 if let Some(to_str) = attrs.get("__toString") {
3765 let result =
3766 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3767 let forced = crate::eval::force_value(&result)?;
3768 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3769 ctx.merge(&c);
3770 s
3771 } else if let Some(out_path) = attrs.get("outPath") {
3772 let forced = crate::eval::force_value(out_path)?;
3773 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3774 ctx.merge(&c);
3775 s
3776 } else {
3777 return Err(EvalError::TypeError(
3778 "cannot coerce set to string (no __toString or outPath)".into(),
3779 ));
3780 }
3781 }
3782 Value::List(items) => {
3783 let mut parts = Vec::new();
3784 for item in items.iter() {
3785 let forced = crate::eval::force_value(item)?;
3786 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3787 ctx.merge(&c);
3788 parts.push(s);
3789 }
3790 parts.join(" ")
3791 }
3792 Value::Thunk(_) => {
3793 let forced = crate::eval::force_value(self)?;
3795 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3796 ctx.merge(&c);
3797 s
3798 }
3799 other => {
3800 return Err(EvalError::TypeError(format!(
3801 "cannot coerce {} to string",
3802 other.type_name()
3803 )));
3804 }
3805 };
3806 Ok((s, ctx))
3807 }
3808}
3809
3810impl From<&serde_json::Value> for Value {
3813 fn from(json: &serde_json::Value) -> Self {
3814 match json {
3815 serde_json::Value::Null => Value::Null,
3816 serde_json::Value::Bool(b) => Value::Bool(*b),
3817 serde_json::Value::Number(n) => {
3818 if let Some(i) = n.as_i64() {
3819 Value::Int(i)
3820 } else {
3821 Value::Float(n.as_f64().unwrap_or(0.0))
3822 }
3823 }
3824 serde_json::Value::String(s) => Value::string(s.clone()),
3825 serde_json::Value::Array(arr) => {
3826 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3827 }
3828 serde_json::Value::Object(obj) => {
3829 let mut attrs = NixAttrs::new();
3830 for (k, v) in obj {
3831 attrs.insert(k.clone(), Value::from(v));
3832 }
3833 Value::Attrs(Rc::new(attrs))
3834 }
3835 }
3836 }
3837}
3838
3839impl From<&toml::Value> for Value {
3840 fn from(v: &toml::Value) -> Self {
3841 match v {
3842 toml::Value::String(s) => Value::string(s.clone()),
3843 toml::Value::Integer(n) => Value::Int(*n),
3844 toml::Value::Float(f) => Value::Float(*f),
3845 toml::Value::Boolean(b) => Value::Bool(*b),
3846 toml::Value::Array(arr) => {
3847 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3848 }
3849 toml::Value::Table(t) => {
3850 let mut attrs = NixAttrs::new();
3851 for (k, val) in t {
3852 attrs.insert(k.clone(), Value::from(val));
3853 }
3854 Value::Attrs(Rc::new(attrs))
3855 }
3856 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
3857 }
3858 }
3859}
3860
3861
3862impl From<bool> for Value {
3865 fn from(b: bool) -> Self {
3866 Value::Bool(b)
3867 }
3868}
3869
3870impl From<i64> for Value {
3871 fn from(n: i64) -> Self {
3872 Value::Int(n)
3873 }
3874}
3875
3876impl From<f64> for Value {
3877 fn from(f: f64) -> Self {
3878 Value::Float(f)
3879 }
3880}
3881
3882impl From<NixString> for Value {
3883 fn from(s: NixString) -> Self {
3884 Value::String(Rc::new(s))
3885 }
3886}
3887
3888impl From<NixAttrs> for Value {
3889 fn from(attrs: NixAttrs) -> Self {
3890 Value::Attrs(Rc::new(attrs))
3891 }
3892}
3893
3894impl From<Vec<Value>> for Value {
3895 fn from(list: Vec<Value>) -> Self {
3896 Value::List(Rc::new(NixList::new(list)))
3897 }
3898}
3899
3900impl PartialEq for Value {
3901 fn eq(&self, other: &Self) -> bool {
3902 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
3904 if Rc::ptr_eq(&a.0, &b.0) { return true; }
3905 }
3906 let l = self.demand().unwrap_or(Concrete::Null);
3909 let r = other.demand().unwrap_or(Concrete::Null);
3910 l == r
3911 }
3912}
3913
3914impl fmt::Display for Value {
3915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3916 match self {
3917 Value::Null => write!(f, "null"),
3918 Value::Bool(b) => write!(f, "{b}"),
3919 Value::Int(n) => write!(f, "{n}"),
3920 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
3921 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
3922 Value::Path(p) => write!(f, "{p}"),
3923 Value::List(items) => {
3924 write!(f, "[ ")?;
3925 for item in items.iter() {
3926 write!(f, "{item} ")?;
3927 }
3928 write!(f, "]")
3929 }
3930 Value::Attrs(attrs) => {
3931 write!(f, "{{ ")?;
3932 for (k, v) in attrs.iter() {
3933 write!(f, "{k} = {v}; ")?;
3934 }
3935 write!(f, "}}")
3936 }
3937 Value::Lambda(_) => write!(f, "<<lambda>>"),
3938 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
3939 Value::Thunk(thunk) => {
3940 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
3941 Ok(v) => write!(f, "{v}"),
3942 Err(_) => write!(f, "<<thunk:error>>"),
3943 }
3944 }
3945 }
3946 }
3947}
3948
3949#[cfg(test)]
3950mod tests {
3951 use super::*;
3952 use std::rc::Rc;
3953
3954 #[test]
3960 #[ignore = "measurement, not a gate: run with --ignored --nocapture"]
3961 fn measure_hamt_vs_flat_attrset_cost() {
3962 use crate::value::census::rss_bytes;
3963 const N: usize = 300_000;
3964 const ENTRIES: usize = 4; let syms: Vec<Symbol> = (0..ENTRIES).map(|i| intern(&format!("k{i}"))).collect();
3967
3968 let base = rss_bytes();
3969 let mut hamts: Vec<FxHashMap<Symbol, Value>> = Vec::with_capacity(N);
3970 for _ in 0..N {
3971 let mut m = FxHashMap::default();
3972 for s in &syms { m.insert(*s, Value::Int(1)); }
3973 hamts.push(m);
3974 }
3975 let after_hamt = rss_bytes();
3976
3977 let mut flats: Vec<std::collections::HashMap<Symbol, Value>> = Vec::with_capacity(N);
3978 for _ in 0..N {
3979 let mut m = std::collections::HashMap::with_capacity(ENTRIES);
3980 for s in &syms { m.insert(*s, Value::Int(1)); }
3981 flats.push(m);
3982 }
3983 let after_flat = rss_bytes();
3984
3985 let hamt_cost = after_hamt.saturating_sub(base);
3986 let flat_cost = after_flat.saturating_sub(after_hamt);
3987 eprintln!("N={N} entries={ENTRIES}");
3988 eprintln!(" im_rc HAMT : {} B total, {} B/map", hamt_cost, hamt_cost / N as u64);
3989 eprintln!(" std flat : {} B total, {} B/map", flat_cost, flat_cost / N as u64);
3990 if flat_cost > 0 {
3991 eprintln!(" ratio : {:.2}x", hamt_cost as f64 / flat_cost as f64);
3992 }
3993 std::hint::black_box((&hamts, &flats));
3994 }
3995
3996 #[test]
3997 fn value_is_16_bytes() {
3998 assert_eq!(std::mem::size_of::<Value>(), 16);
3999 }
4000
4001 #[test]
4014 fn overlay_carries_attr_positions_from_both_sides() {
4015 let tbl = |file: &str, key: &str, off: u32| {
4016 let mut t = crate::pos::AttrPositions::new(Some(std::path::PathBuf::from(file)));
4017 t.insert(intern(key), off);
4018 Rc::new(t)
4019 };
4020 let mk = |file: &str, key: &str, off: u32| {
4024 let mut a = NixAttrs::new();
4025 a.insert(key.to_string(), Value::Int(1));
4026 a.set_positions(tbl(file, key, off));
4027 a
4028 };
4029
4030 let left_only = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "other", 22));
4033 assert_eq!(
4034 left_only.pos_entry(intern("modules")),
4035 Some((Some(std::path::PathBuf::from("/l.nix")), 11)),
4036 );
4037
4038 let both = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "modules", 22));
4040 assert_eq!(
4041 both.pos_entry(intern("modules")),
4042 Some((Some(std::path::PathBuf::from("/r.nix")), 22)),
4043 );
4044
4045 assert_eq!(both.pos_entry(intern("nope")), None);
4047 }
4048
4049 #[test]
4052 fn to_json_null() {
4053 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
4054 }
4055
4056 #[test]
4057 fn to_json_bool() {
4058 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
4059 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
4060 }
4061
4062 #[test]
4063 fn to_json_int() {
4064 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
4065 }
4066
4067 #[test]
4068 fn to_json_float() {
4069 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
4070 }
4071
4072 #[test]
4073 fn to_json_string() {
4074 assert_eq!(
4075 Value::string("hello").to_json(),
4076 serde_json::Value::String("hello".to_string()),
4077 );
4078 }
4079
4080 #[test]
4081 fn to_json_path() {
4082 assert_eq!(
4083 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
4084 serde_json::Value::String("/nix/store".to_string()),
4085 );
4086 }
4087
4088 #[test]
4089 fn to_json_list() {
4090 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
4091 assert_eq!(v.to_json(), serde_json::json!([1, true]));
4092 }
4093
4094 #[test]
4095 fn to_json_attrs() {
4096 let mut attrs = NixAttrs::new();
4097 attrs.insert("a".to_string(), Value::Int(1));
4098 let v = Value::Attrs(Rc::new(attrs));
4099 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
4100 }
4101
4102 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
4105 let mut a = NixAttrs::new();
4106 a.insert("type".to_string(), Value::string("derivation"));
4107 a.insert("outPath".to_string(), Value::string(out_path));
4108 a.insert(extra_key.to_string(), Value::Int(extra_val));
4109 Value::Attrs(Rc::new(a))
4110 }
4111
4112 #[test]
4113 fn derivations_same_outpath_differing_attrs_are_equal() {
4114 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4121 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
4122 assert!(a == b, "same-outPath derivations must compare equal");
4123 assert!(!(a != b));
4124 }
4125
4126 #[test]
4127 fn derivations_differing_outpath_are_unequal() {
4128 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4129 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
4130 assert!(a != b, "different-outPath derivations must compare unequal");
4131 }
4132
4133 #[test]
4134 fn non_derivation_attrs_with_outpath_use_structural_eq() {
4135 let mut a = NixAttrs::new();
4138 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
4139 a.insert("foo".to_string(), Value::Int(1));
4140 let mut b = NixAttrs::new();
4141 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
4142 b.insert("foo".to_string(), Value::Int(2));
4143 assert!(
4144 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
4145 "non-derivation attrs with equal outPath but differing foo must be unequal",
4146 );
4147 }
4148
4149 #[test]
4155 fn attrs_eq_borrow_result_matches_multi_key() {
4156 let mk = || {
4159 let mut inner = NixAttrs::new();
4160 inner.insert("n".to_string(), Value::Int(7));
4161 let mut a = NixAttrs::new();
4162 a.insert("a".to_string(), Value::Int(1));
4163 a.insert("b".to_string(), Value::string("two"));
4164 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
4165 Value::Attrs(Rc::new(a))
4166 };
4167 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
4168
4169 let mut b = NixAttrs::new();
4171 b.insert("a".to_string(), Value::Int(1));
4172 b.insert("b".to_string(), Value::string("TWO"));
4173 let mut a2 = NixAttrs::new();
4174 a2.insert("a".to_string(), Value::Int(1));
4175 a2.insert("b".to_string(), Value::string("two"));
4176 assert!(
4177 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
4178 "attrsets differing in one value must be unequal (borrow path)",
4179 );
4180
4181 let mut a3 = NixAttrs::new();
4183 a3.insert("a".to_string(), Value::Int(1));
4184 let mut b3 = NixAttrs::new();
4185 b3.insert("a".to_string(), Value::Int(1));
4186 b3.insert("extra".to_string(), Value::Int(9));
4187 assert!(
4188 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
4189 "attrsets differing in key set must be unequal (borrow path)",
4190 );
4191 }
4192
4193 #[test]
4194 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
4195 let boom = Value::Thunk(Thunk::new_native(|| {
4206 Err(EvalError::Throw("kaboom".to_string()))
4207 }));
4208 let mut a = NixAttrs::new();
4209 a.insert("x".to_string(), Value::Int(1));
4210 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4212 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4214 let va = Value::Attrs(Rc::new(a));
4218 let vb = Value::Attrs(Rc::new(b));
4219 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4220 }
4221
4222 #[test]
4223 fn attrs_eq_borrow_overlay_still_compares() {
4224 let mut base = NixAttrs::new();
4228 base.insert("a".to_string(), Value::Int(1));
4229 let mut over = NixAttrs::new();
4230 over.insert("b".to_string(), Value::Int(2));
4231 let merged = base.overlay(over);
4234 let mut flat = NixAttrs::new();
4235 flat.insert("a".to_string(), Value::Int(1));
4236 flat.insert("b".to_string(), Value::Int(2));
4237 assert!(
4238 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4239 "overlay and equivalent flat attrset must compare equal (borrow path)",
4240 );
4241 }
4242
4243 #[test]
4244 fn to_json_lambda() {
4245 let root = rnix::Root::parse("x: x");
4247 let expr = root.tree().expr().unwrap();
4248 let lambda = match expr {
4249 rnix::ast::Expr::Lambda(l) => l,
4250 _ => panic!("expected lambda"),
4251 };
4252 let closure = Closure {
4253 param: lambda.param().unwrap(),
4254 body: lambda.body().unwrap(),
4255 env: Env::new(),
4256 };
4257 assert_eq!(
4258 Value::Lambda(Rc::new(closure)).to_json(),
4259 serde_json::Value::String("<lambda>".to_string()),
4260 );
4261 }
4262
4263 #[test]
4264 fn to_json_builtin() {
4265 let b = BuiltinFn {
4266 name: "test",
4267 func: Rc::new(|_| Ok(Value::Null)),
4268 };
4269 assert_eq!(
4270 Value::Builtin(Box::new(b)).to_json(),
4271 serde_json::Value::String("<builtin test>".to_string()),
4272 );
4273 }
4274
4275 #[test]
4278 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4279
4280 #[test]
4281 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4282
4283 #[test]
4284 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4285
4286 #[test]
4287 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4288
4289 #[test]
4290 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4291
4292 #[test]
4293 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4294
4295 #[test]
4296 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4297
4298 #[test]
4299 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4300
4301 #[test]
4302 fn type_name_lambda() {
4303 let root = rnix::Root::parse("x: x");
4304 let expr = root.tree().expr().unwrap();
4305 let lambda = match expr {
4306 rnix::ast::Expr::Lambda(l) => l,
4307 _ => panic!("expected lambda"),
4308 };
4309 let closure = Closure {
4310 param: lambda.param().unwrap(),
4311 body: lambda.body().unwrap(),
4312 env: Env::new(),
4313 };
4314 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4315 }
4316
4317 #[test]
4318 fn type_name_builtin() {
4319 let b = BuiltinFn {
4320 name: "t",
4321 func: Rc::new(|_| Ok(Value::Null)),
4322 };
4323 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4324 }
4325
4326 #[test]
4329 fn as_bool_error_on_non_bool() {
4330 assert!(Value::Int(1).as_bool().is_err());
4331 assert!(Value::string("true").as_bool().is_err());
4332 }
4333
4334 #[test]
4335 fn as_int_error_on_non_int() {
4336 assert!(Value::Bool(true).as_int().is_err());
4337 assert!(Value::Float(1.0).as_int().is_err());
4338 }
4339
4340 #[test]
4341 fn as_string_error_on_non_string() {
4342 assert!(Value::Int(42).as_string().is_err());
4343 assert!(Value::Null.as_string().is_err());
4344 }
4345
4346 #[test]
4347 fn as_attrs_error_on_non_attrs() {
4348 assert!(Value::Int(1).as_attrs().is_err());
4349 assert!(Value::list(vec![]).as_attrs().is_err());
4350 }
4351
4352 #[test]
4353 fn as_list_error_on_non_list() {
4354 assert!(Value::Int(1).as_list().is_err());
4355 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4356 }
4357
4358 #[test]
4361 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4362 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4364 assert!(left.is_uniquely_owned_list());
4365 let right = [Value::Int(3), Value::Int(4)];
4366 let out = super::concat_lists(left, &right).unwrap();
4367 assert_eq!(
4368 out.as_list().unwrap(),
4369 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4370 );
4371 }
4372
4373 #[test]
4374 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4375 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4378 let left = Value::List(Rc::clone(&shared));
4379 assert!(!left.is_uniquely_owned_list());
4380 let right = [Value::Int(3)];
4381 let out = super::concat_lists(left, &right).unwrap();
4382 assert_eq!(
4383 out.as_list().unwrap(),
4384 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4385 );
4386 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4388 }
4389
4390 #[test]
4391 fn concat_lists_empty_operands() {
4392 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4393 assert!(out.as_list().unwrap().is_empty());
4394 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4395 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4396 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4397 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4398 }
4399
4400 #[test]
4401 fn concat_lists_non_list_left_errors() {
4402 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4403 }
4404
4405 #[test]
4406 fn concat_lists_preserves_element_identity() {
4407 let inner = Rc::new(NixString::plain("x"));
4409 let a = Value::String(Rc::clone(&inner));
4410 let left = Value::list(vec![a]);
4411 let out = super::concat_lists(left, &[]).unwrap();
4412 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4413 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4414 } else {
4415 panic!("expected string element");
4416 }
4417 }
4418
4419 #[test]
4422 fn to_float_coerces_int() {
4423 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4424 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4425 assert!(Value::string("x").to_float().is_err());
4426 }
4427
4428 #[test]
4431 fn partial_eq_int_float_cross() {
4432 assert_eq!(Value::Int(3), Value::Float(3.0));
4433 assert_eq!(Value::Float(3.0), Value::Int(3));
4434 assert_ne!(Value::Int(3), Value::Float(3.5));
4435 }
4436
4437 #[test]
4438 fn partial_eq_different_types_not_equal() {
4439 assert_ne!(Value::Int(1), Value::string("1"));
4440 assert_ne!(Value::Bool(true), Value::Int(1));
4441 assert_ne!(Value::Null, Value::Bool(false));
4442 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4443 }
4444
4445 #[test]
4448 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4449
4450 #[test]
4451 fn display_bool() {
4452 assert_eq!(format!("{}", Value::Bool(true)), "true");
4453 assert_eq!(format!("{}", Value::Bool(false)), "false");
4454 }
4455
4456 #[test]
4457 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4458
4459 #[test]
4460 fn display_float() {
4461 let s = format!("{}", Value::Float(3.14));
4462 assert!(s.contains("3.14"));
4463 }
4464
4465 #[test]
4466 fn display_string() {
4467 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4468 }
4469
4470 #[test]
4471 fn display_string_with_escapes() {
4472 let v = Value::string("a\"b\\c");
4473 let s = format!("{v}");
4474 assert!(s.contains("\\\""));
4475 assert!(s.contains("\\\\"));
4476 }
4477
4478 #[test]
4479 fn display_path() {
4480 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4481 }
4482
4483 #[test]
4484 fn display_list() {
4485 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4486 assert_eq!(format!("{v}"), "[ 1 2 ]");
4487 }
4488
4489 #[test]
4490 fn display_attrs() {
4491 let mut attrs = NixAttrs::new();
4492 attrs.insert("x".to_string(), Value::Int(1));
4493 let v = Value::Attrs(Rc::new(attrs));
4494 assert_eq!(format!("{v}"), "{ x = 1; }");
4495 }
4496
4497 #[test]
4498 fn display_lambda() {
4499 let root = rnix::Root::parse("x: x");
4500 let expr = root.tree().expr().unwrap();
4501 let lambda = match expr {
4502 rnix::ast::Expr::Lambda(l) => l,
4503 _ => panic!("expected lambda"),
4504 };
4505 let closure = Closure {
4506 param: lambda.param().unwrap(),
4507 body: lambda.body().unwrap(),
4508 env: Env::new(),
4509 };
4510 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4511 }
4512
4513 #[test]
4514 fn display_builtin() {
4515 let b = BuiltinFn {
4516 name: "add",
4517 func: Rc::new(|_| Ok(Value::Null)),
4518 };
4519 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4520 }
4521
4522 #[test]
4525 fn nixattrs_update_merging() {
4526 let mut a = NixAttrs::new();
4527 a.insert("x".to_string(), Value::Int(1));
4528 a.insert("y".to_string(), Value::Int(2));
4529 let mut b = NixAttrs::new();
4530 b.insert("y".to_string(), Value::Int(99));
4531 b.insert("z".to_string(), Value::Int(3));
4532 let merged = a.update(&b);
4533 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4534 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4535 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4536 assert_eq!(merged.len(), 3);
4537 }
4538
4539 #[test]
4540 fn nixattrs_contains_key() {
4541 let mut a = NixAttrs::new();
4542 a.insert("foo".to_string(), Value::Null);
4543 assert!(a.contains_key("foo"));
4544 assert!(!a.contains_key("bar"));
4545 }
4546
4547 #[test]
4550 fn env_lookup_through_parent_chain() {
4551 let mut root = Env::new();
4552 root.bind("a".to_string(), Value::Int(1));
4553 let mut child = root.child();
4554 child.bind("b".to_string(), Value::Int(2));
4555 let grandchild = child.child();
4556 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4558 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4559 assert_eq!(grandchild.lookup("c"), None);
4560 }
4561
4562 #[test]
4563 fn env_with_scope_lookup() {
4564 let mut attrs = NixAttrs::new();
4565 attrs.insert("x".to_string(), Value::Int(42));
4566 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4567 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4568 assert_eq!(env.lookup("y"), None);
4569 }
4570
4571 #[test]
4572 fn env_local_shadows_with_scope() {
4573 let mut attrs = NixAttrs::new();
4574 attrs.insert("x".to_string(), Value::Int(1));
4575 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4576 env.bind("x".to_string(), Value::Int(99));
4577 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4578 }
4579
4580 #[test]
4583 fn string_context_merge_combines_elements() {
4584 let mut ctx_a = StringContext::new();
4585 ctx_a.add_plain("/nix/store/aaa".to_string());
4586 let mut ctx_b = StringContext::new();
4587 ctx_b.add_plain("/nix/store/bbb".to_string());
4588 ctx_a.merge(&ctx_b);
4589 assert_eq!(ctx_a.len(), 2);
4590 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4591 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4592 }
4593
4594 #[test]
4595 fn string_context_merge_deduplicates() {
4596 let mut ctx = StringContext::new();
4597 ctx.add_plain("/nix/store/same".to_string());
4598 ctx.add_plain("/nix/store/same".to_string());
4599 assert_eq!(ctx.len(), 1);
4600 }
4601
4602 #[test]
4603 fn string_context_mixed_element_types() {
4604 let mut ctx = StringContext::new();
4605 ctx.add_plain("/nix/store/foo".to_string());
4606 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4607 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4608 assert_eq!(ctx.len(), 3);
4609 assert!(!ctx.is_empty());
4610 }
4611
4612 #[test]
4613 fn string_context_new_is_empty() {
4614 let ctx = StringContext::new();
4615 assert!(ctx.is_empty());
4616 assert_eq!(ctx.len(), 0);
4617 }
4618
4619 #[test]
4620 fn string_context_merge_zero_elements() {
4621 let mut ctx_a = StringContext::new();
4622 let ctx_b = StringContext::new();
4623 ctx_a.merge(&ctx_b);
4624 assert!(ctx_a.is_empty());
4625 }
4626
4627 #[test]
4628 fn string_context_merge_one_element() {
4629 let mut ctx = StringContext::new();
4630 let mut other = StringContext::new();
4631 other.add_plain("/nix/store/only".to_string());
4632 ctx.merge(&other);
4633 assert_eq!(ctx.len(), 1);
4634 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4635 }
4636
4637 #[test]
4638 fn string_context_merge_two_elements() {
4639 let mut ctx = StringContext::new();
4640 ctx.add_plain("/nix/store/a".to_string());
4641 let mut other = StringContext::new();
4642 other.add_plain("/nix/store/b".to_string());
4643 ctx.merge(&other);
4644 assert_eq!(ctx.len(), 2);
4645 }
4646
4647 #[test]
4648 fn string_context_merge_five_elements() {
4649 let mut ctx = StringContext::new();
4650 for i in 0..5 {
4651 ctx.add_plain(format!("/nix/store/path-{i}"));
4652 }
4653 assert_eq!(ctx.len(), 5);
4654 for i in 0..5 {
4655 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4656 }
4657 }
4658
4659 #[test]
4660 fn string_context_insert_deduplicates() {
4661 let mut ctx = StringContext::new();
4662 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4663 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4664 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4665 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4666 assert_eq!(ctx.len(), 2);
4667 }
4668
4669 #[test]
4670 fn nix_string_plain_has_no_context() {
4671 let s = NixString::plain("hello");
4672 assert!(!s.has_context());
4673 assert_eq!(s.as_str(), "hello");
4674 }
4675
4676 #[test]
4677 fn nix_string_with_context_reports_context() {
4678 let mut ctx = StringContext::new();
4679 ctx.add_plain("/nix/store/xyz".to_string());
4680 let s = NixString::with_context("hello", ctx);
4681 assert!(s.has_context());
4682 assert_eq!(s.as_str(), "hello");
4683 }
4684
4685 #[test]
4686 fn nix_string_display_shows_chars_only() {
4687 let mut ctx = StringContext::new();
4688 ctx.add_plain("/nix/store/abc".to_string());
4689 let s = NixString::with_context("visible", ctx);
4690 assert_eq!(format!("{s}"), "visible");
4691 }
4692
4693 #[test]
4694 fn nix_string_struct_eq_includes_context() {
4695 let plain = NixString::plain("hello");
4696 let mut ctx = StringContext::new();
4697 ctx.add_plain("/nix/store/xxx".to_string());
4698 let with_ctx = NixString::with_context("hello", ctx);
4699 assert_ne!(plain, with_ctx);
4701 }
4702
4703 #[test]
4704 fn value_string_eq_ignores_context() {
4705 let plain = Value::String(Rc::new(NixString::plain("hello")));
4706 let mut ctx = StringContext::new();
4707 ctx.add_plain("/nix/store/xxx".to_string());
4708 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4709 assert_eq!(plain, with_ctx);
4711 }
4712
4713 #[test]
4716 fn env_nested_with_inner_wins() {
4717 let mut outer_attrs = NixAttrs::new();
4718 outer_attrs.insert("x".to_string(), Value::Int(1));
4719 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4720 let mut inner_attrs = NixAttrs::new();
4721 inner_attrs.insert("x".to_string(), Value::Int(2));
4722 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4723 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4724 }
4725
4726 #[test]
4727 fn env_nested_with_fallback_to_outer() {
4728 let mut outer_attrs = NixAttrs::new();
4729 outer_attrs.insert("x".to_string(), Value::Int(1));
4730 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4731 let mut inner_attrs = NixAttrs::new();
4732 inner_attrs.insert("y".to_string(), Value::Int(2));
4733 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4734 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4735 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4736 }
4737
4738 #[test]
4739 fn env_lexical_binding_wins_over_all_with_scopes() {
4740 let mut outer_attrs = NixAttrs::new();
4741 outer_attrs.insert("x".to_string(), Value::Int(1));
4742 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4743 let mut inner_attrs = NixAttrs::new();
4744 inner_attrs.insert("x".to_string(), Value::Int(2));
4745 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4746 inner.bind("x".to_string(), Value::Int(99));
4747 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4748 }
4749
4750 #[test]
4751 fn env_parent_lexical_wins_over_child_with_scope() {
4752 let mut root = Env::new();
4753 root.bind("x".to_string(), Value::Int(10));
4754 let mut child_attrs = NixAttrs::new();
4755 child_attrs.insert("x".to_string(), Value::Int(20));
4756 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4757 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4758 }
4759
4760 #[test]
4761 fn env_deeply_nested_with_scopes_three_levels() {
4762 let mut a = NixAttrs::new();
4763 a.insert("x".to_string(), Value::Int(1));
4764 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4765
4766 let mut b = NixAttrs::new();
4767 b.insert("y".to_string(), Value::Int(2));
4768 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4769
4770 let mut c = NixAttrs::new();
4771 c.insert("z".to_string(), Value::Int(3));
4772 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4773
4774 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4775 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4776 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4777 assert_eq!(env3.lookup("w"), None);
4778 }
4779
4780 #[test]
4781 fn env_with_scope_does_not_pollute_bindings() {
4782 let mut attrs = NixAttrs::new();
4785 attrs.insert("x".to_string(), Value::Int(42));
4786 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4787 assert!(env.0.bindings.get(&intern("x")).is_none());
4789 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4791 }
4792
4793 #[test]
4794 fn env_lexical_binding_not_in_with_scopes() {
4795 let mut env = Env::new();
4797 env.bind("x".to_string(), Value::Int(42));
4798 assert!(env.0.with_scopes.is_empty());
4800 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4802 }
4803
4804 #[test]
4805 fn env_child_inherits_eval_file() {
4806 let mut env = Env::new();
4807 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4808 let child = env.child();
4809 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4810 }
4811
4812 #[test]
4813 fn env_new_has_no_parent_no_with() {
4814 let env = Env::new();
4815 assert_eq!(env.lookup("anything"), None);
4816 assert!(env.eval_file().is_none());
4817 }
4818
4819 #[test]
4822 fn thunk_new_suspended_is_not_evaluated() {
4823 let root = rnix::Root::parse("42");
4824 let expr = root.tree().expr().unwrap();
4825 let thunk = Thunk::new_suspended(expr, Env::new());
4826 assert!(!thunk.is_evaluated());
4827 }
4828
4829 #[test]
4830 fn thunk_new_evaluated_is_evaluated() {
4831 let thunk = Thunk::new_evaluated(Value::Int(42));
4832 assert!(thunk.is_evaluated());
4833 }
4834
4835 #[test]
4836 fn thunk_force_evaluates_suspended() {
4837 let root = rnix::Root::parse("42");
4838 let expr = root.tree().expr().unwrap();
4839 let thunk = Thunk::new_suspended(expr, Env::new());
4840 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4841 assert!(result.is_ok());
4842 assert_eq!(result.unwrap(), Value::Int(42));
4843 assert!(thunk.is_evaluated());
4844 }
4845
4846 #[test]
4847 fn thunk_force_memoizes_result() {
4848 let root = rnix::Root::parse("1 + 2");
4849 let expr = root.tree().expr().unwrap();
4850 let thunk = Thunk::new_suspended(expr, Env::new());
4851 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4852 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4853 assert_eq!(r1, Value::Int(3));
4854 assert_eq!(r2, Value::Int(3));
4855 }
4856
4857 #[test]
4858 fn thunk_force_already_evaluated_returns_value() {
4859 let thunk = Thunk::new_evaluated(Value::Bool(true));
4860 let result = thunk.force(&|_, _| panic!("should not be called"));
4861 assert_eq!(result.unwrap(), Value::Bool(true));
4862 }
4863
4864 #[test]
4873 fn thunk_force_concrete_skips_redundant_store_but_caches() {
4874 let root = rnix::Root::parse("1 + 2");
4877 let expr = root.tree().expr().unwrap();
4878 let thunk = Thunk::new_suspended(expr, Env::new());
4879
4880 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4881 assert_eq!(r1, Value::Int(3));
4882 assert!(thunk.is_evaluated());
4883
4884 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
4887
4888 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
4890 assert_eq!(r2, Value::Int(3));
4891 }
4892
4893 #[test]
4894 fn thunk_blackhole_detects_infinite_recursion() {
4895 let root = rnix::Root::parse("42");
4896 let expr = root.tree().expr().unwrap();
4897 let thunk = Thunk::new_suspended(expr, Env::new());
4898
4899 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
4902
4903 let result = thunk.force(&|_, _| Ok(Value::Null));
4904 assert!(result.is_err());
4905 let err_msg = format!("{}", result.unwrap_err());
4906 assert!(err_msg.contains("infinite recursion"));
4907 }
4908
4909 #[test]
4910 fn thunk_update_env_replaces_suspended_env() {
4911 let root = rnix::Root::parse("x");
4912 let expr = root.tree().expr().unwrap();
4913 let thunk = Thunk::new_suspended(expr, Env::new());
4914
4915 let mut new_env = Env::new();
4916 new_env.bind("x".to_string(), Value::Int(99));
4917 thunk.update_env(&new_env);
4918
4919 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4920 assert_eq!(result.unwrap(), Value::Int(99));
4921 }
4922
4923 #[test]
4924 fn thunk_update_env_noop_when_evaluated() {
4925 let thunk = Thunk::new_evaluated(Value::Int(1));
4926 let mut new_env = Env::new();
4927 new_env.bind("x".to_string(), Value::Int(99));
4928 thunk.update_env(&new_env);
4929 assert_eq!(
4930 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
4931 Value::Int(1),
4932 );
4933 }
4934
4935 #[test]
4936 fn thunk_debug_suspended() {
4937 let root = rnix::Root::parse("42");
4938 let expr = root.tree().expr().unwrap();
4939 let thunk = Thunk::new_suspended(expr, Env::new());
4940 assert_eq!(format!("{thunk:?}"), "<thunk>");
4941 }
4942
4943 #[test]
4944 fn thunk_debug_evaluated() {
4945 let thunk = Thunk::new_evaluated(Value::Int(42));
4946 let dbg = format!("{thunk:?}");
4947 assert!(dbg.contains("42"));
4948 }
4949
4950 #[test]
4951 fn thunk_error_restores_suspended_state() {
4952 let root = rnix::Root::parse("nonexistent_var");
4953 let expr = root.tree().expr().unwrap();
4954 let thunk = Thunk::new_suspended(expr, Env::new());
4955
4956 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4957 assert!(result.is_err());
4958 assert!(!thunk.is_evaluated());
4960 let dbg = format!("{thunk:?}");
4961 assert_eq!(dbg, "<thunk>");
4962 }
4963
4964 #[test]
4965 fn thunk_inherit_select_forces_and_selects() {
4966 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4967 let expr = root.tree().expr().unwrap();
4968 let source = Thunk::new_suspended(expr, Env::new());
4969 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4970 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4971 assert_eq!(result.unwrap(), Value::Int(42));
4972 assert!(thunk.is_evaluated());
4973 }
4974
4975 #[test]
4976 fn thunk_inherit_select_missing_attr_errors() {
4977 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4978 let expr = root.tree().expr().unwrap();
4979 let source = Thunk::new_suspended(expr, Env::new());
4980 let thunk = Thunk::new_inherit_select(source, "y".to_string());
4981 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4982 assert!(result.is_err());
4983 assert!(!thunk.is_evaluated());
4985 }
4986
4987 #[test]
4988 fn thunk_inherit_select_non_attrs_source_errors() {
4989 let root = rnix::Root::parse("42");
4990 let expr = root.tree().expr().unwrap();
4991 let source = Thunk::new_suspended(expr, Env::new());
4992 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4993 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4994 assert!(result.is_err());
4995 let msg = format!("{}", result.unwrap_err());
4996 assert!(msg.contains("not a set"));
4997 }
4998
4999 #[test]
5000 fn thunk_inherit_select_shares_source_thunk() {
5001 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
5005 let expr = root.tree().expr().unwrap();
5006 let source = Thunk::new_suspended(expr, Env::new());
5007 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
5008 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
5009 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
5010 assert_eq!(result_a.unwrap(), Value::Int(1));
5011 assert!(source.is_evaluated());
5013 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
5015 assert_eq!(result_b.unwrap(), Value::Int(2));
5016 }
5017
5018 #[test]
5021 fn nixattrs_empty_operations() {
5022 let a = NixAttrs::new();
5023 assert!(a.is_empty());
5024 assert_eq!(a.len(), 0);
5025 assert_eq!(a.get("x"), None);
5026 assert!(!a.contains_key("x"));
5027 assert_eq!(a.keys().count(), 0);
5028 assert_eq!(a.iter().count(), 0);
5029 }
5030
5031 #[test]
5032 fn nixattrs_update_with_empty() {
5033 let mut a = NixAttrs::new();
5034 a.insert("x".to_string(), Value::Int(1));
5035 let b = NixAttrs::new();
5036 let merged = a.update(&b);
5037 assert_eq!(merged.len(), 1);
5038 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5039 }
5040
5041 #[test]
5042 fn nixattrs_update_empty_with_nonempty() {
5043 let a = NixAttrs::new();
5044 let mut b = NixAttrs::new();
5045 b.insert("x".to_string(), Value::Int(1));
5046 let merged = a.update(&b);
5047 assert_eq!(merged.len(), 1);
5048 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5049 }
5050
5051 #[test]
5052 fn nixattrs_keys_sorted_order() {
5053 let mut a = NixAttrs::new();
5054 a.insert("c".to_string(), Value::Int(3));
5055 a.insert("a".to_string(), Value::Int(1));
5056 a.insert("b".to_string(), Value::Int(2));
5057 let keys: Vec<String> = a.keys().collect();
5058 assert_eq!(keys, vec!["a", "b", "c"]);
5059 }
5060
5061 #[test]
5064 fn value_to_str_forces_thunks() {
5065 let root = rnix::Root::parse(r#""hello""#);
5066 let expr = root.tree().expr().unwrap();
5067 let thunk = Thunk::new_suspended(expr, Env::new());
5068 let val = Value::Thunk(thunk);
5069 assert_eq!(val.to_str().unwrap(), "hello");
5070 }
5071
5072 #[test]
5073 fn value_to_nix_string_forces_thunks() {
5074 let root = rnix::Root::parse(r#""world""#);
5075 let expr = root.tree().expr().unwrap();
5076 let thunk = Thunk::new_suspended(expr, Env::new());
5077 let val = Value::Thunk(thunk);
5078 let ns = val.to_nix_string().unwrap();
5079 assert_eq!(ns.as_str(), "world");
5080 assert!(!ns.has_context());
5081 }
5082
5083 #[test]
5084 fn value_to_attrs_forces_thunks() {
5085 let root = rnix::Root::parse("{ x = 1; }");
5086 let expr = root.tree().expr().unwrap();
5087 let thunk = Thunk::new_suspended(expr, Env::new());
5088 let val = Value::Thunk(thunk);
5089 let attrs = val.to_attrs().unwrap();
5090 assert_eq!(attrs.len(), 1);
5091 }
5092
5093 #[test]
5094 fn value_to_list_forces_thunks() {
5095 let root = rnix::Root::parse("[1 2 3]");
5096 let expr = root.tree().expr().unwrap();
5097 let thunk = Thunk::new_suspended(expr, Env::new());
5098 let val = Value::Thunk(thunk);
5099 let list = val.to_list().unwrap();
5100 assert_eq!(list.len(), 3);
5101 }
5102
5103 #[test]
5104 fn value_to_float_on_thunk() {
5105 let root = rnix::Root::parse("3.14");
5106 let expr = root.tree().expr().unwrap();
5107 let thunk = Thunk::new_suspended(expr, Env::new());
5108 let val = Value::Thunk(thunk);
5109 let f = val.to_float().unwrap();
5110 assert!((f - 3.14).abs() < f64::EPSILON);
5111 }
5112
5113 #[test]
5114 fn value_as_bool_on_thunk() {
5115 let root = rnix::Root::parse("true");
5116 let expr = root.tree().expr().unwrap();
5117 let thunk = Thunk::new_suspended(expr, Env::new());
5118 let val = Value::Thunk(thunk);
5119 assert!(val.as_bool().unwrap());
5120 }
5121
5122 #[test]
5123 fn value_as_int_on_thunk() {
5124 let root = rnix::Root::parse("42");
5125 let expr = root.tree().expr().unwrap();
5126 let thunk = Thunk::new_suspended(expr, Env::new());
5127 let val = Value::Thunk(thunk);
5128 assert_eq!(val.as_int().unwrap(), 42);
5129 }
5130
5131 #[test]
5132 fn value_string_constructor() {
5133 let v = Value::string("test");
5134 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
5135 }
5136
5137 #[test]
5138 fn value_partial_eq_null_null() {
5139 assert_eq!(Value::Null, Value::Null);
5140 }
5141
5142 #[test]
5143 fn value_partial_eq_lists_deep() {
5144 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5145 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5146 assert_eq!(a, b);
5147 }
5148
5149 #[test]
5150 fn value_partial_eq_attrs_deep() {
5151 let mut a = NixAttrs::new();
5152 a.insert("x".to_string(), Value::Int(1));
5153 let mut b = NixAttrs::new();
5154 b.insert("x".to_string(), Value::Int(1));
5155 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
5156 }
5157
5158 #[test]
5161 fn eval_error_type_error_constructor() {
5162 let e = EvalError::type_error("oops");
5163 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
5164 }
5165
5166 #[test]
5167 fn eval_error_type_mismatch_constructor() {
5168 let e = EvalError::type_mismatch("int", "string");
5169 match e {
5170 EvalError::TypeMismatch { expected, got } => {
5171 assert_eq!(expected, "int");
5172 assert_eq!(got, "string");
5173 }
5174 _ => panic!("expected TypeMismatch"),
5175 }
5176 }
5177
5178 #[test]
5179 fn eval_error_is_throw_yes_no() {
5180 assert!(EvalError::Throw("oops".into()).is_throw());
5181 assert!(!EvalError::TypeError("oops".into()).is_throw());
5182 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
5183 }
5184
5185 #[test]
5186 fn eval_error_is_infinite_recursion_yes_no() {
5187 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
5188 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
5189 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
5190 }
5191
5192 #[test]
5193 fn eval_error_display_undefined_var() {
5194 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
5195 assert!(s.contains("undefined variable"));
5196 assert!(s.contains("foo"));
5197 }
5198
5199 #[test]
5200 fn eval_error_display_type_error() {
5201 let s = format!("{}", EvalError::TypeError("bad".into()));
5202 assert!(s.contains("type error"));
5203 assert!(s.contains("bad"));
5204 }
5205
5206 #[test]
5207 fn eval_error_display_attr_not_found() {
5208 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5209 assert!(s.contains("attribute not found"));
5210 assert!(s.contains("x"));
5211 }
5212
5213 #[test]
5214 fn eval_error_display_type_mismatch() {
5215 let s = format!(
5216 "{}",
5217 EvalError::TypeMismatch { expected: "int", got: "string" }
5218 );
5219 assert!(s.contains("expected int"));
5220 assert!(s.contains("got string"));
5221 }
5222
5223 #[test]
5224 fn eval_error_display_assertion_failed() {
5225 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5226 assert!(s.contains("assertion"));
5227 }
5228
5229 #[test]
5230 fn eval_error_display_division_by_zero() {
5231 let s = format!("{}", EvalError::DivisionByZero);
5232 assert!(s.contains("division by zero"));
5233 }
5234
5235 #[test]
5236 fn eval_error_display_infinite_recursion() {
5237 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5238 assert!(s.contains("infinite recursion"));
5239 assert!(s.contains("loop"));
5240 }
5241
5242 #[test]
5243 fn eval_error_display_io_error() {
5244 let s = format!(
5245 "{}",
5246 EvalError::IoError {
5247 context: "ctx".into(),
5248 message: "no such file".into(),
5249 }
5250 );
5251 assert!(s.contains("I/O"));
5252 assert!(s.contains("ctx"));
5253 assert!(s.contains("no such file"));
5254 }
5255
5256 #[test]
5257 fn eval_error_display_throw() {
5258 let s = format!("{}", EvalError::Throw("boom".into()));
5259 assert_eq!(s, "boom");
5260 }
5261
5262 #[test]
5263 fn eval_error_display_not_implemented() {
5264 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5265 assert!(s.contains("not yet implemented"));
5266 assert!(s.contains("frob"));
5267 }
5268
5269 #[test]
5270 fn eval_error_display_parse_error() {
5271 let s = format!("{}", EvalError::ParseError("syntax".into()));
5272 assert!(s.contains("parse error"));
5273 assert!(s.contains("syntax"));
5274 }
5275
5276 #[test]
5277 fn eval_error_display_recursion_limit() {
5278 let s = format!(
5279 "{}",
5280 EvalError::RecursionLimit("max depth exceeded".into())
5281 );
5282 assert!(s.contains("recursion limit"));
5283 assert!(s.contains("max depth exceeded"));
5284 }
5285
5286 #[test]
5287 fn eval_error_partial_eq_same_variant() {
5288 assert_eq!(
5289 EvalError::UndefinedVar("x".into()),
5290 EvalError::UndefinedVar("x".into()),
5291 );
5292 assert_ne!(
5293 EvalError::UndefinedVar("x".into()),
5294 EvalError::UndefinedVar("y".into()),
5295 );
5296 assert_ne!(
5297 EvalError::UndefinedVar("x".into()),
5298 EvalError::AttrNotFound("x".into()),
5299 );
5300 }
5301
5302 #[test]
5305 fn context_element_display_plain() {
5306 let e = ContextElement::Plain("/nix/store/xyz".into());
5307 assert_eq!(format!("{e}"), "/nix/store/xyz");
5308 }
5309
5310 #[test]
5311 fn context_element_display_output() {
5312 let e = ContextElement::Output {
5313 drv: "/nix/store/abc.drv".into(),
5314 output: "out".into(),
5315 };
5316 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5317 }
5318
5319 #[test]
5320 fn context_element_display_drv_deep() {
5321 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5322 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5323 }
5324
5325 #[test]
5328 fn string_context_iter_yields_all() {
5329 let mut ctx = StringContext::new();
5330 ctx.add_plain("/nix/store/aaa");
5331 ctx.add_plain("/nix/store/bbb");
5332 let count = ctx.iter().count();
5333 assert_eq!(count, 2);
5334 }
5335
5336 #[test]
5337 fn string_context_len_matches_set_size() {
5338 let mut ctx = StringContext::new();
5339 assert_eq!(ctx.len(), 0);
5340 ctx.add_plain("/nix/store/x");
5341 assert_eq!(ctx.len(), 1);
5342 ctx.add_output("/nix/store/y.drv", "out");
5343 assert_eq!(ctx.len(), 2);
5344 }
5345
5346 #[test]
5347 fn string_context_insert_raw_element() {
5348 let mut ctx = StringContext::new();
5349 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5350 assert_eq!(ctx.len(), 1);
5351 }
5352
5353 #[test]
5354 fn string_context_default_is_empty() {
5355 let ctx = StringContext::default();
5356 assert!(ctx.is_empty());
5357 }
5358
5359 #[test]
5362 fn nix_string_as_ref_str() {
5363 let s = NixString::plain("hello");
5364 let r: &str = s.as_ref();
5365 assert_eq!(r, "hello");
5366 }
5367
5368 #[test]
5369 fn nix_string_deref_to_str_methods() {
5370 let s = NixString::plain("Hello World");
5371 assert_eq!(s.len(), 11);
5372 assert!(s.starts_with("Hello"));
5373 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5375 }
5376
5377 #[test]
5380 fn nixattrs_remove_returns_value() {
5381 let mut a = NixAttrs::new();
5382 a.insert("x".into(), Value::Int(1));
5383 let removed = a.remove("x");
5384 assert_eq!(removed, Some(Value::Int(1)));
5385 assert!(!a.contains_key("x"));
5386 assert_eq!(a.remove("y"), None);
5387 }
5388
5389 #[test]
5390 fn nixattrs_values_iter() {
5391 let mut a = NixAttrs::new();
5392 a.insert("a".into(), Value::Int(1));
5393 a.insert("b".into(), Value::Int(2));
5394 let mut vs: Vec<&Value> = a.values().collect();
5395 vs.sort_by_key(|v| match v {
5396 Value::Int(n) => *n,
5397 _ => 0,
5398 });
5399 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5400 }
5401
5402 #[test]
5403 fn nixattrs_iter_returns_sorted_pairs() {
5404 let mut a = NixAttrs::new();
5405 a.insert("zeta".into(), Value::Int(3));
5406 a.insert("alpha".into(), Value::Int(1));
5407 a.insert("mu".into(), Value::Int(2));
5408 let pairs: Vec<(String, &Value)> = a.iter().collect();
5409 assert_eq!(pairs[0].0, "alpha");
5410 assert_eq!(pairs[1].0, "mu");
5411 assert_eq!(pairs[2].0, "zeta");
5412 }
5413
5414 #[test]
5415 fn nixattrs_from_iterator() {
5416 let pairs = vec![
5417 ("a".to_string(), Value::Int(1)),
5418 ("b".to_string(), Value::Int(2)),
5419 ];
5420 let attrs: NixAttrs = pairs.into_iter().collect();
5421 assert_eq!(attrs.len(), 2);
5422 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5423 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5424 }
5425
5426 #[test]
5427 fn nixattrs_into_iterator_yields_owned() {
5428 let mut a = NixAttrs::new();
5429 a.insert("x".into(), Value::Int(42));
5430 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5431 assert_eq!(pairs.len(), 1);
5432 assert_eq!(pairs[0].0, "x");
5433 assert_eq!(pairs[0].1, Value::Int(42));
5434 }
5435
5436 #[test]
5437 fn nixattrs_default_is_empty() {
5438 let a = NixAttrs::default();
5439 assert!(a.is_empty());
5440 }
5441
5442 #[test]
5445 fn value_from_bool() {
5446 assert_eq!(Value::from(true), Value::Bool(true));
5447 assert_eq!(Value::from(false), Value::Bool(false));
5448 }
5449
5450 #[test]
5451 fn value_from_i64() {
5452 assert_eq!(Value::from(42_i64), Value::Int(42));
5453 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5454 }
5455
5456 #[test]
5457 fn value_from_f64() {
5458 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5459 }
5460
5461 #[test]
5462 fn value_from_nix_string() {
5463 let v: Value = NixString::plain("hi").into();
5464 assert_eq!(v, Value::string("hi"));
5465 }
5466
5467 #[test]
5468 fn value_from_nix_attrs() {
5469 let mut a = NixAttrs::new();
5470 a.insert("x".into(), Value::Int(1));
5471 let v: Value = a.into();
5472 match v {
5473 Value::Attrs(_) => {}
5474 _ => panic!("expected Attrs"),
5475 }
5476 }
5477
5478 #[test]
5479 fn value_from_vec() {
5480 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5481 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5482 }
5483
5484 #[test]
5485 fn value_default_is_null() {
5486 let v: Value = Value::default();
5487 assert_eq!(v, Value::Null);
5488 }
5489
5490 #[test]
5493 fn value_from_json_null() {
5494 let v = Value::from(&serde_json::Value::Null);
5495 assert_eq!(v, Value::Null);
5496 }
5497
5498 #[test]
5499 fn value_from_json_bool() {
5500 let v = Value::from(&serde_json::Value::Bool(true));
5501 assert_eq!(v, Value::Bool(true));
5502 }
5503
5504 #[test]
5505 fn value_from_json_int() {
5506 let v = Value::from(&serde_json::json!(42));
5507 assert_eq!(v, Value::Int(42));
5508 }
5509
5510 #[test]
5511 fn value_from_json_float() {
5512 let v = Value::from(&serde_json::json!(3.14));
5513 match v {
5514 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5515 _ => panic!("expected Float"),
5516 }
5517 }
5518
5519 #[test]
5520 fn value_from_json_string() {
5521 let v = Value::from(&serde_json::Value::String("hi".into()));
5522 assert_eq!(v, Value::string("hi"));
5523 }
5524
5525 #[test]
5526 fn value_from_json_array() {
5527 let v = Value::from(&serde_json::json!([1, true, "x"]));
5528 match v {
5529 Value::List(items) => {
5530 assert_eq!(items.len(), 3);
5531 assert_eq!(items[0], Value::Int(1));
5532 assert_eq!(items[1], Value::Bool(true));
5533 assert_eq!(items[2], Value::string("x"));
5534 }
5535 _ => panic!("expected List"),
5536 }
5537 }
5538
5539 #[test]
5540 fn value_from_json_object() {
5541 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5542 match v {
5543 Value::Attrs(attrs) => {
5544 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5545 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5546 }
5547 _ => panic!("expected Attrs"),
5548 }
5549 }
5550
5551 #[test]
5552 fn value_from_json_nested() {
5553 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5554 let json_back = v.to_json();
5555 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5556 }
5557
5558 #[test]
5561 fn value_from_toml_string() {
5562 let t = toml::Value::String("hi".into());
5563 assert_eq!(Value::from(&t), Value::string("hi"));
5564 }
5565
5566 #[test]
5567 fn value_from_toml_int() {
5568 let t = toml::Value::Integer(42);
5569 assert_eq!(Value::from(&t), Value::Int(42));
5570 }
5571
5572 #[test]
5573 fn value_from_toml_float() {
5574 let t = toml::Value::Float(3.14);
5575 match Value::from(&t) {
5576 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5577 _ => panic!("expected Float"),
5578 }
5579 }
5580
5581 #[test]
5582 fn value_from_toml_bool() {
5583 let t = toml::Value::Boolean(true);
5584 assert_eq!(Value::from(&t), Value::Bool(true));
5585 }
5586
5587 #[test]
5588 fn value_from_toml_array() {
5589 let t = toml::Value::Array(vec![
5590 toml::Value::Integer(1),
5591 toml::Value::Integer(2),
5592 ]);
5593 assert_eq!(
5594 Value::from(&t),
5595 Value::list(vec![Value::Int(1), Value::Int(2)]),
5596 );
5597 }
5598
5599 #[test]
5600 fn value_from_toml_table() {
5601 let mut tbl = toml::map::Map::new();
5602 tbl.insert("k".into(), toml::Value::Integer(7));
5603 let t = toml::Value::Table(tbl);
5604 match Value::from(&t) {
5605 Value::Attrs(attrs) => {
5606 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5607 }
5608 _ => panic!("expected Attrs"),
5609 }
5610 }
5611
5612 #[test]
5613 fn value_from_toml_datetime_becomes_string() {
5614 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5616 let t = toml::Value::Datetime(dt);
5617 match Value::from(&t) {
5618 Value::String(_) => {}
5619 other => panic!("expected String, got {other:?}"),
5620 }
5621 }
5622
5623 #[test]
5626 fn coerce_to_path_from_path() {
5627 let v = Value::Path(Box::new("/foo".into()));
5628 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5629 }
5630
5631 #[test]
5632 fn coerce_to_path_from_string() {
5633 let v = Value::string("/bar");
5634 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5635 }
5636
5637 #[test]
5645 fn out_path_needs_realize_matches_output_context() {
5646 let mut ctx = StringContext::new();
5649 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5650 assert_eq!(
5651 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5652 Some("/nix/store/aaa-thing.drv".to_string()),
5653 );
5654 }
5655
5656 #[test]
5657 fn out_path_needs_realize_ignores_plain_context() {
5658 let mut ctx = StringContext::new();
5661 ctx.add_plain("/nix/store/ccc-plain");
5662 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5663 }
5664
5665 #[test]
5666 fn out_path_needs_realize_ignores_non_store_path() {
5667 let mut ctx = StringContext::new();
5670 ctx.add_output("/nix/store/ddd.drv", "out");
5671 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5672 }
5673
5674 #[test]
5675 fn out_path_needs_realize_empty_context_is_none() {
5676 let ctx = StringContext::new();
5678 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5679 }
5680
5681 #[test]
5682 fn coerce_to_realized_path_present_output_is_passthrough() {
5683 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5687 std::fs::create_dir_all(&dir).unwrap();
5688 let file = dir.join("out");
5689 std::fs::write(&file, b"present").unwrap();
5690 let present = file.to_string_lossy().to_string();
5691
5692 let mut ctx = StringContext::new();
5693 ctx.add_plain(&present);
5698 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5699 present.as_str(),
5700 ctx,
5701 )));
5702 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5703 }
5704
5705 #[test]
5706 fn coerce_to_realized_path_absent_output_invokes_hook() {
5707 use std::sync::{Arc, Mutex};
5712 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5713 let seen2 = seen.clone();
5714 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5715 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5716 Ok(())
5717 }));
5718
5719 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5722 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5723 let mut ctx = StringContext::new();
5724 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5725 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5726
5727 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5729 let s = seen.lock().unwrap();
5730 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5731 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5732 assert_eq!(s[0].1, out);
5733 }
5734
5735 #[test]
5736 fn coerce_to_path_errors_on_int() {
5737 let v = Value::Int(1);
5738 let e = v.coerce_to_path("readFile").unwrap_err();
5739 match e {
5740 EvalError::TypeError(ref msg) => {
5741 assert!(msg.contains("readFile"));
5742 assert!(msg.contains("path or string"));
5743 assert!(msg.contains("int"));
5744 }
5745 _ => panic!("expected TypeError"),
5746 }
5747 }
5748
5749 #[test]
5750 fn coerce_to_path_errors_on_null() {
5751 let v = Value::Null;
5752 assert!(v.coerce_to_path("ctx").is_err());
5753 }
5754
5755 #[test]
5756 fn coerce_to_path_attrs_with_outpath() {
5757 let mut attrs = NixAttrs::new();
5758 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5759 let val = Value::Attrs(Rc::new(attrs));
5760 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5761 }
5762
5763 #[test]
5764 fn coerce_to_path_attrs_without_outpath_fails() {
5765 let attrs = NixAttrs::new();
5766 let val = Value::Attrs(Rc::new(attrs));
5767 assert!(val.coerce_to_path("test").is_err());
5768 }
5769
5770 #[test]
5773 fn coerce_to_string_string() {
5774 let v = Value::string("hello");
5775 let (s, _ctx) = v.coerce_to_string().unwrap();
5776 assert_eq!(s, "hello");
5777 }
5778
5779 #[test]
5780 fn coerce_to_string_path() {
5781 let v = Value::Path(Box::new("/foo".into()));
5782 let (s, ctx) = v.coerce_to_string().unwrap();
5783 assert_eq!(s, "/foo");
5784 assert!(!ctx.is_empty()); }
5786
5787 #[test]
5788 fn coerce_to_string_int() {
5789 let v = Value::Int(42);
5790 let (s, _ctx) = v.coerce_to_string().unwrap();
5791 assert_eq!(s, "42");
5792 }
5793
5794 #[test]
5795 fn coerce_to_string_float() {
5796 let v = Value::Float(3.14);
5798 let (s, _ctx) = v.coerce_to_string().unwrap();
5799 assert_eq!(s, "3.140000");
5800 }
5801
5802 #[test]
5803 fn coerce_to_string_bool_true() {
5804 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5805 assert_eq!(s, "1");
5806 }
5807
5808 #[test]
5809 fn coerce_to_string_bool_false() {
5810 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5811 assert_eq!(s, "");
5812 }
5813
5814 #[test]
5815 fn coerce_to_string_null() {
5816 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5817 assert_eq!(s, "");
5818 }
5819
5820 #[test]
5821 fn coerce_to_string_attrs_with_outpath() {
5822 let mut attrs = NixAttrs::new();
5823 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5824 let val = Value::Attrs(Rc::new(attrs));
5825 let (s, _ctx) = val.coerce_to_string().unwrap();
5826 assert_eq!(s, "/nix/store/abc");
5827 }
5828
5829 #[test]
5830 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5831 let attrs = NixAttrs::new();
5832 let val = Value::Attrs(Rc::new(attrs));
5833 assert!(val.coerce_to_string().is_err());
5834 }
5835
5836 #[test]
5837 fn coerce_to_string_lambda_fails() {
5838 let root = rnix::Root::parse("x: x");
5839 let expr = root.tree().expr().unwrap();
5840 let closure = Closure {
5841 param: match expr {
5842 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
5843 _ => panic!("expected lambda"),
5844 },
5845 body: match expr {
5846 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
5847 _ => panic!("expected lambda"),
5848 },
5849 env: Env::new(),
5850 };
5851 let val = Value::Lambda(Rc::new(closure));
5852 assert!(val.coerce_to_string().is_err());
5853 }
5854
5855 #[test]
5858 fn builtin_fn_debug_includes_name() {
5859 let b = BuiltinFn {
5860 name: "myFunc",
5861 func: Rc::new(|_| Ok(Value::Null)),
5862 };
5863 let s = format!("{b:?}");
5864 assert!(s.contains("myFunc"));
5865 assert!(s.contains("builtin"));
5866 }
5867
5868 #[test]
5871 fn thunk_force_chains_through_inner_thunks() {
5872 let inner_root = rnix::Root::parse("99");
5874 let inner_expr = inner_root.tree().expr().unwrap();
5875 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
5876 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
5877 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
5878 match result.unwrap() {
5883 Value::Thunk(_) | Value::Int(99) => {}
5884 other => panic!("unexpected: {other:?}"),
5885 }
5886 }
5887
5888 #[test]
5889 fn thunk_inherit_select_debug_format() {
5890 let root = rnix::Root::parse("{ x = 1; }");
5891 let expr = root.tree().expr().unwrap();
5892 let source = Thunk::new_suspended(expr, Env::new());
5893 let thunk = Thunk::new_inherit_select(source, "x");
5894 let s = format!("{thunk:?}");
5895 assert!(s.contains("inherit-select"));
5896 assert!(s.contains("x"));
5897 }
5898
5899 #[test]
5900 fn thunk_blackhole_debug_format() {
5901 let root = rnix::Root::parse("1");
5902 let expr = root.tree().expr().unwrap();
5903 let thunk = Thunk::new_suspended(expr, Env::new());
5904 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5906 assert_eq!(format!("{thunk:?}"), "<blackhole>");
5907 }
5908
5909 #[test]
5912 fn value_display_thunk_evaluates() {
5913 let root = rnix::Root::parse("42");
5914 let expr = root.tree().expr().unwrap();
5915 let thunk = Thunk::new_suspended(expr, Env::new());
5916 let val = Value::Thunk(thunk);
5917 assert_eq!(format!("{val}"), "42");
5918 }
5919
5920 #[test]
5921 fn value_to_json_thunk_forces() {
5922 let root = rnix::Root::parse(r#""world""#);
5923 let expr = root.tree().expr().unwrap();
5924 let thunk = Thunk::new_suspended(expr, Env::new());
5925 let val = Value::Thunk(thunk);
5926 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
5927 }
5928
5929 #[test]
5930 fn value_type_name_thunk_forces() {
5931 let root = rnix::Root::parse("42");
5932 let expr = root.tree().expr().unwrap();
5933 let thunk = Thunk::new_suspended(expr, Env::new());
5934 let val = Value::Thunk(thunk);
5935 assert_eq!(val.type_name(), "int");
5936 }
5937
5938 #[test]
5941 fn as_string_errors_on_thunk() {
5942 let root = rnix::Root::parse(r#""x""#);
5943 let expr = root.tree().expr().unwrap();
5944 let thunk = Thunk::new_suspended(expr, Env::new());
5945 let val = Value::Thunk(thunk);
5946 let err = val.as_string().unwrap_err();
5947 match err {
5948 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
5949 _ => panic!("expected TypeError"),
5950 }
5951 }
5952
5953 #[test]
5954 fn as_nix_string_errors_on_thunk() {
5955 let root = rnix::Root::parse(r#""x""#);
5956 let expr = root.tree().expr().unwrap();
5957 let thunk = Thunk::new_suspended(expr, Env::new());
5958 let val = Value::Thunk(thunk);
5959 assert!(val.as_nix_string().is_err());
5960 }
5961
5962 #[test]
5963 fn as_attrs_errors_on_thunk() {
5964 let root = rnix::Root::parse("{}");
5965 let expr = root.tree().expr().unwrap();
5966 let thunk = Thunk::new_suspended(expr, Env::new());
5967 let val = Value::Thunk(thunk);
5968 assert!(val.as_attrs().is_err());
5969 }
5970
5971 #[test]
5972 fn as_list_errors_on_thunk() {
5973 let root = rnix::Root::parse("[]");
5974 let expr = root.tree().expr().unwrap();
5975 let thunk = Thunk::new_suspended(expr, Env::new());
5976 let val = Value::Thunk(thunk);
5977 assert!(val.as_list().is_err());
5978 }
5979
5980 #[test]
5983 fn as_nix_string_ok_on_string() {
5984 let v = Value::string("hi");
5985 let ns = v.as_nix_string().unwrap();
5986 assert_eq!(ns.as_str(), "hi");
5987 }
5988
5989 #[test]
5990 fn as_nix_string_errors_on_int() {
5991 let v = Value::Int(1);
5992 match v.as_nix_string() {
5993 Err(EvalError::TypeMismatch { expected, got }) => {
5994 assert_eq!(expected, "string");
5995 assert_eq!(got, "int");
5996 }
5997 _ => panic!("expected TypeMismatch"),
5998 }
5999 }
6000
6001 #[test]
6006 fn oncecell_cache_populated_after_force() {
6007 let root = rnix::Root::parse("42");
6008 let expr = root.tree().expr().unwrap();
6009 let thunk = Thunk::new_suspended(expr, Env::new());
6010 assert!(thunk.0.cache.get().is_none());
6012 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6013 assert!(thunk.0.cache.get().is_some());
6015 }
6016
6017 #[test]
6018 fn oncecell_cache_matches_force_result() {
6019 let root = rnix::Root::parse("1 + 2");
6020 let expr = root.tree().expr().unwrap();
6021 let thunk = Thunk::new_suspended(expr, Env::new());
6022 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6023 let cached = thunk.0.cache.get().unwrap();
6024 assert_eq!((**cached).clone().into_value(), forced);
6027 }
6028
6029 #[test]
6030 fn oncecell_new_evaluated_prepopulates_cache() {
6031 let thunk = Thunk::new_evaluated(Value::Int(77));
6032 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
6034 assert_eq!(**cached, Concrete::Int(77));
6035 }
6036
6037 #[test]
6038 fn oncecell_is_evaluated_uses_cache() {
6039 let thunk = Thunk::new_evaluated(Value::Bool(false));
6040 assert!(thunk.is_evaluated());
6042 assert!(thunk.0.cache.get().is_some());
6043 }
6044
6045 #[test]
6046 fn oncecell_already_evaluated_returns_cached_without_repr() {
6047 let thunk = Thunk::new_evaluated(Value::Int(55));
6051 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
6052 assert_eq!(result.unwrap(), Value::Int(55));
6053 }
6054
6055 #[test]
6060 fn with_scope_created_with_empty_cache() {
6061 let thunk = Thunk::new_suspended(
6063 rnix::Root::parse("{}").tree().expr().unwrap(),
6064 Env::new(),
6065 );
6066 let env = Env::new().with_scope(Value::Thunk(thunk));
6067 let scope = &env.0.with_scopes[0];
6068 assert!(scope.cached.borrow().is_none());
6069 }
6070
6071 #[test]
6072 fn with_scope_concrete_pre_populates_cache() {
6073 let mut attrs = NixAttrs::new();
6075 attrs.insert("x".to_string(), Value::Int(1));
6076 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6077 let scope = &env.0.with_scopes[0];
6078 assert!(scope.cached.borrow().is_some());
6079 }
6080
6081 #[test]
6082 fn with_scope_first_lookup_populates_cache() {
6083 let mut attrs = NixAttrs::new();
6084 attrs.insert("x".to_string(), Value::Int(42));
6085 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6086 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6088 let _ = env.lookup("x");
6090 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6091 }
6092
6093 #[test]
6094 fn with_scope_second_lookup_uses_cache() {
6095 let mut attrs = NixAttrs::new();
6096 attrs.insert("x".to_string(), Value::Int(10));
6097 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6098 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6100 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6101 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6103 }
6104
6105 #[test]
6106 fn with_scope_child_shares_cache_via_rc() {
6107 let mut attrs = NixAttrs::new();
6108 attrs.insert("shared".to_string(), Value::Int(7));
6109 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6110 let child = parent.child();
6111 let _ = parent.lookup("shared");
6113 assert!(child.0.with_scopes[0].cached.borrow().is_some());
6116 }
6117
6118 #[test]
6119 fn with_scope_innermost_checked_first() {
6120 let mut outer = NixAttrs::new();
6121 outer.insert("x".to_string(), Value::Int(1));
6122 outer.insert("y".to_string(), Value::Int(100));
6123 let mut inner = NixAttrs::new();
6124 inner.insert("x".to_string(), Value::Int(2));
6125 let env = Env::new()
6126 .with_scope(Value::Attrs(Rc::new(outer)))
6127 .with_scope(Value::Attrs(Rc::new(inner)));
6128 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
6130 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
6132 }
6133
6134 #[test]
6139 fn fxhashmap_nixattrs_new_creates_empty() {
6140 let a = NixAttrs::new();
6141 assert!(a.is_empty());
6142 assert_eq!(a.len(), 0);
6143 assert!(a.inner().is_empty());
6145 }
6146
6147 #[test]
6148 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
6149 let mut a = NixAttrs::new();
6150 a.insert("mykey".to_string(), Value::Int(42));
6151 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
6152 }
6153
6154 #[test]
6155 fn fxhashmap_contains_key_with_interned_keys() {
6156 let mut a = NixAttrs::new();
6157 a.insert("alpha".to_string(), Value::Int(1));
6158 let sym = intern("alpha");
6159 assert!(a.inner().contains_key(&sym));
6160 let missing_sym = intern("beta");
6161 assert!(!a.inner().contains_key(&missing_sym));
6162 }
6163
6164 #[test]
6165 fn fxhashmap_remove_returns_value() {
6166 let mut a = NixAttrs::new();
6167 a.insert("key".to_string(), Value::Int(99));
6168 let removed = a.remove("key");
6169 assert_eq!(removed, Some(Value::Int(99)));
6170 assert!(a.is_empty());
6171 }
6172
6173 #[test]
6174 fn fxhashmap_keys_returns_sorted_strings() {
6175 let mut a = NixAttrs::new();
6176 a.insert("zulu".to_string(), Value::Int(1));
6177 a.insert("alpha".to_string(), Value::Int(2));
6178 a.insert("mike".to_string(), Value::Int(3));
6179 let keys: Vec<String> = a.keys().collect();
6180 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
6181 }
6182
6183 #[test]
6184 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
6185 let mut a = NixAttrs::new();
6186 a.insert("b".to_string(), Value::Int(2));
6187 a.insert("a".to_string(), Value::Int(1));
6188 let pairs: Vec<(String, &Value)> = a.iter().collect();
6189 assert_eq!(pairs.len(), 2);
6190 assert_eq!(pairs[0].0, "a");
6191 assert_eq!(*pairs[0].1, Value::Int(1));
6192 assert_eq!(pairs[1].0, "b");
6193 assert_eq!(*pairs[1].1, Value::Int(2));
6194 }
6195
6196 #[test]
6197 fn fxhashmap_update_merges_correctly() {
6198 let mut left = NixAttrs::new();
6199 left.insert("a".to_string(), Value::Int(1));
6200 left.insert("b".to_string(), Value::Int(2));
6201 let mut right = NixAttrs::new();
6202 right.insert("b".to_string(), Value::Int(20));
6203 right.insert("c".to_string(), Value::Int(3));
6204 let merged = left.update(&right);
6205 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6206 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6208 assert_eq!(merged.len(), 3);
6209 }
6210
6211 #[test]
6212 fn fxhashmap_from_iterator_collects_with_interning() {
6213 let pairs = vec![
6214 ("x".to_string(), Value::Int(10)),
6215 ("y".to_string(), Value::Int(20)),
6216 ("z".to_string(), Value::Int(30)),
6217 ];
6218 let attrs: NixAttrs = pairs.into_iter().collect();
6219 assert_eq!(attrs.len(), 3);
6220 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6221 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6222 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6223 let sym_x = intern("x");
6225 assert!(attrs.inner().contains_key(&sym_x));
6226 }
6227
6228 #[test]
6233 fn smallvec_context_empty() {
6234 let ctx = StringContext::new();
6235 assert!(ctx.is_empty());
6236 assert_eq!(ctx.len(), 0);
6237 assert_eq!(ctx.elements().len(), 0);
6238 }
6239
6240 #[test]
6241 fn smallvec_context_single_element_inline() {
6242 let mut ctx = StringContext::new();
6243 ctx.add_plain("/nix/store/single");
6244 assert_eq!(ctx.len(), 1);
6245 assert!(!ctx.is_empty());
6247 }
6248
6249 #[test]
6250 fn smallvec_context_two_elements_still_inline() {
6251 let mut ctx = StringContext::new();
6252 ctx.add_plain("/nix/store/one");
6253 ctx.add_output("/nix/store/two.drv", "out");
6254 assert_eq!(ctx.len(), 2);
6255 }
6256
6257 #[test]
6258 fn smallvec_context_three_plus_spills_to_heap() {
6259 let mut ctx = StringContext::new();
6260 ctx.add_plain("/nix/store/a");
6261 ctx.add_plain("/nix/store/b");
6262 ctx.add_drv_deep("/nix/store/c.drv");
6263 assert_eq!(ctx.len(), 3);
6264 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6266 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6267 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6268 }
6269
6270 #[test]
6271 fn smallvec_context_merge_deduplicates() {
6272 let mut ctx1 = StringContext::new();
6273 ctx1.add_plain("/nix/store/dup");
6274 ctx1.add_output("/nix/store/x.drv", "out");
6275 let mut ctx2 = StringContext::new();
6276 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6279 assert_eq!(ctx1.len(), 3); }
6281
6282 #[test]
6283 fn smallvec_context_add_plain_output_drv_deep() {
6284 let mut ctx = StringContext::new();
6285 ctx.add_plain("/nix/store/plain");
6286 assert_eq!(ctx.len(), 1);
6287 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6288
6289 ctx.add_output("/nix/store/out.drv", "lib");
6290 assert_eq!(ctx.len(), 2);
6291 assert!(ctx.elements().contains(&ContextElement::Output {
6292 drv: SmolStr::from("/nix/store/out.drv"),
6293 output: SmolStr::from("lib"),
6294 }));
6295
6296 ctx.add_drv_deep("/nix/store/deep.drv");
6297 assert_eq!(ctx.len(), 3);
6298 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6299 }
6300
6301 #[test]
6306 fn rc_list_constructor_wraps_in_rc() {
6307 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6308 match &v {
6309 Value::List(rc) => {
6310 assert_eq!(rc.len(), 2);
6311 assert_eq!(Rc::strong_count(rc), 1);
6312 }
6313 _ => panic!("expected List"),
6314 }
6315 }
6316
6317 #[test]
6318 fn rc_list_clone_is_refcount_bump() {
6319 let v = Value::list(vec![Value::Int(10)]);
6320 let rc1 = match &v {
6321 Value::List(rc) => rc.clone(),
6322 _ => panic!("expected List"),
6323 };
6324 let v2 = v.clone();
6325 let rc2 = match &v2 {
6326 Value::List(rc) => rc.clone(),
6327 _ => panic!("expected List"),
6328 };
6329 assert!(Rc::ptr_eq(&rc1, &rc2));
6331 assert!(Rc::strong_count(&rc1) >= 2);
6334 }
6335
6336 #[test]
6337 fn rc_list_as_list_returns_slice() {
6338 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6339 let slice = v.as_list().unwrap();
6340 assert_eq!(slice.len(), 3);
6341 assert_eq!(slice[0], Value::Int(1));
6342 assert_eq!(slice[1], Value::Int(2));
6343 assert_eq!(slice[2], Value::Int(3));
6344 }
6345
6346 #[test]
6347 fn rc_list_from_vec_wraps_in_rc() {
6348 let items = vec![Value::Bool(true), Value::Bool(false)];
6349 let v: Value = items.into();
6350 match &v {
6351 Value::List(rc) => {
6352 assert_eq!(rc.len(), 2);
6353 assert_eq!(Rc::strong_count(rc), 1);
6354 }
6355 _ => panic!("expected List"),
6356 }
6357 }
6358
6359 #[test]
6364 fn intern_same_string_returns_same_symbol() {
6365 let s1 = intern("hello_intern_test");
6366 let s2 = intern("hello_intern_test");
6367 assert_eq!(s1, s2);
6368 }
6369
6370 #[test]
6371 fn intern_different_strings_returns_different_symbols() {
6372 let s1 = intern("unique_str_a_9182");
6373 let s2 = intern("unique_str_b_9182");
6374 assert_ne!(s1, s2);
6375 }
6376
6377 #[test]
6378 fn resolve_roundtrips_correctly() {
6379 let sym = intern("roundtrip_test_str");
6380 let resolved = resolve(sym);
6381 assert_eq!(resolved, "roundtrip_test_str");
6382 }
6383
6384 #[test]
6385 fn intern_cached_same_offset_returns_cached_symbol() {
6386 let sid = next_source_id();
6387 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6388 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6389 assert_eq!(sym1, sym2);
6390 }
6391
6392 #[test]
6393 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6394 let sid = next_source_id();
6397 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6398 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6399 assert_eq!(sym1, sym2);
6401 }
6402
6403 #[test]
6404 fn clear_ident_cache_clears() {
6405 let sid = next_source_id();
6406 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6407 clear_ident_cache();
6408 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6412 let resolved = resolve(sym2);
6413 assert_eq!(resolved, "to_be_cleared_99");
6414 }
6415
6416 #[test]
6417 fn next_source_id_increments_monotonically() {
6418 let id1 = next_source_id();
6419 let id2 = next_source_id();
6420 let id3 = next_source_id();
6421 assert_eq!(id2, id1 + 1);
6422 assert_eq!(id3, id2 + 1);
6423 }
6424
6425 #[test]
6430 fn env_new_creates_empty_bindings() {
6431 let env = Env::new();
6432 assert!(env.0.bindings.is_empty());
6433 assert!(env.0.with_scopes.is_empty());
6434 assert!(env.eval_file().is_none());
6435 }
6436
6437 #[test]
6438 fn env_bind_lookup_roundtrip() {
6439 let mut env = Env::new();
6440 env.bind("foo".to_string(), Value::Int(42));
6441 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6442 assert_eq!(env.lookup("bar"), None);
6443 }
6444
6445 #[test]
6446 fn env_child_inherits_parent_bindings_flattened() {
6447 let mut parent = Env::new();
6448 parent.bind("a".to_string(), Value::Int(1));
6449 parent.bind("b".to_string(), Value::Int(2));
6450 let child = parent.child();
6451 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6453 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6454 let sym_a = intern("a");
6456 assert!(child.0.bindings.contains_key(&sym_a));
6457 }
6458
6459 #[test]
6460 fn env_child_inherits_with_scopes() {
6461 let mut attrs = NixAttrs::new();
6462 attrs.insert("ws".to_string(), Value::Int(10));
6463 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6464 let child = parent.child();
6465 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6467 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6468 }
6469
6470 #[test]
6471 fn env_lookup_sym_fast_path_matches_lookup() {
6472 let mut env = Env::new();
6473 env.bind("target".to_string(), Value::Int(88));
6474 let sym = intern("target");
6475 let via_lookup = env.lookup("target");
6476 let via_sym = env.lookup_sym(sym);
6477 assert_eq!(via_lookup, via_sym);
6478 assert_eq!(via_sym, Some(Value::Int(88)));
6479 }
6480
6481 #[test]
6482 fn env_lookup_sym_with_scope_fallback() {
6483 let mut attrs = NixAttrs::new();
6484 attrs.insert("sym_ws".to_string(), Value::Int(33));
6485 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6486 let sym = intern("sym_ws");
6487 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6488 }
6489
6490 #[test]
6491 fn env_with_scope_ordering_multiple_innermost_wins() {
6492 let mut a1 = NixAttrs::new();
6493 a1.insert("x".to_string(), Value::Int(1));
6494 let mut a2 = NixAttrs::new();
6495 a2.insert("x".to_string(), Value::Int(2));
6496 let mut a3 = NixAttrs::new();
6497 a3.insert("x".to_string(), Value::Int(3));
6498 let env = Env::new()
6499 .with_scope(Value::Attrs(Rc::new(a1)))
6500 .with_scope(Value::Attrs(Rc::new(a2)))
6501 .with_scope(Value::Attrs(Rc::new(a3)));
6502 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6504 }
6505
6506 #[test]
6507 fn env_lookup_sym_not_found_returns_none() {
6508 let env = Env::new();
6509 let sym = intern("nonexistent_sym_99");
6510 assert_eq!(env.lookup_sym(sym), None);
6511 }
6512
6513 #[test]
6514 fn env_lookup_sym_lexical_wins_over_with_scope() {
6515 let mut attrs = NixAttrs::new();
6516 attrs.insert("priority".to_string(), Value::Int(1));
6517 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6518 env.bind("priority".to_string(), Value::Int(99));
6519 let sym = intern("priority");
6520 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6521 }
6522}