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)
3624 .or_else(|| {
3625 canon
3626 .file_name()
3627 .map(|n| sui_compat::source::strip_store_hash_prefix(
3628 &n.to_string_lossy()).to_string())
3629 })
3630 .unwrap_or_else(|| "source".to_string());
3631 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3632 .map_err(|e| {
3633 EvalError::TypeError(format!(
3634 "copy-to-store coercion of '{}': {e}",
3635 canon.display()
3636 ))
3637 })?;
3638 ctx.add_plain(src.store_path.clone());
3639 src.store_path
3640 } else {
3641 ctx.add_plain(raw.to_string());
3642 raw.to_string()
3643 }
3644 }
3645 Value::Int(n) => n.to_string(),
3646 Value::Float(f) => format!("{f:.6}"),
3652 Value::Bool(true) => "1".to_string(),
3653 Value::Bool(false) => String::new(),
3654 Value::Null => String::new(),
3655 Value::Attrs(attrs) => {
3656 if let Some(to_str) = attrs.get("__toString") {
3657 let result =
3658 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3659 let forced = crate::eval::force_value(&result)?;
3660 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3661 ctx.merge(&c);
3662 s
3663 } else if let Some(out_path) = attrs.get("outPath") {
3664 let forced = crate::eval::force_value(out_path)?;
3665 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3666 ctx.merge(&c);
3667 s
3668 } else {
3669 return Err(EvalError::TypeError(
3670 "cannot coerce set to string (no __toString or outPath)".into(),
3671 ));
3672 }
3673 }
3674 Value::List(items) => {
3675 let mut parts = Vec::new();
3676 for item in items.iter() {
3677 let forced = crate::eval::force_value(item)?;
3678 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3679 ctx.merge(&c);
3680 parts.push(s);
3681 }
3682 parts.join(" ")
3683 }
3684 Value::Thunk(_) => {
3685 let forced = crate::eval::force_value(self)?;
3687 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3688 ctx.merge(&c);
3689 s
3690 }
3691 other => {
3692 return Err(EvalError::TypeError(format!(
3693 "cannot coerce {} to string",
3694 other.type_name()
3695 )));
3696 }
3697 };
3698 Ok((s, ctx))
3699 }
3700}
3701
3702impl From<&serde_json::Value> for Value {
3705 fn from(json: &serde_json::Value) -> Self {
3706 match json {
3707 serde_json::Value::Null => Value::Null,
3708 serde_json::Value::Bool(b) => Value::Bool(*b),
3709 serde_json::Value::Number(n) => {
3710 if let Some(i) = n.as_i64() {
3711 Value::Int(i)
3712 } else {
3713 Value::Float(n.as_f64().unwrap_or(0.0))
3714 }
3715 }
3716 serde_json::Value::String(s) => Value::string(s.clone()),
3717 serde_json::Value::Array(arr) => {
3718 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3719 }
3720 serde_json::Value::Object(obj) => {
3721 let mut attrs = NixAttrs::new();
3722 for (k, v) in obj {
3723 attrs.insert(k.clone(), Value::from(v));
3724 }
3725 Value::Attrs(Rc::new(attrs))
3726 }
3727 }
3728 }
3729}
3730
3731impl From<&toml::Value> for Value {
3732 fn from(v: &toml::Value) -> Self {
3733 match v {
3734 toml::Value::String(s) => Value::string(s.clone()),
3735 toml::Value::Integer(n) => Value::Int(*n),
3736 toml::Value::Float(f) => Value::Float(*f),
3737 toml::Value::Boolean(b) => Value::Bool(*b),
3738 toml::Value::Array(arr) => {
3739 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3740 }
3741 toml::Value::Table(t) => {
3742 let mut attrs = NixAttrs::new();
3743 for (k, val) in t {
3744 attrs.insert(k.clone(), Value::from(val));
3745 }
3746 Value::Attrs(Rc::new(attrs))
3747 }
3748 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
3749 }
3750 }
3751}
3752
3753
3754impl From<bool> for Value {
3757 fn from(b: bool) -> Self {
3758 Value::Bool(b)
3759 }
3760}
3761
3762impl From<i64> for Value {
3763 fn from(n: i64) -> Self {
3764 Value::Int(n)
3765 }
3766}
3767
3768impl From<f64> for Value {
3769 fn from(f: f64) -> Self {
3770 Value::Float(f)
3771 }
3772}
3773
3774impl From<NixString> for Value {
3775 fn from(s: NixString) -> Self {
3776 Value::String(Rc::new(s))
3777 }
3778}
3779
3780impl From<NixAttrs> for Value {
3781 fn from(attrs: NixAttrs) -> Self {
3782 Value::Attrs(Rc::new(attrs))
3783 }
3784}
3785
3786impl From<Vec<Value>> for Value {
3787 fn from(list: Vec<Value>) -> Self {
3788 Value::List(Rc::new(NixList::new(list)))
3789 }
3790}
3791
3792impl PartialEq for Value {
3793 fn eq(&self, other: &Self) -> bool {
3794 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
3796 if Rc::ptr_eq(&a.0, &b.0) { return true; }
3797 }
3798 let l = self.demand().unwrap_or(Concrete::Null);
3801 let r = other.demand().unwrap_or(Concrete::Null);
3802 l == r
3803 }
3804}
3805
3806impl fmt::Display for Value {
3807 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3808 match self {
3809 Value::Null => write!(f, "null"),
3810 Value::Bool(b) => write!(f, "{b}"),
3811 Value::Int(n) => write!(f, "{n}"),
3812 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
3813 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
3814 Value::Path(p) => write!(f, "{p}"),
3815 Value::List(items) => {
3816 write!(f, "[ ")?;
3817 for item in items.iter() {
3818 write!(f, "{item} ")?;
3819 }
3820 write!(f, "]")
3821 }
3822 Value::Attrs(attrs) => {
3823 write!(f, "{{ ")?;
3824 for (k, v) in attrs.iter() {
3825 write!(f, "{k} = {v}; ")?;
3826 }
3827 write!(f, "}}")
3828 }
3829 Value::Lambda(_) => write!(f, "<<lambda>>"),
3830 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
3831 Value::Thunk(thunk) => {
3832 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
3833 Ok(v) => write!(f, "{v}"),
3834 Err(_) => write!(f, "<<thunk:error>>"),
3835 }
3836 }
3837 }
3838 }
3839}
3840
3841#[cfg(test)]
3842mod tests {
3843 use super::*;
3844 use std::rc::Rc;
3845
3846 #[test]
3849 fn value_is_16_bytes() {
3850 assert_eq!(std::mem::size_of::<Value>(), 16);
3851 }
3852
3853 #[test]
3856 fn to_json_null() {
3857 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
3858 }
3859
3860 #[test]
3861 fn to_json_bool() {
3862 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
3863 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
3864 }
3865
3866 #[test]
3867 fn to_json_int() {
3868 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
3869 }
3870
3871 #[test]
3872 fn to_json_float() {
3873 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
3874 }
3875
3876 #[test]
3877 fn to_json_string() {
3878 assert_eq!(
3879 Value::string("hello").to_json(),
3880 serde_json::Value::String("hello".to_string()),
3881 );
3882 }
3883
3884 #[test]
3885 fn to_json_path() {
3886 assert_eq!(
3887 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
3888 serde_json::Value::String("/nix/store".to_string()),
3889 );
3890 }
3891
3892 #[test]
3893 fn to_json_list() {
3894 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
3895 assert_eq!(v.to_json(), serde_json::json!([1, true]));
3896 }
3897
3898 #[test]
3899 fn to_json_attrs() {
3900 let mut attrs = NixAttrs::new();
3901 attrs.insert("a".to_string(), Value::Int(1));
3902 let v = Value::Attrs(Rc::new(attrs));
3903 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
3904 }
3905
3906 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
3909 let mut a = NixAttrs::new();
3910 a.insert("type".to_string(), Value::string("derivation"));
3911 a.insert("outPath".to_string(), Value::string(out_path));
3912 a.insert(extra_key.to_string(), Value::Int(extra_val));
3913 Value::Attrs(Rc::new(a))
3914 }
3915
3916 #[test]
3917 fn derivations_same_outpath_differing_attrs_are_equal() {
3918 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
3925 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
3926 assert!(a == b, "same-outPath derivations must compare equal");
3927 assert!(!(a != b));
3928 }
3929
3930 #[test]
3931 fn derivations_differing_outpath_are_unequal() {
3932 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
3933 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
3934 assert!(a != b, "different-outPath derivations must compare unequal");
3935 }
3936
3937 #[test]
3938 fn non_derivation_attrs_with_outpath_use_structural_eq() {
3939 let mut a = NixAttrs::new();
3942 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
3943 a.insert("foo".to_string(), Value::Int(1));
3944 let mut b = NixAttrs::new();
3945 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
3946 b.insert("foo".to_string(), Value::Int(2));
3947 assert!(
3948 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
3949 "non-derivation attrs with equal outPath but differing foo must be unequal",
3950 );
3951 }
3952
3953 #[test]
3959 fn attrs_eq_borrow_result_matches_multi_key() {
3960 let mk = || {
3963 let mut inner = NixAttrs::new();
3964 inner.insert("n".to_string(), Value::Int(7));
3965 let mut a = NixAttrs::new();
3966 a.insert("a".to_string(), Value::Int(1));
3967 a.insert("b".to_string(), Value::string("two"));
3968 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
3969 Value::Attrs(Rc::new(a))
3970 };
3971 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
3972
3973 let mut b = NixAttrs::new();
3975 b.insert("a".to_string(), Value::Int(1));
3976 b.insert("b".to_string(), Value::string("TWO"));
3977 let mut a2 = NixAttrs::new();
3978 a2.insert("a".to_string(), Value::Int(1));
3979 a2.insert("b".to_string(), Value::string("two"));
3980 assert!(
3981 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
3982 "attrsets differing in one value must be unequal (borrow path)",
3983 );
3984
3985 let mut a3 = NixAttrs::new();
3987 a3.insert("a".to_string(), Value::Int(1));
3988 let mut b3 = NixAttrs::new();
3989 b3.insert("a".to_string(), Value::Int(1));
3990 b3.insert("extra".to_string(), Value::Int(9));
3991 assert!(
3992 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
3993 "attrsets differing in key set must be unequal (borrow path)",
3994 );
3995 }
3996
3997 #[test]
3998 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
3999 let boom = Value::Thunk(Thunk::new_native(|| {
4010 Err(EvalError::Throw("kaboom".to_string()))
4011 }));
4012 let mut a = NixAttrs::new();
4013 a.insert("x".to_string(), Value::Int(1));
4014 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4016 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4018 let va = Value::Attrs(Rc::new(a));
4022 let vb = Value::Attrs(Rc::new(b));
4023 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4024 }
4025
4026 #[test]
4027 fn attrs_eq_borrow_overlay_still_compares() {
4028 let mut base = NixAttrs::new();
4032 base.insert("a".to_string(), Value::Int(1));
4033 let mut over = NixAttrs::new();
4034 over.insert("b".to_string(), Value::Int(2));
4035 let merged = base.overlay(over);
4038 let mut flat = NixAttrs::new();
4039 flat.insert("a".to_string(), Value::Int(1));
4040 flat.insert("b".to_string(), Value::Int(2));
4041 assert!(
4042 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4043 "overlay and equivalent flat attrset must compare equal (borrow path)",
4044 );
4045 }
4046
4047 #[test]
4048 fn to_json_lambda() {
4049 let root = rnix::Root::parse("x: x");
4051 let expr = root.tree().expr().unwrap();
4052 let lambda = match expr {
4053 rnix::ast::Expr::Lambda(l) => l,
4054 _ => panic!("expected lambda"),
4055 };
4056 let closure = Closure {
4057 param: lambda.param().unwrap(),
4058 body: lambda.body().unwrap(),
4059 env: Env::new(),
4060 };
4061 assert_eq!(
4062 Value::Lambda(Rc::new(closure)).to_json(),
4063 serde_json::Value::String("<lambda>".to_string()),
4064 );
4065 }
4066
4067 #[test]
4068 fn to_json_builtin() {
4069 let b = BuiltinFn {
4070 name: "test",
4071 func: Rc::new(|_| Ok(Value::Null)),
4072 };
4073 assert_eq!(
4074 Value::Builtin(Box::new(b)).to_json(),
4075 serde_json::Value::String("<builtin test>".to_string()),
4076 );
4077 }
4078
4079 #[test]
4082 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4083
4084 #[test]
4085 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4086
4087 #[test]
4088 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4089
4090 #[test]
4091 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4092
4093 #[test]
4094 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4095
4096 #[test]
4097 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4098
4099 #[test]
4100 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4101
4102 #[test]
4103 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4104
4105 #[test]
4106 fn type_name_lambda() {
4107 let root = rnix::Root::parse("x: x");
4108 let expr = root.tree().expr().unwrap();
4109 let lambda = match expr {
4110 rnix::ast::Expr::Lambda(l) => l,
4111 _ => panic!("expected lambda"),
4112 };
4113 let closure = Closure {
4114 param: lambda.param().unwrap(),
4115 body: lambda.body().unwrap(),
4116 env: Env::new(),
4117 };
4118 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4119 }
4120
4121 #[test]
4122 fn type_name_builtin() {
4123 let b = BuiltinFn {
4124 name: "t",
4125 func: Rc::new(|_| Ok(Value::Null)),
4126 };
4127 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4128 }
4129
4130 #[test]
4133 fn as_bool_error_on_non_bool() {
4134 assert!(Value::Int(1).as_bool().is_err());
4135 assert!(Value::string("true").as_bool().is_err());
4136 }
4137
4138 #[test]
4139 fn as_int_error_on_non_int() {
4140 assert!(Value::Bool(true).as_int().is_err());
4141 assert!(Value::Float(1.0).as_int().is_err());
4142 }
4143
4144 #[test]
4145 fn as_string_error_on_non_string() {
4146 assert!(Value::Int(42).as_string().is_err());
4147 assert!(Value::Null.as_string().is_err());
4148 }
4149
4150 #[test]
4151 fn as_attrs_error_on_non_attrs() {
4152 assert!(Value::Int(1).as_attrs().is_err());
4153 assert!(Value::list(vec![]).as_attrs().is_err());
4154 }
4155
4156 #[test]
4157 fn as_list_error_on_non_list() {
4158 assert!(Value::Int(1).as_list().is_err());
4159 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4160 }
4161
4162 #[test]
4165 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4166 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4168 assert!(left.is_uniquely_owned_list());
4169 let right = [Value::Int(3), Value::Int(4)];
4170 let out = super::concat_lists(left, &right).unwrap();
4171 assert_eq!(
4172 out.as_list().unwrap(),
4173 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4174 );
4175 }
4176
4177 #[test]
4178 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4179 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4182 let left = Value::List(Rc::clone(&shared));
4183 assert!(!left.is_uniquely_owned_list());
4184 let right = [Value::Int(3)];
4185 let out = super::concat_lists(left, &right).unwrap();
4186 assert_eq!(
4187 out.as_list().unwrap(),
4188 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4189 );
4190 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4192 }
4193
4194 #[test]
4195 fn concat_lists_empty_operands() {
4196 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4197 assert!(out.as_list().unwrap().is_empty());
4198 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4199 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4200 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4201 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4202 }
4203
4204 #[test]
4205 fn concat_lists_non_list_left_errors() {
4206 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4207 }
4208
4209 #[test]
4210 fn concat_lists_preserves_element_identity() {
4211 let inner = Rc::new(NixString::plain("x"));
4213 let a = Value::String(Rc::clone(&inner));
4214 let left = Value::list(vec![a]);
4215 let out = super::concat_lists(left, &[]).unwrap();
4216 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4217 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4218 } else {
4219 panic!("expected string element");
4220 }
4221 }
4222
4223 #[test]
4226 fn to_float_coerces_int() {
4227 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4228 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4229 assert!(Value::string("x").to_float().is_err());
4230 }
4231
4232 #[test]
4235 fn partial_eq_int_float_cross() {
4236 assert_eq!(Value::Int(3), Value::Float(3.0));
4237 assert_eq!(Value::Float(3.0), Value::Int(3));
4238 assert_ne!(Value::Int(3), Value::Float(3.5));
4239 }
4240
4241 #[test]
4242 fn partial_eq_different_types_not_equal() {
4243 assert_ne!(Value::Int(1), Value::string("1"));
4244 assert_ne!(Value::Bool(true), Value::Int(1));
4245 assert_ne!(Value::Null, Value::Bool(false));
4246 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4247 }
4248
4249 #[test]
4252 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4253
4254 #[test]
4255 fn display_bool() {
4256 assert_eq!(format!("{}", Value::Bool(true)), "true");
4257 assert_eq!(format!("{}", Value::Bool(false)), "false");
4258 }
4259
4260 #[test]
4261 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4262
4263 #[test]
4264 fn display_float() {
4265 let s = format!("{}", Value::Float(3.14));
4266 assert!(s.contains("3.14"));
4267 }
4268
4269 #[test]
4270 fn display_string() {
4271 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4272 }
4273
4274 #[test]
4275 fn display_string_with_escapes() {
4276 let v = Value::string("a\"b\\c");
4277 let s = format!("{v}");
4278 assert!(s.contains("\\\""));
4279 assert!(s.contains("\\\\"));
4280 }
4281
4282 #[test]
4283 fn display_path() {
4284 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4285 }
4286
4287 #[test]
4288 fn display_list() {
4289 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4290 assert_eq!(format!("{v}"), "[ 1 2 ]");
4291 }
4292
4293 #[test]
4294 fn display_attrs() {
4295 let mut attrs = NixAttrs::new();
4296 attrs.insert("x".to_string(), Value::Int(1));
4297 let v = Value::Attrs(Rc::new(attrs));
4298 assert_eq!(format!("{v}"), "{ x = 1; }");
4299 }
4300
4301 #[test]
4302 fn display_lambda() {
4303 let root = rnix::Root::parse("x: x");
4304 let expr = root.tree().expr().unwrap();
4305 let lambda = match expr {
4306 rnix::ast::Expr::Lambda(l) => l,
4307 _ => panic!("expected lambda"),
4308 };
4309 let closure = Closure {
4310 param: lambda.param().unwrap(),
4311 body: lambda.body().unwrap(),
4312 env: Env::new(),
4313 };
4314 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4315 }
4316
4317 #[test]
4318 fn display_builtin() {
4319 let b = BuiltinFn {
4320 name: "add",
4321 func: Rc::new(|_| Ok(Value::Null)),
4322 };
4323 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4324 }
4325
4326 #[test]
4329 fn nixattrs_update_merging() {
4330 let mut a = NixAttrs::new();
4331 a.insert("x".to_string(), Value::Int(1));
4332 a.insert("y".to_string(), Value::Int(2));
4333 let mut b = NixAttrs::new();
4334 b.insert("y".to_string(), Value::Int(99));
4335 b.insert("z".to_string(), Value::Int(3));
4336 let merged = a.update(&b);
4337 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4338 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4339 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4340 assert_eq!(merged.len(), 3);
4341 }
4342
4343 #[test]
4344 fn nixattrs_contains_key() {
4345 let mut a = NixAttrs::new();
4346 a.insert("foo".to_string(), Value::Null);
4347 assert!(a.contains_key("foo"));
4348 assert!(!a.contains_key("bar"));
4349 }
4350
4351 #[test]
4354 fn env_lookup_through_parent_chain() {
4355 let mut root = Env::new();
4356 root.bind("a".to_string(), Value::Int(1));
4357 let mut child = root.child();
4358 child.bind("b".to_string(), Value::Int(2));
4359 let grandchild = child.child();
4360 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4362 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4363 assert_eq!(grandchild.lookup("c"), None);
4364 }
4365
4366 #[test]
4367 fn env_with_scope_lookup() {
4368 let mut attrs = NixAttrs::new();
4369 attrs.insert("x".to_string(), Value::Int(42));
4370 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4371 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4372 assert_eq!(env.lookup("y"), None);
4373 }
4374
4375 #[test]
4376 fn env_local_shadows_with_scope() {
4377 let mut attrs = NixAttrs::new();
4378 attrs.insert("x".to_string(), Value::Int(1));
4379 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4380 env.bind("x".to_string(), Value::Int(99));
4381 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4382 }
4383
4384 #[test]
4387 fn string_context_merge_combines_elements() {
4388 let mut ctx_a = StringContext::new();
4389 ctx_a.add_plain("/nix/store/aaa".to_string());
4390 let mut ctx_b = StringContext::new();
4391 ctx_b.add_plain("/nix/store/bbb".to_string());
4392 ctx_a.merge(&ctx_b);
4393 assert_eq!(ctx_a.len(), 2);
4394 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4395 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4396 }
4397
4398 #[test]
4399 fn string_context_merge_deduplicates() {
4400 let mut ctx = StringContext::new();
4401 ctx.add_plain("/nix/store/same".to_string());
4402 ctx.add_plain("/nix/store/same".to_string());
4403 assert_eq!(ctx.len(), 1);
4404 }
4405
4406 #[test]
4407 fn string_context_mixed_element_types() {
4408 let mut ctx = StringContext::new();
4409 ctx.add_plain("/nix/store/foo".to_string());
4410 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4411 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4412 assert_eq!(ctx.len(), 3);
4413 assert!(!ctx.is_empty());
4414 }
4415
4416 #[test]
4417 fn string_context_new_is_empty() {
4418 let ctx = StringContext::new();
4419 assert!(ctx.is_empty());
4420 assert_eq!(ctx.len(), 0);
4421 }
4422
4423 #[test]
4424 fn string_context_merge_zero_elements() {
4425 let mut ctx_a = StringContext::new();
4426 let ctx_b = StringContext::new();
4427 ctx_a.merge(&ctx_b);
4428 assert!(ctx_a.is_empty());
4429 }
4430
4431 #[test]
4432 fn string_context_merge_one_element() {
4433 let mut ctx = StringContext::new();
4434 let mut other = StringContext::new();
4435 other.add_plain("/nix/store/only".to_string());
4436 ctx.merge(&other);
4437 assert_eq!(ctx.len(), 1);
4438 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4439 }
4440
4441 #[test]
4442 fn string_context_merge_two_elements() {
4443 let mut ctx = StringContext::new();
4444 ctx.add_plain("/nix/store/a".to_string());
4445 let mut other = StringContext::new();
4446 other.add_plain("/nix/store/b".to_string());
4447 ctx.merge(&other);
4448 assert_eq!(ctx.len(), 2);
4449 }
4450
4451 #[test]
4452 fn string_context_merge_five_elements() {
4453 let mut ctx = StringContext::new();
4454 for i in 0..5 {
4455 ctx.add_plain(format!("/nix/store/path-{i}"));
4456 }
4457 assert_eq!(ctx.len(), 5);
4458 for i in 0..5 {
4459 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4460 }
4461 }
4462
4463 #[test]
4464 fn string_context_insert_deduplicates() {
4465 let mut ctx = StringContext::new();
4466 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4467 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4468 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4469 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4470 assert_eq!(ctx.len(), 2);
4471 }
4472
4473 #[test]
4474 fn nix_string_plain_has_no_context() {
4475 let s = NixString::plain("hello");
4476 assert!(!s.has_context());
4477 assert_eq!(s.as_str(), "hello");
4478 }
4479
4480 #[test]
4481 fn nix_string_with_context_reports_context() {
4482 let mut ctx = StringContext::new();
4483 ctx.add_plain("/nix/store/xyz".to_string());
4484 let s = NixString::with_context("hello", ctx);
4485 assert!(s.has_context());
4486 assert_eq!(s.as_str(), "hello");
4487 }
4488
4489 #[test]
4490 fn nix_string_display_shows_chars_only() {
4491 let mut ctx = StringContext::new();
4492 ctx.add_plain("/nix/store/abc".to_string());
4493 let s = NixString::with_context("visible", ctx);
4494 assert_eq!(format!("{s}"), "visible");
4495 }
4496
4497 #[test]
4498 fn nix_string_struct_eq_includes_context() {
4499 let plain = NixString::plain("hello");
4500 let mut ctx = StringContext::new();
4501 ctx.add_plain("/nix/store/xxx".to_string());
4502 let with_ctx = NixString::with_context("hello", ctx);
4503 assert_ne!(plain, with_ctx);
4505 }
4506
4507 #[test]
4508 fn value_string_eq_ignores_context() {
4509 let plain = Value::String(Rc::new(NixString::plain("hello")));
4510 let mut ctx = StringContext::new();
4511 ctx.add_plain("/nix/store/xxx".to_string());
4512 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4513 assert_eq!(plain, with_ctx);
4515 }
4516
4517 #[test]
4520 fn env_nested_with_inner_wins() {
4521 let mut outer_attrs = NixAttrs::new();
4522 outer_attrs.insert("x".to_string(), Value::Int(1));
4523 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4524 let mut inner_attrs = NixAttrs::new();
4525 inner_attrs.insert("x".to_string(), Value::Int(2));
4526 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4527 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4528 }
4529
4530 #[test]
4531 fn env_nested_with_fallback_to_outer() {
4532 let mut outer_attrs = NixAttrs::new();
4533 outer_attrs.insert("x".to_string(), Value::Int(1));
4534 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4535 let mut inner_attrs = NixAttrs::new();
4536 inner_attrs.insert("y".to_string(), Value::Int(2));
4537 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4538 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4539 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4540 }
4541
4542 #[test]
4543 fn env_lexical_binding_wins_over_all_with_scopes() {
4544 let mut outer_attrs = NixAttrs::new();
4545 outer_attrs.insert("x".to_string(), Value::Int(1));
4546 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4547 let mut inner_attrs = NixAttrs::new();
4548 inner_attrs.insert("x".to_string(), Value::Int(2));
4549 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4550 inner.bind("x".to_string(), Value::Int(99));
4551 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4552 }
4553
4554 #[test]
4555 fn env_parent_lexical_wins_over_child_with_scope() {
4556 let mut root = Env::new();
4557 root.bind("x".to_string(), Value::Int(10));
4558 let mut child_attrs = NixAttrs::new();
4559 child_attrs.insert("x".to_string(), Value::Int(20));
4560 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4561 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4562 }
4563
4564 #[test]
4565 fn env_deeply_nested_with_scopes_three_levels() {
4566 let mut a = NixAttrs::new();
4567 a.insert("x".to_string(), Value::Int(1));
4568 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4569
4570 let mut b = NixAttrs::new();
4571 b.insert("y".to_string(), Value::Int(2));
4572 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4573
4574 let mut c = NixAttrs::new();
4575 c.insert("z".to_string(), Value::Int(3));
4576 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4577
4578 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4579 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4580 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4581 assert_eq!(env3.lookup("w"), None);
4582 }
4583
4584 #[test]
4585 fn env_with_scope_does_not_pollute_bindings() {
4586 let mut attrs = NixAttrs::new();
4589 attrs.insert("x".to_string(), Value::Int(42));
4590 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4591 assert!(env.0.bindings.get(&intern("x")).is_none());
4593 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4595 }
4596
4597 #[test]
4598 fn env_lexical_binding_not_in_with_scopes() {
4599 let mut env = Env::new();
4601 env.bind("x".to_string(), Value::Int(42));
4602 assert!(env.0.with_scopes.is_empty());
4604 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4606 }
4607
4608 #[test]
4609 fn env_child_inherits_eval_file() {
4610 let mut env = Env::new();
4611 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4612 let child = env.child();
4613 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4614 }
4615
4616 #[test]
4617 fn env_new_has_no_parent_no_with() {
4618 let env = Env::new();
4619 assert_eq!(env.lookup("anything"), None);
4620 assert!(env.eval_file().is_none());
4621 }
4622
4623 #[test]
4626 fn thunk_new_suspended_is_not_evaluated() {
4627 let root = rnix::Root::parse("42");
4628 let expr = root.tree().expr().unwrap();
4629 let thunk = Thunk::new_suspended(expr, Env::new());
4630 assert!(!thunk.is_evaluated());
4631 }
4632
4633 #[test]
4634 fn thunk_new_evaluated_is_evaluated() {
4635 let thunk = Thunk::new_evaluated(Value::Int(42));
4636 assert!(thunk.is_evaluated());
4637 }
4638
4639 #[test]
4640 fn thunk_force_evaluates_suspended() {
4641 let root = rnix::Root::parse("42");
4642 let expr = root.tree().expr().unwrap();
4643 let thunk = Thunk::new_suspended(expr, Env::new());
4644 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4645 assert!(result.is_ok());
4646 assert_eq!(result.unwrap(), Value::Int(42));
4647 assert!(thunk.is_evaluated());
4648 }
4649
4650 #[test]
4651 fn thunk_force_memoizes_result() {
4652 let root = rnix::Root::parse("1 + 2");
4653 let expr = root.tree().expr().unwrap();
4654 let thunk = Thunk::new_suspended(expr, Env::new());
4655 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4656 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4657 assert_eq!(r1, Value::Int(3));
4658 assert_eq!(r2, Value::Int(3));
4659 }
4660
4661 #[test]
4662 fn thunk_force_already_evaluated_returns_value() {
4663 let thunk = Thunk::new_evaluated(Value::Bool(true));
4664 let result = thunk.force(&|_, _| panic!("should not be called"));
4665 assert_eq!(result.unwrap(), Value::Bool(true));
4666 }
4667
4668 #[test]
4677 fn thunk_force_concrete_skips_redundant_store_but_caches() {
4678 let root = rnix::Root::parse("1 + 2");
4681 let expr = root.tree().expr().unwrap();
4682 let thunk = Thunk::new_suspended(expr, Env::new());
4683
4684 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4685 assert_eq!(r1, Value::Int(3));
4686 assert!(thunk.is_evaluated());
4687
4688 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
4691
4692 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
4694 assert_eq!(r2, Value::Int(3));
4695 }
4696
4697 #[test]
4698 fn thunk_blackhole_detects_infinite_recursion() {
4699 let root = rnix::Root::parse("42");
4700 let expr = root.tree().expr().unwrap();
4701 let thunk = Thunk::new_suspended(expr, Env::new());
4702
4703 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
4706
4707 let result = thunk.force(&|_, _| Ok(Value::Null));
4708 assert!(result.is_err());
4709 let err_msg = format!("{}", result.unwrap_err());
4710 assert!(err_msg.contains("infinite recursion"));
4711 }
4712
4713 #[test]
4714 fn thunk_update_env_replaces_suspended_env() {
4715 let root = rnix::Root::parse("x");
4716 let expr = root.tree().expr().unwrap();
4717 let thunk = Thunk::new_suspended(expr, Env::new());
4718
4719 let mut new_env = Env::new();
4720 new_env.bind("x".to_string(), Value::Int(99));
4721 thunk.update_env(&new_env);
4722
4723 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4724 assert_eq!(result.unwrap(), Value::Int(99));
4725 }
4726
4727 #[test]
4728 fn thunk_update_env_noop_when_evaluated() {
4729 let thunk = Thunk::new_evaluated(Value::Int(1));
4730 let mut new_env = Env::new();
4731 new_env.bind("x".to_string(), Value::Int(99));
4732 thunk.update_env(&new_env);
4733 assert_eq!(
4734 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
4735 Value::Int(1),
4736 );
4737 }
4738
4739 #[test]
4740 fn thunk_debug_suspended() {
4741 let root = rnix::Root::parse("42");
4742 let expr = root.tree().expr().unwrap();
4743 let thunk = Thunk::new_suspended(expr, Env::new());
4744 assert_eq!(format!("{thunk:?}"), "<thunk>");
4745 }
4746
4747 #[test]
4748 fn thunk_debug_evaluated() {
4749 let thunk = Thunk::new_evaluated(Value::Int(42));
4750 let dbg = format!("{thunk:?}");
4751 assert!(dbg.contains("42"));
4752 }
4753
4754 #[test]
4755 fn thunk_error_restores_suspended_state() {
4756 let root = rnix::Root::parse("nonexistent_var");
4757 let expr = root.tree().expr().unwrap();
4758 let thunk = Thunk::new_suspended(expr, Env::new());
4759
4760 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4761 assert!(result.is_err());
4762 assert!(!thunk.is_evaluated());
4764 let dbg = format!("{thunk:?}");
4765 assert_eq!(dbg, "<thunk>");
4766 }
4767
4768 #[test]
4769 fn thunk_inherit_select_forces_and_selects() {
4770 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4771 let expr = root.tree().expr().unwrap();
4772 let source = Thunk::new_suspended(expr, Env::new());
4773 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4774 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4775 assert_eq!(result.unwrap(), Value::Int(42));
4776 assert!(thunk.is_evaluated());
4777 }
4778
4779 #[test]
4780 fn thunk_inherit_select_missing_attr_errors() {
4781 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4782 let expr = root.tree().expr().unwrap();
4783 let source = Thunk::new_suspended(expr, Env::new());
4784 let thunk = Thunk::new_inherit_select(source, "y".to_string());
4785 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4786 assert!(result.is_err());
4787 assert!(!thunk.is_evaluated());
4789 }
4790
4791 #[test]
4792 fn thunk_inherit_select_non_attrs_source_errors() {
4793 let root = rnix::Root::parse("42");
4794 let expr = root.tree().expr().unwrap();
4795 let source = Thunk::new_suspended(expr, Env::new());
4796 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4797 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4798 assert!(result.is_err());
4799 let msg = format!("{}", result.unwrap_err());
4800 assert!(msg.contains("not a set"));
4801 }
4802
4803 #[test]
4804 fn thunk_inherit_select_shares_source_thunk() {
4805 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
4809 let expr = root.tree().expr().unwrap();
4810 let source = Thunk::new_suspended(expr, Env::new());
4811 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
4812 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
4813 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
4814 assert_eq!(result_a.unwrap(), Value::Int(1));
4815 assert!(source.is_evaluated());
4817 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
4819 assert_eq!(result_b.unwrap(), Value::Int(2));
4820 }
4821
4822 #[test]
4825 fn nixattrs_empty_operations() {
4826 let a = NixAttrs::new();
4827 assert!(a.is_empty());
4828 assert_eq!(a.len(), 0);
4829 assert_eq!(a.get("x"), None);
4830 assert!(!a.contains_key("x"));
4831 assert_eq!(a.keys().count(), 0);
4832 assert_eq!(a.iter().count(), 0);
4833 }
4834
4835 #[test]
4836 fn nixattrs_update_with_empty() {
4837 let mut a = NixAttrs::new();
4838 a.insert("x".to_string(), Value::Int(1));
4839 let b = NixAttrs::new();
4840 let merged = a.update(&b);
4841 assert_eq!(merged.len(), 1);
4842 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4843 }
4844
4845 #[test]
4846 fn nixattrs_update_empty_with_nonempty() {
4847 let a = NixAttrs::new();
4848 let mut b = NixAttrs::new();
4849 b.insert("x".to_string(), Value::Int(1));
4850 let merged = a.update(&b);
4851 assert_eq!(merged.len(), 1);
4852 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4853 }
4854
4855 #[test]
4856 fn nixattrs_keys_sorted_order() {
4857 let mut a = NixAttrs::new();
4858 a.insert("c".to_string(), Value::Int(3));
4859 a.insert("a".to_string(), Value::Int(1));
4860 a.insert("b".to_string(), Value::Int(2));
4861 let keys: Vec<String> = a.keys().collect();
4862 assert_eq!(keys, vec!["a", "b", "c"]);
4863 }
4864
4865 #[test]
4868 fn value_to_str_forces_thunks() {
4869 let root = rnix::Root::parse(r#""hello""#);
4870 let expr = root.tree().expr().unwrap();
4871 let thunk = Thunk::new_suspended(expr, Env::new());
4872 let val = Value::Thunk(thunk);
4873 assert_eq!(val.to_str().unwrap(), "hello");
4874 }
4875
4876 #[test]
4877 fn value_to_nix_string_forces_thunks() {
4878 let root = rnix::Root::parse(r#""world""#);
4879 let expr = root.tree().expr().unwrap();
4880 let thunk = Thunk::new_suspended(expr, Env::new());
4881 let val = Value::Thunk(thunk);
4882 let ns = val.to_nix_string().unwrap();
4883 assert_eq!(ns.as_str(), "world");
4884 assert!(!ns.has_context());
4885 }
4886
4887 #[test]
4888 fn value_to_attrs_forces_thunks() {
4889 let root = rnix::Root::parse("{ x = 1; }");
4890 let expr = root.tree().expr().unwrap();
4891 let thunk = Thunk::new_suspended(expr, Env::new());
4892 let val = Value::Thunk(thunk);
4893 let attrs = val.to_attrs().unwrap();
4894 assert_eq!(attrs.len(), 1);
4895 }
4896
4897 #[test]
4898 fn value_to_list_forces_thunks() {
4899 let root = rnix::Root::parse("[1 2 3]");
4900 let expr = root.tree().expr().unwrap();
4901 let thunk = Thunk::new_suspended(expr, Env::new());
4902 let val = Value::Thunk(thunk);
4903 let list = val.to_list().unwrap();
4904 assert_eq!(list.len(), 3);
4905 }
4906
4907 #[test]
4908 fn value_to_float_on_thunk() {
4909 let root = rnix::Root::parse("3.14");
4910 let expr = root.tree().expr().unwrap();
4911 let thunk = Thunk::new_suspended(expr, Env::new());
4912 let val = Value::Thunk(thunk);
4913 let f = val.to_float().unwrap();
4914 assert!((f - 3.14).abs() < f64::EPSILON);
4915 }
4916
4917 #[test]
4918 fn value_as_bool_on_thunk() {
4919 let root = rnix::Root::parse("true");
4920 let expr = root.tree().expr().unwrap();
4921 let thunk = Thunk::new_suspended(expr, Env::new());
4922 let val = Value::Thunk(thunk);
4923 assert!(val.as_bool().unwrap());
4924 }
4925
4926 #[test]
4927 fn value_as_int_on_thunk() {
4928 let root = rnix::Root::parse("42");
4929 let expr = root.tree().expr().unwrap();
4930 let thunk = Thunk::new_suspended(expr, Env::new());
4931 let val = Value::Thunk(thunk);
4932 assert_eq!(val.as_int().unwrap(), 42);
4933 }
4934
4935 #[test]
4936 fn value_string_constructor() {
4937 let v = Value::string("test");
4938 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
4939 }
4940
4941 #[test]
4942 fn value_partial_eq_null_null() {
4943 assert_eq!(Value::Null, Value::Null);
4944 }
4945
4946 #[test]
4947 fn value_partial_eq_lists_deep() {
4948 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
4949 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
4950 assert_eq!(a, b);
4951 }
4952
4953 #[test]
4954 fn value_partial_eq_attrs_deep() {
4955 let mut a = NixAttrs::new();
4956 a.insert("x".to_string(), Value::Int(1));
4957 let mut b = NixAttrs::new();
4958 b.insert("x".to_string(), Value::Int(1));
4959 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
4960 }
4961
4962 #[test]
4965 fn eval_error_type_error_constructor() {
4966 let e = EvalError::type_error("oops");
4967 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
4968 }
4969
4970 #[test]
4971 fn eval_error_type_mismatch_constructor() {
4972 let e = EvalError::type_mismatch("int", "string");
4973 match e {
4974 EvalError::TypeMismatch { expected, got } => {
4975 assert_eq!(expected, "int");
4976 assert_eq!(got, "string");
4977 }
4978 _ => panic!("expected TypeMismatch"),
4979 }
4980 }
4981
4982 #[test]
4983 fn eval_error_is_throw_yes_no() {
4984 assert!(EvalError::Throw("oops".into()).is_throw());
4985 assert!(!EvalError::TypeError("oops".into()).is_throw());
4986 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
4987 }
4988
4989 #[test]
4990 fn eval_error_is_infinite_recursion_yes_no() {
4991 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
4992 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
4993 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
4994 }
4995
4996 #[test]
4997 fn eval_error_display_undefined_var() {
4998 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
4999 assert!(s.contains("undefined variable"));
5000 assert!(s.contains("foo"));
5001 }
5002
5003 #[test]
5004 fn eval_error_display_type_error() {
5005 let s = format!("{}", EvalError::TypeError("bad".into()));
5006 assert!(s.contains("type error"));
5007 assert!(s.contains("bad"));
5008 }
5009
5010 #[test]
5011 fn eval_error_display_attr_not_found() {
5012 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5013 assert!(s.contains("attribute not found"));
5014 assert!(s.contains("x"));
5015 }
5016
5017 #[test]
5018 fn eval_error_display_type_mismatch() {
5019 let s = format!(
5020 "{}",
5021 EvalError::TypeMismatch { expected: "int", got: "string" }
5022 );
5023 assert!(s.contains("expected int"));
5024 assert!(s.contains("got string"));
5025 }
5026
5027 #[test]
5028 fn eval_error_display_assertion_failed() {
5029 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5030 assert!(s.contains("assertion"));
5031 }
5032
5033 #[test]
5034 fn eval_error_display_division_by_zero() {
5035 let s = format!("{}", EvalError::DivisionByZero);
5036 assert!(s.contains("division by zero"));
5037 }
5038
5039 #[test]
5040 fn eval_error_display_infinite_recursion() {
5041 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5042 assert!(s.contains("infinite recursion"));
5043 assert!(s.contains("loop"));
5044 }
5045
5046 #[test]
5047 fn eval_error_display_io_error() {
5048 let s = format!(
5049 "{}",
5050 EvalError::IoError {
5051 context: "ctx".into(),
5052 message: "no such file".into(),
5053 }
5054 );
5055 assert!(s.contains("I/O"));
5056 assert!(s.contains("ctx"));
5057 assert!(s.contains("no such file"));
5058 }
5059
5060 #[test]
5061 fn eval_error_display_throw() {
5062 let s = format!("{}", EvalError::Throw("boom".into()));
5063 assert_eq!(s, "boom");
5064 }
5065
5066 #[test]
5067 fn eval_error_display_not_implemented() {
5068 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5069 assert!(s.contains("not yet implemented"));
5070 assert!(s.contains("frob"));
5071 }
5072
5073 #[test]
5074 fn eval_error_display_parse_error() {
5075 let s = format!("{}", EvalError::ParseError("syntax".into()));
5076 assert!(s.contains("parse error"));
5077 assert!(s.contains("syntax"));
5078 }
5079
5080 #[test]
5081 fn eval_error_display_recursion_limit() {
5082 let s = format!(
5083 "{}",
5084 EvalError::RecursionLimit("max depth exceeded".into())
5085 );
5086 assert!(s.contains("recursion limit"));
5087 assert!(s.contains("max depth exceeded"));
5088 }
5089
5090 #[test]
5091 fn eval_error_partial_eq_same_variant() {
5092 assert_eq!(
5093 EvalError::UndefinedVar("x".into()),
5094 EvalError::UndefinedVar("x".into()),
5095 );
5096 assert_ne!(
5097 EvalError::UndefinedVar("x".into()),
5098 EvalError::UndefinedVar("y".into()),
5099 );
5100 assert_ne!(
5101 EvalError::UndefinedVar("x".into()),
5102 EvalError::AttrNotFound("x".into()),
5103 );
5104 }
5105
5106 #[test]
5109 fn context_element_display_plain() {
5110 let e = ContextElement::Plain("/nix/store/xyz".into());
5111 assert_eq!(format!("{e}"), "/nix/store/xyz");
5112 }
5113
5114 #[test]
5115 fn context_element_display_output() {
5116 let e = ContextElement::Output {
5117 drv: "/nix/store/abc.drv".into(),
5118 output: "out".into(),
5119 };
5120 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5121 }
5122
5123 #[test]
5124 fn context_element_display_drv_deep() {
5125 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5126 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5127 }
5128
5129 #[test]
5132 fn string_context_iter_yields_all() {
5133 let mut ctx = StringContext::new();
5134 ctx.add_plain("/nix/store/aaa");
5135 ctx.add_plain("/nix/store/bbb");
5136 let count = ctx.iter().count();
5137 assert_eq!(count, 2);
5138 }
5139
5140 #[test]
5141 fn string_context_len_matches_set_size() {
5142 let mut ctx = StringContext::new();
5143 assert_eq!(ctx.len(), 0);
5144 ctx.add_plain("/nix/store/x");
5145 assert_eq!(ctx.len(), 1);
5146 ctx.add_output("/nix/store/y.drv", "out");
5147 assert_eq!(ctx.len(), 2);
5148 }
5149
5150 #[test]
5151 fn string_context_insert_raw_element() {
5152 let mut ctx = StringContext::new();
5153 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5154 assert_eq!(ctx.len(), 1);
5155 }
5156
5157 #[test]
5158 fn string_context_default_is_empty() {
5159 let ctx = StringContext::default();
5160 assert!(ctx.is_empty());
5161 }
5162
5163 #[test]
5166 fn nix_string_as_ref_str() {
5167 let s = NixString::plain("hello");
5168 let r: &str = s.as_ref();
5169 assert_eq!(r, "hello");
5170 }
5171
5172 #[test]
5173 fn nix_string_deref_to_str_methods() {
5174 let s = NixString::plain("Hello World");
5175 assert_eq!(s.len(), 11);
5176 assert!(s.starts_with("Hello"));
5177 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5179 }
5180
5181 #[test]
5184 fn nixattrs_remove_returns_value() {
5185 let mut a = NixAttrs::new();
5186 a.insert("x".into(), Value::Int(1));
5187 let removed = a.remove("x");
5188 assert_eq!(removed, Some(Value::Int(1)));
5189 assert!(!a.contains_key("x"));
5190 assert_eq!(a.remove("y"), None);
5191 }
5192
5193 #[test]
5194 fn nixattrs_values_iter() {
5195 let mut a = NixAttrs::new();
5196 a.insert("a".into(), Value::Int(1));
5197 a.insert("b".into(), Value::Int(2));
5198 let mut vs: Vec<&Value> = a.values().collect();
5199 vs.sort_by_key(|v| match v {
5200 Value::Int(n) => *n,
5201 _ => 0,
5202 });
5203 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5204 }
5205
5206 #[test]
5207 fn nixattrs_iter_returns_sorted_pairs() {
5208 let mut a = NixAttrs::new();
5209 a.insert("zeta".into(), Value::Int(3));
5210 a.insert("alpha".into(), Value::Int(1));
5211 a.insert("mu".into(), Value::Int(2));
5212 let pairs: Vec<(String, &Value)> = a.iter().collect();
5213 assert_eq!(pairs[0].0, "alpha");
5214 assert_eq!(pairs[1].0, "mu");
5215 assert_eq!(pairs[2].0, "zeta");
5216 }
5217
5218 #[test]
5219 fn nixattrs_from_iterator() {
5220 let pairs = vec![
5221 ("a".to_string(), Value::Int(1)),
5222 ("b".to_string(), Value::Int(2)),
5223 ];
5224 let attrs: NixAttrs = pairs.into_iter().collect();
5225 assert_eq!(attrs.len(), 2);
5226 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5227 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5228 }
5229
5230 #[test]
5231 fn nixattrs_into_iterator_yields_owned() {
5232 let mut a = NixAttrs::new();
5233 a.insert("x".into(), Value::Int(42));
5234 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5235 assert_eq!(pairs.len(), 1);
5236 assert_eq!(pairs[0].0, "x");
5237 assert_eq!(pairs[0].1, Value::Int(42));
5238 }
5239
5240 #[test]
5241 fn nixattrs_default_is_empty() {
5242 let a = NixAttrs::default();
5243 assert!(a.is_empty());
5244 }
5245
5246 #[test]
5249 fn value_from_bool() {
5250 assert_eq!(Value::from(true), Value::Bool(true));
5251 assert_eq!(Value::from(false), Value::Bool(false));
5252 }
5253
5254 #[test]
5255 fn value_from_i64() {
5256 assert_eq!(Value::from(42_i64), Value::Int(42));
5257 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5258 }
5259
5260 #[test]
5261 fn value_from_f64() {
5262 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5263 }
5264
5265 #[test]
5266 fn value_from_nix_string() {
5267 let v: Value = NixString::plain("hi").into();
5268 assert_eq!(v, Value::string("hi"));
5269 }
5270
5271 #[test]
5272 fn value_from_nix_attrs() {
5273 let mut a = NixAttrs::new();
5274 a.insert("x".into(), Value::Int(1));
5275 let v: Value = a.into();
5276 match v {
5277 Value::Attrs(_) => {}
5278 _ => panic!("expected Attrs"),
5279 }
5280 }
5281
5282 #[test]
5283 fn value_from_vec() {
5284 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5285 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5286 }
5287
5288 #[test]
5289 fn value_default_is_null() {
5290 let v: Value = Value::default();
5291 assert_eq!(v, Value::Null);
5292 }
5293
5294 #[test]
5297 fn value_from_json_null() {
5298 let v = Value::from(&serde_json::Value::Null);
5299 assert_eq!(v, Value::Null);
5300 }
5301
5302 #[test]
5303 fn value_from_json_bool() {
5304 let v = Value::from(&serde_json::Value::Bool(true));
5305 assert_eq!(v, Value::Bool(true));
5306 }
5307
5308 #[test]
5309 fn value_from_json_int() {
5310 let v = Value::from(&serde_json::json!(42));
5311 assert_eq!(v, Value::Int(42));
5312 }
5313
5314 #[test]
5315 fn value_from_json_float() {
5316 let v = Value::from(&serde_json::json!(3.14));
5317 match v {
5318 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5319 _ => panic!("expected Float"),
5320 }
5321 }
5322
5323 #[test]
5324 fn value_from_json_string() {
5325 let v = Value::from(&serde_json::Value::String("hi".into()));
5326 assert_eq!(v, Value::string("hi"));
5327 }
5328
5329 #[test]
5330 fn value_from_json_array() {
5331 let v = Value::from(&serde_json::json!([1, true, "x"]));
5332 match v {
5333 Value::List(items) => {
5334 assert_eq!(items.len(), 3);
5335 assert_eq!(items[0], Value::Int(1));
5336 assert_eq!(items[1], Value::Bool(true));
5337 assert_eq!(items[2], Value::string("x"));
5338 }
5339 _ => panic!("expected List"),
5340 }
5341 }
5342
5343 #[test]
5344 fn value_from_json_object() {
5345 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5346 match v {
5347 Value::Attrs(attrs) => {
5348 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5349 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5350 }
5351 _ => panic!("expected Attrs"),
5352 }
5353 }
5354
5355 #[test]
5356 fn value_from_json_nested() {
5357 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5358 let json_back = v.to_json();
5359 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5360 }
5361
5362 #[test]
5365 fn value_from_toml_string() {
5366 let t = toml::Value::String("hi".into());
5367 assert_eq!(Value::from(&t), Value::string("hi"));
5368 }
5369
5370 #[test]
5371 fn value_from_toml_int() {
5372 let t = toml::Value::Integer(42);
5373 assert_eq!(Value::from(&t), Value::Int(42));
5374 }
5375
5376 #[test]
5377 fn value_from_toml_float() {
5378 let t = toml::Value::Float(3.14);
5379 match Value::from(&t) {
5380 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5381 _ => panic!("expected Float"),
5382 }
5383 }
5384
5385 #[test]
5386 fn value_from_toml_bool() {
5387 let t = toml::Value::Boolean(true);
5388 assert_eq!(Value::from(&t), Value::Bool(true));
5389 }
5390
5391 #[test]
5392 fn value_from_toml_array() {
5393 let t = toml::Value::Array(vec![
5394 toml::Value::Integer(1),
5395 toml::Value::Integer(2),
5396 ]);
5397 assert_eq!(
5398 Value::from(&t),
5399 Value::list(vec![Value::Int(1), Value::Int(2)]),
5400 );
5401 }
5402
5403 #[test]
5404 fn value_from_toml_table() {
5405 let mut tbl = toml::map::Map::new();
5406 tbl.insert("k".into(), toml::Value::Integer(7));
5407 let t = toml::Value::Table(tbl);
5408 match Value::from(&t) {
5409 Value::Attrs(attrs) => {
5410 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5411 }
5412 _ => panic!("expected Attrs"),
5413 }
5414 }
5415
5416 #[test]
5417 fn value_from_toml_datetime_becomes_string() {
5418 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5420 let t = toml::Value::Datetime(dt);
5421 match Value::from(&t) {
5422 Value::String(_) => {}
5423 other => panic!("expected String, got {other:?}"),
5424 }
5425 }
5426
5427 #[test]
5430 fn coerce_to_path_from_path() {
5431 let v = Value::Path(Box::new("/foo".into()));
5432 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5433 }
5434
5435 #[test]
5436 fn coerce_to_path_from_string() {
5437 let v = Value::string("/bar");
5438 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5439 }
5440
5441 #[test]
5449 fn out_path_needs_realize_matches_output_context() {
5450 let mut ctx = StringContext::new();
5453 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5454 assert_eq!(
5455 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5456 Some("/nix/store/aaa-thing.drv".to_string()),
5457 );
5458 }
5459
5460 #[test]
5461 fn out_path_needs_realize_ignores_plain_context() {
5462 let mut ctx = StringContext::new();
5465 ctx.add_plain("/nix/store/ccc-plain");
5466 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5467 }
5468
5469 #[test]
5470 fn out_path_needs_realize_ignores_non_store_path() {
5471 let mut ctx = StringContext::new();
5474 ctx.add_output("/nix/store/ddd.drv", "out");
5475 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5476 }
5477
5478 #[test]
5479 fn out_path_needs_realize_empty_context_is_none() {
5480 let ctx = StringContext::new();
5482 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5483 }
5484
5485 #[test]
5486 fn coerce_to_realized_path_present_output_is_passthrough() {
5487 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5491 std::fs::create_dir_all(&dir).unwrap();
5492 let file = dir.join("out");
5493 std::fs::write(&file, b"present").unwrap();
5494 let present = file.to_string_lossy().to_string();
5495
5496 let mut ctx = StringContext::new();
5497 ctx.add_plain(&present);
5502 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5503 present.as_str(),
5504 ctx,
5505 )));
5506 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5507 }
5508
5509 #[test]
5510 fn coerce_to_realized_path_absent_output_invokes_hook() {
5511 use std::sync::{Arc, Mutex};
5516 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5517 let seen2 = seen.clone();
5518 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5519 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5520 Ok(())
5521 }));
5522
5523 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5526 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5527 let mut ctx = StringContext::new();
5528 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5529 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5530
5531 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5533 let s = seen.lock().unwrap();
5534 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5535 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5536 assert_eq!(s[0].1, out);
5537 }
5538
5539 #[test]
5540 fn coerce_to_path_errors_on_int() {
5541 let v = Value::Int(1);
5542 let e = v.coerce_to_path("readFile").unwrap_err();
5543 match e {
5544 EvalError::TypeError(ref msg) => {
5545 assert!(msg.contains("readFile"));
5546 assert!(msg.contains("path or string"));
5547 assert!(msg.contains("int"));
5548 }
5549 _ => panic!("expected TypeError"),
5550 }
5551 }
5552
5553 #[test]
5554 fn coerce_to_path_errors_on_null() {
5555 let v = Value::Null;
5556 assert!(v.coerce_to_path("ctx").is_err());
5557 }
5558
5559 #[test]
5560 fn coerce_to_path_attrs_with_outpath() {
5561 let mut attrs = NixAttrs::new();
5562 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5563 let val = Value::Attrs(Rc::new(attrs));
5564 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5565 }
5566
5567 #[test]
5568 fn coerce_to_path_attrs_without_outpath_fails() {
5569 let attrs = NixAttrs::new();
5570 let val = Value::Attrs(Rc::new(attrs));
5571 assert!(val.coerce_to_path("test").is_err());
5572 }
5573
5574 #[test]
5577 fn coerce_to_string_string() {
5578 let v = Value::string("hello");
5579 let (s, _ctx) = v.coerce_to_string().unwrap();
5580 assert_eq!(s, "hello");
5581 }
5582
5583 #[test]
5584 fn coerce_to_string_path() {
5585 let v = Value::Path(Box::new("/foo".into()));
5586 let (s, ctx) = v.coerce_to_string().unwrap();
5587 assert_eq!(s, "/foo");
5588 assert!(!ctx.is_empty()); }
5590
5591 #[test]
5592 fn coerce_to_string_int() {
5593 let v = Value::Int(42);
5594 let (s, _ctx) = v.coerce_to_string().unwrap();
5595 assert_eq!(s, "42");
5596 }
5597
5598 #[test]
5599 fn coerce_to_string_float() {
5600 let v = Value::Float(3.14);
5602 let (s, _ctx) = v.coerce_to_string().unwrap();
5603 assert_eq!(s, "3.140000");
5604 }
5605
5606 #[test]
5607 fn coerce_to_string_bool_true() {
5608 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5609 assert_eq!(s, "1");
5610 }
5611
5612 #[test]
5613 fn coerce_to_string_bool_false() {
5614 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5615 assert_eq!(s, "");
5616 }
5617
5618 #[test]
5619 fn coerce_to_string_null() {
5620 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5621 assert_eq!(s, "");
5622 }
5623
5624 #[test]
5625 fn coerce_to_string_attrs_with_outpath() {
5626 let mut attrs = NixAttrs::new();
5627 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5628 let val = Value::Attrs(Rc::new(attrs));
5629 let (s, _ctx) = val.coerce_to_string().unwrap();
5630 assert_eq!(s, "/nix/store/abc");
5631 }
5632
5633 #[test]
5634 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5635 let attrs = NixAttrs::new();
5636 let val = Value::Attrs(Rc::new(attrs));
5637 assert!(val.coerce_to_string().is_err());
5638 }
5639
5640 #[test]
5641 fn coerce_to_string_lambda_fails() {
5642 let root = rnix::Root::parse("x: x");
5643 let expr = root.tree().expr().unwrap();
5644 let closure = Closure {
5645 param: match expr {
5646 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
5647 _ => panic!("expected lambda"),
5648 },
5649 body: match expr {
5650 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
5651 _ => panic!("expected lambda"),
5652 },
5653 env: Env::new(),
5654 };
5655 let val = Value::Lambda(Rc::new(closure));
5656 assert!(val.coerce_to_string().is_err());
5657 }
5658
5659 #[test]
5662 fn builtin_fn_debug_includes_name() {
5663 let b = BuiltinFn {
5664 name: "myFunc",
5665 func: Rc::new(|_| Ok(Value::Null)),
5666 };
5667 let s = format!("{b:?}");
5668 assert!(s.contains("myFunc"));
5669 assert!(s.contains("builtin"));
5670 }
5671
5672 #[test]
5675 fn thunk_force_chains_through_inner_thunks() {
5676 let inner_root = rnix::Root::parse("99");
5678 let inner_expr = inner_root.tree().expr().unwrap();
5679 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
5680 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
5681 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
5682 match result.unwrap() {
5687 Value::Thunk(_) | Value::Int(99) => {}
5688 other => panic!("unexpected: {other:?}"),
5689 }
5690 }
5691
5692 #[test]
5693 fn thunk_inherit_select_debug_format() {
5694 let root = rnix::Root::parse("{ x = 1; }");
5695 let expr = root.tree().expr().unwrap();
5696 let source = Thunk::new_suspended(expr, Env::new());
5697 let thunk = Thunk::new_inherit_select(source, "x");
5698 let s = format!("{thunk:?}");
5699 assert!(s.contains("inherit-select"));
5700 assert!(s.contains("x"));
5701 }
5702
5703 #[test]
5704 fn thunk_blackhole_debug_format() {
5705 let root = rnix::Root::parse("1");
5706 let expr = root.tree().expr().unwrap();
5707 let thunk = Thunk::new_suspended(expr, Env::new());
5708 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5710 assert_eq!(format!("{thunk:?}"), "<blackhole>");
5711 }
5712
5713 #[test]
5716 fn value_display_thunk_evaluates() {
5717 let root = rnix::Root::parse("42");
5718 let expr = root.tree().expr().unwrap();
5719 let thunk = Thunk::new_suspended(expr, Env::new());
5720 let val = Value::Thunk(thunk);
5721 assert_eq!(format!("{val}"), "42");
5722 }
5723
5724 #[test]
5725 fn value_to_json_thunk_forces() {
5726 let root = rnix::Root::parse(r#""world""#);
5727 let expr = root.tree().expr().unwrap();
5728 let thunk = Thunk::new_suspended(expr, Env::new());
5729 let val = Value::Thunk(thunk);
5730 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
5731 }
5732
5733 #[test]
5734 fn value_type_name_thunk_forces() {
5735 let root = rnix::Root::parse("42");
5736 let expr = root.tree().expr().unwrap();
5737 let thunk = Thunk::new_suspended(expr, Env::new());
5738 let val = Value::Thunk(thunk);
5739 assert_eq!(val.type_name(), "int");
5740 }
5741
5742 #[test]
5745 fn as_string_errors_on_thunk() {
5746 let root = rnix::Root::parse(r#""x""#);
5747 let expr = root.tree().expr().unwrap();
5748 let thunk = Thunk::new_suspended(expr, Env::new());
5749 let val = Value::Thunk(thunk);
5750 let err = val.as_string().unwrap_err();
5751 match err {
5752 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
5753 _ => panic!("expected TypeError"),
5754 }
5755 }
5756
5757 #[test]
5758 fn as_nix_string_errors_on_thunk() {
5759 let root = rnix::Root::parse(r#""x""#);
5760 let expr = root.tree().expr().unwrap();
5761 let thunk = Thunk::new_suspended(expr, Env::new());
5762 let val = Value::Thunk(thunk);
5763 assert!(val.as_nix_string().is_err());
5764 }
5765
5766 #[test]
5767 fn as_attrs_errors_on_thunk() {
5768 let root = rnix::Root::parse("{}");
5769 let expr = root.tree().expr().unwrap();
5770 let thunk = Thunk::new_suspended(expr, Env::new());
5771 let val = Value::Thunk(thunk);
5772 assert!(val.as_attrs().is_err());
5773 }
5774
5775 #[test]
5776 fn as_list_errors_on_thunk() {
5777 let root = rnix::Root::parse("[]");
5778 let expr = root.tree().expr().unwrap();
5779 let thunk = Thunk::new_suspended(expr, Env::new());
5780 let val = Value::Thunk(thunk);
5781 assert!(val.as_list().is_err());
5782 }
5783
5784 #[test]
5787 fn as_nix_string_ok_on_string() {
5788 let v = Value::string("hi");
5789 let ns = v.as_nix_string().unwrap();
5790 assert_eq!(ns.as_str(), "hi");
5791 }
5792
5793 #[test]
5794 fn as_nix_string_errors_on_int() {
5795 let v = Value::Int(1);
5796 match v.as_nix_string() {
5797 Err(EvalError::TypeMismatch { expected, got }) => {
5798 assert_eq!(expected, "string");
5799 assert_eq!(got, "int");
5800 }
5801 _ => panic!("expected TypeMismatch"),
5802 }
5803 }
5804
5805 #[test]
5810 fn oncecell_cache_populated_after_force() {
5811 let root = rnix::Root::parse("42");
5812 let expr = root.tree().expr().unwrap();
5813 let thunk = Thunk::new_suspended(expr, Env::new());
5814 assert!(thunk.0.cache.get().is_none());
5816 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5817 assert!(thunk.0.cache.get().is_some());
5819 }
5820
5821 #[test]
5822 fn oncecell_cache_matches_force_result() {
5823 let root = rnix::Root::parse("1 + 2");
5824 let expr = root.tree().expr().unwrap();
5825 let thunk = Thunk::new_suspended(expr, Env::new());
5826 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5827 let cached = thunk.0.cache.get().unwrap();
5828 assert_eq!((**cached).clone().into_value(), forced);
5831 }
5832
5833 #[test]
5834 fn oncecell_new_evaluated_prepopulates_cache() {
5835 let thunk = Thunk::new_evaluated(Value::Int(77));
5836 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
5838 assert_eq!(**cached, Concrete::Int(77));
5839 }
5840
5841 #[test]
5842 fn oncecell_is_evaluated_uses_cache() {
5843 let thunk = Thunk::new_evaluated(Value::Bool(false));
5844 assert!(thunk.is_evaluated());
5846 assert!(thunk.0.cache.get().is_some());
5847 }
5848
5849 #[test]
5850 fn oncecell_already_evaluated_returns_cached_without_repr() {
5851 let thunk = Thunk::new_evaluated(Value::Int(55));
5855 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
5856 assert_eq!(result.unwrap(), Value::Int(55));
5857 }
5858
5859 #[test]
5864 fn with_scope_created_with_empty_cache() {
5865 let thunk = Thunk::new_suspended(
5867 rnix::Root::parse("{}").tree().expr().unwrap(),
5868 Env::new(),
5869 );
5870 let env = Env::new().with_scope(Value::Thunk(thunk));
5871 let scope = &env.0.with_scopes[0];
5872 assert!(scope.cached.borrow().is_none());
5873 }
5874
5875 #[test]
5876 fn with_scope_concrete_pre_populates_cache() {
5877 let mut attrs = NixAttrs::new();
5879 attrs.insert("x".to_string(), Value::Int(1));
5880 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5881 let scope = &env.0.with_scopes[0];
5882 assert!(scope.cached.borrow().is_some());
5883 }
5884
5885 #[test]
5886 fn with_scope_first_lookup_populates_cache() {
5887 let mut attrs = NixAttrs::new();
5888 attrs.insert("x".to_string(), Value::Int(42));
5889 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5890 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5892 let _ = env.lookup("x");
5894 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5895 }
5896
5897 #[test]
5898 fn with_scope_second_lookup_uses_cache() {
5899 let mut attrs = NixAttrs::new();
5900 attrs.insert("x".to_string(), Value::Int(10));
5901 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5902 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
5904 assert!(env.0.with_scopes[0].cached.borrow().is_some());
5905 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
5907 }
5908
5909 #[test]
5910 fn with_scope_child_shares_cache_via_rc() {
5911 let mut attrs = NixAttrs::new();
5912 attrs.insert("shared".to_string(), Value::Int(7));
5913 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5914 let child = parent.child();
5915 let _ = parent.lookup("shared");
5917 assert!(child.0.with_scopes[0].cached.borrow().is_some());
5920 }
5921
5922 #[test]
5923 fn with_scope_innermost_checked_first() {
5924 let mut outer = NixAttrs::new();
5925 outer.insert("x".to_string(), Value::Int(1));
5926 outer.insert("y".to_string(), Value::Int(100));
5927 let mut inner = NixAttrs::new();
5928 inner.insert("x".to_string(), Value::Int(2));
5929 let env = Env::new()
5930 .with_scope(Value::Attrs(Rc::new(outer)))
5931 .with_scope(Value::Attrs(Rc::new(inner)));
5932 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
5934 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
5936 }
5937
5938 #[test]
5943 fn fxhashmap_nixattrs_new_creates_empty() {
5944 let a = NixAttrs::new();
5945 assert!(a.is_empty());
5946 assert_eq!(a.len(), 0);
5947 assert!(a.inner().is_empty());
5949 }
5950
5951 #[test]
5952 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
5953 let mut a = NixAttrs::new();
5954 a.insert("mykey".to_string(), Value::Int(42));
5955 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
5956 }
5957
5958 #[test]
5959 fn fxhashmap_contains_key_with_interned_keys() {
5960 let mut a = NixAttrs::new();
5961 a.insert("alpha".to_string(), Value::Int(1));
5962 let sym = intern("alpha");
5963 assert!(a.inner().contains_key(&sym));
5964 let missing_sym = intern("beta");
5965 assert!(!a.inner().contains_key(&missing_sym));
5966 }
5967
5968 #[test]
5969 fn fxhashmap_remove_returns_value() {
5970 let mut a = NixAttrs::new();
5971 a.insert("key".to_string(), Value::Int(99));
5972 let removed = a.remove("key");
5973 assert_eq!(removed, Some(Value::Int(99)));
5974 assert!(a.is_empty());
5975 }
5976
5977 #[test]
5978 fn fxhashmap_keys_returns_sorted_strings() {
5979 let mut a = NixAttrs::new();
5980 a.insert("zulu".to_string(), Value::Int(1));
5981 a.insert("alpha".to_string(), Value::Int(2));
5982 a.insert("mike".to_string(), Value::Int(3));
5983 let keys: Vec<String> = a.keys().collect();
5984 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
5985 }
5986
5987 #[test]
5988 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
5989 let mut a = NixAttrs::new();
5990 a.insert("b".to_string(), Value::Int(2));
5991 a.insert("a".to_string(), Value::Int(1));
5992 let pairs: Vec<(String, &Value)> = a.iter().collect();
5993 assert_eq!(pairs.len(), 2);
5994 assert_eq!(pairs[0].0, "a");
5995 assert_eq!(*pairs[0].1, Value::Int(1));
5996 assert_eq!(pairs[1].0, "b");
5997 assert_eq!(*pairs[1].1, Value::Int(2));
5998 }
5999
6000 #[test]
6001 fn fxhashmap_update_merges_correctly() {
6002 let mut left = NixAttrs::new();
6003 left.insert("a".to_string(), Value::Int(1));
6004 left.insert("b".to_string(), Value::Int(2));
6005 let mut right = NixAttrs::new();
6006 right.insert("b".to_string(), Value::Int(20));
6007 right.insert("c".to_string(), Value::Int(3));
6008 let merged = left.update(&right);
6009 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6010 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6012 assert_eq!(merged.len(), 3);
6013 }
6014
6015 #[test]
6016 fn fxhashmap_from_iterator_collects_with_interning() {
6017 let pairs = vec![
6018 ("x".to_string(), Value::Int(10)),
6019 ("y".to_string(), Value::Int(20)),
6020 ("z".to_string(), Value::Int(30)),
6021 ];
6022 let attrs: NixAttrs = pairs.into_iter().collect();
6023 assert_eq!(attrs.len(), 3);
6024 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6025 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6026 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6027 let sym_x = intern("x");
6029 assert!(attrs.inner().contains_key(&sym_x));
6030 }
6031
6032 #[test]
6037 fn smallvec_context_empty() {
6038 let ctx = StringContext::new();
6039 assert!(ctx.is_empty());
6040 assert_eq!(ctx.len(), 0);
6041 assert_eq!(ctx.elements().len(), 0);
6042 }
6043
6044 #[test]
6045 fn smallvec_context_single_element_inline() {
6046 let mut ctx = StringContext::new();
6047 ctx.add_plain("/nix/store/single");
6048 assert_eq!(ctx.len(), 1);
6049 assert!(!ctx.is_empty());
6051 }
6052
6053 #[test]
6054 fn smallvec_context_two_elements_still_inline() {
6055 let mut ctx = StringContext::new();
6056 ctx.add_plain("/nix/store/one");
6057 ctx.add_output("/nix/store/two.drv", "out");
6058 assert_eq!(ctx.len(), 2);
6059 }
6060
6061 #[test]
6062 fn smallvec_context_three_plus_spills_to_heap() {
6063 let mut ctx = StringContext::new();
6064 ctx.add_plain("/nix/store/a");
6065 ctx.add_plain("/nix/store/b");
6066 ctx.add_drv_deep("/nix/store/c.drv");
6067 assert_eq!(ctx.len(), 3);
6068 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6070 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6071 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6072 }
6073
6074 #[test]
6075 fn smallvec_context_merge_deduplicates() {
6076 let mut ctx1 = StringContext::new();
6077 ctx1.add_plain("/nix/store/dup");
6078 ctx1.add_output("/nix/store/x.drv", "out");
6079 let mut ctx2 = StringContext::new();
6080 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6083 assert_eq!(ctx1.len(), 3); }
6085
6086 #[test]
6087 fn smallvec_context_add_plain_output_drv_deep() {
6088 let mut ctx = StringContext::new();
6089 ctx.add_plain("/nix/store/plain");
6090 assert_eq!(ctx.len(), 1);
6091 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6092
6093 ctx.add_output("/nix/store/out.drv", "lib");
6094 assert_eq!(ctx.len(), 2);
6095 assert!(ctx.elements().contains(&ContextElement::Output {
6096 drv: SmolStr::from("/nix/store/out.drv"),
6097 output: SmolStr::from("lib"),
6098 }));
6099
6100 ctx.add_drv_deep("/nix/store/deep.drv");
6101 assert_eq!(ctx.len(), 3);
6102 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6103 }
6104
6105 #[test]
6110 fn rc_list_constructor_wraps_in_rc() {
6111 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6112 match &v {
6113 Value::List(rc) => {
6114 assert_eq!(rc.len(), 2);
6115 assert_eq!(Rc::strong_count(rc), 1);
6116 }
6117 _ => panic!("expected List"),
6118 }
6119 }
6120
6121 #[test]
6122 fn rc_list_clone_is_refcount_bump() {
6123 let v = Value::list(vec![Value::Int(10)]);
6124 let rc1 = match &v {
6125 Value::List(rc) => rc.clone(),
6126 _ => panic!("expected List"),
6127 };
6128 let v2 = v.clone();
6129 let rc2 = match &v2 {
6130 Value::List(rc) => rc.clone(),
6131 _ => panic!("expected List"),
6132 };
6133 assert!(Rc::ptr_eq(&rc1, &rc2));
6135 assert!(Rc::strong_count(&rc1) >= 2);
6138 }
6139
6140 #[test]
6141 fn rc_list_as_list_returns_slice() {
6142 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6143 let slice = v.as_list().unwrap();
6144 assert_eq!(slice.len(), 3);
6145 assert_eq!(slice[0], Value::Int(1));
6146 assert_eq!(slice[1], Value::Int(2));
6147 assert_eq!(slice[2], Value::Int(3));
6148 }
6149
6150 #[test]
6151 fn rc_list_from_vec_wraps_in_rc() {
6152 let items = vec![Value::Bool(true), Value::Bool(false)];
6153 let v: Value = items.into();
6154 match &v {
6155 Value::List(rc) => {
6156 assert_eq!(rc.len(), 2);
6157 assert_eq!(Rc::strong_count(rc), 1);
6158 }
6159 _ => panic!("expected List"),
6160 }
6161 }
6162
6163 #[test]
6168 fn intern_same_string_returns_same_symbol() {
6169 let s1 = intern("hello_intern_test");
6170 let s2 = intern("hello_intern_test");
6171 assert_eq!(s1, s2);
6172 }
6173
6174 #[test]
6175 fn intern_different_strings_returns_different_symbols() {
6176 let s1 = intern("unique_str_a_9182");
6177 let s2 = intern("unique_str_b_9182");
6178 assert_ne!(s1, s2);
6179 }
6180
6181 #[test]
6182 fn resolve_roundtrips_correctly() {
6183 let sym = intern("roundtrip_test_str");
6184 let resolved = resolve(sym);
6185 assert_eq!(resolved, "roundtrip_test_str");
6186 }
6187
6188 #[test]
6189 fn intern_cached_same_offset_returns_cached_symbol() {
6190 let sid = next_source_id();
6191 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6192 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6193 assert_eq!(sym1, sym2);
6194 }
6195
6196 #[test]
6197 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6198 let sid = next_source_id();
6201 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6202 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6203 assert_eq!(sym1, sym2);
6205 }
6206
6207 #[test]
6208 fn clear_ident_cache_clears() {
6209 let sid = next_source_id();
6210 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6211 clear_ident_cache();
6212 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6216 let resolved = resolve(sym2);
6217 assert_eq!(resolved, "to_be_cleared_99");
6218 }
6219
6220 #[test]
6221 fn next_source_id_increments_monotonically() {
6222 let id1 = next_source_id();
6223 let id2 = next_source_id();
6224 let id3 = next_source_id();
6225 assert_eq!(id2, id1 + 1);
6226 assert_eq!(id3, id2 + 1);
6227 }
6228
6229 #[test]
6234 fn env_new_creates_empty_bindings() {
6235 let env = Env::new();
6236 assert!(env.0.bindings.is_empty());
6237 assert!(env.0.with_scopes.is_empty());
6238 assert!(env.eval_file().is_none());
6239 }
6240
6241 #[test]
6242 fn env_bind_lookup_roundtrip() {
6243 let mut env = Env::new();
6244 env.bind("foo".to_string(), Value::Int(42));
6245 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6246 assert_eq!(env.lookup("bar"), None);
6247 }
6248
6249 #[test]
6250 fn env_child_inherits_parent_bindings_flattened() {
6251 let mut parent = Env::new();
6252 parent.bind("a".to_string(), Value::Int(1));
6253 parent.bind("b".to_string(), Value::Int(2));
6254 let child = parent.child();
6255 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6257 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6258 let sym_a = intern("a");
6260 assert!(child.0.bindings.contains_key(&sym_a));
6261 }
6262
6263 #[test]
6264 fn env_child_inherits_with_scopes() {
6265 let mut attrs = NixAttrs::new();
6266 attrs.insert("ws".to_string(), Value::Int(10));
6267 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6268 let child = parent.child();
6269 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6271 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6272 }
6273
6274 #[test]
6275 fn env_lookup_sym_fast_path_matches_lookup() {
6276 let mut env = Env::new();
6277 env.bind("target".to_string(), Value::Int(88));
6278 let sym = intern("target");
6279 let via_lookup = env.lookup("target");
6280 let via_sym = env.lookup_sym(sym);
6281 assert_eq!(via_lookup, via_sym);
6282 assert_eq!(via_sym, Some(Value::Int(88)));
6283 }
6284
6285 #[test]
6286 fn env_lookup_sym_with_scope_fallback() {
6287 let mut attrs = NixAttrs::new();
6288 attrs.insert("sym_ws".to_string(), Value::Int(33));
6289 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6290 let sym = intern("sym_ws");
6291 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6292 }
6293
6294 #[test]
6295 fn env_with_scope_ordering_multiple_innermost_wins() {
6296 let mut a1 = NixAttrs::new();
6297 a1.insert("x".to_string(), Value::Int(1));
6298 let mut a2 = NixAttrs::new();
6299 a2.insert("x".to_string(), Value::Int(2));
6300 let mut a3 = NixAttrs::new();
6301 a3.insert("x".to_string(), Value::Int(3));
6302 let env = Env::new()
6303 .with_scope(Value::Attrs(Rc::new(a1)))
6304 .with_scope(Value::Attrs(Rc::new(a2)))
6305 .with_scope(Value::Attrs(Rc::new(a3)));
6306 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6308 }
6309
6310 #[test]
6311 fn env_lookup_sym_not_found_returns_none() {
6312 let env = Env::new();
6313 let sym = intern("nonexistent_sym_99");
6314 assert_eq!(env.lookup_sym(sym), None);
6315 }
6316
6317 #[test]
6318 fn env_lookup_sym_lexical_wins_over_with_scope() {
6319 let mut attrs = NixAttrs::new();
6320 attrs.insert("priority".to_string(), Value::Int(1));
6321 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6322 env.bind("priority".to_string(), Value::Int(99));
6323 let sym = intern("priority");
6324 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6325 }
6326}