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 }
172
173 pub fn spawn_poller() {
177 if !enabled() {
178 return;
179 }
180 std::thread::spawn(|| loop {
181 std::thread::sleep(std::time::Duration::from_millis(2000));
182 dump("periodic");
183 });
184 }
185}
186
187pub fn intern(s: &str) -> Symbol {
200 sui_intern::intern(s)
201}
202
203pub fn resolve(sym: Symbol) -> String {
208 sui_intern::resolve(sym)
209}
210
211pub fn resolve_rc(sym: Symbol) -> std::rc::Rc<str> {
213 sui_intern::resolve_rc(sym)
214}
215
216pub fn with_resolved<F, R>(sym: Symbol, f: F) -> R
218where
219 F: FnOnce(&str) -> R,
220{
221 sui_intern::with_resolved(sym, f)
222}
223
224thread_local! {
235 static SOURCE_GEN: Cell<u32> = const { Cell::new(1) };
245
246 static IDENT_CACHE: RefCell<rustc_hash::FxHashMap<u64, Symbol>> =
248 RefCell::new(rustc_hash::FxHashMap::default());
249}
250
251pub fn next_source_id() -> u32 {
257 SOURCE_GEN.with(|g| {
258 let id = g.get();
259 g.set(id.wrapping_add(1));
260 id
261 })
262}
263
264pub fn intern_cached(name: &str, source_id: u32, text_offset: u32) -> Symbol {
270 intern_cached_with(source_id, text_offset, || intern(name))
271}
272
273pub fn intern_cached_with<F>(source_id: u32, text_offset: u32, cold: F) -> Symbol
281where
282 F: FnOnce() -> Symbol,
283{
284 let key = (u64::from(source_id) << 32) | u64::from(text_offset);
285 IDENT_CACHE.with(|c| {
286 let mut cache = c.borrow_mut();
287 *cache.entry(key).or_insert_with(cold)
288 })
289}
290
291pub fn clear_ident_cache() {
296 IDENT_CACHE.with(|c| c.borrow_mut().clear());
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
303pub enum ContextElement {
304 Plain(SmolStr),
306 Output { drv: SmolStr, output: SmolStr },
308 DrvDeep(SmolStr),
310}
311
312impl fmt::Display for ContextElement {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 match self {
315 ContextElement::Plain(p) => write!(f, "{p}"),
316 ContextElement::Output { drv, output } => write!(f, "{drv}!{output}"),
317 ContextElement::DrvDeep(d) => write!(f, "={d}"),
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Default)]
330pub struct StringContext(SmallVec<[ContextElement; 2]>);
331
332impl StringContext {
333 pub fn new() -> Self {
335 Self(SmallVec::new())
336 }
337
338 pub fn merge(&mut self, other: &StringContext) {
340 for elem in &other.0 {
341 if !self.0.contains(elem) {
342 self.0.push(elem.clone());
343 }
344 }
345 }
346
347 pub fn add_plain(&mut self, path: impl Into<SmolStr>) {
349 let elem = ContextElement::Plain(path.into());
350 if !self.0.contains(&elem) {
351 self.0.push(elem);
352 }
353 }
354
355 pub fn add_output(&mut self, drv: impl Into<SmolStr>, output: impl Into<SmolStr>) {
357 let elem = ContextElement::Output { drv: drv.into(), output: output.into() };
358 if !self.0.contains(&elem) {
359 self.0.push(elem);
360 }
361 }
362
363 pub fn add_drv_deep(&mut self, drv: impl Into<SmolStr>) {
365 let elem = ContextElement::DrvDeep(drv.into());
366 if !self.0.contains(&elem) {
367 self.0.push(elem);
368 }
369 }
370
371 #[must_use]
373 pub fn is_empty(&self) -> bool {
374 self.0.is_empty()
375 }
376
377 #[must_use]
379 pub fn len(&self) -> usize {
380 self.0.len()
381 }
382
383 pub fn iter(&self) -> impl Iterator<Item = &ContextElement> {
385 self.0.iter()
386 }
387
388 pub fn insert(&mut self, elem: ContextElement) {
390 if !self.0.contains(&elem) {
391 self.0.push(elem);
392 }
393 }
394
395 pub fn elements(&self) -> &[ContextElement] {
397 &self.0
398 }
399}
400
401#[derive(Debug, PartialEq, Eq)]
403pub struct NixString {
404 pub chars: SmolStr,
406 pub context: StringContext,
408}
409
410impl Clone for NixString {
414 fn clone(&self) -> Self {
415 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
416 Self {
417 chars: self.chars.clone(),
418 context: self.context.clone(),
419 }
420 }
421}
422
423impl Drop for NixString {
424 fn drop(&mut self) {
425 census::dropped(&census::NIXSTR_LIVE);
426 }
427}
428
429impl NixString {
430 pub fn plain(s: impl Into<SmolStr>) -> Self {
432 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
433 Self {
434 chars: s.into(),
435 context: StringContext::default(),
436 }
437 }
438
439 pub fn with_context(s: impl Into<SmolStr>, ctx: StringContext) -> Self {
441 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
442 Self {
443 chars: s.into(),
444 context: ctx,
445 }
446 }
447
448 #[must_use]
450 pub fn as_str(&self) -> &str {
451 &self.chars
452 }
453
454 #[must_use]
456 pub fn has_context(&self) -> bool {
457 !self.context.is_empty()
458 }
459}
460
461impl AsRef<str> for NixString {
462 fn as_ref(&self) -> &str {
463 &self.chars
464 }
465}
466
467#[repr(transparent)]
474#[derive(Debug, PartialEq)]
475pub struct NixList(pub Vec<Value>);
476
477impl NixList {
478 #[inline]
479 pub fn new(v: Vec<Value>) -> Self {
480 census::made(&census::LIST_MADE, &census::LIST_LIVE);
481 NixList(v)
482 }
483
484 #[inline]
488 pub fn into_vec(mut self) -> Vec<Value> {
489 std::mem::take(&mut self.0)
490 }
491}
492
493impl From<Vec<Value>> for NixList {
494 #[inline]
495 fn from(v: Vec<Value>) -> Self {
496 NixList::new(v)
497 }
498}
499
500impl<T: AsRef<[Value]>> PartialEq<T> for NixList {
502 #[inline]
503 fn eq(&self, other: &T) -> bool {
504 self.0.as_slice() == other.as_ref()
505 }
506}
507
508impl Clone for NixList {
509 fn clone(&self) -> Self {
510 census::made(&census::LIST_MADE, &census::LIST_LIVE);
511 NixList(self.0.clone())
512 }
513}
514
515impl Drop for NixList {
516 fn drop(&mut self) {
517 census::dropped(&census::LIST_LIVE);
518 }
519}
520
521impl FromIterator<Value> for NixList {
522 #[inline]
523 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
524 NixList::new(iter.into_iter().collect())
525 }
526}
527
528impl std::ops::Deref for NixList {
529 type Target = Vec<Value>;
530 #[inline]
531 fn deref(&self) -> &Vec<Value> {
532 &self.0
533 }
534}
535
536impl std::ops::DerefMut for NixList {
537 #[inline]
538 fn deref_mut(&mut self) -> &mut Vec<Value> {
539 &mut self.0
540 }
541}
542
543impl<'a> IntoIterator for &'a NixList {
544 type Item = &'a Value;
545 type IntoIter = std::slice::Iter<'a, Value>;
546 #[inline]
547 fn into_iter(self) -> Self::IntoIter {
548 self.0.iter()
549 }
550}
551
552impl IntoIterator for NixList {
553 type Item = Value;
554 type IntoIter = std::vec::IntoIter<Value>;
555 #[inline]
556 fn into_iter(mut self) -> Self::IntoIter {
557 std::mem::take(&mut self.0).into_iter()
561 }
562}
563
564impl std::ops::Deref for NixString {
565 type Target = str;
566
567 fn deref(&self) -> &str {
568 &self.chars
569 }
570}
571
572impl fmt::Display for NixString {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 write!(f, "{}", self.chars)
575 }
576}
577
578#[derive(Debug, Clone)]
586#[derive(Default)]
587pub enum Value {
588 #[default]
589 Null,
590 Bool(bool),
591 Int(i64),
592 Float(f64),
593 String(Rc<NixString>),
594 Path(Box<SmolStr>),
595 List(Rc<NixList>),
596 Attrs(Rc<NixAttrs>),
597 Lambda(Rc<Closure>),
598 Builtin(Box<BuiltinFn>),
599 Thunk(Thunk),
601}
602
603#[derive(Debug, Clone)]
619pub enum Concrete {
620 Null,
621 Bool(bool),
622 Int(i64),
623 Float(f64),
624 String(Rc<NixString>),
625 Path(Box<SmolStr>),
626 List(Rc<NixList>), Attrs(Rc<NixAttrs>), Lambda(Rc<Closure>),
629 Builtin(Box<BuiltinFn>),
630 }
632
633impl Concrete {
634 #[inline]
636 pub fn into_value(self) -> Value {
637 match self {
638 Concrete::Null => Value::Null,
639 Concrete::Bool(b) => Value::Bool(b),
640 Concrete::Int(n) => Value::Int(n),
641 Concrete::Float(f) => Value::Float(f),
642 Concrete::String(s) => Value::String(s),
643 Concrete::Path(p) => Value::Path(p),
644 Concrete::List(l) => Value::List(l),
645 Concrete::Attrs(a) => Value::Attrs(a),
646 Concrete::Lambda(c) => Value::Lambda(c),
647 Concrete::Builtin(b) => Value::Builtin(b),
648 }
649 }
650
651 pub fn to_value(&self) -> Value {
654 self.clone().into_value()
655 }
656
657 pub fn as_bool(&self) -> Result<bool, EvalError> {
659 match self {
660 Concrete::Bool(b) => Ok(*b),
661 other => Err(EvalError::TypeMismatch { expected: "bool", got: other.type_name() }),
662 }
663 }
664
665 pub fn as_int(&self) -> Result<i64, EvalError> {
667 match self {
668 Concrete::Int(n) => Ok(*n),
669 other => Err(EvalError::TypeMismatch { expected: "int", got: other.type_name() }),
670 }
671 }
672
673 pub fn as_str(&self) -> Result<&str, EvalError> {
675 match self {
676 Concrete::String(s) => Ok(&s.chars),
677 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
678 }
679 }
680
681 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
683 match self {
684 Concrete::String(s) => Ok(s),
685 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
686 }
687 }
688
689 pub fn as_list(&self) -> Result<&[Value], EvalError> {
692 match self {
693 Concrete::List(l) => Ok(l.as_slice()),
694 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
695 }
696 }
697
698 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
701 match self {
702 Concrete::Attrs(a) => Ok(a),
703 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
704 }
705 }
706
707 pub fn as_float(&self) -> Result<f64, EvalError> {
709 match self {
710 Concrete::Float(f) => Ok(*f),
711 Concrete::Int(n) => Ok(*n as f64),
712 other => Err(EvalError::TypeMismatch { expected: "float", got: other.type_name() }),
713 }
714 }
715
716 pub fn type_name(&self) -> &'static str {
718 match self {
719 Concrete::Null => "null",
720 Concrete::Bool(_) => "bool",
721 Concrete::Int(_) => "int",
722 Concrete::Float(_) => "float",
723 Concrete::String(_) => "string",
724 Concrete::Path(_) => "path",
725 Concrete::List(_) => "list",
726 Concrete::Attrs(_) => "set",
727 Concrete::Lambda(_) | Concrete::Builtin(_) => "lambda",
728 }
729 }
730
731 pub fn as_string(&self) -> Result<&str, EvalError> {
733 self.as_str()
734 }
735
736 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
738 match self {
739 Concrete::Attrs(a) => Ok((**a).clone()),
740 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
741 }
742 }
743
744 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
746 match self {
747 Concrete::List(l) => Ok((**l).0.clone()),
748 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
749 }
750 }
751
752 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
754 match self {
755 Concrete::Path(p) => Ok(p.to_string()),
756 Concrete::String(ns) => Ok(ns.chars.to_string()),
757 Concrete::Attrs(attrs) => {
758 if let Some(out_path) = attrs.get("outPath") {
759 let forced = crate::eval::force_value(out_path)?;
760 forced.coerce_to_path(context)
761 } else {
762 Err(EvalError::type_error(format!(
763 "{context}: expected path or string, got set without outPath"
764 )))
765 }
766 }
767 other => Err(EvalError::type_error(format!(
768 "{context}: expected path or string, got {}", other.type_name()
769 ))),
770 }
771 }
772
773 pub fn to_str(&self) -> Result<String, EvalError> {
775 match self {
776 Concrete::String(s) => Ok(s.chars.to_string()),
777 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
778 }
779 }
780
781 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
783 match self {
784 Concrete::String(s) => Ok((**s).clone()),
785 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
786 }
787 }
788
789 pub fn is_function(&self) -> bool {
791 matches!(self, Concrete::Lambda(_) | Concrete::Builtin(_))
792 }
793}
794
795impl From<Concrete> for Value {
797 fn from(c: Concrete) -> Value {
798 c.into_value()
799 }
800}
801
802impl PartialEq for Concrete {
803 fn eq(&self, other: &Self) -> bool {
804 match (self, other) {
805 (Concrete::Null, Concrete::Null) => true,
806 (Concrete::Bool(a), Concrete::Bool(b)) => a == b,
807 (Concrete::Int(a), Concrete::Int(b)) => a == b,
808 (Concrete::Float(a), Concrete::Float(b)) => a == b,
809 (Concrete::Int(a), Concrete::Float(b)) | (Concrete::Float(b), Concrete::Int(a)) => (*a as f64) == *b,
810 (Concrete::String(a), Concrete::String(b)) => Rc::ptr_eq(a, b) || a.chars == b.chars,
811 (Concrete::Path(a), Concrete::Path(b)) => a == b,
812 (Concrete::List(a), Concrete::List(b)) => Rc::ptr_eq(a, b) || a == b,
813 (Concrete::Attrs(a), Concrete::Attrs(b)) => {
814 if Rc::ptr_eq(a, b) {
815 return true;
816 }
817 if let (Some(pa), Some(pb)) =
828 (derivation_out_path(a), derivation_out_path(b))
829 {
830 return pa == pb;
831 }
832 let (fa, fb) = (a.as_flat(), b.as_flat());
851 if crate::perf::enabled() {
852 crate::perf::inc(crate::perf::Counter::AttrsEqStructuralCalls);
853 crate::perf::add(
856 crate::perf::Counter::AttrsEqEntriesCloneElided,
857 (fa.len() + fb.len()) as u64,
858 );
859 }
860 fa == fb
861 }
862 (Concrete::Lambda(a), Concrete::Lambda(b)) => Rc::ptr_eq(a, b),
863 _ => false,
864 }
865 }
866}
867
868pub fn concat_lists(left: Value, right_elems: &[Value]) -> Result<Value, EvalError> {
884 let mut la = match left {
888 Value::List(rc) => {
889 let reused = Rc::strong_count(&rc) == 1;
890 let vec: Vec<Value> = match Rc::try_unwrap(rc) {
891 Ok(v) => v.into_vec(), Err(rc) => (*rc).0.clone(), };
894 if crate::perf::enabled() {
895 crate::perf::inc(crate::perf::Counter::ListConcatCalls);
896 if reused {
897 crate::perf::add(
899 crate::perf::Counter::ListConcatElemsReused,
900 vec.len() as u64,
901 );
902 } else {
903 crate::perf::add(
905 crate::perf::Counter::ListConcatElemsCopied,
906 vec.len() as u64,
907 );
908 }
909 }
910 vec
911 }
912 other => {
913 return Err(EvalError::TypeMismatch {
914 expected: "list",
915 got: other.type_name(),
916 });
917 }
918 };
919 la.extend_from_slice(right_elems);
921 Ok(Value::list(la))
922}
923
924fn derivation_out_path(attrs: &NixAttrs) -> Option<String> {
930 match attrs.get("type")?.demand().ok()? {
931 Concrete::String(s) if s.chars == "derivation" => {}
932 _ => return None,
933 }
934 match attrs.get("outPath")?.demand().ok()? {
935 Concrete::String(s) => Some(s.chars.to_string()),
936 _ => None,
937 }
938}
939
940fn derivation_drv_and_out(
954 attrs: &NixAttrs,
955) -> Result<Option<(String, String)>, EvalError> {
956 match attrs.get("type") {
958 Some(t) => match crate::eval::force_value(t)? {
959 Value::String(s) if s.chars == "derivation" => {}
960 _ => return Ok(None),
961 },
962 None => return Ok(None),
963 }
964 let drv_path = match attrs.get("drvPath") {
967 Some(d) => crate::eval::force_value(d)?.coerce_to_path("drvPath")?,
968 None => return Ok(None),
969 };
970 let out_path = match attrs.get("outPath") {
971 Some(o) => crate::eval::force_value(o)?.coerce_to_path("outPath")?,
972 None => return Ok(None),
973 };
974 Ok(Some((drv_path, out_path)))
975}
976
977fn out_path_needs_realize(out_path: &str, ctx: &StringContext) -> Option<String> {
991 if !out_path.starts_with("/nix/store/") {
993 return None;
994 }
995 for elem in ctx.iter() {
996 if let ContextElement::Output { drv, output } = elem {
997 let _ = output; return Some(drv.to_string());
1004 }
1005 }
1006 None
1007}
1008
1009impl Value {
1010 pub(crate) fn demand_unchecked(self) -> Concrete {
1013 match self {
1014 Value::Null => Concrete::Null,
1015 Value::Bool(b) => Concrete::Bool(b),
1016 Value::Int(n) => Concrete::Int(n),
1017 Value::Float(f) => Concrete::Float(f),
1018 Value::String(s) => Concrete::String(s),
1019 Value::Path(p) => Concrete::Path(p),
1020 Value::List(l) => Concrete::List(l),
1021 Value::Attrs(a) => Concrete::Attrs(a),
1022 Value::Lambda(c) => Concrete::Lambda(c),
1023 Value::Builtin(b) => Concrete::Builtin(b),
1024 Value::Thunk(_) => panic!("demand_unchecked called on Thunk"),
1025 }
1026 }
1027}
1028
1029impl Value {
1030 pub fn demand(&self) -> Result<Concrete, EvalError> {
1035 let v = match self {
1036 Value::Thunk(_) => crate::eval::force_value(self)?,
1037 other => other.clone(),
1038 };
1039 match v {
1041 Value::Null => Ok(Concrete::Null),
1042 Value::Bool(b) => Ok(Concrete::Bool(b)),
1043 Value::Int(n) => Ok(Concrete::Int(n)),
1044 Value::Float(f) => Ok(Concrete::Float(f)),
1045 Value::String(s) => Ok(Concrete::String(s)),
1046 Value::Path(p) => Ok(Concrete::Path(p)),
1047 Value::List(l) => Ok(Concrete::List(l)),
1048 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1049 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1050 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1051 Value::Thunk(_) => {
1052 let re_forced = crate::eval::force_value(&v)?;
1056 match re_forced {
1057 Value::Null => Ok(Concrete::Null),
1058 Value::Bool(b) => Ok(Concrete::Bool(b)),
1059 Value::Int(n) => Ok(Concrete::Int(n)),
1060 Value::Float(f) => Ok(Concrete::Float(f)),
1061 Value::String(s) => Ok(Concrete::String(s)),
1062 Value::Path(p) => Ok(Concrete::Path(p)),
1063 Value::List(l) => Ok(Concrete::List(l)),
1064 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1065 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1066 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1067 Value::Thunk(_) => Err(EvalError::InfiniteRecursion(
1068 "demand: thunk chain could not be resolved".to_string(),
1069 )),
1070 }
1071 }
1072 }
1073 }
1074}
1075
1076#[cfg(target_pointer_width = "64")]
1077const _: () = assert!(std::mem::size_of::<Value>() <= 16);
1078
1079const FIXPOINT_PROMOTE_NEST_CAP: u32 = 32;
1096
1097const PROMOTION_RUNAWAY_FORCE_DEPTH: usize = 500;
1107
1108thread_local! {
1109 pub(crate) static IN_PROMISE_EVAL: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
1116
1117 pub(crate) static PROMOTION_OCCURRED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1125}
1126
1127#[inline(always)]
1129pub fn promotion_occurred() -> bool {
1130 PROMOTION_OCCURRED.with(|c| c.get())
1131}
1132
1133#[inline(always)]
1137pub fn in_promise_eval() -> bool {
1138 IN_PROMISE_EVAL.with(|c| c.get() > 0)
1139}
1140
1141pub enum ThunkRepr {
1146 Suspended {
1148 expr: rnix::ast::Expr,
1149 env: Env,
1150 },
1151 InheritSelect {
1167 source_thunk: Thunk,
1168 name: SmolStr,
1169 },
1170 Native(Box<dyn FnOnce() -> Result<Value, EvalError>>),
1175 WithIdent {
1185 name: SmolStr,
1187 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1192 scope_value: Value,
1194 env: Env,
1197 },
1198 Blackhole,
1200 Promise(Rc<RefCell<Value>>),
1213 Failed(EvalError),
1224 Evaluated(Box<Value>),
1228 EvaluatedConcrete,
1239}
1240
1241struct ThunkInner {
1250 cache: OnceCell<Box<Concrete>>,
1254 repr: UnsafeCell<ThunkRepr>,
1256 recursive: bool,
1263}
1264
1265impl Drop for ThunkInner {
1266 fn drop(&mut self) {
1267 census::dropped(&census::THUNK_LIVE);
1268 }
1269}
1270
1271#[derive(Clone)]
1273pub struct Thunk(pub(crate) Rc<ThunkInner>);
1274
1275impl Thunk {
1276 pub fn new_suspended(expr: rnix::ast::Expr, env: Env) -> Self {
1278 crate::trace::inc_thunks_created();
1279 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1280 Self(Rc::new(ThunkInner {
1281 cache: OnceCell::new(),
1282 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1283 recursive: false,
1284 }))
1285 }
1286
1287 pub fn new_suspended_recursive(expr: rnix::ast::Expr, env: Env) -> Self {
1294 crate::trace::inc_thunks_created();
1295 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1296 crate::perf::inc(crate::perf::Counter::ThunkSiteLetForward);
1297 Self(Rc::new(ThunkInner {
1298 cache: OnceCell::new(),
1299 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1300 recursive: true,
1301 }))
1302 }
1303
1304 pub fn new_inherit_select(source_thunk: Thunk, name: impl Into<SmolStr>) -> Self {
1312 crate::trace::inc_thunks_created();
1313 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1314 crate::perf::inc(crate::perf::Counter::ThunkSiteInheritSrc);
1315 Self(Rc::new(ThunkInner {
1316 cache: OnceCell::new(),
1317 repr: UnsafeCell::new(ThunkRepr::InheritSelect {
1318 source_thunk,
1319 name: name.into(),
1320 }),
1321 recursive: false,
1322 }))
1323 }
1324
1325 pub fn new_with_ident(
1329 name: SmolStr,
1330 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1331 scope_value: Value,
1332 env: Env,
1333 ) -> Self {
1334 crate::trace::inc_thunks_created();
1335 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1336 crate::perf::inc(crate::perf::Counter::ThunkSiteOther);
1337 Self(Rc::new(ThunkInner {
1338 cache: OnceCell::new(),
1339 repr: UnsafeCell::new(ThunkRepr::WithIdent {
1340 name,
1341 scope_cache,
1342 scope_value,
1343 env,
1344 }),
1345 recursive: false,
1346 }))
1347 }
1348
1349 pub fn new_native(f: impl FnOnce() -> Result<Value, EvalError> + 'static) -> Self {
1353 crate::trace::inc_thunks_created();
1354 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1355 crate::perf::inc(crate::perf::Counter::ThunkSiteNative);
1356 Self(Rc::new(ThunkInner {
1357 cache: OnceCell::new(),
1358 repr: UnsafeCell::new(ThunkRepr::Native(Box::new(f))),
1359 recursive: false,
1360 }))
1361 }
1362
1363 pub fn new_evaluated(value: Value) -> Self {
1367 crate::trace::inc_thunks_created();
1368 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1369 crate::perf::inc(crate::perf::Counter::ThunkSiteEvaluated);
1370 let cache = OnceCell::new();
1371 let repr = if matches!(value, Value::Thunk(_)) {
1375 ThunkRepr::Evaluated(Box::new(value))
1376 } else {
1377 let _ = cache.set(Box::new(value.demand_unchecked()));
1378 ThunkRepr::EvaluatedConcrete
1379 };
1380 Self(Rc::new(ThunkInner {
1381 cache,
1382 repr: UnsafeCell::new(repr),
1383 recursive: false,
1384 }))
1385 }
1386
1387 pub fn is_evaluated(&self) -> bool {
1390 self.0.cache.get().is_some()
1391 }
1392
1393 pub fn is_native(&self) -> bool {
1399 matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Native(_))
1402 }
1403
1404 pub fn peek(&self) -> Option<&Concrete> {
1410 self.0.cache.get().map(|v| &**v)
1411 }
1412
1413 pub fn update_env(&self, new_env: &Env) {
1418 let repr = unsafe { &mut *self.0.repr.get() };
1421 match repr {
1422 ThunkRepr::Suspended { env, .. } => {
1423 *env = new_env.clone();
1424 }
1425 ThunkRepr::InheritSelect { source_thunk, .. } => {
1426 source_thunk.update_env(new_env);
1427 }
1428 _ => {}
1429 }
1430 }
1431
1432 #[inline]
1455 unsafe fn store_evaluated(&self, value: &Value) {
1456 census::evaluated();
1457 if matches!(value, Value::Thunk(_)) {
1458 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1459 } else {
1460 let _ = self.0.cache.set(Box::new(value.clone().demand_unchecked()));
1461 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1462 }
1463 }
1464
1465 #[inline]
1491 unsafe fn store_evaluated_owned(&self, value: Value) -> Value {
1492 census::evaluated();
1493 let concrete = value.demand_unchecked();
1494 let ret = concrete.clone().into_value();
1495 let _ = self.0.cache.set(Box::new(concrete));
1496 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1497 ret
1498 }
1499
1500 pub fn force(
1509 &self,
1510 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1511 ) -> Result<Value, EvalError> {
1512 if let Some(cached) = self.0.cache.get() {
1516 crate::perf::inc(crate::perf::Counter::ThunkHit);
1517 return Ok((**cached).clone().into_value());
1518 }
1519 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1521 self.force_inner(evaluator)
1522 })
1523 }
1524
1525 fn force_inner(
1528 &self,
1529 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1530 ) -> Result<Value, EvalError> {
1531 if let Some(cached) = self.0.cache.get() {
1540 crate::perf::inc(crate::perf::Counter::ThunkHit);
1541 return Ok((**cached).clone().into_value());
1542 }
1543
1544 let thunk_id = Rc::as_ptr(&self.0) as usize;
1545
1546 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1559 return Ok(cell.borrow().clone());
1560 }
1561
1562 let new_repr_on_force = if self.0.recursive {
1571 ThunkRepr::Promise(Rc::new(RefCell::new(
1572 Value::Attrs(Rc::new(NixAttrs::new())),
1573 )))
1574 } else {
1575 ThunkRepr::Blackhole
1576 };
1577 let is_promise = self.0.recursive;
1578 let repr = std::mem::replace(unsafe { &mut *self.0.repr.get() }, new_repr_on_force);
1579
1580 match repr {
1581 ThunkRepr::Suspended { expr, env } => {
1582 crate::perf::inc(crate::perf::Counter::ThunkForce);
1583 crate::trace::inc_thunks_forced_unique();
1584 let tracing = crate::trace::trace_enabled();
1585 let desc: String = if tracing {
1593 expr.syntax().text().to_string().chars().take(60).collect()
1594 } else {
1595 String::new()
1596 };
1597 crate::trace::push_force(crate::trace::ForceFrame {
1598 defined_in: env.eval_file().cloned(),
1599 description: desc.clone(),
1600 thunk_id,
1601 });
1602 if crate::value::promotion_occurred()
1628 && crate::trace::current_force_depth() as usize
1629 > PROMOTION_RUNAWAY_FORCE_DEPTH
1630 {
1631 crate::trace::pop_force();
1632 *unsafe { &mut *self.0.repr.get() } =
1633 ThunkRepr::Suspended { expr, env };
1634 return Err(EvalError::InfiniteRecursion(
1635 "overlay-fixpoint promotion runaway (force depth exceeded)".into(),
1636 ));
1637 }
1638 if tracing {
1639 crate::trace::trace_force_enter(
1640 env.eval_file().map(|p| p.as_path()),
1641 &desc,
1642 );
1643 if let Err(msg) = crate::trace::check_force_depth() {
1644 crate::trace::dump_trace_on_error();
1645 crate::trace::pop_force();
1646 crate::trace::trace_force_exit();
1647 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended {
1648 expr,
1649 env,
1650 };
1651 return Err(EvalError::InfiniteRecursion(msg));
1652 }
1653 }
1654 let _file_guard = env.eval_file().cloned().map(crate::eval::push_eval_file);
1660 let _srcid_guard = crate::eval::push_source_id(env.source_id());
1669 if is_promise {
1675 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
1676 }
1677 let result = evaluator(&expr, &env);
1678 if is_promise {
1679 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1680 }
1681 let became_promise = !is_promise
1691 && matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Promise(_));
1692 if became_promise {
1693 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1694 }
1695 match result {
1696 Ok(mut value) => {
1697 crate::perf::inc(crate::perf::Counter::ThunkStoreWrites);
1698 if is_promise || became_promise {
1705 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1706 *cell.borrow_mut() = value.clone();
1707 }
1708 }
1709 let was_thunk_before_loop = matches!(value, Value::Thunk(_));
1731 if !was_thunk_before_loop {
1732 crate::perf::inc(crate::perf::Counter::ThunkStoreRedundant);
1737 let ret = unsafe { self.store_evaluated_owned(value) };
1738 crate::trace::pop_force();
1739 if tracing { crate::trace::trace_force_exit(); }
1740 return Ok(ret);
1741 }
1742 unsafe { self.store_evaluated(&value) };
1744 while let Value::Thunk(ref inner) = value {
1749 match inner.peek() {
1750 Some(cached) => value = cached.clone().into_value(),
1751 None => break,
1752 }
1753 }
1754 if !matches!(value, Value::Thunk(_)) {
1755 crate::perf::inc(crate::perf::Counter::ThunkStoreLoopMutated);
1756 }
1757 unsafe { self.store_evaluated(&value) };
1758 crate::trace::pop_force();
1759 if tracing { crate::trace::trace_force_exit(); }
1760 Ok(value)
1761 }
1762 Err(e) => {
1763 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended { expr, env };
1764 if tracing { crate::trace::dump_trace_on_error(); }
1765 crate::trace::pop_force();
1766 if tracing { crate::trace::trace_force_exit(); }
1767 Err(e)
1768 }
1769 }
1770 }
1771 ThunkRepr::InheritSelect { source_thunk, name } => {
1772 let tracing = crate::trace::trace_enabled();
1773 let desc = if tracing { format!("inherit (..) {name}") } else { String::new() };
1774 crate::trace::push_force(crate::trace::ForceFrame {
1775 defined_in: None,
1776 description: desc.clone(),
1777 thunk_id,
1778 });
1779 if tracing {
1780 crate::trace::trace_force_enter(None, &desc);
1781 }
1782 crate::trace::inc_thunks_forced_unique();
1783 if tracing {
1784 if let Err(msg) = crate::trace::check_force_depth() {
1785 crate::trace::dump_trace_on_error();
1786 crate::trace::pop_force();
1787 crate::trace::trace_force_exit();
1788 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect {
1789 source_thunk,
1790 name,
1791 };
1792 return Err(EvalError::InfiniteRecursion(msg));
1793 }
1794 }
1795 let attempt = (|| -> Result<Value, EvalError> {
1796 let mut forced = source_thunk.force(evaluator)?;
1797 while let Value::Thunk(inner) = forced {
1798 forced = inner.force(evaluator)?;
1799 }
1800 let attrs = match &forced {
1801 Value::Attrs(a) => a,
1802 _ => {
1803 return Err(EvalError::TypeError(format!(
1804 "inherit (source) {name}: source is {}, not a set",
1805 forced.type_name()
1806 )))
1807 }
1808 };
1809 attrs
1810 .get(&name)
1811 .cloned()
1812 .ok_or_else(|| EvalError::AttrNotFound(name.to_string()))
1813 })();
1814 match attempt {
1815 Ok(mut value) => {
1816 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1817 while let Value::Thunk(ref inner) = value {
1818 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1819 }
1820 unsafe { self.store_evaluated(&value) };
1821 crate::trace::pop_force();
1822 if tracing { crate::trace::trace_force_exit(); }
1823 Ok(value)
1824 }
1825 Err(e) => {
1826 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect { source_thunk, name };
1827 if tracing { crate::trace::dump_trace_on_error(); }
1828 crate::trace::pop_force();
1829 if tracing { crate::trace::trace_force_exit(); }
1830 Err(e)
1831 }
1832 }
1833 }
1834 ThunkRepr::Native(f) => {
1835 let tracing = crate::trace::trace_enabled();
1836 crate::trace::push_force(crate::trace::ForceFrame {
1837 defined_in: None,
1838 description: if tracing { "<native-thunk>".into() } else { String::new() },
1839 thunk_id,
1840 });
1841 if tracing {
1842 crate::trace::trace_force_enter(None, "<native-thunk>");
1843 }
1844 crate::trace::inc_thunks_forced_unique();
1845 match f() {
1850 Ok(mut value) => {
1851 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1852 while let Value::Thunk(ref inner) = value {
1853 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1854 }
1855 unsafe { self.store_evaluated(&value) };
1856 crate::trace::pop_force();
1857 if tracing { crate::trace::trace_force_exit(); }
1858 Ok(value)
1859 }
1860 Err(e) => {
1861 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e.clone());
1875 if tracing { crate::trace::dump_trace_on_error(); }
1876 crate::trace::pop_force();
1877 if tracing { crate::trace::trace_force_exit(); }
1878 Err(e)
1879 }
1880 }
1881 }
1882 ThunkRepr::WithIdent { name, scope_cache, scope_value, env } => {
1883 crate::perf::inc(crate::perf::Counter::ThunkForce);
1884 crate::trace::inc_thunks_forced_unique();
1885 {
1890 let cache = scope_cache.borrow();
1891 if let Some(ref attrs) = *cache {
1892 if let Some(v) = attrs.get(&name) {
1893 let value = v.clone();
1894 unsafe { self.store_evaluated(&value) };
1895 return Ok(value);
1896 }
1897 }
1899 }
1900 if let Ok(forced) = crate::eval::force_value(&scope_value) {
1902 if let Value::Attrs(ref attrs) = forced {
1903 *scope_cache.borrow_mut() = Some((**attrs).clone());
1904 if let Some(v) = attrs.get(&name) {
1905 let value = v.clone();
1906 unsafe { self.store_evaluated(&value) };
1907 return Ok(value);
1908 }
1909 }
1910 }
1911 let result = match env.lookup(&name) {
1941 Some(v) => v,
1942 None => match env.lookup_fresh(&name) {
1943 Some(v) => v,
1944 None if in_promise_eval() => Value::Null,
1945 None => return Err(EvalError::UndefinedVar(format!("'{name}'"))),
1946 },
1947 };
1948 unsafe { self.store_evaluated(&result) };
1949 Ok(result)
1950 }
1951 ThunkRepr::Blackhole => {
1952 if std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some() {
1976 return Ok(Value::Null);
1977 }
1978 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_LIST").is_some() {
1979 return Ok(Value::List(Rc::new(NixList::new(Vec::new()))));
1980 }
1981 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some() {
1982 return Ok(Value::Attrs(Rc::new(NixAttrs::new())));
1983 }
1984 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
1985 let same = crate::trace::force_stack_contains(thunk_id);
1986 eprintln!(
1987 "[SUI_DEBUG_CYCLE] blackhole re-entry thunk_id={thunk_id:#x} same_thunk_on_stack={same} recursive_flag={}",
1988 self.0.recursive
1989 );
1990 crate::trace::dump_force_stack_ids();
1991 }
1992 if crate::trace::force_stack_contains(thunk_id)
2031 && IN_PROMISE_EVAL.with(|c| c.get()) < FIXPOINT_PROMOTE_NEST_CAP
2032 {
2033 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2034 let chain = crate::trace::capture_cycle(thunk_id);
2035 let nest = IN_PROMISE_EVAL.with(|c| c.get());
2036 let fdepth = crate::trace::current_force_depth();
2037 eprintln!("[SUI_PROMOTE] thunk_id={thunk_id:#x} cycle_len={} nest={nest} fdepth={fdepth}", chain.0.len());
2038 }
2039 let cell = Rc::new(RefCell::new(
2040 Value::Attrs(Rc::new(NixAttrs::new())),
2041 ));
2042 *unsafe { &mut *self.0.repr.get() } =
2045 ThunkRepr::Promise(cell.clone());
2046 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
2050 PROMOTION_OCCURRED.with(|c| c.set(true));
2052 return Ok(cell.borrow().clone());
2053 }
2054 let chain = crate::trace::capture_cycle(thunk_id);
2055 crate::trace::dump_trace_on_error();
2056 Err(EvalError::InfiniteRecursion(chain.to_string()))
2057 }
2058 ThunkRepr::Promise(cell) => {
2059 Ok(cell.borrow().clone())
2068 }
2069 ThunkRepr::Evaluated(v) => {
2070 crate::perf::inc(crate::perf::Counter::ThunkHit);
2074 let cloned = (*v).clone();
2075 if !matches!(cloned, Value::Thunk(_)) {
2076 if !matches!(cloned, Value::Thunk(_)) { let _ = self.0.cache.set(Box::new(cloned.clone().demand_unchecked())); }
2077 }
2078 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(v);
2079 Ok(cloned)
2080 }
2081 ThunkRepr::EvaluatedConcrete => {
2082 crate::perf::inc(crate::perf::Counter::ThunkHit);
2092 let value = self
2093 .0
2094 .cache
2095 .get()
2096 .expect("EvaluatedConcrete implies a populated cache")
2097 .as_ref()
2098 .clone()
2099 .into_value();
2100 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
2101 Ok(value)
2102 }
2103 ThunkRepr::Failed(e) => {
2104 let err = e.clone();
2109 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e);
2110 Err(err)
2111 }
2112 }
2113 }
2114}
2115
2116impl fmt::Debug for Thunk {
2117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2118 match unsafe { &*self.0.repr.get() } {
2120 ThunkRepr::Suspended { .. } => write!(f, "<thunk>"),
2121 ThunkRepr::InheritSelect { name, .. } => write!(f, "<inherit-select {name}>"),
2122 ThunkRepr::Native(_) => write!(f, "<native-thunk>"),
2123 ThunkRepr::WithIdent { name, .. } => write!(f, "<with-ident {name}>"),
2124 ThunkRepr::Blackhole => write!(f, "<blackhole>"),
2125 ThunkRepr::Promise(_) => write!(f, "<promise>"),
2126 ThunkRepr::Failed(e) => write!(f, "<failed-thunk: {e}>"),
2127 ThunkRepr::Evaluated(v) => write!(f, "{v:?}"),
2128 ThunkRepr::EvaluatedConcrete => match self.0.cache.get() {
2129 Some(c) => write!(f, "{:?}", c.as_ref().clone().into_value()),
2130 None => write!(f, "<evaluated-concrete>"),
2131 },
2132 }
2133 }
2134}
2135
2136pub struct NixAttrs(AttrsInner, Option<Rc<crate::pos::AttrPositions>>);
2150
2151impl Clone for NixAttrs {
2156 fn clone(&self) -> Self {
2157 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2158 NixAttrs(self.0.clone(), self.1.clone())
2159 }
2160}
2161
2162impl Drop for NixAttrs {
2163 fn drop(&mut self) {
2164 census::dropped(&census::ATTRS_LIVE);
2165 }
2166}
2167
2168#[derive(Clone)]
2170enum AttrsInner {
2171 Flat(AttrsMap<Symbol, Value>),
2173 Overlay {
2184 left: RefCell<Rc<NixAttrs>>,
2185 right: RefCell<Rc<NixAttrs>>,
2186 cache: Rc<OnceCell<AttrsMap<Symbol, Value>>>,
2187 },
2188}
2189
2190impl fmt::Debug for NixAttrs {
2191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2192 write!(f, "NixAttrs({})", self.len())
2193 }
2194}
2195
2196impl Default for NixAttrs {
2197 fn default() -> Self {
2198 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2199 Self(AttrsInner::Flat(AttrsMap::default()), None)
2200 }
2201}
2202
2203impl NixAttrs {
2204 pub fn new() -> Self {
2205 Self::default()
2206 }
2207
2208 pub fn with_capacity(_capacity: usize) -> Self {
2209 Self::default()
2210 }
2211
2212 pub fn set_positions(&mut self, pos: Rc<crate::pos::AttrPositions>) {
2216 self.1 = Some(pos);
2217 }
2218
2219 #[must_use]
2223 pub fn positions(&self) -> Option<&Rc<crate::pos::AttrPositions>> {
2224 self.1.as_ref()
2225 }
2226
2227 #[must_use]
2232 pub fn pos_for(&self, key: &str) -> Option<crate::pos::ResolvedPos> {
2233 let table = self.1.as_ref()?;
2234 let sym = intern(key);
2235 let offset = *table.keys.get(&sym)?;
2236 crate::pos::resolve(table.file.as_deref(), offset)
2237 }
2238
2239 #[must_use]
2241 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2242 self.as_flat().clone()
2243 }
2244
2245 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2247 match &self.0 {
2248 AttrsInner::Flat(m) => m,
2249 AttrsInner::Overlay { left, right, cache } => {
2250 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2251 let flat = cache.get_or_init(|| {
2252 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2255 let timed = crate::perf::enabled();
2256 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2257 let mut result = left.borrow().as_flat().clone();
2258 for (k, v) in right.borrow().as_flat().iter() {
2259 result.insert(*k, v.clone());
2260 }
2261 crate::perf::add(
2262 crate::perf::Counter::OverlayFlattenEntries,
2263 result.len() as u64,
2264 );
2265 if let Some(t0) = t0 {
2266 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2267 }
2268 result
2269 });
2270 {
2278 let mut l = left.borrow_mut();
2279 if !l.is_empty() { *l = Rc::new(NixAttrs::new()); }
2280 }
2281 {
2282 let mut r = right.borrow_mut();
2283 if !r.is_empty() { *r = Rc::new(NixAttrs::new()); }
2284 }
2285 flat
2286 }
2287 }
2288 }
2289
2290 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2291 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2292 let m = self.as_flat();
2293 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2294 let timed = crate::perf::enabled();
2295 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2296 let mut pairs: Vec<(String, &Value)> = m.iter()
2297 .map(|(sym, v)| (resolve(*sym), v))
2298 .collect();
2299 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2300 if let Some(t0) = t0 {
2301 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2302 }
2303 pairs
2304 }
2305
2306 #[must_use]
2308 pub fn get(&self, key: &str) -> Option<&Value> {
2309 let sym = intern(key);
2310 self.get_sym(&sym)
2311 }
2312
2313 #[must_use]
2327 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2328 match &self.0 {
2329 AttrsInner::Flat(m) => m.get(sym),
2330 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2336 }
2337 }
2338
2339 pub fn insert(&mut self, key: String, value: Value) {
2341 self.ensure_flat();
2342 if let AttrsInner::Flat(ref mut m) = self.0 {
2343 m.insert(intern(&key), value);
2344 }
2345 }
2346
2347 fn ensure_flat(&mut self) {
2349 if matches!(self.0, AttrsInner::Overlay { .. }) {
2350 self.0 = AttrsInner::Flat(self.as_flat().clone());
2351 }
2352 }
2353
2354 #[must_use]
2355 pub fn contains_key(&self, key: &str) -> bool {
2356 let sym = intern(key);
2357 self.contains_key_sym(&sym)
2358 }
2359
2360 #[must_use]
2361 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2362 match &self.0 {
2363 AttrsInner::Flat(m) => m.contains_key(sym),
2364 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2366 }
2367 }
2368
2369 pub fn keys(&self) -> impl Iterator<Item = String> {
2370 self.sorted_entries().into_iter().map(|(k, _)| k)
2371 }
2372
2373 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2374 self.sorted_entries().into_iter()
2375 }
2376
2377 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2378 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2379 }
2380
2381 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2399 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2400 }
2401
2402 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2405 self.ensure_flat();
2406 if let AttrsInner::Flat(ref mut m) = self.0 {
2407 m.insert(sym, value);
2408 }
2409 }
2410
2411 pub fn values(&self) -> impl Iterator<Item = &Value> {
2412 self.sorted_entries().into_iter().map(|(_, v)| v)
2413 }
2414
2415
2416 pub fn remove(&mut self, key: &str) -> Option<Value> {
2417 self.ensure_flat();
2418 if let AttrsInner::Flat(ref mut m) = self.0 {
2419 m.remove(&intern(key))
2420 } else {
2421 None
2422 }
2423 }
2424
2425 #[must_use]
2426 pub fn len(&self) -> usize {
2427 match &self.0 {
2428 AttrsInner::Flat(m) => m.len(),
2429 AttrsInner::Overlay { .. } => {
2430 self.as_flat().len()
2434 }
2435 }
2436 }
2437
2438 #[must_use]
2439 pub fn is_empty(&self) -> bool {
2440 match &self.0 {
2441 AttrsInner::Flat(m) => m.is_empty(),
2442 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2446 }
2447 }
2448
2449 #[must_use]
2451 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2452 if other.is_empty() { return self; }
2453 if self.is_empty() { return other; }
2454 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2455 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2456 NixAttrs(AttrsInner::Overlay {
2457 left: RefCell::new(Rc::new(self)),
2458 right: RefCell::new(Rc::new(other)),
2459 cache: Rc::new(OnceCell::new()),
2460 }, None)
2461 }
2462
2463 #[must_use]
2465 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2466 match (&self.0, &other.0) {
2467 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2468 let mut result = l.clone();
2469 for (k, v) in r.iter() {
2470 result.insert(*k, v.clone());
2471 }
2472 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2473 NixAttrs(AttrsInner::Flat(result), None)
2474 }
2475 _ => {
2476 let mut result = self.as_flat().clone();
2478 let other_flat = other.as_flat();
2479 for (k, v) in other_flat.iter() {
2480 result.insert(*k, v.clone());
2481 }
2482 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2483 NixAttrs(AttrsInner::Flat(result), None)
2484 }
2485 }
2486 }
2487}
2488
2489impl FromIterator<(String, Value)> for NixAttrs {
2490 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2491 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2492 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2493 }
2494}
2495
2496impl IntoIterator for NixAttrs {
2497 type Item = (String, Value);
2498 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2499
2500 fn into_iter(self) -> Self::IntoIter {
2501 let flat = self.as_flat().clone();
2502 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2503 }
2504}
2505
2506#[derive(Debug, Clone)]
2514pub struct Closure {
2515 pub param: rnix::ast::Param,
2516 pub body: rnix::ast::Expr,
2517 pub env: Env,
2518}
2519
2520pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2522
2523#[derive(Clone)]
2528pub struct BuiltinFn {
2529 pub name: &'static str,
2531 pub func: Rc<BuiltinFunc>,
2533}
2534
2535impl fmt::Debug for BuiltinFn {
2536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2537 write!(f, "<builtin {}>", self.name)
2538 }
2539}
2540
2541#[derive(Clone)]
2551struct WithScope {
2552 value: Value,
2553 cached: Rc<RefCell<Option<NixAttrs>>>,
2556}
2557
2558impl fmt::Debug for WithScope {
2559 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2560 f.debug_struct("WithScope")
2561 .field("value", &self.value)
2562 .field("cached", &self.cached.borrow().is_some())
2563 .finish()
2564 }
2565}
2566
2567#[derive(Debug, Clone, Default)]
2577struct EnvInner {
2578 bindings: FxHashMap<Symbol, Value>,
2579 with_scopes: Vec<WithScope>,
2581 eval_file: Option<std::path::PathBuf>,
2585 source_id: u32,
2592}
2593
2594#[derive(Clone, Default)]
2602pub struct Env(Rc<EnvInner>);
2603
2604impl fmt::Debug for Env {
2605 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2606 self.0.fmt(f)
2607 }
2608}
2609
2610impl Env {
2611 #[must_use]
2613 pub fn new() -> Self {
2614 Self(Rc::new(EnvInner {
2615 bindings: FxHashMap::default(),
2616 with_scopes: Vec::new(),
2617 eval_file: None,
2618 source_id: 0,
2619 }))
2620 }
2621
2622 #[must_use]
2627 pub fn child(&self) -> Self {
2628 crate::perf::inc(crate::perf::Counter::EnvClone);
2629 Self(Rc::new(EnvInner {
2630 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2632 eval_file: self.0.eval_file.clone(),
2636 source_id: self.0.source_id,
2640 }))
2641 }
2642
2643 #[must_use]
2651 pub fn with_scope(mut self, value: Value) -> Self {
2652 let pre_cached = match &value {
2654 Value::Attrs(attrs) => Some((**attrs).clone()),
2655 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2656 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2657 }),
2658 _ => None,
2659 };
2660 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2661 value,
2662 cached: Rc::new(RefCell::new(pre_cached)),
2663 });
2664 self
2665 }
2666
2667 pub fn bind(&mut self, name: String, value: Value) {
2672 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2673 }
2674
2675 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2685 let inner = Rc::make_mut(&mut self.0);
2686 for (name, value) in pairs {
2687 inner.bindings.insert(intern(&name), value);
2688 }
2689 }
2690
2691 #[must_use]
2693 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2694 self.0.eval_file.as_ref()
2695 }
2696
2697 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2699 Rc::make_mut(&mut self.0).eval_file = file;
2700 }
2701
2702 #[must_use]
2704 pub fn source_id(&self) -> u32 {
2705 self.0.source_id
2706 }
2707
2708 pub fn set_source_id(&mut self, id: u32) {
2711 Rc::make_mut(&mut self.0).source_id = id;
2712 }
2713
2714 #[must_use]
2716 pub fn binding_count(&self) -> usize {
2717 self.0.bindings.len()
2718 }
2719
2720 #[must_use]
2722 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2723 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2724 }
2725
2726 #[must_use]
2728 pub fn with_scope_count(&self) -> usize {
2729 self.0.with_scopes.len()
2730 }
2731
2732 #[must_use]
2736 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2737 let sym = intern(name);
2738 self.0.bindings.get(&sym).cloned()
2739 }
2740
2741 #[must_use]
2752 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
2753 self.0.bindings.get(&sym).cloned()
2754 }
2755
2756 #[must_use]
2760 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
2761 for scope in self.0.with_scopes.iter().rev() {
2762 let cache = scope.cached.borrow();
2763 if let Some(ref attrs) = *cache {
2764 if let Some(v) = attrs.get(name) {
2765 return Some(v.clone());
2766 }
2767 }
2768 drop(cache);
2770 if let Value::Thunk(ref thunk) = scope.value {
2771 if let Some(cached_val) = thunk.peek() {
2772 if let Concrete::Attrs(ref attrs) = *cached_val {
2773 *scope.cached.borrow_mut() = Some((**attrs).clone());
2775 if let Some(v) = attrs.get(name) {
2776 return Some(v.clone());
2777 }
2778 }
2779 }
2780 } else if let Value::Attrs(ref attrs) = scope.value {
2781 *scope.cached.borrow_mut() = Some((**attrs).clone());
2782 if let Some(v) = attrs.get(name) {
2783 return Some(v.clone());
2784 }
2785 }
2786 }
2787 None
2788 }
2789
2790 #[must_use]
2793 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
2794 self.0.with_scopes.last().map(|scope| {
2795 (scope.cached.clone(), scope.value.clone())
2796 })
2797 }
2798
2799 #[must_use]
2808 pub fn lookup(&self, name: &str) -> Option<Value> {
2809 self.lookup_fast(intern(name), name)
2810 }
2811
2812 #[must_use]
2824 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
2825 let sym = intern(name);
2826 if let Some(v) = self.0.bindings.get(&sym) {
2827 return Some(v.clone());
2828 }
2829 for scope in self.0.with_scopes.iter().rev() {
2830 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
2831 if let Some(v) = attrs.get_sym(&sym) {
2832 *scope.cached.borrow_mut() = Some((*attrs).clone());
2835 return Some(v.clone());
2836 }
2837 }
2838 }
2839 None
2840 }
2841
2842 #[must_use]
2844 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
2845 crate::perf::inc(crate::perf::Counter::EnvLookup);
2846 if let Some(v) = self.0.bindings.get(&sym) {
2847 return Some(v.clone());
2848 }
2849 for scope in self.0.with_scopes.iter().rev() {
2851 {
2853 let cache = scope.cached.borrow();
2854 if let Some(ref attrs) = *cache {
2855 if let Some(v) = attrs.get_sym(&sym) {
2856 return Some(v.clone());
2857 }
2858 continue;
2859 }
2860 }
2861 let resolved = match &scope.value {
2866 Value::Attrs(attrs) => {
2867 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2869 *scope.cached.borrow_mut() = Some((**attrs).clone());
2870 Some((**attrs).clone())
2871 }
2872 Value::Thunk(thunk) => {
2873 if let Some(cached_val) = thunk.peek() {
2876 if let Concrete::Attrs(ref attrs) = *cached_val {
2877 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2878 *scope.cached.borrow_mut() = Some((**attrs).clone());
2879 Some((**attrs).clone())
2880 } else {
2881 None
2882 }
2883 } else {
2884 match crate::eval::force_value(&scope.value) {
2895 Ok(forced) => {
2896 if let Value::Attrs(ref attrs) = forced {
2897 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2898 *scope.cached.borrow_mut() = Some((**attrs).clone());
2899 Some((**attrs).clone())
2900 } else {
2901 None
2902 }
2903 }
2904 Err(_) => None, }
2906 }
2907 }
2908 _ => {
2909 match crate::eval::force_value(&scope.value) {
2911 Ok(forced) => {
2912 if let Value::Attrs(ref attrs) = forced {
2913 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2914 *scope.cached.borrow_mut() = Some((**attrs).clone());
2915 Some((**attrs).clone())
2916 } else {
2917 None
2918 }
2919 }
2920 Err(_) => None,
2921 }
2922 }
2923 };
2924 if let Some(ref attrs) = resolved {
2925 if let Some(v) = attrs.get(name) {
2926 return Some(v.clone());
2927 }
2928 }
2929 }
2931 None
2932 }
2933
2934 #[must_use]
2940 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
2941 crate::perf::inc(crate::perf::Counter::EnvLookup);
2942 if let Some(v) = self.0.bindings.get(&sym) {
2944 return Some(v.clone());
2945 }
2946 for scope in self.0.with_scopes.iter().rev() {
2948 {
2950 let cache = scope.cached.borrow();
2951 if let Some(ref attrs) = *cache {
2952 if let Some(v) = attrs.get_sym(&sym) {
2953 return Some(v.clone());
2954 }
2955 continue;
2956 }
2957 }
2958 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
2960 if let Value::Attrs(ref attrs) = forced {
2961 let result = attrs.get_sym(&sym).cloned();
2962 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2963 *scope.cached.borrow_mut() = Some((**attrs).clone());
2964 if result.is_some() {
2965 return result;
2966 }
2967 }
2968 }
2969 }
2971 None
2972 }
2973}
2974
2975#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
2977#[non_exhaustive]
2978pub enum EvalError {
2979 #[error("undefined variable: {0}")]
2981 UndefinedVar(String),
2982 #[error("type error: {0}")]
2984 TypeError(String),
2985 #[error("attribute not found: {0}")]
2987 AttrNotFound(String),
2988 #[error("type error: expected {expected}, got {got}")]
2990 TypeMismatch {
2991 expected: &'static str,
2992 got: &'static str,
2993 },
2994 #[error("assertion failed{0}")]
2996 AssertionFailed(String),
2997 #[error("division by zero")]
2999 DivisionByZero,
3000 #[error("infinite recursion ({0})")]
3002 InfiniteRecursion(String),
3003 #[error("I/O error: {context}: {message}")]
3005 IoError { context: String, message: String },
3006 #[error("{0}")]
3008 Throw(String),
3009 #[error("{0}")]
3013 Abort(String),
3014 #[error("not yet implemented: {0}")]
3016 NotImplemented(String),
3017 #[error("parse error: {0}")]
3019 ParseError(String),
3020 #[error("recursion limit: {0}")]
3022 RecursionLimit(String),
3023}
3024
3025impl EvalError {
3026 #[must_use]
3028 pub fn type_error(msg: impl Into<String>) -> Self {
3029 EvalError::TypeError(msg.into())
3030 }
3031
3032 #[must_use]
3034 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3035 EvalError::TypeMismatch { expected, got }
3036 }
3037
3038 #[must_use]
3040 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3041 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3042 }
3043
3044 #[must_use]
3065 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3066 EvalError::TypeError(format!(
3067 "cannot {op} {lhs} and {rhs}{}",
3068 crate::eval::eval_file_ctx()
3069 ))
3070 }
3071
3072 #[must_use]
3074 pub fn is_throw(&self) -> bool {
3075 matches!(self, EvalError::Throw(_))
3076 }
3077
3078 #[must_use]
3080 pub fn is_infinite_recursion(&self) -> bool {
3081 matches!(self, EvalError::InfiniteRecursion(_))
3082 }
3083}
3084
3085impl Value {
3086 #[must_use]
3088 pub fn string(s: impl Into<SmolStr>) -> Self {
3089 Value::String(Rc::new(NixString::plain(s)))
3090 }
3091
3092 #[must_use]
3095 pub fn list(items: Vec<Value>) -> Self {
3096 Value::List(Rc::new(NixList::new(items)))
3097 }
3098
3099 #[must_use]
3102 pub fn is_uniquely_owned_list(&self) -> bool {
3103 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3104 }
3105
3106 #[must_use]
3108 pub fn to_json(&self) -> serde_json::Value {
3109 match self {
3110 Value::Null => serde_json::Value::Null,
3111 Value::Bool(b) => serde_json::Value::Bool(*b),
3112 Value::Int(n) => serde_json::json!(n),
3113 Value::Float(f) => serde_json::json!(f),
3114 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3115 Value::Path(p) => serde_json::Value::String(p.to_string()),
3116 Value::List(items) => {
3117 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3118 }
3119 Value::Attrs(attrs) => {
3120 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3127 if let Ok((s, _ctx)) = self.coerce_to_string() {
3128 return serde_json::Value::String(s);
3129 }
3130 }
3131 let map: serde_json::Map<String, serde_json::Value> = attrs
3132 .iter()
3133 .map(|(k, v)| (k.clone(), v.to_json()))
3134 .collect();
3135 serde_json::Value::Object(map)
3136 }
3137 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3138 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3139 Value::Thunk(thunk) => {
3140 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3142 Ok(v) => v.to_json(),
3143 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3144 }
3145 }
3146 }
3147 }
3148
3149 pub fn to_json_with_context(
3156 &self,
3157 ctx: &mut StringContext,
3158 ) -> Result<serde_json::Value, EvalError> {
3159 Ok(match self {
3160 Value::Null => serde_json::Value::Null,
3161 Value::Bool(b) => serde_json::Value::Bool(*b),
3162 Value::Int(n) => serde_json::json!(n),
3163 Value::Float(f) => serde_json::json!(f),
3164 Value::String(s) => {
3165 ctx.merge(&s.context);
3166 serde_json::Value::String(s.chars.to_string())
3167 }
3168 Value::Path(_) => {
3169 let (str, c) = self.coerce_to_string_copy_to_store()?;
3170 ctx.merge(&c);
3171 serde_json::Value::String(str)
3172 }
3173 Value::List(items) => {
3174 let mut arr = Vec::with_capacity(items.len());
3175 for v in items.iter() {
3176 let fv = crate::eval::force_value(v)?;
3177 arr.push(fv.to_json_with_context(ctx)?);
3178 }
3179 serde_json::Value::Array(arr)
3180 }
3181 Value::Attrs(attrs) => {
3182 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3185 let (s, c) = self.coerce_to_string_copy_to_store()?;
3186 ctx.merge(&c);
3187 return Ok(serde_json::Value::String(s));
3188 }
3189 let mut map = serde_json::Map::new();
3190 for (k, v) in attrs.iter() {
3191 let fv = crate::eval::force_value(v)?;
3192 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3193 }
3194 serde_json::Value::Object(map)
3195 }
3196 Value::Thunk(_) => {
3197 let forced = crate::eval::force_value(self)?;
3198 forced.to_json_with_context(ctx)?
3199 }
3200 other => {
3201 return Err(EvalError::TypeError(format!(
3202 "cannot serialize {} to JSON (__structuredAttrs)",
3203 other.type_name()
3204 )));
3205 }
3206 })
3207 }
3208
3209 #[must_use]
3211 pub fn type_name(&self) -> &'static str {
3212 match self {
3213 Value::Null => "null",
3214 Value::Bool(_) => "bool",
3215 Value::Int(_) => "int",
3216 Value::Float(_) => "float",
3217 Value::String(_) => "string",
3218 Value::Path(_) => "path",
3219 Value::List(_) => "list",
3220 Value::Attrs(_) => "set",
3221 Value::Lambda(_) => "lambda",
3222 Value::Builtin(_) => "lambda",
3223 Value::Thunk(thunk) => {
3224 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3226 Ok(v) => v.type_name(),
3227 Err(_) => "thunk",
3228 }
3229 }
3230 }
3231 }
3232
3233 pub fn as_bool(&self) -> Result<bool, EvalError> {
3254 match self {
3255 Value::Bool(b) => Ok(*b),
3256 Value::Thunk(thunk) => {
3257 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3258 }
3259 _ if in_promise_eval() => Ok(false),
3263 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3264 }
3265 }
3266
3267 pub fn as_int(&self) -> Result<i64, EvalError> {
3269 match self {
3270 Value::Int(n) => Ok(*n),
3271 Value::Thunk(thunk) => {
3272 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3273 }
3274 _ if in_promise_eval() => Ok(0),
3277 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3278 }
3279 }
3280
3281 pub fn as_string(&self) -> Result<&str, EvalError> {
3283 match self {
3284 Value::String(s) => Ok(&s.chars),
3285 Value::Thunk(_) => Err(EvalError::TypeError(
3286 "thunk in as_string: force first via force_value()".into(),
3287 )),
3288 _ if in_promise_eval() => Ok(""),
3289 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3290 }
3291 }
3292
3293 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3295 match self {
3296 Value::String(ns) => Ok(ns),
3297 Value::Thunk(_) => Err(EvalError::TypeError(
3298 "thunk in as_nix_string: force first via force_value()".into(),
3299 )),
3300 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3301 }
3302 }
3303
3304 pub fn to_str(&self) -> Result<String, EvalError> {
3308 match self {
3309 Value::String(s) => Ok(s.chars.to_string()),
3310 Value::Thunk(thunk) => {
3311 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3312 forced.to_str()
3313 }
3314 _ if in_promise_eval() => Ok(String::new()),
3315 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3316 }
3317 }
3318
3319 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3322 match self {
3323 Value::String(s) => Ok((**s).clone()),
3324 Value::Thunk(thunk) => {
3325 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3326 forced.to_nix_string()
3327 }
3328 _ if in_promise_eval() => Ok(NixString::plain("")),
3329 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3330 }
3331 }
3332
3333 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3342 match self {
3343 Value::Attrs(a) => Ok(a),
3344 Value::Thunk(_) => Err(EvalError::TypeError(
3345 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3346 )),
3347 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3348 }
3349 }
3350
3351 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3353 match self {
3354 Value::List(l) => Ok(l.as_slice()),
3355 Value::Thunk(_) => Err(EvalError::TypeError(
3356 "thunk in as_list: force first via force_value()".into(),
3357 )),
3358 _ => Err(crate::eval::attach_trace(
3359 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3360 )),
3361 }
3362 }
3363
3364 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3366 match self {
3367 Value::Attrs(a) => Ok((**a).clone()),
3368 Value::Thunk(thunk) => {
3369 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3370 forced.to_attrs()
3371 }
3372 _ if in_promise_eval() => Ok(NixAttrs::new()),
3378 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3379 }
3380 }
3381
3382 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3384 match self {
3385 Value::List(l) => Ok((**l).0.clone()),
3386 Value::Thunk(thunk) => {
3387 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3388 forced.to_list()
3389 }
3390 _ if in_promise_eval() => Ok(Vec::new()),
3393 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3394 }
3395 }
3396
3397 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3403 match self {
3404 Value::Path(p) => Ok(p.to_string()),
3405 Value::String(ns) => Ok(ns.chars.to_string()),
3406 Value::Attrs(attrs) => {
3407 if let Some(out_path) = attrs.get("outPath") {
3408 let forced = crate::eval::force_value(out_path)?;
3409 forced.coerce_to_path(context)
3410 } else {
3411 Err(EvalError::TypeError(format!(
3412 "{context}: expected path or string, got set without outPath"
3413 )))
3414 }
3415 }
3416 _ => Err(EvalError::TypeError(format!(
3417 "{context}: expected path or string, got {}",
3418 self.type_name()
3419 ))),
3420 }
3421 }
3422
3423 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3447 match self {
3448 Value::Attrs(attrs) => {
3451 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3452 self.realize_if_absent(&drv_path, &out_path, context)?;
3453 return Ok(out_path);
3454 }
3455 }
3456 Value::String(ns) => {
3463 let out_path = ns.chars.to_string();
3464 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3465 self.realize_if_absent(&drv_path, &out_path, context)?;
3466 }
3467 return Ok(out_path);
3468 }
3469 _ => {}
3470 }
3471 self.coerce_to_path(context)
3472 }
3473
3474 fn realize_if_absent(
3479 &self,
3480 drv_path: &str,
3481 out_path: &str,
3482 context: &str,
3483 ) -> Result<(), EvalError> {
3484 let read_path = crate::path::materialize_str(out_path);
3487 if std::path::Path::new(&read_path).exists() {
3488 return Ok(());
3489 }
3490 match crate::realize::realize_output(drv_path, out_path) {
3491 Ok(true) | Ok(false) => Ok(()),
3492 Err(msg) => Err(EvalError::IoError {
3493 context: context.to_string(),
3494 message: format!(
3495 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3496 ),
3497 }),
3498 }
3499 }
3500
3501 pub fn to_float(&self) -> Result<f64, EvalError> {
3503 match self {
3504 Value::Float(f) => Ok(*f),
3505 Value::Int(n) => Ok(*n as f64),
3506 Value::Thunk(thunk) => {
3507 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3508 }
3509 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3510 }
3511 }
3512
3513 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3531 self.coerce_to_string_impl(false)
3532 }
3533
3534 pub fn coerce_to_string_copy_to_store(
3544 &self,
3545 ) -> Result<(String, StringContext), EvalError> {
3546 self.coerce_to_string_impl(true)
3547 }
3548
3549 fn coerce_to_string_impl(
3550 &self,
3551 copy_to_store: bool,
3552 ) -> Result<(String, StringContext), EvalError> {
3553 let mut ctx = StringContext::new();
3554 let s = match self {
3555 Value::String(ns) => {
3556 ctx.merge(&ns.context);
3557 ns.chars.to_string()
3558 }
3559 Value::Path(p) => {
3560 let raw: &str = &**p;
3561 if copy_to_store {
3562 let pb = std::path::Path::new(raw);
3582 let abs = if pb.is_absolute() {
3583 pb.to_path_buf()
3584 } else if let Some(dir) = crate::eval::current_eval_dir() {
3585 dir.join(pb)
3586 } else {
3587 std::env::current_dir()
3588 .map_err(|e| EvalError::IoError {
3589 context: format!("copy-to-store coercion of {raw}"),
3590 message: e.to_string(),
3591 })?
3592 .join(pb)
3593 };
3594 let read_abs = crate::path::materialize(&abs);
3601 let canon = read_abs.canonicalize().map_err(|_| {
3602 EvalError::TypeError(format!(
3603 "path '{}' does not exist",
3604 abs.display()
3605 ))
3606 })?;
3607 let name = crate::path::source_name_for_read_dir(&canon)
3620 .or_else(|| {
3621 canon
3622 .file_name()
3623 .map(|n| n.to_string_lossy().into_owned())
3624 })
3625 .unwrap_or_else(|| "source".to_string());
3626 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3627 .map_err(|e| {
3628 EvalError::TypeError(format!(
3629 "copy-to-store coercion of '{}': {e}",
3630 canon.display()
3631 ))
3632 })?;
3633 ctx.add_plain(src.store_path.clone());
3634 src.store_path
3635 } else {
3636 ctx.add_plain(raw.to_string());
3637 raw.to_string()
3638 }
3639 }
3640 Value::Int(n) => n.to_string(),
3641 Value::Float(f) => format!("{f:.6}"),
3647 Value::Bool(true) => "1".to_string(),
3648 Value::Bool(false) => String::new(),
3649 Value::Null => String::new(),
3650 Value::Attrs(attrs) => {
3651 if let Some(to_str) = attrs.get("__toString") {
3652 let result =
3653 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3654 let forced = crate::eval::force_value(&result)?;
3655 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3656 ctx.merge(&c);
3657 s
3658 } else if let Some(out_path) = attrs.get("outPath") {
3659 let forced = crate::eval::force_value(out_path)?;
3660 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3661 ctx.merge(&c);
3662 s
3663 } else {
3664 return Err(EvalError::TypeError(
3665 "cannot coerce set to string (no __toString or outPath)".into(),
3666 ));
3667 }
3668 }
3669 Value::List(items) => {
3670 let mut parts = Vec::new();
3671 for item in items.iter() {
3672 let forced = crate::eval::force_value(item)?;
3673 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3674 ctx.merge(&c);
3675 parts.push(s);
3676 }
3677 parts.join(" ")
3678 }
3679 Value::Thunk(_) => {
3680 let forced = crate::eval::force_value(self)?;
3682 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3683 ctx.merge(&c);
3684 s
3685 }
3686 other => {
3687 return Err(EvalError::TypeError(format!(
3688 "cannot coerce {} to string",
3689 other.type_name()
3690 )));
3691 }
3692 };
3693 Ok((s, ctx))
3694 }
3695}
3696
3697impl From<&serde_json::Value> for Value {
3700 fn from(json: &serde_json::Value) -> Self {
3701 match json {
3702 serde_json::Value::Null => Value::Null,
3703 serde_json::Value::Bool(b) => Value::Bool(*b),
3704 serde_json::Value::Number(n) => {
3705 if let Some(i) = n.as_i64() {
3706 Value::Int(i)
3707 } else {
3708 Value::Float(n.as_f64().unwrap_or(0.0))
3709 }
3710 }
3711 serde_json::Value::String(s) => Value::string(s.clone()),
3712 serde_json::Value::Array(arr) => {
3713 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3714 }
3715 serde_json::Value::Object(obj) => {
3716 let mut attrs = NixAttrs::new();
3717 for (k, v) in obj {
3718 attrs.insert(k.clone(), Value::from(v));
3719 }
3720 Value::Attrs(Rc::new(attrs))
3721 }
3722 }
3723 }
3724}
3725
3726impl From<&toml::Value> for Value {
3727 fn from(v: &toml::Value) -> Self {
3728 match v {
3729 toml::Value::String(s) => Value::string(s.clone()),
3730 toml::Value::Integer(n) => Value::Int(*n),
3731 toml::Value::Float(f) => Value::Float(*f),
3732 toml::Value::Boolean(b) => Value::Bool(*b),
3733 toml::Value::Array(arr) => {
3734 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3735 }
3736 toml::Value::Table(t) => {
3737 let mut attrs = NixAttrs::new();
3738 for (k, val) in t {
3739 attrs.insert(k.clone(), Value::from(val));
3740 }
3741 Value::Attrs(Rc::new(attrs))
3742 }
3743 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
3744 }
3745 }
3746}
3747
3748
3749impl From<bool> for Value {
3752 fn from(b: bool) -> Self {
3753 Value::Bool(b)
3754 }
3755}
3756
3757impl From<i64> for Value {
3758 fn from(n: i64) -> Self {
3759 Value::Int(n)
3760 }
3761}
3762
3763impl From<f64> for Value {
3764 fn from(f: f64) -> Self {
3765 Value::Float(f)
3766 }
3767}
3768
3769impl From<NixString> for Value {
3770 fn from(s: NixString) -> Self {
3771 Value::String(Rc::new(s))
3772 }
3773}
3774
3775impl From<NixAttrs> for Value {
3776 fn from(attrs: NixAttrs) -> Self {
3777 Value::Attrs(Rc::new(attrs))
3778 }
3779}
3780
3781impl From<Vec<Value>> for Value {
3782 fn from(list: Vec<Value>) -> Self {
3783 Value::List(Rc::new(NixList::new(list)))
3784 }
3785}
3786
3787impl PartialEq for Value {
3788 fn eq(&self, other: &Self) -> bool {
3789 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
3791 if Rc::ptr_eq(&a.0, &b.0) { return true; }
3792 }
3793 let l = self.demand().unwrap_or(Concrete::Null);
3796 let r = other.demand().unwrap_or(Concrete::Null);
3797 l == r
3798 }
3799}
3800
3801impl fmt::Display for Value {
3802 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3803 match self {
3804 Value::Null => write!(f, "null"),
3805 Value::Bool(b) => write!(f, "{b}"),
3806 Value::Int(n) => write!(f, "{n}"),
3807 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
3808 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
3809 Value::Path(p) => write!(f, "{p}"),
3810 Value::List(items) => {
3811 write!(f, "[ ")?;
3812 for item in items.iter() {
3813 write!(f, "{item} ")?;
3814 }
3815 write!(f, "]")
3816 }
3817 Value::Attrs(attrs) => {
3818 write!(f, "{{ ")?;
3819 for (k, v) in attrs.iter() {
3820 write!(f, "{k} = {v}; ")?;
3821 }
3822 write!(f, "}}")
3823 }
3824 Value::Lambda(_) => write!(f, "<<lambda>>"),
3825 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
3826 Value::Thunk(thunk) => {
3827 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
3828 Ok(v) => write!(f, "{v}"),
3829 Err(_) => write!(f, "<<thunk:error>>"),
3830 }
3831 }
3832 }
3833 }
3834}
3835
3836#[cfg(test)]
3837mod tests {
3838 use super::*;
3839 use std::rc::Rc;
3840
3841 #[test]
3844 fn value_is_16_bytes() {
3845 assert_eq!(std::mem::size_of::<Value>(), 16);
3846 }
3847
3848 #[test]
3851 fn to_json_null() {
3852 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
3853 }
3854
3855 #[test]
3856 fn to_json_bool() {
3857 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
3858 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
3859 }
3860
3861 #[test]
3862 fn to_json_int() {
3863 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
3864 }
3865
3866 #[test]
3867 fn to_json_float() {
3868 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
3869 }
3870
3871 #[test]
3872 fn to_json_string() {
3873 assert_eq!(
3874 Value::string("hello").to_json(),
3875 serde_json::Value::String("hello".to_string()),
3876 );
3877 }
3878
3879 #[test]
3880 fn to_json_path() {
3881 assert_eq!(
3882 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
3883 serde_json::Value::String("/nix/store".to_string()),
3884 );
3885 }
3886
3887 #[test]
3888 fn to_json_list() {
3889 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
3890 assert_eq!(v.to_json(), serde_json::json!([1, true]));
3891 }
3892
3893 #[test]
3894 fn to_json_attrs() {
3895 let mut attrs = NixAttrs::new();
3896 attrs.insert("a".to_string(), Value::Int(1));
3897 let v = Value::Attrs(Rc::new(attrs));
3898 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
3899 }
3900
3901 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
3904 let mut a = NixAttrs::new();
3905 a.insert("type".to_string(), Value::string("derivation"));
3906 a.insert("outPath".to_string(), Value::string(out_path));
3907 a.insert(extra_key.to_string(), Value::Int(extra_val));
3908 Value::Attrs(Rc::new(a))
3909 }
3910
3911 #[test]
3912 fn derivations_same_outpath_differing_attrs_are_equal() {
3913 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
3920 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
3921 assert!(a == b, "same-outPath derivations must compare equal");
3922 assert!(!(a != b));
3923 }
3924
3925 #[test]
3926 fn derivations_differing_outpath_are_unequal() {
3927 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
3928 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
3929 assert!(a != b, "different-outPath derivations must compare unequal");
3930 }
3931
3932 #[test]
3933 fn non_derivation_attrs_with_outpath_use_structural_eq() {
3934 let mut a = NixAttrs::new();
3937 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
3938 a.insert("foo".to_string(), Value::Int(1));
3939 let mut b = NixAttrs::new();
3940 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
3941 b.insert("foo".to_string(), Value::Int(2));
3942 assert!(
3943 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
3944 "non-derivation attrs with equal outPath but differing foo must be unequal",
3945 );
3946 }
3947
3948 #[test]
3954 fn attrs_eq_borrow_result_matches_multi_key() {
3955 let mk = || {
3958 let mut inner = NixAttrs::new();
3959 inner.insert("n".to_string(), Value::Int(7));
3960 let mut a = NixAttrs::new();
3961 a.insert("a".to_string(), Value::Int(1));
3962 a.insert("b".to_string(), Value::string("two"));
3963 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
3964 Value::Attrs(Rc::new(a))
3965 };
3966 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
3967
3968 let mut b = NixAttrs::new();
3970 b.insert("a".to_string(), Value::Int(1));
3971 b.insert("b".to_string(), Value::string("TWO"));
3972 let mut a2 = NixAttrs::new();
3973 a2.insert("a".to_string(), Value::Int(1));
3974 a2.insert("b".to_string(), Value::string("two"));
3975 assert!(
3976 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
3977 "attrsets differing in one value must be unequal (borrow path)",
3978 );
3979
3980 let mut a3 = NixAttrs::new();
3982 a3.insert("a".to_string(), Value::Int(1));
3983 let mut b3 = NixAttrs::new();
3984 b3.insert("a".to_string(), Value::Int(1));
3985 b3.insert("extra".to_string(), Value::Int(9));
3986 assert!(
3987 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
3988 "attrsets differing in key set must be unequal (borrow path)",
3989 );
3990 }
3991
3992 #[test]
3993 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
3994 let boom = Value::Thunk(Thunk::new_native(|| {
4005 Err(EvalError::Throw("kaboom".to_string()))
4006 }));
4007 let mut a = NixAttrs::new();
4008 a.insert("x".to_string(), Value::Int(1));
4009 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4011 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4013 let va = Value::Attrs(Rc::new(a));
4017 let vb = Value::Attrs(Rc::new(b));
4018 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4019 }
4020
4021 #[test]
4022 fn attrs_eq_borrow_overlay_still_compares() {
4023 let mut base = NixAttrs::new();
4027 base.insert("a".to_string(), Value::Int(1));
4028 let mut over = NixAttrs::new();
4029 over.insert("b".to_string(), Value::Int(2));
4030 let merged = base.overlay(over);
4033 let mut flat = NixAttrs::new();
4034 flat.insert("a".to_string(), Value::Int(1));
4035 flat.insert("b".to_string(), Value::Int(2));
4036 assert!(
4037 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4038 "overlay and equivalent flat attrset must compare equal (borrow path)",
4039 );
4040 }
4041
4042 #[test]
4043 fn to_json_lambda() {
4044 let root = rnix::Root::parse("x: x");
4046 let expr = root.tree().expr().unwrap();
4047 let lambda = match expr {
4048 rnix::ast::Expr::Lambda(l) => l,
4049 _ => panic!("expected lambda"),
4050 };
4051 let closure = Closure {
4052 param: lambda.param().unwrap(),
4053 body: lambda.body().unwrap(),
4054 env: Env::new(),
4055 };
4056 assert_eq!(
4057 Value::Lambda(Rc::new(closure)).to_json(),
4058 serde_json::Value::String("<lambda>".to_string()),
4059 );
4060 }
4061
4062 #[test]
4063 fn to_json_builtin() {
4064 let b = BuiltinFn {
4065 name: "test",
4066 func: Rc::new(|_| Ok(Value::Null)),
4067 };
4068 assert_eq!(
4069 Value::Builtin(Box::new(b)).to_json(),
4070 serde_json::Value::String("<builtin test>".to_string()),
4071 );
4072 }
4073
4074 #[test]
4077 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4078
4079 #[test]
4080 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4081
4082 #[test]
4083 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4084
4085 #[test]
4086 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4087
4088 #[test]
4089 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4090
4091 #[test]
4092 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4093
4094 #[test]
4095 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4096
4097 #[test]
4098 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4099
4100 #[test]
4101 fn type_name_lambda() {
4102 let root = rnix::Root::parse("x: x");
4103 let expr = root.tree().expr().unwrap();
4104 let lambda = match expr {
4105 rnix::ast::Expr::Lambda(l) => l,
4106 _ => panic!("expected lambda"),
4107 };
4108 let closure = Closure {
4109 param: lambda.param().unwrap(),
4110 body: lambda.body().unwrap(),
4111 env: Env::new(),
4112 };
4113 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4114 }
4115
4116 #[test]
4117 fn type_name_builtin() {
4118 let b = BuiltinFn {
4119 name: "t",
4120 func: Rc::new(|_| Ok(Value::Null)),
4121 };
4122 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4123 }
4124
4125 #[test]
4128 fn as_bool_error_on_non_bool() {
4129 assert!(Value::Int(1).as_bool().is_err());
4130 assert!(Value::string("true").as_bool().is_err());
4131 }
4132
4133 #[test]
4134 fn as_int_error_on_non_int() {
4135 assert!(Value::Bool(true).as_int().is_err());
4136 assert!(Value::Float(1.0).as_int().is_err());
4137 }
4138
4139 #[test]
4140 fn as_string_error_on_non_string() {
4141 assert!(Value::Int(42).as_string().is_err());
4142 assert!(Value::Null.as_string().is_err());
4143 }
4144
4145 #[test]
4146 fn as_attrs_error_on_non_attrs() {
4147 assert!(Value::Int(1).as_attrs().is_err());
4148 assert!(Value::list(vec![]).as_attrs().is_err());
4149 }
4150
4151 #[test]
4152 fn as_list_error_on_non_list() {
4153 assert!(Value::Int(1).as_list().is_err());
4154 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4155 }
4156
4157 #[test]
4160 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4161 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4163 assert!(left.is_uniquely_owned_list());
4164 let right = [Value::Int(3), Value::Int(4)];
4165 let out = super::concat_lists(left, &right).unwrap();
4166 assert_eq!(
4167 out.as_list().unwrap(),
4168 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4169 );
4170 }
4171
4172 #[test]
4173 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4174 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4177 let left = Value::List(Rc::clone(&shared));
4178 assert!(!left.is_uniquely_owned_list());
4179 let right = [Value::Int(3)];
4180 let out = super::concat_lists(left, &right).unwrap();
4181 assert_eq!(
4182 out.as_list().unwrap(),
4183 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4184 );
4185 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4187 }
4188
4189 #[test]
4190 fn concat_lists_empty_operands() {
4191 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4192 assert!(out.as_list().unwrap().is_empty());
4193 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4194 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4195 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4196 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4197 }
4198
4199 #[test]
4200 fn concat_lists_non_list_left_errors() {
4201 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4202 }
4203
4204 #[test]
4205 fn concat_lists_preserves_element_identity() {
4206 let inner = Rc::new(NixString::plain("x"));
4208 let a = Value::String(Rc::clone(&inner));
4209 let left = Value::list(vec![a]);
4210 let out = super::concat_lists(left, &[]).unwrap();
4211 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4212 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4213 } else {
4214 panic!("expected string element");
4215 }
4216 }
4217
4218 #[test]
4221 fn to_float_coerces_int() {
4222 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4223 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4224 assert!(Value::string("x").to_float().is_err());
4225 }
4226
4227 #[test]
4230 fn partial_eq_int_float_cross() {
4231 assert_eq!(Value::Int(3), Value::Float(3.0));
4232 assert_eq!(Value::Float(3.0), Value::Int(3));
4233 assert_ne!(Value::Int(3), Value::Float(3.5));
4234 }
4235
4236 #[test]
4237 fn partial_eq_different_types_not_equal() {
4238 assert_ne!(Value::Int(1), Value::string("1"));
4239 assert_ne!(Value::Bool(true), Value::Int(1));
4240 assert_ne!(Value::Null, Value::Bool(false));
4241 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4242 }
4243
4244 #[test]
4247 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4248
4249 #[test]
4250 fn display_bool() {
4251 assert_eq!(format!("{}", Value::Bool(true)), "true");
4252 assert_eq!(format!("{}", Value::Bool(false)), "false");
4253 }
4254
4255 #[test]
4256 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4257
4258 #[test]
4259 fn display_float() {
4260 let s = format!("{}", Value::Float(3.14));
4261 assert!(s.contains("3.14"));
4262 }
4263
4264 #[test]
4265 fn display_string() {
4266 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4267 }
4268
4269 #[test]
4270 fn display_string_with_escapes() {
4271 let v = Value::string("a\"b\\c");
4272 let s = format!("{v}");
4273 assert!(s.contains("\\\""));
4274 assert!(s.contains("\\\\"));
4275 }
4276
4277 #[test]
4278 fn display_path() {
4279 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4280 }
4281
4282 #[test]
4283 fn display_list() {
4284 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4285 assert_eq!(format!("{v}"), "[ 1 2 ]");
4286 }
4287
4288 #[test]
4289 fn display_attrs() {
4290 let mut attrs = NixAttrs::new();
4291 attrs.insert("x".to_string(), Value::Int(1));
4292 let v = Value::Attrs(Rc::new(attrs));
4293 assert_eq!(format!("{v}"), "{ x = 1; }");
4294 }
4295
4296 #[test]
4297 fn display_lambda() {
4298 let root = rnix::Root::parse("x: x");
4299 let expr = root.tree().expr().unwrap();
4300 let lambda = match expr {
4301 rnix::ast::Expr::Lambda(l) => l,
4302 _ => panic!("expected lambda"),
4303 };
4304 let closure = Closure {
4305 param: lambda.param().unwrap(),
4306 body: lambda.body().unwrap(),
4307 env: Env::new(),
4308 };
4309 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4310 }
4311
4312 #[test]
4313 fn display_builtin() {
4314 let b = BuiltinFn {
4315 name: "add",
4316 func: Rc::new(|_| Ok(Value::Null)),
4317 };
4318 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4319 }
4320
4321 #[test]
4324 fn nixattrs_update_merging() {
4325 let mut a = NixAttrs::new();
4326 a.insert("x".to_string(), Value::Int(1));
4327 a.insert("y".to_string(), Value::Int(2));
4328 let mut b = NixAttrs::new();
4329 b.insert("y".to_string(), Value::Int(99));
4330 b.insert("z".to_string(), Value::Int(3));
4331 let merged = a.update(&b);
4332 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4333 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4334 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4335 assert_eq!(merged.len(), 3);
4336 }
4337
4338 #[test]
4339 fn nixattrs_contains_key() {
4340 let mut a = NixAttrs::new();
4341 a.insert("foo".to_string(), Value::Null);
4342 assert!(a.contains_key("foo"));
4343 assert!(!a.contains_key("bar"));
4344 }
4345
4346 #[test]
4349 fn env_lookup_through_parent_chain() {
4350 let mut root = Env::new();
4351 root.bind("a".to_string(), Value::Int(1));
4352 let mut child = root.child();
4353 child.bind("b".to_string(), Value::Int(2));
4354 let grandchild = child.child();
4355 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4357 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4358 assert_eq!(grandchild.lookup("c"), None);
4359 }
4360
4361 #[test]
4362 fn env_with_scope_lookup() {
4363 let mut attrs = NixAttrs::new();
4364 attrs.insert("x".to_string(), Value::Int(42));
4365 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4366 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4367 assert_eq!(env.lookup("y"), None);
4368 }
4369
4370 #[test]
4371 fn env_local_shadows_with_scope() {
4372 let mut attrs = NixAttrs::new();
4373 attrs.insert("x".to_string(), Value::Int(1));
4374 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4375 env.bind("x".to_string(), Value::Int(99));
4376 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4377 }
4378
4379 #[test]
4382 fn string_context_merge_combines_elements() {
4383 let mut ctx_a = StringContext::new();
4384 ctx_a.add_plain("/nix/store/aaa".to_string());
4385 let mut ctx_b = StringContext::new();
4386 ctx_b.add_plain("/nix/store/bbb".to_string());
4387 ctx_a.merge(&ctx_b);
4388 assert_eq!(ctx_a.len(), 2);
4389 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4390 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4391 }
4392
4393 #[test]
4394 fn string_context_merge_deduplicates() {
4395 let mut ctx = StringContext::new();
4396 ctx.add_plain("/nix/store/same".to_string());
4397 ctx.add_plain("/nix/store/same".to_string());
4398 assert_eq!(ctx.len(), 1);
4399 }
4400
4401 #[test]
4402 fn string_context_mixed_element_types() {
4403 let mut ctx = StringContext::new();
4404 ctx.add_plain("/nix/store/foo".to_string());
4405 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4406 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4407 assert_eq!(ctx.len(), 3);
4408 assert!(!ctx.is_empty());
4409 }
4410
4411 #[test]
4412 fn string_context_new_is_empty() {
4413 let ctx = StringContext::new();
4414 assert!(ctx.is_empty());
4415 assert_eq!(ctx.len(), 0);
4416 }
4417
4418 #[test]
4419 fn string_context_merge_zero_elements() {
4420 let mut ctx_a = StringContext::new();
4421 let ctx_b = StringContext::new();
4422 ctx_a.merge(&ctx_b);
4423 assert!(ctx_a.is_empty());
4424 }
4425
4426 #[test]
4427 fn string_context_merge_one_element() {
4428 let mut ctx = StringContext::new();
4429 let mut other = StringContext::new();
4430 other.add_plain("/nix/store/only".to_string());
4431 ctx.merge(&other);
4432 assert_eq!(ctx.len(), 1);
4433 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4434 }
4435
4436 #[test]
4437 fn string_context_merge_two_elements() {
4438 let mut ctx = StringContext::new();
4439 ctx.add_plain("/nix/store/a".to_string());
4440 let mut other = StringContext::new();
4441 other.add_plain("/nix/store/b".to_string());
4442 ctx.merge(&other);
4443 assert_eq!(ctx.len(), 2);
4444 }
4445
4446 #[test]
4447 fn string_context_merge_five_elements() {
4448 let mut ctx = StringContext::new();
4449 for i in 0..5 {
4450 ctx.add_plain(format!("/nix/store/path-{i}"));
4451 }
4452 assert_eq!(ctx.len(), 5);
4453 for i in 0..5 {
4454 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4455 }
4456 }
4457
4458 #[test]
4459 fn string_context_insert_deduplicates() {
4460 let mut ctx = StringContext::new();
4461 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4462 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4463 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4464 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4465 assert_eq!(ctx.len(), 2);
4466 }
4467
4468 #[test]
4469 fn nix_string_plain_has_no_context() {
4470 let s = NixString::plain("hello");
4471 assert!(!s.has_context());
4472 assert_eq!(s.as_str(), "hello");
4473 }
4474
4475 #[test]
4476 fn nix_string_with_context_reports_context() {
4477 let mut ctx = StringContext::new();
4478 ctx.add_plain("/nix/store/xyz".to_string());
4479 let s = NixString::with_context("hello", ctx);
4480 assert!(s.has_context());
4481 assert_eq!(s.as_str(), "hello");
4482 }
4483
4484 #[test]
4485 fn nix_string_display_shows_chars_only() {
4486 let mut ctx = StringContext::new();
4487 ctx.add_plain("/nix/store/abc".to_string());
4488 let s = NixString::with_context("visible", ctx);
4489 assert_eq!(format!("{s}"), "visible");
4490 }
4491
4492 #[test]
4493 fn nix_string_struct_eq_includes_context() {
4494 let plain = NixString::plain("hello");
4495 let mut ctx = StringContext::new();
4496 ctx.add_plain("/nix/store/xxx".to_string());
4497 let with_ctx = NixString::with_context("hello", ctx);
4498 assert_ne!(plain, with_ctx);
4500 }
4501
4502 #[test]
4503 fn value_string_eq_ignores_context() {
4504 let plain = Value::String(Rc::new(NixString::plain("hello")));
4505 let mut ctx = StringContext::new();
4506 ctx.add_plain("/nix/store/xxx".to_string());
4507 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4508 assert_eq!(plain, with_ctx);
4510 }
4511
4512 #[test]
4515 fn env_nested_with_inner_wins() {
4516 let mut outer_attrs = NixAttrs::new();
4517 outer_attrs.insert("x".to_string(), Value::Int(1));
4518 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4519 let mut inner_attrs = NixAttrs::new();
4520 inner_attrs.insert("x".to_string(), Value::Int(2));
4521 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4522 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4523 }
4524
4525 #[test]
4526 fn env_nested_with_fallback_to_outer() {
4527 let mut outer_attrs = NixAttrs::new();
4528 outer_attrs.insert("x".to_string(), Value::Int(1));
4529 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4530 let mut inner_attrs = NixAttrs::new();
4531 inner_attrs.insert("y".to_string(), Value::Int(2));
4532 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4533 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4534 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4535 }
4536
4537 #[test]
4538 fn env_lexical_binding_wins_over_all_with_scopes() {
4539 let mut outer_attrs = NixAttrs::new();
4540 outer_attrs.insert("x".to_string(), Value::Int(1));
4541 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4542 let mut inner_attrs = NixAttrs::new();
4543 inner_attrs.insert("x".to_string(), Value::Int(2));
4544 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4545 inner.bind("x".to_string(), Value::Int(99));
4546 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4547 }
4548
4549 #[test]
4550 fn env_parent_lexical_wins_over_child_with_scope() {
4551 let mut root = Env::new();
4552 root.bind("x".to_string(), Value::Int(10));
4553 let mut child_attrs = NixAttrs::new();
4554 child_attrs.insert("x".to_string(), Value::Int(20));
4555 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4556 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4557 }
4558
4559 #[test]
4560 fn env_deeply_nested_with_scopes_three_levels() {
4561 let mut a = NixAttrs::new();
4562 a.insert("x".to_string(), Value::Int(1));
4563 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4564
4565 let mut b = NixAttrs::new();
4566 b.insert("y".to_string(), Value::Int(2));
4567 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4568
4569 let mut c = NixAttrs::new();
4570 c.insert("z".to_string(), Value::Int(3));
4571 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4572
4573 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4574 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4575 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4576 assert_eq!(env3.lookup("w"), None);
4577 }
4578
4579 #[test]
4580 fn env_with_scope_does_not_pollute_bindings() {
4581 let mut attrs = NixAttrs::new();
4584 attrs.insert("x".to_string(), Value::Int(42));
4585 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4586 assert!(env.0.bindings.get(&intern("x")).is_none());
4588 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4590 }
4591
4592 #[test]
4593 fn env_lexical_binding_not_in_with_scopes() {
4594 let mut env = Env::new();
4596 env.bind("x".to_string(), Value::Int(42));
4597 assert!(env.0.with_scopes.is_empty());
4599 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4601 }
4602
4603 #[test]
4604 fn env_child_inherits_eval_file() {
4605 let mut env = Env::new();
4606 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4607 let child = env.child();
4608 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4609 }
4610
4611 #[test]
4612 fn env_new_has_no_parent_no_with() {
4613 let env = Env::new();
4614 assert_eq!(env.lookup("anything"), None);
4615 assert!(env.eval_file().is_none());
4616 }
4617
4618 #[test]
4621 fn thunk_new_suspended_is_not_evaluated() {
4622 let root = rnix::Root::parse("42");
4623 let expr = root.tree().expr().unwrap();
4624 let thunk = Thunk::new_suspended(expr, Env::new());
4625 assert!(!thunk.is_evaluated());
4626 }
4627
4628 #[test]
4629 fn thunk_new_evaluated_is_evaluated() {
4630 let thunk = Thunk::new_evaluated(Value::Int(42));
4631 assert!(thunk.is_evaluated());
4632 }
4633
4634 #[test]
4635 fn thunk_force_evaluates_suspended() {
4636 let root = rnix::Root::parse("42");
4637 let expr = root.tree().expr().unwrap();
4638 let thunk = Thunk::new_suspended(expr, Env::new());
4639 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4640 assert!(result.is_ok());
4641 assert_eq!(result.unwrap(), Value::Int(42));
4642 assert!(thunk.is_evaluated());
4643 }
4644
4645 #[test]
4646 fn thunk_force_memoizes_result() {
4647 let root = rnix::Root::parse("1 + 2");
4648 let expr = root.tree().expr().unwrap();
4649 let thunk = Thunk::new_suspended(expr, Env::new());
4650 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4651 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4652 assert_eq!(r1, Value::Int(3));
4653 assert_eq!(r2, Value::Int(3));
4654 }
4655
4656 #[test]
4657 fn thunk_force_already_evaluated_returns_value() {
4658 let thunk = Thunk::new_evaluated(Value::Bool(true));
4659 let result = thunk.force(&|_, _| panic!("should not be called"));
4660 assert_eq!(result.unwrap(), Value::Bool(true));
4661 }
4662
4663 #[test]
4672 fn thunk_force_concrete_skips_redundant_store_but_caches() {
4673 let root = rnix::Root::parse("1 + 2");
4676 let expr = root.tree().expr().unwrap();
4677 let thunk = Thunk::new_suspended(expr, Env::new());
4678
4679 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4680 assert_eq!(r1, Value::Int(3));
4681 assert!(thunk.is_evaluated());
4682
4683 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
4686
4687 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
4689 assert_eq!(r2, Value::Int(3));
4690 }
4691
4692 #[test]
4693 fn thunk_blackhole_detects_infinite_recursion() {
4694 let root = rnix::Root::parse("42");
4695 let expr = root.tree().expr().unwrap();
4696 let thunk = Thunk::new_suspended(expr, Env::new());
4697
4698 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
4701
4702 let result = thunk.force(&|_, _| Ok(Value::Null));
4703 assert!(result.is_err());
4704 let err_msg = format!("{}", result.unwrap_err());
4705 assert!(err_msg.contains("infinite recursion"));
4706 }
4707
4708 #[test]
4709 fn thunk_update_env_replaces_suspended_env() {
4710 let root = rnix::Root::parse("x");
4711 let expr = root.tree().expr().unwrap();
4712 let thunk = Thunk::new_suspended(expr, Env::new());
4713
4714 let mut new_env = Env::new();
4715 new_env.bind("x".to_string(), Value::Int(99));
4716 thunk.update_env(&new_env);
4717
4718 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4719 assert_eq!(result.unwrap(), Value::Int(99));
4720 }
4721
4722 #[test]
4723 fn thunk_update_env_noop_when_evaluated() {
4724 let thunk = Thunk::new_evaluated(Value::Int(1));
4725 let mut new_env = Env::new();
4726 new_env.bind("x".to_string(), Value::Int(99));
4727 thunk.update_env(&new_env);
4728 assert_eq!(
4729 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
4730 Value::Int(1),
4731 );
4732 }
4733
4734 #[test]
4735 fn thunk_debug_suspended() {
4736 let root = rnix::Root::parse("42");
4737 let expr = root.tree().expr().unwrap();
4738 let thunk = Thunk::new_suspended(expr, Env::new());
4739 assert_eq!(format!("{thunk:?}"), "<thunk>");
4740 }
4741
4742 #[test]
4743 fn thunk_debug_evaluated() {
4744 let thunk = Thunk::new_evaluated(Value::Int(42));
4745 let dbg = format!("{thunk:?}");
4746 assert!(dbg.contains("42"));
4747 }
4748
4749 #[test]
4750 fn thunk_error_restores_suspended_state() {
4751 let root = rnix::Root::parse("nonexistent_var");
4752 let expr = root.tree().expr().unwrap();
4753 let thunk = Thunk::new_suspended(expr, Env::new());
4754
4755 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4756 assert!(result.is_err());
4757 assert!(!thunk.is_evaluated());
4759 let dbg = format!("{thunk:?}");
4760 assert_eq!(dbg, "<thunk>");
4761 }
4762
4763 #[test]
4764 fn thunk_inherit_select_forces_and_selects() {
4765 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4766 let expr = root.tree().expr().unwrap();
4767 let source = Thunk::new_suspended(expr, Env::new());
4768 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4769 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4770 assert_eq!(result.unwrap(), Value::Int(42));
4771 assert!(thunk.is_evaluated());
4772 }
4773
4774 #[test]
4775 fn thunk_inherit_select_missing_attr_errors() {
4776 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4777 let expr = root.tree().expr().unwrap();
4778 let source = Thunk::new_suspended(expr, Env::new());
4779 let thunk = Thunk::new_inherit_select(source, "y".to_string());
4780 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4781 assert!(result.is_err());
4782 assert!(!thunk.is_evaluated());
4784 }
4785
4786 #[test]
4787 fn thunk_inherit_select_non_attrs_source_errors() {
4788 let root = rnix::Root::parse("42");
4789 let expr = root.tree().expr().unwrap();
4790 let source = Thunk::new_suspended(expr, Env::new());
4791 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4792 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4793 assert!(result.is_err());
4794 let msg = format!("{}", result.unwrap_err());
4795 assert!(msg.contains("not a set"));
4796 }
4797
4798 #[test]
4799 fn thunk_inherit_select_shares_source_thunk() {
4800 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
4804 let expr = root.tree().expr().unwrap();
4805 let source = Thunk::new_suspended(expr, Env::new());
4806 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
4807 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
4808 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
4809 assert_eq!(result_a.unwrap(), Value::Int(1));
4810 assert!(source.is_evaluated());
4812 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
4814 assert_eq!(result_b.unwrap(), Value::Int(2));
4815 }
4816
4817 #[test]
4820 fn nixattrs_empty_operations() {
4821 let a = NixAttrs::new();
4822 assert!(a.is_empty());
4823 assert_eq!(a.len(), 0);
4824 assert_eq!(a.get("x"), None);
4825 assert!(!a.contains_key("x"));
4826 assert_eq!(a.keys().count(), 0);
4827 assert_eq!(a.iter().count(), 0);
4828 }
4829
4830 #[test]
4831 fn nixattrs_update_with_empty() {
4832 let mut a = NixAttrs::new();
4833 a.insert("x".to_string(), Value::Int(1));
4834 let b = NixAttrs::new();
4835 let merged = a.update(&b);
4836 assert_eq!(merged.len(), 1);
4837 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4838 }
4839
4840 #[test]
4841 fn nixattrs_update_empty_with_nonempty() {
4842 let a = NixAttrs::new();
4843 let mut b = NixAttrs::new();
4844 b.insert("x".to_string(), Value::Int(1));
4845 let merged = a.update(&b);
4846 assert_eq!(merged.len(), 1);
4847 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4848 }
4849
4850 #[test]
4851 fn nixattrs_keys_sorted_order() {
4852 let mut a = NixAttrs::new();
4853 a.insert("c".to_string(), Value::Int(3));
4854 a.insert("a".to_string(), Value::Int(1));
4855 a.insert("b".to_string(), Value::Int(2));
4856 let keys: Vec<String> = a.keys().collect();
4857 assert_eq!(keys, vec!["a", "b", "c"]);
4858 }
4859
4860 #[test]
4863 fn value_to_str_forces_thunks() {
4864 let root = rnix::Root::parse(r#""hello""#);
4865 let expr = root.tree().expr().unwrap();
4866 let thunk = Thunk::new_suspended(expr, Env::new());
4867 let val = Value::Thunk(thunk);
4868 assert_eq!(val.to_str().unwrap(), "hello");
4869 }
4870
4871 #[test]
4872 fn value_to_nix_string_forces_thunks() {
4873 let root = rnix::Root::parse(r#""world""#);
4874 let expr = root.tree().expr().unwrap();
4875 let thunk = Thunk::new_suspended(expr, Env::new());
4876 let val = Value::Thunk(thunk);
4877 let ns = val.to_nix_string().unwrap();
4878 assert_eq!(ns.as_str(), "world");
4879 assert!(!ns.has_context());
4880 }
4881
4882 #[test]
4883 fn value_to_attrs_forces_thunks() {
4884 let root = rnix::Root::parse("{ x = 1; }");
4885 let expr = root.tree().expr().unwrap();
4886 let thunk = Thunk::new_suspended(expr, Env::new());
4887 let val = Value::Thunk(thunk);
4888 let attrs = val.to_attrs().unwrap();
4889 assert_eq!(attrs.len(), 1);
4890 }
4891
4892 #[test]
4893 fn value_to_list_forces_thunks() {
4894 let root = rnix::Root::parse("[1 2 3]");
4895 let expr = root.tree().expr().unwrap();
4896 let thunk = Thunk::new_suspended(expr, Env::new());
4897 let val = Value::Thunk(thunk);
4898 let list = val.to_list().unwrap();
4899 assert_eq!(list.len(), 3);
4900 }
4901
4902 #[test]
4903 fn value_to_float_on_thunk() {
4904 let root = rnix::Root::parse("3.14");
4905 let expr = root.tree().expr().unwrap();
4906 let thunk = Thunk::new_suspended(expr, Env::new());
4907 let val = Value::Thunk(thunk);
4908 let f = val.to_float().unwrap();
4909 assert!((f - 3.14).abs() < f64::EPSILON);
4910 }
4911
4912 #[test]
4913 fn value_as_bool_on_thunk() {
4914 let root = rnix::Root::parse("true");
4915 let expr = root.tree().expr().unwrap();
4916 let thunk = Thunk::new_suspended(expr, Env::new());
4917 let val = Value::Thunk(thunk);
4918 assert!(val.as_bool().unwrap());
4919 }
4920
4921 #[test]
4922 fn value_as_int_on_thunk() {
4923 let root = rnix::Root::parse("42");
4924 let expr = root.tree().expr().unwrap();
4925 let thunk = Thunk::new_suspended(expr, Env::new());
4926 let val = Value::Thunk(thunk);
4927 assert_eq!(val.as_int().unwrap(), 42);
4928 }
4929
4930 #[test]
4931 fn value_string_constructor() {
4932 let v = Value::string("test");
4933 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
4934 }
4935
4936 #[test]
4937 fn value_partial_eq_null_null() {
4938 assert_eq!(Value::Null, Value::Null);
4939 }
4940
4941 #[test]
4942 fn value_partial_eq_lists_deep() {
4943 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
4944 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
4945 assert_eq!(a, b);
4946 }
4947
4948 #[test]
4949 fn value_partial_eq_attrs_deep() {
4950 let mut a = NixAttrs::new();
4951 a.insert("x".to_string(), Value::Int(1));
4952 let mut b = NixAttrs::new();
4953 b.insert("x".to_string(), Value::Int(1));
4954 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
4955 }
4956
4957 #[test]
4960 fn eval_error_type_error_constructor() {
4961 let e = EvalError::type_error("oops");
4962 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
4963 }
4964
4965 #[test]
4966 fn eval_error_type_mismatch_constructor() {
4967 let e = EvalError::type_mismatch("int", "string");
4968 match e {
4969 EvalError::TypeMismatch { expected, got } => {
4970 assert_eq!(expected, "int");
4971 assert_eq!(got, "string");
4972 }
4973 _ => panic!("expected TypeMismatch"),
4974 }
4975 }
4976
4977 #[test]
4978 fn eval_error_is_throw_yes_no() {
4979 assert!(EvalError::Throw("oops".into()).is_throw());
4980 assert!(!EvalError::TypeError("oops".into()).is_throw());
4981 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
4982 }
4983
4984 #[test]
4985 fn eval_error_is_infinite_recursion_yes_no() {
4986 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
4987 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
4988 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
4989 }
4990
4991 #[test]
4992 fn eval_error_display_undefined_var() {
4993 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
4994 assert!(s.contains("undefined variable"));
4995 assert!(s.contains("foo"));
4996 }
4997
4998 #[test]
4999 fn eval_error_display_type_error() {
5000 let s = format!("{}", EvalError::TypeError("bad".into()));
5001 assert!(s.contains("type error"));
5002 assert!(s.contains("bad"));
5003 }
5004
5005 #[test]
5006 fn eval_error_display_attr_not_found() {
5007 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5008 assert!(s.contains("attribute not found"));
5009 assert!(s.contains("x"));
5010 }
5011
5012 #[test]
5013 fn eval_error_display_type_mismatch() {
5014 let s = format!(
5015 "{}",
5016 EvalError::TypeMismatch { expected: "int", got: "string" }
5017 );
5018 assert!(s.contains("expected int"));
5019 assert!(s.contains("got string"));
5020 }
5021
5022 #[test]
5023 fn eval_error_display_assertion_failed() {
5024 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5025 assert!(s.contains("assertion"));
5026 }
5027
5028 #[test]
5029 fn eval_error_display_division_by_zero() {
5030 let s = format!("{}", EvalError::DivisionByZero);
5031 assert!(s.contains("division by zero"));
5032 }
5033
5034 #[test]
5035 fn eval_error_display_infinite_recursion() {
5036 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5037 assert!(s.contains("infinite recursion"));
5038 assert!(s.contains("loop"));
5039 }
5040
5041 #[test]
5042 fn eval_error_display_io_error() {
5043 let s = format!(
5044 "{}",
5045 EvalError::IoError {
5046 context: "ctx".into(),
5047 message: "no such file".into(),
5048 }
5049 );
5050 assert!(s.contains("I/O"));
5051 assert!(s.contains("ctx"));
5052 assert!(s.contains("no such file"));
5053 }
5054
5055 #[test]
5056 fn eval_error_display_throw() {
5057 let s = format!("{}", EvalError::Throw("boom".into()));
5058 assert_eq!(s, "boom");
5059 }
5060
5061 #[test]
5062 fn eval_error_display_not_implemented() {
5063 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5064 assert!(s.contains("not yet implemented"));
5065 assert!(s.contains("frob"));
5066 }
5067
5068 #[test]
5069 fn eval_error_display_parse_error() {
5070 let s = format!("{}", EvalError::ParseError("syntax".into()));
5071 assert!(s.contains("parse error"));
5072 assert!(s.contains("syntax"));
5073 }
5074
5075 #[test]
5076 fn eval_error_display_recursion_limit() {
5077 let s = format!(
5078 "{}",
5079 EvalError::RecursionLimit("max depth exceeded".into())
5080 );
5081 assert!(s.contains("recursion limit"));
5082 assert!(s.contains("max depth exceeded"));
5083 }
5084
5085 #[test]
5086 fn eval_error_partial_eq_same_variant() {
5087 assert_eq!(
5088 EvalError::UndefinedVar("x".into()),
5089 EvalError::UndefinedVar("x".into()),
5090 );
5091 assert_ne!(
5092 EvalError::UndefinedVar("x".into()),
5093 EvalError::UndefinedVar("y".into()),
5094 );
5095 assert_ne!(
5096 EvalError::UndefinedVar("x".into()),
5097 EvalError::AttrNotFound("x".into()),
5098 );
5099 }
5100
5101 #[test]
5104 fn context_element_display_plain() {
5105 let e = ContextElement::Plain("/nix/store/xyz".into());
5106 assert_eq!(format!("{e}"), "/nix/store/xyz");
5107 }
5108
5109 #[test]
5110 fn context_element_display_output() {
5111 let e = ContextElement::Output {
5112 drv: "/nix/store/abc.drv".into(),
5113 output: "out".into(),
5114 };
5115 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5116 }
5117
5118 #[test]
5119 fn context_element_display_drv_deep() {
5120 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5121 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5122 }
5123
5124 #[test]
5127 fn string_context_iter_yields_all() {
5128 let mut ctx = StringContext::new();
5129 ctx.add_plain("/nix/store/aaa");
5130 ctx.add_plain("/nix/store/bbb");
5131 let count = ctx.iter().count();
5132 assert_eq!(count, 2);
5133 }
5134
5135 #[test]
5136 fn string_context_len_matches_set_size() {
5137 let mut ctx = StringContext::new();
5138 assert_eq!(ctx.len(), 0);
5139 ctx.add_plain("/nix/store/x");
5140 assert_eq!(ctx.len(), 1);
5141 ctx.add_output("/nix/store/y.drv", "out");
5142 assert_eq!(ctx.len(), 2);
5143 }
5144
5145 #[test]
5146 fn string_context_insert_raw_element() {
5147 let mut ctx = StringContext::new();
5148 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5149 assert_eq!(ctx.len(), 1);
5150 }
5151
5152 #[test]
5153 fn string_context_default_is_empty() {
5154 let ctx = StringContext::default();
5155 assert!(ctx.is_empty());
5156 }
5157
5158 #[test]
5161 fn nix_string_as_ref_str() {
5162 let s = NixString::plain("hello");
5163 let r: &str = s.as_ref();
5164 assert_eq!(r, "hello");
5165 }
5166
5167 #[test]
5168 fn nix_string_deref_to_str_methods() {
5169 let s = NixString::plain("Hello World");
5170 assert_eq!(s.len(), 11);
5171 assert!(s.starts_with("Hello"));
5172 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5174 }
5175
5176 #[test]
5179 fn nixattrs_remove_returns_value() {
5180 let mut a = NixAttrs::new();
5181 a.insert("x".into(), Value::Int(1));
5182 let removed = a.remove("x");
5183 assert_eq!(removed, Some(Value::Int(1)));
5184 assert!(!a.contains_key("x"));
5185 assert_eq!(a.remove("y"), None);
5186 }
5187
5188 #[test]
5189 fn nixattrs_values_iter() {
5190 let mut a = NixAttrs::new();
5191 a.insert("a".into(), Value::Int(1));
5192 a.insert("b".into(), Value::Int(2));
5193 let mut vs: Vec<&Value> = a.values().collect();
5194 vs.sort_by_key(|v| match v {
5195 Value::Int(n) => *n,
5196 _ => 0,
5197 });
5198 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5199 }
5200
5201 #[test]
5202 fn nixattrs_iter_returns_sorted_pairs() {
5203 let mut a = NixAttrs::new();
5204 a.insert("zeta".into(), Value::Int(3));
5205 a.insert("alpha".into(), Value::Int(1));
5206 a.insert("mu".into(), Value::Int(2));
5207 let pairs: Vec<(String, &Value)> = a.iter().collect();
5208 assert_eq!(pairs[0].0, "alpha");
5209 assert_eq!(pairs[1].0, "mu");
5210 assert_eq!(pairs[2].0, "zeta");
5211 }
5212
5213 #[test]
5214 fn nixattrs_from_iterator() {
5215 let pairs = vec![
5216 ("a".to_string(), Value::Int(1)),
5217 ("b".to_string(), Value::Int(2)),
5218 ];
5219 let attrs: NixAttrs = pairs.into_iter().collect();
5220 assert_eq!(attrs.len(), 2);
5221 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5222 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5223 }
5224
5225 #[test]
5226 fn nixattrs_into_iterator_yields_owned() {
5227 let mut a = NixAttrs::new();
5228 a.insert("x".into(), Value::Int(42));
5229 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5230 assert_eq!(pairs.len(), 1);
5231 assert_eq!(pairs[0].0, "x");
5232 assert_eq!(pairs[0].1, Value::Int(42));
5233 }
5234
5235 #[test]
5236 fn nixattrs_default_is_empty() {
5237 let a = NixAttrs::default();
5238 assert!(a.is_empty());
5239 }
5240
5241 #[test]
5244 fn value_from_bool() {
5245 assert_eq!(Value::from(true), Value::Bool(true));
5246 assert_eq!(Value::from(false), Value::Bool(false));
5247 }
5248
5249 #[test]
5250 fn value_from_i64() {
5251 assert_eq!(Value::from(42_i64), Value::Int(42));
5252 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5253 }
5254
5255 #[test]
5256 fn value_from_f64() {
5257 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5258 }
5259
5260 #[test]
5261 fn value_from_nix_string() {
5262 let v: Value = NixString::plain("hi").into();
5263 assert_eq!(v, Value::string("hi"));
5264 }
5265
5266 #[test]
5267 fn value_from_nix_attrs() {
5268 let mut a = NixAttrs::new();
5269 a.insert("x".into(), Value::Int(1));
5270 let v: Value = a.into();
5271 match v {
5272 Value::Attrs(_) => {}
5273 _ => panic!("expected Attrs"),
5274 }
5275 }
5276
5277 #[test]
5278 fn value_from_vec() {
5279 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5280 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5281 }
5282
5283 #[test]
5284 fn value_default_is_null() {
5285 let v: Value = Value::default();
5286 assert_eq!(v, Value::Null);
5287 }
5288
5289 #[test]
5292 fn value_from_json_null() {
5293 let v = Value::from(&serde_json::Value::Null);
5294 assert_eq!(v, Value::Null);
5295 }
5296
5297 #[test]
5298 fn value_from_json_bool() {
5299 let v = Value::from(&serde_json::Value::Bool(true));
5300 assert_eq!(v, Value::Bool(true));
5301 }
5302
5303 #[test]
5304 fn value_from_json_int() {
5305 let v = Value::from(&serde_json::json!(42));
5306 assert_eq!(v, Value::Int(42));
5307 }
5308
5309 #[test]
5310 fn value_from_json_float() {
5311 let v = Value::from(&serde_json::json!(3.14));
5312 match v {
5313 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5314 _ => panic!("expected Float"),
5315 }
5316 }
5317
5318 #[test]
5319 fn value_from_json_string() {
5320 let v = Value::from(&serde_json::Value::String("hi".into()));
5321 assert_eq!(v, Value::string("hi"));
5322 }
5323
5324 #[test]
5325 fn value_from_json_array() {
5326 let v = Value::from(&serde_json::json!([1, true, "x"]));
5327 match v {
5328 Value::List(items) => {
5329 assert_eq!(items.len(), 3);
5330 assert_eq!(items[0], Value::Int(1));
5331 assert_eq!(items[1], Value::Bool(true));
5332 assert_eq!(items[2], Value::string("x"));
5333 }
5334 _ => panic!("expected List"),
5335 }
5336 }
5337
5338 #[test]
5339 fn value_from_json_object() {
5340 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5341 match v {
5342 Value::Attrs(attrs) => {
5343 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5344 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5345 }
5346 _ => panic!("expected Attrs"),
5347 }
5348 }
5349
5350 #[test]
5351 fn value_from_json_nested() {
5352 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5353 let json_back = v.to_json();
5354 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5355 }
5356
5357 #[test]
5360 fn value_from_toml_string() {
5361 let t = toml::Value::String("hi".into());
5362 assert_eq!(Value::from(&t), Value::string("hi"));
5363 }
5364
5365 #[test]
5366 fn value_from_toml_int() {
5367 let t = toml::Value::Integer(42);
5368 assert_eq!(Value::from(&t), Value::Int(42));
5369 }
5370
5371 #[test]
5372 fn value_from_toml_float() {
5373 let t = toml::Value::Float(3.14);
5374 match Value::from(&t) {
5375 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5376 _ => panic!("expected Float"),
5377 }
5378 }
5379
5380 #[test]
5381 fn value_from_toml_bool() {
5382 let t = toml::Value::Boolean(true);
5383 assert_eq!(Value::from(&t), Value::Bool(true));
5384 }
5385
5386 #[test]
5387 fn value_from_toml_array() {
5388 let t = toml::Value::Array(vec![
5389 toml::Value::Integer(1),
5390 toml::Value::Integer(2),
5391 ]);
5392 assert_eq!(
5393 Value::from(&t),
5394 Value::list(vec![Value::Int(1), Value::Int(2)]),
5395 );
5396 }
5397
5398 #[test]
5399 fn value_from_toml_table() {
5400 let mut tbl = toml::map::Map::new();
5401 tbl.insert("k".into(), toml::Value::Integer(7));
5402 let t = toml::Value::Table(tbl);
5403 match Value::from(&t) {
5404 Value::Attrs(attrs) => {
5405 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5406 }
5407 _ => panic!("expected Attrs"),
5408 }
5409 }
5410
5411 #[test]
5412 fn value_from_toml_datetime_becomes_string() {
5413 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5415 let t = toml::Value::Datetime(dt);
5416 match Value::from(&t) {
5417 Value::String(_) => {}
5418 other => panic!("expected String, got {other:?}"),
5419 }
5420 }
5421
5422 #[test]
5425 fn coerce_to_path_from_path() {
5426 let v = Value::Path(Box::new("/foo".into()));
5427 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5428 }
5429
5430 #[test]
5431 fn coerce_to_path_from_string() {
5432 let v = Value::string("/bar");
5433 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5434 }
5435
5436 #[test]
5444 fn out_path_needs_realize_matches_output_context() {
5445 let mut ctx = StringContext::new();
5448 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5449 assert_eq!(
5450 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5451 Some("/nix/store/aaa-thing.drv".to_string()),
5452 );
5453 }
5454
5455 #[test]
5456 fn out_path_needs_realize_ignores_plain_context() {
5457 let mut ctx = StringContext::new();
5460 ctx.add_plain("/nix/store/ccc-plain");
5461 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5462 }
5463
5464 #[test]
5465 fn out_path_needs_realize_ignores_non_store_path() {
5466 let mut ctx = StringContext::new();
5469 ctx.add_output("/nix/store/ddd.drv", "out");
5470 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5471 }
5472
5473 #[test]
5474 fn out_path_needs_realize_empty_context_is_none() {
5475 let ctx = StringContext::new();
5477 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5478 }
5479
5480 #[test]
5481 fn coerce_to_realized_path_present_output_is_passthrough() {
5482 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5486 std::fs::create_dir_all(&dir).unwrap();
5487 let file = dir.join("out");
5488 std::fs::write(&file, b"present").unwrap();
5489 let present = file.to_string_lossy().to_string();
5490
5491 let mut ctx = StringContext::new();
5492 ctx.add_plain(&present);
5497 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5498 present.as_str(),
5499 ctx,
5500 )));
5501 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5502 }
5503
5504 #[test]
5505 fn coerce_to_realized_path_absent_output_invokes_hook() {
5506 use std::sync::{Arc, Mutex};
5511 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5512 let seen2 = seen.clone();
5513 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5514 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5515 Ok(())
5516 }));
5517
5518 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5521 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5522 let mut ctx = StringContext::new();
5523 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5524 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5525
5526 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5528 let s = seen.lock().unwrap();
5529 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5530 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5531 assert_eq!(s[0].1, out);
5532 }
5533
5534 #[test]
5535 fn coerce_to_path_errors_on_int() {
5536 let v = Value::Int(1);
5537 let e = v.coerce_to_path("readFile").unwrap_err();
5538 match e {
5539 EvalError::TypeError(ref msg) => {
5540 assert!(msg.contains("readFile"));
5541 assert!(msg.contains("path or string"));
5542 assert!(msg.contains("int"));
5543 }
5544 _ => panic!("expected TypeError"),
5545 }
5546 }
5547
5548 #[test]
5549 fn coerce_to_path_errors_on_null() {
5550 let v = Value::Null;
5551 assert!(v.coerce_to_path("ctx").is_err());
5552 }
5553
5554 #[test]
5555 fn coerce_to_path_attrs_with_outpath() {
5556 let mut attrs = NixAttrs::new();
5557 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5558 let val = Value::Attrs(Rc::new(attrs));
5559 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5560 }
5561
5562 #[test]
5563 fn coerce_to_path_attrs_without_outpath_fails() {
5564 let attrs = NixAttrs::new();
5565 let val = Value::Attrs(Rc::new(attrs));
5566 assert!(val.coerce_to_path("test").is_err());
5567 }
5568
5569 #[test]
5572 fn coerce_to_string_string() {
5573 let v = Value::string("hello");
5574 let (s, _ctx) = v.coerce_to_string().unwrap();
5575 assert_eq!(s, "hello");
5576 }
5577
5578 #[test]
5579 fn coerce_to_string_path() {
5580 let v = Value::Path(Box::new("/foo".into()));
5581 let (s, ctx) = v.coerce_to_string().unwrap();
5582 assert_eq!(s, "/foo");
5583 assert!(!ctx.is_empty()); }
5585
5586 #[test]
5587 fn coerce_to_string_int() {
5588 let v = Value::Int(42);
5589 let (s, _ctx) = v.coerce_to_string().unwrap();
5590 assert_eq!(s, "42");
5591 }
5592
5593 #[test]
5594 fn coerce_to_string_float() {
5595 let v = Value::Float(3.14);
5597 let (s, _ctx) = v.coerce_to_string().unwrap();
5598 assert_eq!(s, "3.140000");
5599 }
5600
5601 #[test]
5602 fn coerce_to_string_bool_true() {
5603 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5604 assert_eq!(s, "1");
5605 }
5606
5607 #[test]
5608 fn coerce_to_string_bool_false() {
5609 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5610 assert_eq!(s, "");
5611 }
5612
5613 #[test]
5614 fn coerce_to_string_null() {
5615 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5616 assert_eq!(s, "");
5617 }
5618
5619 #[test]
5620 fn coerce_to_string_attrs_with_outpath() {
5621 let mut attrs = NixAttrs::new();
5622 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5623 let val = Value::Attrs(Rc::new(attrs));
5624 let (s, _ctx) = val.coerce_to_string().unwrap();
5625 assert_eq!(s, "/nix/store/abc");
5626 }
5627
5628 #[test]
5629 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5630 let attrs = NixAttrs::new();
5631 let val = Value::Attrs(Rc::new(attrs));
5632 assert!(val.coerce_to_string().is_err());
5633 }
5634
5635 #[test]
5636 fn coerce_to_string_lambda_fails() {
5637 let root = rnix::Root::parse("x: x");
5638 let expr = root.tree().expr().unwrap();
5639 let closure = Closure {
5640 param: match expr {
5641 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
5642 _ => panic!("expected lambda"),
5643 },
5644 body: match expr {
5645 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
5646 _ => panic!("expected lambda"),
5647 },
5648 env: Env::new(),
5649 };
5650 let val = Value::Lambda(Rc::new(closure));
5651 assert!(val.coerce_to_string().is_err());
5652 }
5653
5654 #[test]
5657 fn builtin_fn_debug_includes_name() {
5658 let b = BuiltinFn {
5659 name: "myFunc",
5660 func: Rc::new(|_| Ok(Value::Null)),
5661 };
5662 let s = format!("{b:?}");
5663 assert!(s.contains("myFunc"));
5664 assert!(s.contains("builtin"));
5665 }
5666
5667 #[test]
5670 fn thunk_force_chains_through_inner_thunks() {
5671 let inner_root = rnix::Root::parse("99");
5673 let inner_expr = inner_root.tree().expr().unwrap();
5674 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
5675 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
5676 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
5677 match result.unwrap() {
5682 Value::Thunk(_) | Value::Int(99) => {}
5683 other => panic!("unexpected: {other:?}"),
5684 }
5685 }
5686
5687 #[test]
5688 fn thunk_inherit_select_debug_format() {
5689 let root = rnix::Root::parse("{ x = 1; }");
5690 let expr = root.tree().expr().unwrap();
5691 let source = Thunk::new_suspended(expr, Env::new());
5692 let thunk = Thunk::new_inherit_select(source, "x");
5693 let s = format!("{thunk:?}");
5694 assert!(s.contains("inherit-select"));
5695 assert!(s.contains("x"));
5696 }
5697
5698 #[test]
5699 fn thunk_blackhole_debug_format() {
5700 let root = rnix::Root::parse("1");
5701 let expr = root.tree().expr().unwrap();
5702 let thunk = Thunk::new_suspended(expr, Env::new());
5703 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5705 assert_eq!(format!("{thunk:?}"), "<blackhole>");
5706 }
5707
5708 #[test]
5711 fn value_display_thunk_evaluates() {
5712 let root = rnix::Root::parse("42");
5713 let expr = root.tree().expr().unwrap();
5714 let thunk = Thunk::new_suspended(expr, Env::new());
5715 let val = Value::Thunk(thunk);
5716 assert_eq!(format!("{val}"), "42");
5717 }
5718
5719 #[test]
5720 fn value_to_json_thunk_forces() {
5721 let root = rnix::Root::parse(r#""world""#);
5722 let expr = root.tree().expr().unwrap();
5723 let thunk = Thunk::new_suspended(expr, Env::new());
5724 let val = Value::Thunk(thunk);
5725 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
5726 }
5727
5728 #[test]
5729 fn value_type_name_thunk_forces() {
5730 let root = rnix::Root::parse("42");
5731 let expr = root.tree().expr().unwrap();
5732 let thunk = Thunk::new_suspended(expr, Env::new());
5733 let val = Value::Thunk(thunk);
5734 assert_eq!(val.type_name(), "int");
5735 }
5736
5737 #[test]
5740 fn as_string_errors_on_thunk() {
5741 let root = rnix::Root::parse(r#""x""#);
5742 let expr = root.tree().expr().unwrap();
5743 let thunk = Thunk::new_suspended(expr, Env::new());
5744 let val = Value::Thunk(thunk);
5745 let err = val.as_string().unwrap_err();
5746 match err {
5747 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
5748 _ => panic!("expected TypeError"),
5749 }
5750 }
5751
5752 #[test]
5753 fn as_nix_string_errors_on_thunk() {
5754 let root = rnix::Root::parse(r#""x""#);
5755 let expr = root.tree().expr().unwrap();
5756 let thunk = Thunk::new_suspended(expr, Env::new());
5757 let val = Value::Thunk(thunk);
5758 assert!(val.as_nix_string().is_err());
5759 }
5760
5761 #[test]
5762 fn as_attrs_errors_on_thunk() {
5763 let root = rnix::Root::parse("{}");
5764 let expr = root.tree().expr().unwrap();
5765 let thunk = Thunk::new_suspended(expr, Env::new());
5766 let val = Value::Thunk(thunk);
5767 assert!(val.as_attrs().is_err());
5768 }
5769
5770 #[test]
5771 fn as_list_errors_on_thunk() {
5772 let root = rnix::Root::parse("[]");
5773 let expr = root.tree().expr().unwrap();
5774 let thunk = Thunk::new_suspended(expr, Env::new());
5775 let val = Value::Thunk(thunk);
5776 assert!(val.as_list().is_err());
5777 }
5778
5779 #[test]
5782 fn as_nix_string_ok_on_string() {
5783 let v = Value::string("hi");
5784 let ns = v.as_nix_string().unwrap();
5785 assert_eq!(ns.as_str(), "hi");
5786 }
5787
5788 #[test]
5789 fn as_nix_string_errors_on_int() {
5790 let v = Value::Int(1);
5791 match v.as_nix_string() {
5792 Err(EvalError::TypeMismatch { expected, got }) => {
5793 assert_eq!(expected, "string");
5794 assert_eq!(got, "int");
5795 }
5796 _ => panic!("expected TypeMismatch"),
5797 }
5798 }
5799
5800 #[test]
5805 fn oncecell_cache_populated_after_force() {
5806 let root = rnix::Root::parse("42");
5807 let expr = root.tree().expr().unwrap();
5808 let thunk = Thunk::new_suspended(expr, Env::new());
5809 assert!(thunk.0.cache.get().is_none());
5811 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5812 assert!(thunk.0.cache.get().is_some());
5814 }
5815
5816 #[test]
5817 fn oncecell_cache_matches_force_result() {
5818 let root = rnix::Root::parse("1 + 2");
5819 let expr = root.tree().expr().unwrap();
5820 let thunk = Thunk::new_suspended(expr, Env::new());
5821 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5822 let cached = thunk.0.cache.get().unwrap();
5823 assert_eq!((**cached).clone().into_value(), forced);
5826 }
5827
5828 #[test]
5829 fn oncecell_new_evaluated_prepopulates_cache() {
5830 let thunk = Thunk::new_evaluated(Value::Int(77));
5831 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
5833 assert_eq!(**cached, Concrete::Int(77));
5834 }
5835
5836 #[test]
5837 fn oncecell_is_evaluated_uses_cache() {
5838 let thunk = Thunk::new_evaluated(Value::Bool(false));
5839 assert!(thunk.is_evaluated());
5841 assert!(thunk.0.cache.get().is_some());
5842 }
5843
5844 #[test]
5845 fn oncecell_already_evaluated_returns_cached_without_repr() {
5846 let thunk = Thunk::new_evaluated(Value::Int(55));
5850 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
5851 assert_eq!(result.unwrap(), Value::Int(55));
5852 }
5853
5854 #[test]
5859 fn with_scope_created_with_empty_cache() {
5860 let thunk = Thunk::new_suspended(
5862 rnix::Root::parse("{}").tree().expr().unwrap(),
5863 Env::new(),
5864 );
5865 let env = Env::new().with_scope(Value::Thunk(thunk));
5866 let scope = &env.0.with_scopes[0];
5867 assert!(scope.cached.borrow().is_none());
5868 }
5869
5870 #[test]
5871 fn with_scope_concrete_pre_populates_cache() {
5872 let mut attrs = NixAttrs::new();
5874 attrs.insert("x".to_string(), Value::Int(1));
5875 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5876 let scope = &env.0.with_scopes[0];
5877 assert!(scope.cached.borrow().is_some());
5878 }
5879
5880 #[test]
5881 fn with_scope_first_lookup_populates_cache() {
5882 let mut attrs = NixAttrs::new();
5883 attrs.insert("x".to_string(), Value::Int(42));
5884 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5885 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5887 let _ = env.lookup("x");
5889 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5890 }
5891
5892 #[test]
5893 fn with_scope_second_lookup_uses_cache() {
5894 let mut attrs = NixAttrs::new();
5895 attrs.insert("x".to_string(), Value::Int(10));
5896 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5897 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
5899 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5900 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
5902 }
5903
5904 #[test]
5905 fn with_scope_child_shares_cache_via_rc() {
5906 let mut attrs = NixAttrs::new();
5907 attrs.insert("shared".to_string(), Value::Int(7));
5908 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5909 let child = parent.child();
5910 let _ = parent.lookup("shared");
5912 assert!(child.0.with_scopes[0].cached.borrow().is_some());
5915 }
5916
5917 #[test]
5918 fn with_scope_innermost_checked_first() {
5919 let mut outer = NixAttrs::new();
5920 outer.insert("x".to_string(), Value::Int(1));
5921 outer.insert("y".to_string(), Value::Int(100));
5922 let mut inner = NixAttrs::new();
5923 inner.insert("x".to_string(), Value::Int(2));
5924 let env = Env::new()
5925 .with_scope(Value::Attrs(Rc::new(outer)))
5926 .with_scope(Value::Attrs(Rc::new(inner)));
5927 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
5929 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
5931 }
5932
5933 #[test]
5938 fn fxhashmap_nixattrs_new_creates_empty() {
5939 let a = NixAttrs::new();
5940 assert!(a.is_empty());
5941 assert_eq!(a.len(), 0);
5942 assert!(a.inner().is_empty());
5944 }
5945
5946 #[test]
5947 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
5948 let mut a = NixAttrs::new();
5949 a.insert("mykey".to_string(), Value::Int(42));
5950 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
5951 }
5952
5953 #[test]
5954 fn fxhashmap_contains_key_with_interned_keys() {
5955 let mut a = NixAttrs::new();
5956 a.insert("alpha".to_string(), Value::Int(1));
5957 let sym = intern("alpha");
5958 assert!(a.inner().contains_key(&sym));
5959 let missing_sym = intern("beta");
5960 assert!(!a.inner().contains_key(&missing_sym));
5961 }
5962
5963 #[test]
5964 fn fxhashmap_remove_returns_value() {
5965 let mut a = NixAttrs::new();
5966 a.insert("key".to_string(), Value::Int(99));
5967 let removed = a.remove("key");
5968 assert_eq!(removed, Some(Value::Int(99)));
5969 assert!(a.is_empty());
5970 }
5971
5972 #[test]
5973 fn fxhashmap_keys_returns_sorted_strings() {
5974 let mut a = NixAttrs::new();
5975 a.insert("zulu".to_string(), Value::Int(1));
5976 a.insert("alpha".to_string(), Value::Int(2));
5977 a.insert("mike".to_string(), Value::Int(3));
5978 let keys: Vec<String> = a.keys().collect();
5979 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
5980 }
5981
5982 #[test]
5983 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
5984 let mut a = NixAttrs::new();
5985 a.insert("b".to_string(), Value::Int(2));
5986 a.insert("a".to_string(), Value::Int(1));
5987 let pairs: Vec<(String, &Value)> = a.iter().collect();
5988 assert_eq!(pairs.len(), 2);
5989 assert_eq!(pairs[0].0, "a");
5990 assert_eq!(*pairs[0].1, Value::Int(1));
5991 assert_eq!(pairs[1].0, "b");
5992 assert_eq!(*pairs[1].1, Value::Int(2));
5993 }
5994
5995 #[test]
5996 fn fxhashmap_update_merges_correctly() {
5997 let mut left = NixAttrs::new();
5998 left.insert("a".to_string(), Value::Int(1));
5999 left.insert("b".to_string(), Value::Int(2));
6000 let mut right = NixAttrs::new();
6001 right.insert("b".to_string(), Value::Int(20));
6002 right.insert("c".to_string(), Value::Int(3));
6003 let merged = left.update(&right);
6004 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6005 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6007 assert_eq!(merged.len(), 3);
6008 }
6009
6010 #[test]
6011 fn fxhashmap_from_iterator_collects_with_interning() {
6012 let pairs = vec![
6013 ("x".to_string(), Value::Int(10)),
6014 ("y".to_string(), Value::Int(20)),
6015 ("z".to_string(), Value::Int(30)),
6016 ];
6017 let attrs: NixAttrs = pairs.into_iter().collect();
6018 assert_eq!(attrs.len(), 3);
6019 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6020 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6021 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6022 let sym_x = intern("x");
6024 assert!(attrs.inner().contains_key(&sym_x));
6025 }
6026
6027 #[test]
6032 fn smallvec_context_empty() {
6033 let ctx = StringContext::new();
6034 assert!(ctx.is_empty());
6035 assert_eq!(ctx.len(), 0);
6036 assert_eq!(ctx.elements().len(), 0);
6037 }
6038
6039 #[test]
6040 fn smallvec_context_single_element_inline() {
6041 let mut ctx = StringContext::new();
6042 ctx.add_plain("/nix/store/single");
6043 assert_eq!(ctx.len(), 1);
6044 assert!(!ctx.is_empty());
6046 }
6047
6048 #[test]
6049 fn smallvec_context_two_elements_still_inline() {
6050 let mut ctx = StringContext::new();
6051 ctx.add_plain("/nix/store/one");
6052 ctx.add_output("/nix/store/two.drv", "out");
6053 assert_eq!(ctx.len(), 2);
6054 }
6055
6056 #[test]
6057 fn smallvec_context_three_plus_spills_to_heap() {
6058 let mut ctx = StringContext::new();
6059 ctx.add_plain("/nix/store/a");
6060 ctx.add_plain("/nix/store/b");
6061 ctx.add_drv_deep("/nix/store/c.drv");
6062 assert_eq!(ctx.len(), 3);
6063 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6065 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6066 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6067 }
6068
6069 #[test]
6070 fn smallvec_context_merge_deduplicates() {
6071 let mut ctx1 = StringContext::new();
6072 ctx1.add_plain("/nix/store/dup");
6073 ctx1.add_output("/nix/store/x.drv", "out");
6074 let mut ctx2 = StringContext::new();
6075 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6078 assert_eq!(ctx1.len(), 3); }
6080
6081 #[test]
6082 fn smallvec_context_add_plain_output_drv_deep() {
6083 let mut ctx = StringContext::new();
6084 ctx.add_plain("/nix/store/plain");
6085 assert_eq!(ctx.len(), 1);
6086 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6087
6088 ctx.add_output("/nix/store/out.drv", "lib");
6089 assert_eq!(ctx.len(), 2);
6090 assert!(ctx.elements().contains(&ContextElement::Output {
6091 drv: SmolStr::from("/nix/store/out.drv"),
6092 output: SmolStr::from("lib"),
6093 }));
6094
6095 ctx.add_drv_deep("/nix/store/deep.drv");
6096 assert_eq!(ctx.len(), 3);
6097 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6098 }
6099
6100 #[test]
6105 fn rc_list_constructor_wraps_in_rc() {
6106 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6107 match &v {
6108 Value::List(rc) => {
6109 assert_eq!(rc.len(), 2);
6110 assert_eq!(Rc::strong_count(rc), 1);
6111 }
6112 _ => panic!("expected List"),
6113 }
6114 }
6115
6116 #[test]
6117 fn rc_list_clone_is_refcount_bump() {
6118 let v = Value::list(vec![Value::Int(10)]);
6119 let rc1 = match &v {
6120 Value::List(rc) => rc.clone(),
6121 _ => panic!("expected List"),
6122 };
6123 let v2 = v.clone();
6124 let rc2 = match &v2 {
6125 Value::List(rc) => rc.clone(),
6126 _ => panic!("expected List"),
6127 };
6128 assert!(Rc::ptr_eq(&rc1, &rc2));
6130 assert!(Rc::strong_count(&rc1) >= 2);
6133 }
6134
6135 #[test]
6136 fn rc_list_as_list_returns_slice() {
6137 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6138 let slice = v.as_list().unwrap();
6139 assert_eq!(slice.len(), 3);
6140 assert_eq!(slice[0], Value::Int(1));
6141 assert_eq!(slice[1], Value::Int(2));
6142 assert_eq!(slice[2], Value::Int(3));
6143 }
6144
6145 #[test]
6146 fn rc_list_from_vec_wraps_in_rc() {
6147 let items = vec![Value::Bool(true), Value::Bool(false)];
6148 let v: Value = items.into();
6149 match &v {
6150 Value::List(rc) => {
6151 assert_eq!(rc.len(), 2);
6152 assert_eq!(Rc::strong_count(rc), 1);
6153 }
6154 _ => panic!("expected List"),
6155 }
6156 }
6157
6158 #[test]
6163 fn intern_same_string_returns_same_symbol() {
6164 let s1 = intern("hello_intern_test");
6165 let s2 = intern("hello_intern_test");
6166 assert_eq!(s1, s2);
6167 }
6168
6169 #[test]
6170 fn intern_different_strings_returns_different_symbols() {
6171 let s1 = intern("unique_str_a_9182");
6172 let s2 = intern("unique_str_b_9182");
6173 assert_ne!(s1, s2);
6174 }
6175
6176 #[test]
6177 fn resolve_roundtrips_correctly() {
6178 let sym = intern("roundtrip_test_str");
6179 let resolved = resolve(sym);
6180 assert_eq!(resolved, "roundtrip_test_str");
6181 }
6182
6183 #[test]
6184 fn intern_cached_same_offset_returns_cached_symbol() {
6185 let sid = next_source_id();
6186 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6187 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6188 assert_eq!(sym1, sym2);
6189 }
6190
6191 #[test]
6192 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6193 let sid = next_source_id();
6196 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6197 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6198 assert_eq!(sym1, sym2);
6200 }
6201
6202 #[test]
6203 fn clear_ident_cache_clears() {
6204 let sid = next_source_id();
6205 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6206 clear_ident_cache();
6207 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6211 let resolved = resolve(sym2);
6212 assert_eq!(resolved, "to_be_cleared_99");
6213 }
6214
6215 #[test]
6216 fn next_source_id_increments_monotonically() {
6217 let id1 = next_source_id();
6218 let id2 = next_source_id();
6219 let id3 = next_source_id();
6220 assert_eq!(id2, id1 + 1);
6221 assert_eq!(id3, id2 + 1);
6222 }
6223
6224 #[test]
6229 fn env_new_creates_empty_bindings() {
6230 let env = Env::new();
6231 assert!(env.0.bindings.is_empty());
6232 assert!(env.0.with_scopes.is_empty());
6233 assert!(env.eval_file().is_none());
6234 }
6235
6236 #[test]
6237 fn env_bind_lookup_roundtrip() {
6238 let mut env = Env::new();
6239 env.bind("foo".to_string(), Value::Int(42));
6240 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6241 assert_eq!(env.lookup("bar"), None);
6242 }
6243
6244 #[test]
6245 fn env_child_inherits_parent_bindings_flattened() {
6246 let mut parent = Env::new();
6247 parent.bind("a".to_string(), Value::Int(1));
6248 parent.bind("b".to_string(), Value::Int(2));
6249 let child = parent.child();
6250 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6252 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6253 let sym_a = intern("a");
6255 assert!(child.0.bindings.contains_key(&sym_a));
6256 }
6257
6258 #[test]
6259 fn env_child_inherits_with_scopes() {
6260 let mut attrs = NixAttrs::new();
6261 attrs.insert("ws".to_string(), Value::Int(10));
6262 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6263 let child = parent.child();
6264 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6266 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6267 }
6268
6269 #[test]
6270 fn env_lookup_sym_fast_path_matches_lookup() {
6271 let mut env = Env::new();
6272 env.bind("target".to_string(), Value::Int(88));
6273 let sym = intern("target");
6274 let via_lookup = env.lookup("target");
6275 let via_sym = env.lookup_sym(sym);
6276 assert_eq!(via_lookup, via_sym);
6277 assert_eq!(via_sym, Some(Value::Int(88)));
6278 }
6279
6280 #[test]
6281 fn env_lookup_sym_with_scope_fallback() {
6282 let mut attrs = NixAttrs::new();
6283 attrs.insert("sym_ws".to_string(), Value::Int(33));
6284 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6285 let sym = intern("sym_ws");
6286 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6287 }
6288
6289 #[test]
6290 fn env_with_scope_ordering_multiple_innermost_wins() {
6291 let mut a1 = NixAttrs::new();
6292 a1.insert("x".to_string(), Value::Int(1));
6293 let mut a2 = NixAttrs::new();
6294 a2.insert("x".to_string(), Value::Int(2));
6295 let mut a3 = NixAttrs::new();
6296 a3.insert("x".to_string(), Value::Int(3));
6297 let env = Env::new()
6298 .with_scope(Value::Attrs(Rc::new(a1)))
6299 .with_scope(Value::Attrs(Rc::new(a2)))
6300 .with_scope(Value::Attrs(Rc::new(a3)));
6301 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6303 }
6304
6305 #[test]
6306 fn env_lookup_sym_not_found_returns_none() {
6307 let env = Env::new();
6308 let sym = intern("nonexistent_sym_99");
6309 assert_eq!(env.lookup_sym(sym), None);
6310 }
6311
6312 #[test]
6313 fn env_lookup_sym_lexical_wins_over_with_scope() {
6314 let mut attrs = NixAttrs::new();
6315 attrs.insert("priority".to_string(), Value::Int(1));
6316 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6317 env.bind("priority".to_string(), Value::Int(99));
6318 let sym = intern("priority");
6319 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6320 }
6321}