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 sym = intern(key);
2234 let (file, offset) = self.pos_entry(sym)?;
2235 crate::pos::resolve(file.as_deref(), offset)
2236 }
2237
2238 fn pos_entry(&self, sym: Symbol) -> Option<(Option<std::path::PathBuf>, u32)> {
2258 if let Some(table) = self.1.as_ref() {
2259 if let Some(offset) = table.keys.get(&sym) {
2260 return Some((table.file.clone(), *offset));
2261 }
2262 }
2263 match &self.0 {
2264 AttrsInner::Overlay { left, right, .. } => {
2265 let r = right.borrow().pos_entry(sym);
2266 if r.is_some() {
2267 return r;
2268 }
2269 let l = left.borrow().pos_entry(sym);
2270 l
2271 }
2272 _ => None,
2273 }
2274 }
2275
2276 #[must_use]
2278 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2279 self.as_flat().clone()
2280 }
2281
2282 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2284 match &self.0 {
2285 AttrsInner::Flat(m) => m,
2286 AttrsInner::Overlay { left, right, cache } => {
2287 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2288 let flat = cache.get_or_init(|| {
2289 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2292 let timed = crate::perf::enabled();
2293 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2294 let mut result = left.borrow().as_flat().clone();
2295 for (k, v) in right.borrow().as_flat().iter() {
2296 result.insert(*k, v.clone());
2297 }
2298 crate::perf::add(
2299 crate::perf::Counter::OverlayFlattenEntries,
2300 result.len() as u64,
2301 );
2302 if let Some(t0) = t0 {
2303 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2304 }
2305 result
2306 });
2307 {
2327 let mut l = left.borrow_mut();
2328 if !l.is_empty() { *l = Rc::new(l.position_husk()); }
2329 }
2330 {
2331 let mut r = right.borrow_mut();
2332 if !r.is_empty() { *r = Rc::new(r.position_husk()); }
2333 }
2334 flat
2335 }
2336 }
2337 }
2338
2339 fn position_husk(&self) -> NixAttrs {
2349 match &self.0 {
2350 AttrsInner::Overlay { left, right, .. } => {
2351 let (l, r) = (left.borrow().position_husk(), right.borrow().position_husk());
2352 if l.1.is_none() && r.1.is_none() && !matches!(l.0, AttrsInner::Overlay { .. })
2353 && !matches!(r.0, AttrsInner::Overlay { .. })
2354 {
2355 return NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone());
2358 }
2359 NixAttrs(
2360 AttrsInner::Overlay {
2361 left: RefCell::new(Rc::new(l)),
2362 right: RefCell::new(Rc::new(r)),
2363 cache: Rc::new(OnceCell::new()),
2364 },
2365 self.1.clone(),
2366 )
2367 }
2368 AttrsInner::Flat(_) => NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone()),
2369 }
2370 }
2371
2372 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2373 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2374 let m = self.as_flat();
2375 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2376 let timed = crate::perf::enabled();
2377 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2378 let mut pairs: Vec<(String, &Value)> = m.iter()
2379 .map(|(sym, v)| (resolve(*sym), v))
2380 .collect();
2381 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2382 if let Some(t0) = t0 {
2383 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2384 }
2385 pairs
2386 }
2387
2388 #[must_use]
2390 pub fn get(&self, key: &str) -> Option<&Value> {
2391 let sym = intern(key);
2392 self.get_sym(&sym)
2393 }
2394
2395 #[must_use]
2409 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2410 match &self.0 {
2411 AttrsInner::Flat(m) => m.get(sym),
2412 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2418 }
2419 }
2420
2421 pub fn insert(&mut self, key: String, value: Value) {
2423 self.ensure_flat();
2424 if let AttrsInner::Flat(ref mut m) = self.0 {
2425 m.insert(intern(&key), value);
2426 }
2427 }
2428
2429 fn ensure_flat(&mut self) {
2431 if matches!(self.0, AttrsInner::Overlay { .. }) {
2432 self.0 = AttrsInner::Flat(self.as_flat().clone());
2433 }
2434 }
2435
2436 #[must_use]
2437 pub fn contains_key(&self, key: &str) -> bool {
2438 let sym = intern(key);
2439 self.contains_key_sym(&sym)
2440 }
2441
2442 #[must_use]
2443 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2444 match &self.0 {
2445 AttrsInner::Flat(m) => m.contains_key(sym),
2446 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2448 }
2449 }
2450
2451 pub fn keys(&self) -> impl Iterator<Item = String> {
2452 self.sorted_entries().into_iter().map(|(k, _)| k)
2453 }
2454
2455 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2456 self.sorted_entries().into_iter()
2457 }
2458
2459 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2460 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2461 }
2462
2463 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2481 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2482 }
2483
2484 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2487 self.ensure_flat();
2488 if let AttrsInner::Flat(ref mut m) = self.0 {
2489 m.insert(sym, value);
2490 }
2491 }
2492
2493 pub fn values(&self) -> impl Iterator<Item = &Value> {
2494 self.sorted_entries().into_iter().map(|(_, v)| v)
2495 }
2496
2497
2498 pub fn remove(&mut self, key: &str) -> Option<Value> {
2499 self.ensure_flat();
2500 if let AttrsInner::Flat(ref mut m) = self.0 {
2501 m.remove(&intern(key))
2502 } else {
2503 None
2504 }
2505 }
2506
2507 #[must_use]
2508 pub fn len(&self) -> usize {
2509 match &self.0 {
2510 AttrsInner::Flat(m) => m.len(),
2511 AttrsInner::Overlay { .. } => {
2512 self.as_flat().len()
2516 }
2517 }
2518 }
2519
2520 #[must_use]
2521 pub fn is_empty(&self) -> bool {
2522 match &self.0 {
2523 AttrsInner::Flat(m) => m.is_empty(),
2524 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2528 }
2529 }
2530
2531 #[must_use]
2533 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2534 if other.is_empty() { return self; }
2535 if self.is_empty() { return other; }
2536 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2537 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2538 NixAttrs(AttrsInner::Overlay {
2539 left: RefCell::new(Rc::new(self)),
2540 right: RefCell::new(Rc::new(other)),
2541 cache: Rc::new(OnceCell::new()),
2542 }, None)
2543 }
2544
2545 #[must_use]
2547 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2548 match (&self.0, &other.0) {
2549 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2550 let mut result = l.clone();
2551 for (k, v) in r.iter() {
2552 result.insert(*k, v.clone());
2553 }
2554 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2555 NixAttrs(AttrsInner::Flat(result), None)
2556 }
2557 _ => {
2558 let mut result = self.as_flat().clone();
2560 let other_flat = other.as_flat();
2561 for (k, v) in other_flat.iter() {
2562 result.insert(*k, v.clone());
2563 }
2564 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2565 NixAttrs(AttrsInner::Flat(result), None)
2566 }
2567 }
2568 }
2569}
2570
2571impl FromIterator<(String, Value)> for NixAttrs {
2572 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2573 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2574 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2575 }
2576}
2577
2578impl IntoIterator for NixAttrs {
2579 type Item = (String, Value);
2580 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2581
2582 fn into_iter(self) -> Self::IntoIter {
2583 let flat = self.as_flat().clone();
2584 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2585 }
2586}
2587
2588#[derive(Debug, Clone)]
2596pub struct Closure {
2597 pub param: rnix::ast::Param,
2598 pub body: rnix::ast::Expr,
2599 pub env: Env,
2600}
2601
2602pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2604
2605#[derive(Clone)]
2610pub struct BuiltinFn {
2611 pub name: &'static str,
2613 pub func: Rc<BuiltinFunc>,
2615}
2616
2617impl fmt::Debug for BuiltinFn {
2618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2619 write!(f, "<builtin {}>", self.name)
2620 }
2621}
2622
2623#[derive(Clone)]
2633struct WithScope {
2634 value: Value,
2635 cached: Rc<RefCell<Option<NixAttrs>>>,
2638}
2639
2640impl fmt::Debug for WithScope {
2641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2642 f.debug_struct("WithScope")
2643 .field("value", &self.value)
2644 .field("cached", &self.cached.borrow().is_some())
2645 .finish()
2646 }
2647}
2648
2649#[derive(Debug, Clone, Default)]
2659struct EnvInner {
2660 bindings: FxHashMap<Symbol, Value>,
2661 with_scopes: Vec<WithScope>,
2663 eval_file: Option<std::path::PathBuf>,
2667 source_id: u32,
2674}
2675
2676#[derive(Clone, Default)]
2684pub struct Env(Rc<EnvInner>);
2685
2686impl fmt::Debug for Env {
2687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2688 self.0.fmt(f)
2689 }
2690}
2691
2692impl Env {
2693 #[must_use]
2695 pub fn new() -> Self {
2696 Self(Rc::new(EnvInner {
2697 bindings: FxHashMap::default(),
2698 with_scopes: Vec::new(),
2699 eval_file: None,
2700 source_id: 0,
2701 }))
2702 }
2703
2704 #[must_use]
2709 pub fn child(&self) -> Self {
2710 crate::perf::inc(crate::perf::Counter::EnvClone);
2711 Self(Rc::new(EnvInner {
2712 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2714 eval_file: self.0.eval_file.clone(),
2718 source_id: self.0.source_id,
2722 }))
2723 }
2724
2725 #[must_use]
2733 pub fn with_scope(mut self, value: Value) -> Self {
2734 let pre_cached = match &value {
2736 Value::Attrs(attrs) => Some((**attrs).clone()),
2737 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2738 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2739 }),
2740 _ => None,
2741 };
2742 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2743 value,
2744 cached: Rc::new(RefCell::new(pre_cached)),
2745 });
2746 self
2747 }
2748
2749 pub fn bind(&mut self, name: String, value: Value) {
2754 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2755 }
2756
2757 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2767 let inner = Rc::make_mut(&mut self.0);
2768 for (name, value) in pairs {
2769 inner.bindings.insert(intern(&name), value);
2770 }
2771 }
2772
2773 #[must_use]
2775 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2776 self.0.eval_file.as_ref()
2777 }
2778
2779 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2781 Rc::make_mut(&mut self.0).eval_file = file;
2782 }
2783
2784 #[must_use]
2786 pub fn source_id(&self) -> u32 {
2787 self.0.source_id
2788 }
2789
2790 pub fn set_source_id(&mut self, id: u32) {
2793 Rc::make_mut(&mut self.0).source_id = id;
2794 }
2795
2796 #[must_use]
2798 pub fn binding_count(&self) -> usize {
2799 self.0.bindings.len()
2800 }
2801
2802 #[must_use]
2804 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2805 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2806 }
2807
2808 #[must_use]
2810 pub fn with_scope_count(&self) -> usize {
2811 self.0.with_scopes.len()
2812 }
2813
2814 #[must_use]
2818 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2819 let sym = intern(name);
2820 self.0.bindings.get(&sym).cloned()
2821 }
2822
2823 #[must_use]
2834 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
2835 self.0.bindings.get(&sym).cloned()
2836 }
2837
2838 #[must_use]
2842 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
2843 for scope in self.0.with_scopes.iter().rev() {
2844 let cache = scope.cached.borrow();
2845 if let Some(ref attrs) = *cache {
2846 if let Some(v) = attrs.get(name) {
2847 return Some(v.clone());
2848 }
2849 }
2850 drop(cache);
2852 if let Value::Thunk(ref thunk) = scope.value {
2853 if let Some(cached_val) = thunk.peek() {
2854 if let Concrete::Attrs(ref attrs) = *cached_val {
2855 *scope.cached.borrow_mut() = Some((**attrs).clone());
2857 if let Some(v) = attrs.get(name) {
2858 return Some(v.clone());
2859 }
2860 }
2861 }
2862 } else if let Value::Attrs(ref attrs) = scope.value {
2863 *scope.cached.borrow_mut() = Some((**attrs).clone());
2864 if let Some(v) = attrs.get(name) {
2865 return Some(v.clone());
2866 }
2867 }
2868 }
2869 None
2870 }
2871
2872 #[must_use]
2875 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
2876 self.0.with_scopes.last().map(|scope| {
2877 (scope.cached.clone(), scope.value.clone())
2878 })
2879 }
2880
2881 #[must_use]
2890 pub fn lookup(&self, name: &str) -> Option<Value> {
2891 self.lookup_fast(intern(name), name)
2892 }
2893
2894 #[must_use]
2906 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
2907 let sym = intern(name);
2908 if let Some(v) = self.0.bindings.get(&sym) {
2909 return Some(v.clone());
2910 }
2911 for scope in self.0.with_scopes.iter().rev() {
2912 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
2913 if let Some(v) = attrs.get_sym(&sym) {
2914 *scope.cached.borrow_mut() = Some((*attrs).clone());
2917 return Some(v.clone());
2918 }
2919 }
2920 }
2921 None
2922 }
2923
2924 #[must_use]
2926 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
2927 crate::perf::inc(crate::perf::Counter::EnvLookup);
2928 if let Some(v) = self.0.bindings.get(&sym) {
2929 return Some(v.clone());
2930 }
2931 for scope in self.0.with_scopes.iter().rev() {
2933 {
2935 let cache = scope.cached.borrow();
2936 if let Some(ref attrs) = *cache {
2937 if let Some(v) = attrs.get_sym(&sym) {
2938 return Some(v.clone());
2939 }
2940 continue;
2941 }
2942 }
2943 let resolved = match &scope.value {
2948 Value::Attrs(attrs) => {
2949 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2951 *scope.cached.borrow_mut() = Some((**attrs).clone());
2952 Some((**attrs).clone())
2953 }
2954 Value::Thunk(thunk) => {
2955 if let Some(cached_val) = thunk.peek() {
2958 if let Concrete::Attrs(ref attrs) = *cached_val {
2959 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2960 *scope.cached.borrow_mut() = Some((**attrs).clone());
2961 Some((**attrs).clone())
2962 } else {
2963 None
2964 }
2965 } else {
2966 match crate::eval::force_value(&scope.value) {
2977 Ok(forced) => {
2978 if let Value::Attrs(ref attrs) = forced {
2979 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2980 *scope.cached.borrow_mut() = Some((**attrs).clone());
2981 Some((**attrs).clone())
2982 } else {
2983 None
2984 }
2985 }
2986 Err(_) => None, }
2988 }
2989 }
2990 _ => {
2991 match crate::eval::force_value(&scope.value) {
2993 Ok(forced) => {
2994 if let Value::Attrs(ref attrs) = forced {
2995 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
2996 *scope.cached.borrow_mut() = Some((**attrs).clone());
2997 Some((**attrs).clone())
2998 } else {
2999 None
3000 }
3001 }
3002 Err(_) => None,
3003 }
3004 }
3005 };
3006 if let Some(ref attrs) = resolved {
3007 if let Some(v) = attrs.get(name) {
3008 return Some(v.clone());
3009 }
3010 }
3011 }
3013 None
3014 }
3015
3016 #[must_use]
3022 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
3023 crate::perf::inc(crate::perf::Counter::EnvLookup);
3024 if let Some(v) = self.0.bindings.get(&sym) {
3026 return Some(v.clone());
3027 }
3028 for scope in self.0.with_scopes.iter().rev() {
3030 {
3032 let cache = scope.cached.borrow();
3033 if let Some(ref attrs) = *cache {
3034 if let Some(v) = attrs.get_sym(&sym) {
3035 return Some(v.clone());
3036 }
3037 continue;
3038 }
3039 }
3040 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
3042 if let Value::Attrs(ref attrs) = forced {
3043 let result = attrs.get_sym(&sym).cloned();
3044 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3045 *scope.cached.borrow_mut() = Some((**attrs).clone());
3046 if result.is_some() {
3047 return result;
3048 }
3049 }
3050 }
3051 }
3053 None
3054 }
3055}
3056
3057#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
3059#[non_exhaustive]
3060pub enum EvalError {
3061 #[error("undefined variable: {0}")]
3063 UndefinedVar(String),
3064 #[error("type error: {0}")]
3066 TypeError(String),
3067 #[error("attribute not found: {0}")]
3069 AttrNotFound(String),
3070 #[error("type error: expected {expected}, got {got}")]
3072 TypeMismatch {
3073 expected: &'static str,
3074 got: &'static str,
3075 },
3076 #[error("assertion failed{0}")]
3078 AssertionFailed(String),
3079 #[error("division by zero")]
3081 DivisionByZero,
3082 #[error("infinite recursion ({0})")]
3084 InfiniteRecursion(String),
3085 #[error("I/O error: {context}: {message}")]
3087 IoError { context: String, message: String },
3088 #[error("{0}")]
3090 Throw(String),
3091 #[error("{0}")]
3095 Abort(String),
3096 #[error("not yet implemented: {0}")]
3098 NotImplemented(String),
3099 #[error("parse error: {0}")]
3101 ParseError(String),
3102 #[error("recursion limit: {0}")]
3104 RecursionLimit(String),
3105}
3106
3107impl EvalError {
3108 #[must_use]
3110 pub fn type_error(msg: impl Into<String>) -> Self {
3111 EvalError::TypeError(msg.into())
3112 }
3113
3114 #[must_use]
3116 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3117 EvalError::TypeMismatch { expected, got }
3118 }
3119
3120 #[must_use]
3122 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3123 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3124 }
3125
3126 #[must_use]
3147 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3148 EvalError::TypeError(format!(
3149 "cannot {op} {lhs} and {rhs}{}",
3150 crate::eval::eval_file_ctx()
3151 ))
3152 }
3153
3154 #[must_use]
3156 pub fn is_throw(&self) -> bool {
3157 matches!(self, EvalError::Throw(_))
3158 }
3159
3160 #[must_use]
3162 pub fn is_infinite_recursion(&self) -> bool {
3163 matches!(self, EvalError::InfiniteRecursion(_))
3164 }
3165}
3166
3167impl Value {
3168 #[must_use]
3170 pub fn string(s: impl Into<SmolStr>) -> Self {
3171 Value::String(Rc::new(NixString::plain(s)))
3172 }
3173
3174 #[must_use]
3177 pub fn list(items: Vec<Value>) -> Self {
3178 Value::List(Rc::new(NixList::new(items)))
3179 }
3180
3181 #[must_use]
3184 pub fn is_uniquely_owned_list(&self) -> bool {
3185 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3186 }
3187
3188 #[must_use]
3190 pub fn to_json(&self) -> serde_json::Value {
3191 match self {
3192 Value::Null => serde_json::Value::Null,
3193 Value::Bool(b) => serde_json::Value::Bool(*b),
3194 Value::Int(n) => serde_json::json!(n),
3195 Value::Float(f) => serde_json::json!(f),
3196 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3197 Value::Path(p) => serde_json::Value::String(p.to_string()),
3198 Value::List(items) => {
3199 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3200 }
3201 Value::Attrs(attrs) => {
3202 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3209 if let Ok((s, _ctx)) = self.coerce_to_string() {
3210 return serde_json::Value::String(s);
3211 }
3212 }
3213 let map: serde_json::Map<String, serde_json::Value> = attrs
3214 .iter()
3215 .map(|(k, v)| (k.clone(), v.to_json()))
3216 .collect();
3217 serde_json::Value::Object(map)
3218 }
3219 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3220 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3221 Value::Thunk(thunk) => {
3222 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3224 Ok(v) => v.to_json(),
3225 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3226 }
3227 }
3228 }
3229 }
3230
3231 pub fn to_json_with_context(
3238 &self,
3239 ctx: &mut StringContext,
3240 ) -> Result<serde_json::Value, EvalError> {
3241 Ok(match self {
3242 Value::Null => serde_json::Value::Null,
3243 Value::Bool(b) => serde_json::Value::Bool(*b),
3244 Value::Int(n) => serde_json::json!(n),
3245 Value::Float(f) => serde_json::json!(f),
3246 Value::String(s) => {
3247 ctx.merge(&s.context);
3248 serde_json::Value::String(s.chars.to_string())
3249 }
3250 Value::Path(_) => {
3251 let (str, c) = self.coerce_to_string_copy_to_store()?;
3252 ctx.merge(&c);
3253 serde_json::Value::String(str)
3254 }
3255 Value::List(items) => {
3256 let mut arr = Vec::with_capacity(items.len());
3257 for v in items.iter() {
3258 let fv = crate::eval::force_value(v)?;
3259 arr.push(fv.to_json_with_context(ctx)?);
3260 }
3261 serde_json::Value::Array(arr)
3262 }
3263 Value::Attrs(attrs) => {
3264 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3267 let (s, c) = self.coerce_to_string_copy_to_store()?;
3268 ctx.merge(&c);
3269 return Ok(serde_json::Value::String(s));
3270 }
3271 let mut map = serde_json::Map::new();
3272 for (k, v) in attrs.iter() {
3273 let fv = crate::eval::force_value(v)?;
3274 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3275 }
3276 serde_json::Value::Object(map)
3277 }
3278 Value::Thunk(_) => {
3279 let forced = crate::eval::force_value(self)?;
3280 forced.to_json_with_context(ctx)?
3281 }
3282 other => {
3283 return Err(EvalError::TypeError(format!(
3284 "cannot serialize {} to JSON (__structuredAttrs)",
3285 other.type_name()
3286 )));
3287 }
3288 })
3289 }
3290
3291 #[must_use]
3293 pub fn type_name(&self) -> &'static str {
3294 match self {
3295 Value::Null => "null",
3296 Value::Bool(_) => "bool",
3297 Value::Int(_) => "int",
3298 Value::Float(_) => "float",
3299 Value::String(_) => "string",
3300 Value::Path(_) => "path",
3301 Value::List(_) => "list",
3302 Value::Attrs(_) => "set",
3303 Value::Lambda(_) => "lambda",
3304 Value::Builtin(_) => "lambda",
3305 Value::Thunk(thunk) => {
3306 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3308 Ok(v) => v.type_name(),
3309 Err(_) => "thunk",
3310 }
3311 }
3312 }
3313 }
3314
3315 pub fn as_bool(&self) -> Result<bool, EvalError> {
3336 match self {
3337 Value::Bool(b) => Ok(*b),
3338 Value::Thunk(thunk) => {
3339 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3340 }
3341 _ if in_promise_eval() => Ok(false),
3345 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3346 }
3347 }
3348
3349 pub fn as_int(&self) -> Result<i64, EvalError> {
3351 match self {
3352 Value::Int(n) => Ok(*n),
3353 Value::Thunk(thunk) => {
3354 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3355 }
3356 _ if in_promise_eval() => Ok(0),
3359 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3360 }
3361 }
3362
3363 pub fn as_string(&self) -> Result<&str, EvalError> {
3365 match self {
3366 Value::String(s) => Ok(&s.chars),
3367 Value::Thunk(_) => Err(EvalError::TypeError(
3368 "thunk in as_string: force first via force_value()".into(),
3369 )),
3370 _ if in_promise_eval() => Ok(""),
3371 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3372 }
3373 }
3374
3375 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3377 match self {
3378 Value::String(ns) => Ok(ns),
3379 Value::Thunk(_) => Err(EvalError::TypeError(
3380 "thunk in as_nix_string: force first via force_value()".into(),
3381 )),
3382 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3383 }
3384 }
3385
3386 pub fn to_str(&self) -> Result<String, EvalError> {
3390 match self {
3391 Value::String(s) => Ok(s.chars.to_string()),
3392 Value::Thunk(thunk) => {
3393 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3394 forced.to_str()
3395 }
3396 _ if in_promise_eval() => Ok(String::new()),
3397 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3398 }
3399 }
3400
3401 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3404 match self {
3405 Value::String(s) => Ok((**s).clone()),
3406 Value::Thunk(thunk) => {
3407 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3408 forced.to_nix_string()
3409 }
3410 _ if in_promise_eval() => Ok(NixString::plain("")),
3411 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3412 }
3413 }
3414
3415 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3424 match self {
3425 Value::Attrs(a) => Ok(a),
3426 Value::Thunk(_) => Err(EvalError::TypeError(
3427 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3428 )),
3429 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3430 }
3431 }
3432
3433 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3435 match self {
3436 Value::List(l) => Ok(l.as_slice()),
3437 Value::Thunk(_) => Err(EvalError::TypeError(
3438 "thunk in as_list: force first via force_value()".into(),
3439 )),
3440 _ => Err(crate::eval::attach_trace(
3441 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3442 )),
3443 }
3444 }
3445
3446 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3448 match self {
3449 Value::Attrs(a) => Ok((**a).clone()),
3450 Value::Thunk(thunk) => {
3451 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3452 forced.to_attrs()
3453 }
3454 _ if in_promise_eval() => Ok(NixAttrs::new()),
3460 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3461 }
3462 }
3463
3464 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3466 match self {
3467 Value::List(l) => Ok((**l).0.clone()),
3468 Value::Thunk(thunk) => {
3469 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3470 forced.to_list()
3471 }
3472 _ if in_promise_eval() => Ok(Vec::new()),
3475 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3476 }
3477 }
3478
3479 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3485 match self {
3486 Value::Path(p) => Ok(p.to_string()),
3487 Value::String(ns) => Ok(ns.chars.to_string()),
3488 Value::Attrs(attrs) => {
3489 if let Some(out_path) = attrs.get("outPath") {
3490 let forced = crate::eval::force_value(out_path)?;
3491 forced.coerce_to_path(context)
3492 } else {
3493 Err(EvalError::TypeError(format!(
3494 "{context}: expected path or string, got set without outPath"
3495 )))
3496 }
3497 }
3498 _ => Err(EvalError::TypeError(format!(
3499 "{context}: expected path or string, got {}",
3500 self.type_name()
3501 ))),
3502 }
3503 }
3504
3505 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3529 match self {
3530 Value::Attrs(attrs) => {
3533 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3534 self.realize_if_absent(&drv_path, &out_path, context)?;
3535 return Ok(out_path);
3536 }
3537 }
3538 Value::String(ns) => {
3545 let out_path = ns.chars.to_string();
3546 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3547 self.realize_if_absent(&drv_path, &out_path, context)?;
3548 }
3549 return Ok(out_path);
3550 }
3551 _ => {}
3552 }
3553 self.coerce_to_path(context)
3554 }
3555
3556 fn realize_if_absent(
3561 &self,
3562 drv_path: &str,
3563 out_path: &str,
3564 context: &str,
3565 ) -> Result<(), EvalError> {
3566 let read_path = crate::path::materialize_str(out_path);
3569 if std::path::Path::new(&read_path).exists() {
3570 return Ok(());
3571 }
3572 match crate::realize::realize_output(drv_path, out_path) {
3573 Ok(true) | Ok(false) => Ok(()),
3574 Err(msg) => Err(EvalError::IoError {
3575 context: context.to_string(),
3576 message: format!(
3577 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3578 ),
3579 }),
3580 }
3581 }
3582
3583 pub fn to_float(&self) -> Result<f64, EvalError> {
3585 match self {
3586 Value::Float(f) => Ok(*f),
3587 Value::Int(n) => Ok(*n as f64),
3588 Value::Thunk(thunk) => {
3589 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3590 }
3591 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3592 }
3593 }
3594
3595 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3613 self.coerce_to_string_impl(false)
3614 }
3615
3616 pub fn coerce_to_string_copy_to_store(
3626 &self,
3627 ) -> Result<(String, StringContext), EvalError> {
3628 self.coerce_to_string_impl(true)
3629 }
3630
3631 fn coerce_to_string_impl(
3632 &self,
3633 copy_to_store: bool,
3634 ) -> Result<(String, StringContext), EvalError> {
3635 let mut ctx = StringContext::new();
3636 let s = match self {
3637 Value::String(ns) => {
3638 ctx.merge(&ns.context);
3639 ns.chars.to_string()
3640 }
3641 Value::Path(p) => {
3642 let raw: &str = &**p;
3643 if copy_to_store {
3644 let pb = std::path::Path::new(raw);
3664 let abs = if pb.is_absolute() {
3665 pb.to_path_buf()
3666 } else if let Some(dir) = crate::eval::current_eval_dir() {
3667 dir.join(pb)
3668 } else {
3669 std::env::current_dir()
3670 .map_err(|e| EvalError::IoError {
3671 context: format!("copy-to-store coercion of {raw}"),
3672 message: e.to_string(),
3673 })?
3674 .join(pb)
3675 };
3676 let read_abs = crate::path::materialize(&abs);
3683 let canon = read_abs.canonicalize().map_err(|_| {
3684 EvalError::TypeError(format!(
3685 "path '{}' does not exist",
3686 abs.display()
3687 ))
3688 })?;
3689 let name = crate::path::source_name_for_read_dir(&canon)
3706 .or_else(|| {
3707 canon
3708 .file_name()
3709 .map(|n| sui_compat::source::strip_store_hash_prefix(
3710 &n.to_string_lossy()).to_string())
3711 })
3712 .unwrap_or_else(|| "source".to_string());
3713 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3714 .map_err(|e| {
3715 EvalError::TypeError(format!(
3716 "copy-to-store coercion of '{}': {e}",
3717 canon.display()
3718 ))
3719 })?;
3720 ctx.add_plain(src.store_path.clone());
3721 src.store_path
3722 } else {
3723 ctx.add_plain(raw.to_string());
3724 raw.to_string()
3725 }
3726 }
3727 Value::Int(n) => n.to_string(),
3728 Value::Float(f) => format!("{f:.6}"),
3734 Value::Bool(true) => "1".to_string(),
3735 Value::Bool(false) => String::new(),
3736 Value::Null => String::new(),
3737 Value::Attrs(attrs) => {
3738 if let Some(to_str) = attrs.get("__toString") {
3739 let result =
3740 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3741 let forced = crate::eval::force_value(&result)?;
3742 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3743 ctx.merge(&c);
3744 s
3745 } else if let Some(out_path) = attrs.get("outPath") {
3746 let forced = crate::eval::force_value(out_path)?;
3747 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3748 ctx.merge(&c);
3749 s
3750 } else {
3751 return Err(EvalError::TypeError(
3752 "cannot coerce set to string (no __toString or outPath)".into(),
3753 ));
3754 }
3755 }
3756 Value::List(items) => {
3757 let mut parts = Vec::new();
3758 for item in items.iter() {
3759 let forced = crate::eval::force_value(item)?;
3760 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3761 ctx.merge(&c);
3762 parts.push(s);
3763 }
3764 parts.join(" ")
3765 }
3766 Value::Thunk(_) => {
3767 let forced = crate::eval::force_value(self)?;
3769 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3770 ctx.merge(&c);
3771 s
3772 }
3773 other => {
3774 return Err(EvalError::TypeError(format!(
3775 "cannot coerce {} to string",
3776 other.type_name()
3777 )));
3778 }
3779 };
3780 Ok((s, ctx))
3781 }
3782}
3783
3784impl From<&serde_json::Value> for Value {
3787 fn from(json: &serde_json::Value) -> Self {
3788 match json {
3789 serde_json::Value::Null => Value::Null,
3790 serde_json::Value::Bool(b) => Value::Bool(*b),
3791 serde_json::Value::Number(n) => {
3792 if let Some(i) = n.as_i64() {
3793 Value::Int(i)
3794 } else {
3795 Value::Float(n.as_f64().unwrap_or(0.0))
3796 }
3797 }
3798 serde_json::Value::String(s) => Value::string(s.clone()),
3799 serde_json::Value::Array(arr) => {
3800 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3801 }
3802 serde_json::Value::Object(obj) => {
3803 let mut attrs = NixAttrs::new();
3804 for (k, v) in obj {
3805 attrs.insert(k.clone(), Value::from(v));
3806 }
3807 Value::Attrs(Rc::new(attrs))
3808 }
3809 }
3810 }
3811}
3812
3813impl From<&toml::Value> for Value {
3814 fn from(v: &toml::Value) -> Self {
3815 match v {
3816 toml::Value::String(s) => Value::string(s.clone()),
3817 toml::Value::Integer(n) => Value::Int(*n),
3818 toml::Value::Float(f) => Value::Float(*f),
3819 toml::Value::Boolean(b) => Value::Bool(*b),
3820 toml::Value::Array(arr) => {
3821 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3822 }
3823 toml::Value::Table(t) => {
3824 let mut attrs = NixAttrs::new();
3825 for (k, val) in t {
3826 attrs.insert(k.clone(), Value::from(val));
3827 }
3828 Value::Attrs(Rc::new(attrs))
3829 }
3830 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
3831 }
3832 }
3833}
3834
3835
3836impl From<bool> for Value {
3839 fn from(b: bool) -> Self {
3840 Value::Bool(b)
3841 }
3842}
3843
3844impl From<i64> for Value {
3845 fn from(n: i64) -> Self {
3846 Value::Int(n)
3847 }
3848}
3849
3850impl From<f64> for Value {
3851 fn from(f: f64) -> Self {
3852 Value::Float(f)
3853 }
3854}
3855
3856impl From<NixString> for Value {
3857 fn from(s: NixString) -> Self {
3858 Value::String(Rc::new(s))
3859 }
3860}
3861
3862impl From<NixAttrs> for Value {
3863 fn from(attrs: NixAttrs) -> Self {
3864 Value::Attrs(Rc::new(attrs))
3865 }
3866}
3867
3868impl From<Vec<Value>> for Value {
3869 fn from(list: Vec<Value>) -> Self {
3870 Value::List(Rc::new(NixList::new(list)))
3871 }
3872}
3873
3874impl PartialEq for Value {
3875 fn eq(&self, other: &Self) -> bool {
3876 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
3878 if Rc::ptr_eq(&a.0, &b.0) { return true; }
3879 }
3880 let l = self.demand().unwrap_or(Concrete::Null);
3883 let r = other.demand().unwrap_or(Concrete::Null);
3884 l == r
3885 }
3886}
3887
3888impl fmt::Display for Value {
3889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3890 match self {
3891 Value::Null => write!(f, "null"),
3892 Value::Bool(b) => write!(f, "{b}"),
3893 Value::Int(n) => write!(f, "{n}"),
3894 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
3895 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
3896 Value::Path(p) => write!(f, "{p}"),
3897 Value::List(items) => {
3898 write!(f, "[ ")?;
3899 for item in items.iter() {
3900 write!(f, "{item} ")?;
3901 }
3902 write!(f, "]")
3903 }
3904 Value::Attrs(attrs) => {
3905 write!(f, "{{ ")?;
3906 for (k, v) in attrs.iter() {
3907 write!(f, "{k} = {v}; ")?;
3908 }
3909 write!(f, "}}")
3910 }
3911 Value::Lambda(_) => write!(f, "<<lambda>>"),
3912 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
3913 Value::Thunk(thunk) => {
3914 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
3915 Ok(v) => write!(f, "{v}"),
3916 Err(_) => write!(f, "<<thunk:error>>"),
3917 }
3918 }
3919 }
3920 }
3921}
3922
3923#[cfg(test)]
3924mod tests {
3925 use super::*;
3926 use std::rc::Rc;
3927
3928 #[test]
3931 fn value_is_16_bytes() {
3932 assert_eq!(std::mem::size_of::<Value>(), 16);
3933 }
3934
3935 #[test]
3948 fn overlay_carries_attr_positions_from_both_sides() {
3949 let tbl = |file: &str, key: &str, off: u32| {
3950 let mut t = crate::pos::AttrPositions::new(Some(std::path::PathBuf::from(file)));
3951 t.insert(intern(key), off);
3952 Rc::new(t)
3953 };
3954 let mk = |file: &str, key: &str, off: u32| {
3958 let mut a = NixAttrs::new();
3959 a.insert(key.to_string(), Value::Int(1));
3960 a.set_positions(tbl(file, key, off));
3961 a
3962 };
3963
3964 let left_only = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "other", 22));
3967 assert_eq!(
3968 left_only.pos_entry(intern("modules")),
3969 Some((Some(std::path::PathBuf::from("/l.nix")), 11)),
3970 );
3971
3972 let both = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "modules", 22));
3974 assert_eq!(
3975 both.pos_entry(intern("modules")),
3976 Some((Some(std::path::PathBuf::from("/r.nix")), 22)),
3977 );
3978
3979 assert_eq!(both.pos_entry(intern("nope")), None);
3981 }
3982
3983 #[test]
3986 fn to_json_null() {
3987 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
3988 }
3989
3990 #[test]
3991 fn to_json_bool() {
3992 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
3993 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
3994 }
3995
3996 #[test]
3997 fn to_json_int() {
3998 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
3999 }
4000
4001 #[test]
4002 fn to_json_float() {
4003 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
4004 }
4005
4006 #[test]
4007 fn to_json_string() {
4008 assert_eq!(
4009 Value::string("hello").to_json(),
4010 serde_json::Value::String("hello".to_string()),
4011 );
4012 }
4013
4014 #[test]
4015 fn to_json_path() {
4016 assert_eq!(
4017 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
4018 serde_json::Value::String("/nix/store".to_string()),
4019 );
4020 }
4021
4022 #[test]
4023 fn to_json_list() {
4024 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
4025 assert_eq!(v.to_json(), serde_json::json!([1, true]));
4026 }
4027
4028 #[test]
4029 fn to_json_attrs() {
4030 let mut attrs = NixAttrs::new();
4031 attrs.insert("a".to_string(), Value::Int(1));
4032 let v = Value::Attrs(Rc::new(attrs));
4033 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
4034 }
4035
4036 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
4039 let mut a = NixAttrs::new();
4040 a.insert("type".to_string(), Value::string("derivation"));
4041 a.insert("outPath".to_string(), Value::string(out_path));
4042 a.insert(extra_key.to_string(), Value::Int(extra_val));
4043 Value::Attrs(Rc::new(a))
4044 }
4045
4046 #[test]
4047 fn derivations_same_outpath_differing_attrs_are_equal() {
4048 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4055 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
4056 assert!(a == b, "same-outPath derivations must compare equal");
4057 assert!(!(a != b));
4058 }
4059
4060 #[test]
4061 fn derivations_differing_outpath_are_unequal() {
4062 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4063 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
4064 assert!(a != b, "different-outPath derivations must compare unequal");
4065 }
4066
4067 #[test]
4068 fn non_derivation_attrs_with_outpath_use_structural_eq() {
4069 let mut a = NixAttrs::new();
4072 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
4073 a.insert("foo".to_string(), Value::Int(1));
4074 let mut b = NixAttrs::new();
4075 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
4076 b.insert("foo".to_string(), Value::Int(2));
4077 assert!(
4078 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
4079 "non-derivation attrs with equal outPath but differing foo must be unequal",
4080 );
4081 }
4082
4083 #[test]
4089 fn attrs_eq_borrow_result_matches_multi_key() {
4090 let mk = || {
4093 let mut inner = NixAttrs::new();
4094 inner.insert("n".to_string(), Value::Int(7));
4095 let mut a = NixAttrs::new();
4096 a.insert("a".to_string(), Value::Int(1));
4097 a.insert("b".to_string(), Value::string("two"));
4098 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
4099 Value::Attrs(Rc::new(a))
4100 };
4101 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
4102
4103 let mut b = NixAttrs::new();
4105 b.insert("a".to_string(), Value::Int(1));
4106 b.insert("b".to_string(), Value::string("TWO"));
4107 let mut a2 = NixAttrs::new();
4108 a2.insert("a".to_string(), Value::Int(1));
4109 a2.insert("b".to_string(), Value::string("two"));
4110 assert!(
4111 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
4112 "attrsets differing in one value must be unequal (borrow path)",
4113 );
4114
4115 let mut a3 = NixAttrs::new();
4117 a3.insert("a".to_string(), Value::Int(1));
4118 let mut b3 = NixAttrs::new();
4119 b3.insert("a".to_string(), Value::Int(1));
4120 b3.insert("extra".to_string(), Value::Int(9));
4121 assert!(
4122 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
4123 "attrsets differing in key set must be unequal (borrow path)",
4124 );
4125 }
4126
4127 #[test]
4128 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
4129 let boom = Value::Thunk(Thunk::new_native(|| {
4140 Err(EvalError::Throw("kaboom".to_string()))
4141 }));
4142 let mut a = NixAttrs::new();
4143 a.insert("x".to_string(), Value::Int(1));
4144 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4146 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4148 let va = Value::Attrs(Rc::new(a));
4152 let vb = Value::Attrs(Rc::new(b));
4153 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4154 }
4155
4156 #[test]
4157 fn attrs_eq_borrow_overlay_still_compares() {
4158 let mut base = NixAttrs::new();
4162 base.insert("a".to_string(), Value::Int(1));
4163 let mut over = NixAttrs::new();
4164 over.insert("b".to_string(), Value::Int(2));
4165 let merged = base.overlay(over);
4168 let mut flat = NixAttrs::new();
4169 flat.insert("a".to_string(), Value::Int(1));
4170 flat.insert("b".to_string(), Value::Int(2));
4171 assert!(
4172 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4173 "overlay and equivalent flat attrset must compare equal (borrow path)",
4174 );
4175 }
4176
4177 #[test]
4178 fn to_json_lambda() {
4179 let root = rnix::Root::parse("x: x");
4181 let expr = root.tree().expr().unwrap();
4182 let lambda = match expr {
4183 rnix::ast::Expr::Lambda(l) => l,
4184 _ => panic!("expected lambda"),
4185 };
4186 let closure = Closure {
4187 param: lambda.param().unwrap(),
4188 body: lambda.body().unwrap(),
4189 env: Env::new(),
4190 };
4191 assert_eq!(
4192 Value::Lambda(Rc::new(closure)).to_json(),
4193 serde_json::Value::String("<lambda>".to_string()),
4194 );
4195 }
4196
4197 #[test]
4198 fn to_json_builtin() {
4199 let b = BuiltinFn {
4200 name: "test",
4201 func: Rc::new(|_| Ok(Value::Null)),
4202 };
4203 assert_eq!(
4204 Value::Builtin(Box::new(b)).to_json(),
4205 serde_json::Value::String("<builtin test>".to_string()),
4206 );
4207 }
4208
4209 #[test]
4212 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4213
4214 #[test]
4215 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4216
4217 #[test]
4218 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4219
4220 #[test]
4221 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4222
4223 #[test]
4224 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4225
4226 #[test]
4227 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4228
4229 #[test]
4230 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4231
4232 #[test]
4233 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4234
4235 #[test]
4236 fn type_name_lambda() {
4237 let root = rnix::Root::parse("x: x");
4238 let expr = root.tree().expr().unwrap();
4239 let lambda = match expr {
4240 rnix::ast::Expr::Lambda(l) => l,
4241 _ => panic!("expected lambda"),
4242 };
4243 let closure = Closure {
4244 param: lambda.param().unwrap(),
4245 body: lambda.body().unwrap(),
4246 env: Env::new(),
4247 };
4248 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4249 }
4250
4251 #[test]
4252 fn type_name_builtin() {
4253 let b = BuiltinFn {
4254 name: "t",
4255 func: Rc::new(|_| Ok(Value::Null)),
4256 };
4257 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4258 }
4259
4260 #[test]
4263 fn as_bool_error_on_non_bool() {
4264 assert!(Value::Int(1).as_bool().is_err());
4265 assert!(Value::string("true").as_bool().is_err());
4266 }
4267
4268 #[test]
4269 fn as_int_error_on_non_int() {
4270 assert!(Value::Bool(true).as_int().is_err());
4271 assert!(Value::Float(1.0).as_int().is_err());
4272 }
4273
4274 #[test]
4275 fn as_string_error_on_non_string() {
4276 assert!(Value::Int(42).as_string().is_err());
4277 assert!(Value::Null.as_string().is_err());
4278 }
4279
4280 #[test]
4281 fn as_attrs_error_on_non_attrs() {
4282 assert!(Value::Int(1).as_attrs().is_err());
4283 assert!(Value::list(vec![]).as_attrs().is_err());
4284 }
4285
4286 #[test]
4287 fn as_list_error_on_non_list() {
4288 assert!(Value::Int(1).as_list().is_err());
4289 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4290 }
4291
4292 #[test]
4295 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4296 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4298 assert!(left.is_uniquely_owned_list());
4299 let right = [Value::Int(3), Value::Int(4)];
4300 let out = super::concat_lists(left, &right).unwrap();
4301 assert_eq!(
4302 out.as_list().unwrap(),
4303 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4304 );
4305 }
4306
4307 #[test]
4308 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4309 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4312 let left = Value::List(Rc::clone(&shared));
4313 assert!(!left.is_uniquely_owned_list());
4314 let right = [Value::Int(3)];
4315 let out = super::concat_lists(left, &right).unwrap();
4316 assert_eq!(
4317 out.as_list().unwrap(),
4318 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4319 );
4320 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4322 }
4323
4324 #[test]
4325 fn concat_lists_empty_operands() {
4326 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4327 assert!(out.as_list().unwrap().is_empty());
4328 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4329 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4330 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4331 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4332 }
4333
4334 #[test]
4335 fn concat_lists_non_list_left_errors() {
4336 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4337 }
4338
4339 #[test]
4340 fn concat_lists_preserves_element_identity() {
4341 let inner = Rc::new(NixString::plain("x"));
4343 let a = Value::String(Rc::clone(&inner));
4344 let left = Value::list(vec![a]);
4345 let out = super::concat_lists(left, &[]).unwrap();
4346 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4347 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4348 } else {
4349 panic!("expected string element");
4350 }
4351 }
4352
4353 #[test]
4356 fn to_float_coerces_int() {
4357 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4358 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4359 assert!(Value::string("x").to_float().is_err());
4360 }
4361
4362 #[test]
4365 fn partial_eq_int_float_cross() {
4366 assert_eq!(Value::Int(3), Value::Float(3.0));
4367 assert_eq!(Value::Float(3.0), Value::Int(3));
4368 assert_ne!(Value::Int(3), Value::Float(3.5));
4369 }
4370
4371 #[test]
4372 fn partial_eq_different_types_not_equal() {
4373 assert_ne!(Value::Int(1), Value::string("1"));
4374 assert_ne!(Value::Bool(true), Value::Int(1));
4375 assert_ne!(Value::Null, Value::Bool(false));
4376 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4377 }
4378
4379 #[test]
4382 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4383
4384 #[test]
4385 fn display_bool() {
4386 assert_eq!(format!("{}", Value::Bool(true)), "true");
4387 assert_eq!(format!("{}", Value::Bool(false)), "false");
4388 }
4389
4390 #[test]
4391 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4392
4393 #[test]
4394 fn display_float() {
4395 let s = format!("{}", Value::Float(3.14));
4396 assert!(s.contains("3.14"));
4397 }
4398
4399 #[test]
4400 fn display_string() {
4401 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4402 }
4403
4404 #[test]
4405 fn display_string_with_escapes() {
4406 let v = Value::string("a\"b\\c");
4407 let s = format!("{v}");
4408 assert!(s.contains("\\\""));
4409 assert!(s.contains("\\\\"));
4410 }
4411
4412 #[test]
4413 fn display_path() {
4414 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4415 }
4416
4417 #[test]
4418 fn display_list() {
4419 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4420 assert_eq!(format!("{v}"), "[ 1 2 ]");
4421 }
4422
4423 #[test]
4424 fn display_attrs() {
4425 let mut attrs = NixAttrs::new();
4426 attrs.insert("x".to_string(), Value::Int(1));
4427 let v = Value::Attrs(Rc::new(attrs));
4428 assert_eq!(format!("{v}"), "{ x = 1; }");
4429 }
4430
4431 #[test]
4432 fn display_lambda() {
4433 let root = rnix::Root::parse("x: x");
4434 let expr = root.tree().expr().unwrap();
4435 let lambda = match expr {
4436 rnix::ast::Expr::Lambda(l) => l,
4437 _ => panic!("expected lambda"),
4438 };
4439 let closure = Closure {
4440 param: lambda.param().unwrap(),
4441 body: lambda.body().unwrap(),
4442 env: Env::new(),
4443 };
4444 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4445 }
4446
4447 #[test]
4448 fn display_builtin() {
4449 let b = BuiltinFn {
4450 name: "add",
4451 func: Rc::new(|_| Ok(Value::Null)),
4452 };
4453 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4454 }
4455
4456 #[test]
4459 fn nixattrs_update_merging() {
4460 let mut a = NixAttrs::new();
4461 a.insert("x".to_string(), Value::Int(1));
4462 a.insert("y".to_string(), Value::Int(2));
4463 let mut b = NixAttrs::new();
4464 b.insert("y".to_string(), Value::Int(99));
4465 b.insert("z".to_string(), Value::Int(3));
4466 let merged = a.update(&b);
4467 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4468 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4469 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4470 assert_eq!(merged.len(), 3);
4471 }
4472
4473 #[test]
4474 fn nixattrs_contains_key() {
4475 let mut a = NixAttrs::new();
4476 a.insert("foo".to_string(), Value::Null);
4477 assert!(a.contains_key("foo"));
4478 assert!(!a.contains_key("bar"));
4479 }
4480
4481 #[test]
4484 fn env_lookup_through_parent_chain() {
4485 let mut root = Env::new();
4486 root.bind("a".to_string(), Value::Int(1));
4487 let mut child = root.child();
4488 child.bind("b".to_string(), Value::Int(2));
4489 let grandchild = child.child();
4490 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4492 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4493 assert_eq!(grandchild.lookup("c"), None);
4494 }
4495
4496 #[test]
4497 fn env_with_scope_lookup() {
4498 let mut attrs = NixAttrs::new();
4499 attrs.insert("x".to_string(), Value::Int(42));
4500 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4501 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4502 assert_eq!(env.lookup("y"), None);
4503 }
4504
4505 #[test]
4506 fn env_local_shadows_with_scope() {
4507 let mut attrs = NixAttrs::new();
4508 attrs.insert("x".to_string(), Value::Int(1));
4509 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4510 env.bind("x".to_string(), Value::Int(99));
4511 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4512 }
4513
4514 #[test]
4517 fn string_context_merge_combines_elements() {
4518 let mut ctx_a = StringContext::new();
4519 ctx_a.add_plain("/nix/store/aaa".to_string());
4520 let mut ctx_b = StringContext::new();
4521 ctx_b.add_plain("/nix/store/bbb".to_string());
4522 ctx_a.merge(&ctx_b);
4523 assert_eq!(ctx_a.len(), 2);
4524 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4525 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4526 }
4527
4528 #[test]
4529 fn string_context_merge_deduplicates() {
4530 let mut ctx = StringContext::new();
4531 ctx.add_plain("/nix/store/same".to_string());
4532 ctx.add_plain("/nix/store/same".to_string());
4533 assert_eq!(ctx.len(), 1);
4534 }
4535
4536 #[test]
4537 fn string_context_mixed_element_types() {
4538 let mut ctx = StringContext::new();
4539 ctx.add_plain("/nix/store/foo".to_string());
4540 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4541 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4542 assert_eq!(ctx.len(), 3);
4543 assert!(!ctx.is_empty());
4544 }
4545
4546 #[test]
4547 fn string_context_new_is_empty() {
4548 let ctx = StringContext::new();
4549 assert!(ctx.is_empty());
4550 assert_eq!(ctx.len(), 0);
4551 }
4552
4553 #[test]
4554 fn string_context_merge_zero_elements() {
4555 let mut ctx_a = StringContext::new();
4556 let ctx_b = StringContext::new();
4557 ctx_a.merge(&ctx_b);
4558 assert!(ctx_a.is_empty());
4559 }
4560
4561 #[test]
4562 fn string_context_merge_one_element() {
4563 let mut ctx = StringContext::new();
4564 let mut other = StringContext::new();
4565 other.add_plain("/nix/store/only".to_string());
4566 ctx.merge(&other);
4567 assert_eq!(ctx.len(), 1);
4568 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4569 }
4570
4571 #[test]
4572 fn string_context_merge_two_elements() {
4573 let mut ctx = StringContext::new();
4574 ctx.add_plain("/nix/store/a".to_string());
4575 let mut other = StringContext::new();
4576 other.add_plain("/nix/store/b".to_string());
4577 ctx.merge(&other);
4578 assert_eq!(ctx.len(), 2);
4579 }
4580
4581 #[test]
4582 fn string_context_merge_five_elements() {
4583 let mut ctx = StringContext::new();
4584 for i in 0..5 {
4585 ctx.add_plain(format!("/nix/store/path-{i}"));
4586 }
4587 assert_eq!(ctx.len(), 5);
4588 for i in 0..5 {
4589 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4590 }
4591 }
4592
4593 #[test]
4594 fn string_context_insert_deduplicates() {
4595 let mut ctx = StringContext::new();
4596 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4597 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4598 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4599 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4600 assert_eq!(ctx.len(), 2);
4601 }
4602
4603 #[test]
4604 fn nix_string_plain_has_no_context() {
4605 let s = NixString::plain("hello");
4606 assert!(!s.has_context());
4607 assert_eq!(s.as_str(), "hello");
4608 }
4609
4610 #[test]
4611 fn nix_string_with_context_reports_context() {
4612 let mut ctx = StringContext::new();
4613 ctx.add_plain("/nix/store/xyz".to_string());
4614 let s = NixString::with_context("hello", ctx);
4615 assert!(s.has_context());
4616 assert_eq!(s.as_str(), "hello");
4617 }
4618
4619 #[test]
4620 fn nix_string_display_shows_chars_only() {
4621 let mut ctx = StringContext::new();
4622 ctx.add_plain("/nix/store/abc".to_string());
4623 let s = NixString::with_context("visible", ctx);
4624 assert_eq!(format!("{s}"), "visible");
4625 }
4626
4627 #[test]
4628 fn nix_string_struct_eq_includes_context() {
4629 let plain = NixString::plain("hello");
4630 let mut ctx = StringContext::new();
4631 ctx.add_plain("/nix/store/xxx".to_string());
4632 let with_ctx = NixString::with_context("hello", ctx);
4633 assert_ne!(plain, with_ctx);
4635 }
4636
4637 #[test]
4638 fn value_string_eq_ignores_context() {
4639 let plain = Value::String(Rc::new(NixString::plain("hello")));
4640 let mut ctx = StringContext::new();
4641 ctx.add_plain("/nix/store/xxx".to_string());
4642 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4643 assert_eq!(plain, with_ctx);
4645 }
4646
4647 #[test]
4650 fn env_nested_with_inner_wins() {
4651 let mut outer_attrs = NixAttrs::new();
4652 outer_attrs.insert("x".to_string(), Value::Int(1));
4653 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4654 let mut inner_attrs = NixAttrs::new();
4655 inner_attrs.insert("x".to_string(), Value::Int(2));
4656 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4657 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4658 }
4659
4660 #[test]
4661 fn env_nested_with_fallback_to_outer() {
4662 let mut outer_attrs = NixAttrs::new();
4663 outer_attrs.insert("x".to_string(), Value::Int(1));
4664 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4665 let mut inner_attrs = NixAttrs::new();
4666 inner_attrs.insert("y".to_string(), Value::Int(2));
4667 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4668 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4669 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4670 }
4671
4672 #[test]
4673 fn env_lexical_binding_wins_over_all_with_scopes() {
4674 let mut outer_attrs = NixAttrs::new();
4675 outer_attrs.insert("x".to_string(), Value::Int(1));
4676 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4677 let mut inner_attrs = NixAttrs::new();
4678 inner_attrs.insert("x".to_string(), Value::Int(2));
4679 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4680 inner.bind("x".to_string(), Value::Int(99));
4681 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4682 }
4683
4684 #[test]
4685 fn env_parent_lexical_wins_over_child_with_scope() {
4686 let mut root = Env::new();
4687 root.bind("x".to_string(), Value::Int(10));
4688 let mut child_attrs = NixAttrs::new();
4689 child_attrs.insert("x".to_string(), Value::Int(20));
4690 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4691 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4692 }
4693
4694 #[test]
4695 fn env_deeply_nested_with_scopes_three_levels() {
4696 let mut a = NixAttrs::new();
4697 a.insert("x".to_string(), Value::Int(1));
4698 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4699
4700 let mut b = NixAttrs::new();
4701 b.insert("y".to_string(), Value::Int(2));
4702 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4703
4704 let mut c = NixAttrs::new();
4705 c.insert("z".to_string(), Value::Int(3));
4706 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4707
4708 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4709 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4710 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4711 assert_eq!(env3.lookup("w"), None);
4712 }
4713
4714 #[test]
4715 fn env_with_scope_does_not_pollute_bindings() {
4716 let mut attrs = NixAttrs::new();
4719 attrs.insert("x".to_string(), Value::Int(42));
4720 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4721 assert!(env.0.bindings.get(&intern("x")).is_none());
4723 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4725 }
4726
4727 #[test]
4728 fn env_lexical_binding_not_in_with_scopes() {
4729 let mut env = Env::new();
4731 env.bind("x".to_string(), Value::Int(42));
4732 assert!(env.0.with_scopes.is_empty());
4734 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4736 }
4737
4738 #[test]
4739 fn env_child_inherits_eval_file() {
4740 let mut env = Env::new();
4741 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4742 let child = env.child();
4743 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4744 }
4745
4746 #[test]
4747 fn env_new_has_no_parent_no_with() {
4748 let env = Env::new();
4749 assert_eq!(env.lookup("anything"), None);
4750 assert!(env.eval_file().is_none());
4751 }
4752
4753 #[test]
4756 fn thunk_new_suspended_is_not_evaluated() {
4757 let root = rnix::Root::parse("42");
4758 let expr = root.tree().expr().unwrap();
4759 let thunk = Thunk::new_suspended(expr, Env::new());
4760 assert!(!thunk.is_evaluated());
4761 }
4762
4763 #[test]
4764 fn thunk_new_evaluated_is_evaluated() {
4765 let thunk = Thunk::new_evaluated(Value::Int(42));
4766 assert!(thunk.is_evaluated());
4767 }
4768
4769 #[test]
4770 fn thunk_force_evaluates_suspended() {
4771 let root = rnix::Root::parse("42");
4772 let expr = root.tree().expr().unwrap();
4773 let thunk = Thunk::new_suspended(expr, Env::new());
4774 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4775 assert!(result.is_ok());
4776 assert_eq!(result.unwrap(), Value::Int(42));
4777 assert!(thunk.is_evaluated());
4778 }
4779
4780 #[test]
4781 fn thunk_force_memoizes_result() {
4782 let root = rnix::Root::parse("1 + 2");
4783 let expr = root.tree().expr().unwrap();
4784 let thunk = Thunk::new_suspended(expr, Env::new());
4785 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4786 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4787 assert_eq!(r1, Value::Int(3));
4788 assert_eq!(r2, Value::Int(3));
4789 }
4790
4791 #[test]
4792 fn thunk_force_already_evaluated_returns_value() {
4793 let thunk = Thunk::new_evaluated(Value::Bool(true));
4794 let result = thunk.force(&|_, _| panic!("should not be called"));
4795 assert_eq!(result.unwrap(), Value::Bool(true));
4796 }
4797
4798 #[test]
4807 fn thunk_force_concrete_skips_redundant_store_but_caches() {
4808 let root = rnix::Root::parse("1 + 2");
4811 let expr = root.tree().expr().unwrap();
4812 let thunk = Thunk::new_suspended(expr, Env::new());
4813
4814 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
4815 assert_eq!(r1, Value::Int(3));
4816 assert!(thunk.is_evaluated());
4817
4818 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
4821
4822 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
4824 assert_eq!(r2, Value::Int(3));
4825 }
4826
4827 #[test]
4828 fn thunk_blackhole_detects_infinite_recursion() {
4829 let root = rnix::Root::parse("42");
4830 let expr = root.tree().expr().unwrap();
4831 let thunk = Thunk::new_suspended(expr, Env::new());
4832
4833 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
4836
4837 let result = thunk.force(&|_, _| Ok(Value::Null));
4838 assert!(result.is_err());
4839 let err_msg = format!("{}", result.unwrap_err());
4840 assert!(err_msg.contains("infinite recursion"));
4841 }
4842
4843 #[test]
4844 fn thunk_update_env_replaces_suspended_env() {
4845 let root = rnix::Root::parse("x");
4846 let expr = root.tree().expr().unwrap();
4847 let thunk = Thunk::new_suspended(expr, Env::new());
4848
4849 let mut new_env = Env::new();
4850 new_env.bind("x".to_string(), Value::Int(99));
4851 thunk.update_env(&new_env);
4852
4853 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4854 assert_eq!(result.unwrap(), Value::Int(99));
4855 }
4856
4857 #[test]
4858 fn thunk_update_env_noop_when_evaluated() {
4859 let thunk = Thunk::new_evaluated(Value::Int(1));
4860 let mut new_env = Env::new();
4861 new_env.bind("x".to_string(), Value::Int(99));
4862 thunk.update_env(&new_env);
4863 assert_eq!(
4864 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
4865 Value::Int(1),
4866 );
4867 }
4868
4869 #[test]
4870 fn thunk_debug_suspended() {
4871 let root = rnix::Root::parse("42");
4872 let expr = root.tree().expr().unwrap();
4873 let thunk = Thunk::new_suspended(expr, Env::new());
4874 assert_eq!(format!("{thunk:?}"), "<thunk>");
4875 }
4876
4877 #[test]
4878 fn thunk_debug_evaluated() {
4879 let thunk = Thunk::new_evaluated(Value::Int(42));
4880 let dbg = format!("{thunk:?}");
4881 assert!(dbg.contains("42"));
4882 }
4883
4884 #[test]
4885 fn thunk_error_restores_suspended_state() {
4886 let root = rnix::Root::parse("nonexistent_var");
4887 let expr = root.tree().expr().unwrap();
4888 let thunk = Thunk::new_suspended(expr, Env::new());
4889
4890 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4891 assert!(result.is_err());
4892 assert!(!thunk.is_evaluated());
4894 let dbg = format!("{thunk:?}");
4895 assert_eq!(dbg, "<thunk>");
4896 }
4897
4898 #[test]
4899 fn thunk_inherit_select_forces_and_selects() {
4900 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4901 let expr = root.tree().expr().unwrap();
4902 let source = Thunk::new_suspended(expr, Env::new());
4903 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4904 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4905 assert_eq!(result.unwrap(), Value::Int(42));
4906 assert!(thunk.is_evaluated());
4907 }
4908
4909 #[test]
4910 fn thunk_inherit_select_missing_attr_errors() {
4911 let root = rnix::Root::parse(r#"{ x = 42; }"#);
4912 let expr = root.tree().expr().unwrap();
4913 let source = Thunk::new_suspended(expr, Env::new());
4914 let thunk = Thunk::new_inherit_select(source, "y".to_string());
4915 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4916 assert!(result.is_err());
4917 assert!(!thunk.is_evaluated());
4919 }
4920
4921 #[test]
4922 fn thunk_inherit_select_non_attrs_source_errors() {
4923 let root = rnix::Root::parse("42");
4924 let expr = root.tree().expr().unwrap();
4925 let source = Thunk::new_suspended(expr, Env::new());
4926 let thunk = Thunk::new_inherit_select(source, "x".to_string());
4927 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
4928 assert!(result.is_err());
4929 let msg = format!("{}", result.unwrap_err());
4930 assert!(msg.contains("not a set"));
4931 }
4932
4933 #[test]
4934 fn thunk_inherit_select_shares_source_thunk() {
4935 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
4939 let expr = root.tree().expr().unwrap();
4940 let source = Thunk::new_suspended(expr, Env::new());
4941 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
4942 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
4943 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
4944 assert_eq!(result_a.unwrap(), Value::Int(1));
4945 assert!(source.is_evaluated());
4947 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
4949 assert_eq!(result_b.unwrap(), Value::Int(2));
4950 }
4951
4952 #[test]
4955 fn nixattrs_empty_operations() {
4956 let a = NixAttrs::new();
4957 assert!(a.is_empty());
4958 assert_eq!(a.len(), 0);
4959 assert_eq!(a.get("x"), None);
4960 assert!(!a.contains_key("x"));
4961 assert_eq!(a.keys().count(), 0);
4962 assert_eq!(a.iter().count(), 0);
4963 }
4964
4965 #[test]
4966 fn nixattrs_update_with_empty() {
4967 let mut a = NixAttrs::new();
4968 a.insert("x".to_string(), Value::Int(1));
4969 let b = NixAttrs::new();
4970 let merged = a.update(&b);
4971 assert_eq!(merged.len(), 1);
4972 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4973 }
4974
4975 #[test]
4976 fn nixattrs_update_empty_with_nonempty() {
4977 let a = NixAttrs::new();
4978 let mut b = NixAttrs::new();
4979 b.insert("x".to_string(), Value::Int(1));
4980 let merged = a.update(&b);
4981 assert_eq!(merged.len(), 1);
4982 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4983 }
4984
4985 #[test]
4986 fn nixattrs_keys_sorted_order() {
4987 let mut a = NixAttrs::new();
4988 a.insert("c".to_string(), Value::Int(3));
4989 a.insert("a".to_string(), Value::Int(1));
4990 a.insert("b".to_string(), Value::Int(2));
4991 let keys: Vec<String> = a.keys().collect();
4992 assert_eq!(keys, vec!["a", "b", "c"]);
4993 }
4994
4995 #[test]
4998 fn value_to_str_forces_thunks() {
4999 let root = rnix::Root::parse(r#""hello""#);
5000 let expr = root.tree().expr().unwrap();
5001 let thunk = Thunk::new_suspended(expr, Env::new());
5002 let val = Value::Thunk(thunk);
5003 assert_eq!(val.to_str().unwrap(), "hello");
5004 }
5005
5006 #[test]
5007 fn value_to_nix_string_forces_thunks() {
5008 let root = rnix::Root::parse(r#""world""#);
5009 let expr = root.tree().expr().unwrap();
5010 let thunk = Thunk::new_suspended(expr, Env::new());
5011 let val = Value::Thunk(thunk);
5012 let ns = val.to_nix_string().unwrap();
5013 assert_eq!(ns.as_str(), "world");
5014 assert!(!ns.has_context());
5015 }
5016
5017 #[test]
5018 fn value_to_attrs_forces_thunks() {
5019 let root = rnix::Root::parse("{ x = 1; }");
5020 let expr = root.tree().expr().unwrap();
5021 let thunk = Thunk::new_suspended(expr, Env::new());
5022 let val = Value::Thunk(thunk);
5023 let attrs = val.to_attrs().unwrap();
5024 assert_eq!(attrs.len(), 1);
5025 }
5026
5027 #[test]
5028 fn value_to_list_forces_thunks() {
5029 let root = rnix::Root::parse("[1 2 3]");
5030 let expr = root.tree().expr().unwrap();
5031 let thunk = Thunk::new_suspended(expr, Env::new());
5032 let val = Value::Thunk(thunk);
5033 let list = val.to_list().unwrap();
5034 assert_eq!(list.len(), 3);
5035 }
5036
5037 #[test]
5038 fn value_to_float_on_thunk() {
5039 let root = rnix::Root::parse("3.14");
5040 let expr = root.tree().expr().unwrap();
5041 let thunk = Thunk::new_suspended(expr, Env::new());
5042 let val = Value::Thunk(thunk);
5043 let f = val.to_float().unwrap();
5044 assert!((f - 3.14).abs() < f64::EPSILON);
5045 }
5046
5047 #[test]
5048 fn value_as_bool_on_thunk() {
5049 let root = rnix::Root::parse("true");
5050 let expr = root.tree().expr().unwrap();
5051 let thunk = Thunk::new_suspended(expr, Env::new());
5052 let val = Value::Thunk(thunk);
5053 assert!(val.as_bool().unwrap());
5054 }
5055
5056 #[test]
5057 fn value_as_int_on_thunk() {
5058 let root = rnix::Root::parse("42");
5059 let expr = root.tree().expr().unwrap();
5060 let thunk = Thunk::new_suspended(expr, Env::new());
5061 let val = Value::Thunk(thunk);
5062 assert_eq!(val.as_int().unwrap(), 42);
5063 }
5064
5065 #[test]
5066 fn value_string_constructor() {
5067 let v = Value::string("test");
5068 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
5069 }
5070
5071 #[test]
5072 fn value_partial_eq_null_null() {
5073 assert_eq!(Value::Null, Value::Null);
5074 }
5075
5076 #[test]
5077 fn value_partial_eq_lists_deep() {
5078 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5079 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5080 assert_eq!(a, b);
5081 }
5082
5083 #[test]
5084 fn value_partial_eq_attrs_deep() {
5085 let mut a = NixAttrs::new();
5086 a.insert("x".to_string(), Value::Int(1));
5087 let mut b = NixAttrs::new();
5088 b.insert("x".to_string(), Value::Int(1));
5089 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
5090 }
5091
5092 #[test]
5095 fn eval_error_type_error_constructor() {
5096 let e = EvalError::type_error("oops");
5097 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
5098 }
5099
5100 #[test]
5101 fn eval_error_type_mismatch_constructor() {
5102 let e = EvalError::type_mismatch("int", "string");
5103 match e {
5104 EvalError::TypeMismatch { expected, got } => {
5105 assert_eq!(expected, "int");
5106 assert_eq!(got, "string");
5107 }
5108 _ => panic!("expected TypeMismatch"),
5109 }
5110 }
5111
5112 #[test]
5113 fn eval_error_is_throw_yes_no() {
5114 assert!(EvalError::Throw("oops".into()).is_throw());
5115 assert!(!EvalError::TypeError("oops".into()).is_throw());
5116 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
5117 }
5118
5119 #[test]
5120 fn eval_error_is_infinite_recursion_yes_no() {
5121 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
5122 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
5123 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
5124 }
5125
5126 #[test]
5127 fn eval_error_display_undefined_var() {
5128 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
5129 assert!(s.contains("undefined variable"));
5130 assert!(s.contains("foo"));
5131 }
5132
5133 #[test]
5134 fn eval_error_display_type_error() {
5135 let s = format!("{}", EvalError::TypeError("bad".into()));
5136 assert!(s.contains("type error"));
5137 assert!(s.contains("bad"));
5138 }
5139
5140 #[test]
5141 fn eval_error_display_attr_not_found() {
5142 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5143 assert!(s.contains("attribute not found"));
5144 assert!(s.contains("x"));
5145 }
5146
5147 #[test]
5148 fn eval_error_display_type_mismatch() {
5149 let s = format!(
5150 "{}",
5151 EvalError::TypeMismatch { expected: "int", got: "string" }
5152 );
5153 assert!(s.contains("expected int"));
5154 assert!(s.contains("got string"));
5155 }
5156
5157 #[test]
5158 fn eval_error_display_assertion_failed() {
5159 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5160 assert!(s.contains("assertion"));
5161 }
5162
5163 #[test]
5164 fn eval_error_display_division_by_zero() {
5165 let s = format!("{}", EvalError::DivisionByZero);
5166 assert!(s.contains("division by zero"));
5167 }
5168
5169 #[test]
5170 fn eval_error_display_infinite_recursion() {
5171 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5172 assert!(s.contains("infinite recursion"));
5173 assert!(s.contains("loop"));
5174 }
5175
5176 #[test]
5177 fn eval_error_display_io_error() {
5178 let s = format!(
5179 "{}",
5180 EvalError::IoError {
5181 context: "ctx".into(),
5182 message: "no such file".into(),
5183 }
5184 );
5185 assert!(s.contains("I/O"));
5186 assert!(s.contains("ctx"));
5187 assert!(s.contains("no such file"));
5188 }
5189
5190 #[test]
5191 fn eval_error_display_throw() {
5192 let s = format!("{}", EvalError::Throw("boom".into()));
5193 assert_eq!(s, "boom");
5194 }
5195
5196 #[test]
5197 fn eval_error_display_not_implemented() {
5198 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5199 assert!(s.contains("not yet implemented"));
5200 assert!(s.contains("frob"));
5201 }
5202
5203 #[test]
5204 fn eval_error_display_parse_error() {
5205 let s = format!("{}", EvalError::ParseError("syntax".into()));
5206 assert!(s.contains("parse error"));
5207 assert!(s.contains("syntax"));
5208 }
5209
5210 #[test]
5211 fn eval_error_display_recursion_limit() {
5212 let s = format!(
5213 "{}",
5214 EvalError::RecursionLimit("max depth exceeded".into())
5215 );
5216 assert!(s.contains("recursion limit"));
5217 assert!(s.contains("max depth exceeded"));
5218 }
5219
5220 #[test]
5221 fn eval_error_partial_eq_same_variant() {
5222 assert_eq!(
5223 EvalError::UndefinedVar("x".into()),
5224 EvalError::UndefinedVar("x".into()),
5225 );
5226 assert_ne!(
5227 EvalError::UndefinedVar("x".into()),
5228 EvalError::UndefinedVar("y".into()),
5229 );
5230 assert_ne!(
5231 EvalError::UndefinedVar("x".into()),
5232 EvalError::AttrNotFound("x".into()),
5233 );
5234 }
5235
5236 #[test]
5239 fn context_element_display_plain() {
5240 let e = ContextElement::Plain("/nix/store/xyz".into());
5241 assert_eq!(format!("{e}"), "/nix/store/xyz");
5242 }
5243
5244 #[test]
5245 fn context_element_display_output() {
5246 let e = ContextElement::Output {
5247 drv: "/nix/store/abc.drv".into(),
5248 output: "out".into(),
5249 };
5250 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5251 }
5252
5253 #[test]
5254 fn context_element_display_drv_deep() {
5255 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5256 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5257 }
5258
5259 #[test]
5262 fn string_context_iter_yields_all() {
5263 let mut ctx = StringContext::new();
5264 ctx.add_plain("/nix/store/aaa");
5265 ctx.add_plain("/nix/store/bbb");
5266 let count = ctx.iter().count();
5267 assert_eq!(count, 2);
5268 }
5269
5270 #[test]
5271 fn string_context_len_matches_set_size() {
5272 let mut ctx = StringContext::new();
5273 assert_eq!(ctx.len(), 0);
5274 ctx.add_plain("/nix/store/x");
5275 assert_eq!(ctx.len(), 1);
5276 ctx.add_output("/nix/store/y.drv", "out");
5277 assert_eq!(ctx.len(), 2);
5278 }
5279
5280 #[test]
5281 fn string_context_insert_raw_element() {
5282 let mut ctx = StringContext::new();
5283 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5284 assert_eq!(ctx.len(), 1);
5285 }
5286
5287 #[test]
5288 fn string_context_default_is_empty() {
5289 let ctx = StringContext::default();
5290 assert!(ctx.is_empty());
5291 }
5292
5293 #[test]
5296 fn nix_string_as_ref_str() {
5297 let s = NixString::plain("hello");
5298 let r: &str = s.as_ref();
5299 assert_eq!(r, "hello");
5300 }
5301
5302 #[test]
5303 fn nix_string_deref_to_str_methods() {
5304 let s = NixString::plain("Hello World");
5305 assert_eq!(s.len(), 11);
5306 assert!(s.starts_with("Hello"));
5307 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5309 }
5310
5311 #[test]
5314 fn nixattrs_remove_returns_value() {
5315 let mut a = NixAttrs::new();
5316 a.insert("x".into(), Value::Int(1));
5317 let removed = a.remove("x");
5318 assert_eq!(removed, Some(Value::Int(1)));
5319 assert!(!a.contains_key("x"));
5320 assert_eq!(a.remove("y"), None);
5321 }
5322
5323 #[test]
5324 fn nixattrs_values_iter() {
5325 let mut a = NixAttrs::new();
5326 a.insert("a".into(), Value::Int(1));
5327 a.insert("b".into(), Value::Int(2));
5328 let mut vs: Vec<&Value> = a.values().collect();
5329 vs.sort_by_key(|v| match v {
5330 Value::Int(n) => *n,
5331 _ => 0,
5332 });
5333 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5334 }
5335
5336 #[test]
5337 fn nixattrs_iter_returns_sorted_pairs() {
5338 let mut a = NixAttrs::new();
5339 a.insert("zeta".into(), Value::Int(3));
5340 a.insert("alpha".into(), Value::Int(1));
5341 a.insert("mu".into(), Value::Int(2));
5342 let pairs: Vec<(String, &Value)> = a.iter().collect();
5343 assert_eq!(pairs[0].0, "alpha");
5344 assert_eq!(pairs[1].0, "mu");
5345 assert_eq!(pairs[2].0, "zeta");
5346 }
5347
5348 #[test]
5349 fn nixattrs_from_iterator() {
5350 let pairs = vec![
5351 ("a".to_string(), Value::Int(1)),
5352 ("b".to_string(), Value::Int(2)),
5353 ];
5354 let attrs: NixAttrs = pairs.into_iter().collect();
5355 assert_eq!(attrs.len(), 2);
5356 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5357 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5358 }
5359
5360 #[test]
5361 fn nixattrs_into_iterator_yields_owned() {
5362 let mut a = NixAttrs::new();
5363 a.insert("x".into(), Value::Int(42));
5364 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5365 assert_eq!(pairs.len(), 1);
5366 assert_eq!(pairs[0].0, "x");
5367 assert_eq!(pairs[0].1, Value::Int(42));
5368 }
5369
5370 #[test]
5371 fn nixattrs_default_is_empty() {
5372 let a = NixAttrs::default();
5373 assert!(a.is_empty());
5374 }
5375
5376 #[test]
5379 fn value_from_bool() {
5380 assert_eq!(Value::from(true), Value::Bool(true));
5381 assert_eq!(Value::from(false), Value::Bool(false));
5382 }
5383
5384 #[test]
5385 fn value_from_i64() {
5386 assert_eq!(Value::from(42_i64), Value::Int(42));
5387 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5388 }
5389
5390 #[test]
5391 fn value_from_f64() {
5392 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5393 }
5394
5395 #[test]
5396 fn value_from_nix_string() {
5397 let v: Value = NixString::plain("hi").into();
5398 assert_eq!(v, Value::string("hi"));
5399 }
5400
5401 #[test]
5402 fn value_from_nix_attrs() {
5403 let mut a = NixAttrs::new();
5404 a.insert("x".into(), Value::Int(1));
5405 let v: Value = a.into();
5406 match v {
5407 Value::Attrs(_) => {}
5408 _ => panic!("expected Attrs"),
5409 }
5410 }
5411
5412 #[test]
5413 fn value_from_vec() {
5414 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5415 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5416 }
5417
5418 #[test]
5419 fn value_default_is_null() {
5420 let v: Value = Value::default();
5421 assert_eq!(v, Value::Null);
5422 }
5423
5424 #[test]
5427 fn value_from_json_null() {
5428 let v = Value::from(&serde_json::Value::Null);
5429 assert_eq!(v, Value::Null);
5430 }
5431
5432 #[test]
5433 fn value_from_json_bool() {
5434 let v = Value::from(&serde_json::Value::Bool(true));
5435 assert_eq!(v, Value::Bool(true));
5436 }
5437
5438 #[test]
5439 fn value_from_json_int() {
5440 let v = Value::from(&serde_json::json!(42));
5441 assert_eq!(v, Value::Int(42));
5442 }
5443
5444 #[test]
5445 fn value_from_json_float() {
5446 let v = Value::from(&serde_json::json!(3.14));
5447 match v {
5448 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5449 _ => panic!("expected Float"),
5450 }
5451 }
5452
5453 #[test]
5454 fn value_from_json_string() {
5455 let v = Value::from(&serde_json::Value::String("hi".into()));
5456 assert_eq!(v, Value::string("hi"));
5457 }
5458
5459 #[test]
5460 fn value_from_json_array() {
5461 let v = Value::from(&serde_json::json!([1, true, "x"]));
5462 match v {
5463 Value::List(items) => {
5464 assert_eq!(items.len(), 3);
5465 assert_eq!(items[0], Value::Int(1));
5466 assert_eq!(items[1], Value::Bool(true));
5467 assert_eq!(items[2], Value::string("x"));
5468 }
5469 _ => panic!("expected List"),
5470 }
5471 }
5472
5473 #[test]
5474 fn value_from_json_object() {
5475 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5476 match v {
5477 Value::Attrs(attrs) => {
5478 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5479 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5480 }
5481 _ => panic!("expected Attrs"),
5482 }
5483 }
5484
5485 #[test]
5486 fn value_from_json_nested() {
5487 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5488 let json_back = v.to_json();
5489 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5490 }
5491
5492 #[test]
5495 fn value_from_toml_string() {
5496 let t = toml::Value::String("hi".into());
5497 assert_eq!(Value::from(&t), Value::string("hi"));
5498 }
5499
5500 #[test]
5501 fn value_from_toml_int() {
5502 let t = toml::Value::Integer(42);
5503 assert_eq!(Value::from(&t), Value::Int(42));
5504 }
5505
5506 #[test]
5507 fn value_from_toml_float() {
5508 let t = toml::Value::Float(3.14);
5509 match Value::from(&t) {
5510 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5511 _ => panic!("expected Float"),
5512 }
5513 }
5514
5515 #[test]
5516 fn value_from_toml_bool() {
5517 let t = toml::Value::Boolean(true);
5518 assert_eq!(Value::from(&t), Value::Bool(true));
5519 }
5520
5521 #[test]
5522 fn value_from_toml_array() {
5523 let t = toml::Value::Array(vec![
5524 toml::Value::Integer(1),
5525 toml::Value::Integer(2),
5526 ]);
5527 assert_eq!(
5528 Value::from(&t),
5529 Value::list(vec![Value::Int(1), Value::Int(2)]),
5530 );
5531 }
5532
5533 #[test]
5534 fn value_from_toml_table() {
5535 let mut tbl = toml::map::Map::new();
5536 tbl.insert("k".into(), toml::Value::Integer(7));
5537 let t = toml::Value::Table(tbl);
5538 match Value::from(&t) {
5539 Value::Attrs(attrs) => {
5540 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5541 }
5542 _ => panic!("expected Attrs"),
5543 }
5544 }
5545
5546 #[test]
5547 fn value_from_toml_datetime_becomes_string() {
5548 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5550 let t = toml::Value::Datetime(dt);
5551 match Value::from(&t) {
5552 Value::String(_) => {}
5553 other => panic!("expected String, got {other:?}"),
5554 }
5555 }
5556
5557 #[test]
5560 fn coerce_to_path_from_path() {
5561 let v = Value::Path(Box::new("/foo".into()));
5562 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5563 }
5564
5565 #[test]
5566 fn coerce_to_path_from_string() {
5567 let v = Value::string("/bar");
5568 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5569 }
5570
5571 #[test]
5579 fn out_path_needs_realize_matches_output_context() {
5580 let mut ctx = StringContext::new();
5583 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5584 assert_eq!(
5585 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5586 Some("/nix/store/aaa-thing.drv".to_string()),
5587 );
5588 }
5589
5590 #[test]
5591 fn out_path_needs_realize_ignores_plain_context() {
5592 let mut ctx = StringContext::new();
5595 ctx.add_plain("/nix/store/ccc-plain");
5596 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5597 }
5598
5599 #[test]
5600 fn out_path_needs_realize_ignores_non_store_path() {
5601 let mut ctx = StringContext::new();
5604 ctx.add_output("/nix/store/ddd.drv", "out");
5605 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5606 }
5607
5608 #[test]
5609 fn out_path_needs_realize_empty_context_is_none() {
5610 let ctx = StringContext::new();
5612 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5613 }
5614
5615 #[test]
5616 fn coerce_to_realized_path_present_output_is_passthrough() {
5617 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5621 std::fs::create_dir_all(&dir).unwrap();
5622 let file = dir.join("out");
5623 std::fs::write(&file, b"present").unwrap();
5624 let present = file.to_string_lossy().to_string();
5625
5626 let mut ctx = StringContext::new();
5627 ctx.add_plain(&present);
5632 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5633 present.as_str(),
5634 ctx,
5635 )));
5636 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5637 }
5638
5639 #[test]
5640 fn coerce_to_realized_path_absent_output_invokes_hook() {
5641 use std::sync::{Arc, Mutex};
5646 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5647 let seen2 = seen.clone();
5648 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5649 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5650 Ok(())
5651 }));
5652
5653 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5656 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5657 let mut ctx = StringContext::new();
5658 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5659 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5660
5661 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5663 let s = seen.lock().unwrap();
5664 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5665 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5666 assert_eq!(s[0].1, out);
5667 }
5668
5669 #[test]
5670 fn coerce_to_path_errors_on_int() {
5671 let v = Value::Int(1);
5672 let e = v.coerce_to_path("readFile").unwrap_err();
5673 match e {
5674 EvalError::TypeError(ref msg) => {
5675 assert!(msg.contains("readFile"));
5676 assert!(msg.contains("path or string"));
5677 assert!(msg.contains("int"));
5678 }
5679 _ => panic!("expected TypeError"),
5680 }
5681 }
5682
5683 #[test]
5684 fn coerce_to_path_errors_on_null() {
5685 let v = Value::Null;
5686 assert!(v.coerce_to_path("ctx").is_err());
5687 }
5688
5689 #[test]
5690 fn coerce_to_path_attrs_with_outpath() {
5691 let mut attrs = NixAttrs::new();
5692 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5693 let val = Value::Attrs(Rc::new(attrs));
5694 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5695 }
5696
5697 #[test]
5698 fn coerce_to_path_attrs_without_outpath_fails() {
5699 let attrs = NixAttrs::new();
5700 let val = Value::Attrs(Rc::new(attrs));
5701 assert!(val.coerce_to_path("test").is_err());
5702 }
5703
5704 #[test]
5707 fn coerce_to_string_string() {
5708 let v = Value::string("hello");
5709 let (s, _ctx) = v.coerce_to_string().unwrap();
5710 assert_eq!(s, "hello");
5711 }
5712
5713 #[test]
5714 fn coerce_to_string_path() {
5715 let v = Value::Path(Box::new("/foo".into()));
5716 let (s, ctx) = v.coerce_to_string().unwrap();
5717 assert_eq!(s, "/foo");
5718 assert!(!ctx.is_empty()); }
5720
5721 #[test]
5722 fn coerce_to_string_int() {
5723 let v = Value::Int(42);
5724 let (s, _ctx) = v.coerce_to_string().unwrap();
5725 assert_eq!(s, "42");
5726 }
5727
5728 #[test]
5729 fn coerce_to_string_float() {
5730 let v = Value::Float(3.14);
5732 let (s, _ctx) = v.coerce_to_string().unwrap();
5733 assert_eq!(s, "3.140000");
5734 }
5735
5736 #[test]
5737 fn coerce_to_string_bool_true() {
5738 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5739 assert_eq!(s, "1");
5740 }
5741
5742 #[test]
5743 fn coerce_to_string_bool_false() {
5744 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5745 assert_eq!(s, "");
5746 }
5747
5748 #[test]
5749 fn coerce_to_string_null() {
5750 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5751 assert_eq!(s, "");
5752 }
5753
5754 #[test]
5755 fn coerce_to_string_attrs_with_outpath() {
5756 let mut attrs = NixAttrs::new();
5757 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5758 let val = Value::Attrs(Rc::new(attrs));
5759 let (s, _ctx) = val.coerce_to_string().unwrap();
5760 assert_eq!(s, "/nix/store/abc");
5761 }
5762
5763 #[test]
5764 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5765 let attrs = NixAttrs::new();
5766 let val = Value::Attrs(Rc::new(attrs));
5767 assert!(val.coerce_to_string().is_err());
5768 }
5769
5770 #[test]
5771 fn coerce_to_string_lambda_fails() {
5772 let root = rnix::Root::parse("x: x");
5773 let expr = root.tree().expr().unwrap();
5774 let closure = Closure {
5775 param: match expr {
5776 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
5777 _ => panic!("expected lambda"),
5778 },
5779 body: match expr {
5780 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
5781 _ => panic!("expected lambda"),
5782 },
5783 env: Env::new(),
5784 };
5785 let val = Value::Lambda(Rc::new(closure));
5786 assert!(val.coerce_to_string().is_err());
5787 }
5788
5789 #[test]
5792 fn builtin_fn_debug_includes_name() {
5793 let b = BuiltinFn {
5794 name: "myFunc",
5795 func: Rc::new(|_| Ok(Value::Null)),
5796 };
5797 let s = format!("{b:?}");
5798 assert!(s.contains("myFunc"));
5799 assert!(s.contains("builtin"));
5800 }
5801
5802 #[test]
5805 fn thunk_force_chains_through_inner_thunks() {
5806 let inner_root = rnix::Root::parse("99");
5808 let inner_expr = inner_root.tree().expr().unwrap();
5809 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
5810 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
5811 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
5812 match result.unwrap() {
5817 Value::Thunk(_) | Value::Int(99) => {}
5818 other => panic!("unexpected: {other:?}"),
5819 }
5820 }
5821
5822 #[test]
5823 fn thunk_inherit_select_debug_format() {
5824 let root = rnix::Root::parse("{ x = 1; }");
5825 let expr = root.tree().expr().unwrap();
5826 let source = Thunk::new_suspended(expr, Env::new());
5827 let thunk = Thunk::new_inherit_select(source, "x");
5828 let s = format!("{thunk:?}");
5829 assert!(s.contains("inherit-select"));
5830 assert!(s.contains("x"));
5831 }
5832
5833 #[test]
5834 fn thunk_blackhole_debug_format() {
5835 let root = rnix::Root::parse("1");
5836 let expr = root.tree().expr().unwrap();
5837 let thunk = Thunk::new_suspended(expr, Env::new());
5838 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5840 assert_eq!(format!("{thunk:?}"), "<blackhole>");
5841 }
5842
5843 #[test]
5846 fn value_display_thunk_evaluates() {
5847 let root = rnix::Root::parse("42");
5848 let expr = root.tree().expr().unwrap();
5849 let thunk = Thunk::new_suspended(expr, Env::new());
5850 let val = Value::Thunk(thunk);
5851 assert_eq!(format!("{val}"), "42");
5852 }
5853
5854 #[test]
5855 fn value_to_json_thunk_forces() {
5856 let root = rnix::Root::parse(r#""world""#);
5857 let expr = root.tree().expr().unwrap();
5858 let thunk = Thunk::new_suspended(expr, Env::new());
5859 let val = Value::Thunk(thunk);
5860 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
5861 }
5862
5863 #[test]
5864 fn value_type_name_thunk_forces() {
5865 let root = rnix::Root::parse("42");
5866 let expr = root.tree().expr().unwrap();
5867 let thunk = Thunk::new_suspended(expr, Env::new());
5868 let val = Value::Thunk(thunk);
5869 assert_eq!(val.type_name(), "int");
5870 }
5871
5872 #[test]
5875 fn as_string_errors_on_thunk() {
5876 let root = rnix::Root::parse(r#""x""#);
5877 let expr = root.tree().expr().unwrap();
5878 let thunk = Thunk::new_suspended(expr, Env::new());
5879 let val = Value::Thunk(thunk);
5880 let err = val.as_string().unwrap_err();
5881 match err {
5882 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
5883 _ => panic!("expected TypeError"),
5884 }
5885 }
5886
5887 #[test]
5888 fn as_nix_string_errors_on_thunk() {
5889 let root = rnix::Root::parse(r#""x""#);
5890 let expr = root.tree().expr().unwrap();
5891 let thunk = Thunk::new_suspended(expr, Env::new());
5892 let val = Value::Thunk(thunk);
5893 assert!(val.as_nix_string().is_err());
5894 }
5895
5896 #[test]
5897 fn as_attrs_errors_on_thunk() {
5898 let root = rnix::Root::parse("{}");
5899 let expr = root.tree().expr().unwrap();
5900 let thunk = Thunk::new_suspended(expr, Env::new());
5901 let val = Value::Thunk(thunk);
5902 assert!(val.as_attrs().is_err());
5903 }
5904
5905 #[test]
5906 fn as_list_errors_on_thunk() {
5907 let root = rnix::Root::parse("[]");
5908 let expr = root.tree().expr().unwrap();
5909 let thunk = Thunk::new_suspended(expr, Env::new());
5910 let val = Value::Thunk(thunk);
5911 assert!(val.as_list().is_err());
5912 }
5913
5914 #[test]
5917 fn as_nix_string_ok_on_string() {
5918 let v = Value::string("hi");
5919 let ns = v.as_nix_string().unwrap();
5920 assert_eq!(ns.as_str(), "hi");
5921 }
5922
5923 #[test]
5924 fn as_nix_string_errors_on_int() {
5925 let v = Value::Int(1);
5926 match v.as_nix_string() {
5927 Err(EvalError::TypeMismatch { expected, got }) => {
5928 assert_eq!(expected, "string");
5929 assert_eq!(got, "int");
5930 }
5931 _ => panic!("expected TypeMismatch"),
5932 }
5933 }
5934
5935 #[test]
5940 fn oncecell_cache_populated_after_force() {
5941 let root = rnix::Root::parse("42");
5942 let expr = root.tree().expr().unwrap();
5943 let thunk = Thunk::new_suspended(expr, Env::new());
5944 assert!(thunk.0.cache.get().is_none());
5946 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5947 assert!(thunk.0.cache.get().is_some());
5949 }
5950
5951 #[test]
5952 fn oncecell_cache_matches_force_result() {
5953 let root = rnix::Root::parse("1 + 2");
5954 let expr = root.tree().expr().unwrap();
5955 let thunk = Thunk::new_suspended(expr, Env::new());
5956 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5957 let cached = thunk.0.cache.get().unwrap();
5958 assert_eq!((**cached).clone().into_value(), forced);
5961 }
5962
5963 #[test]
5964 fn oncecell_new_evaluated_prepopulates_cache() {
5965 let thunk = Thunk::new_evaluated(Value::Int(77));
5966 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
5968 assert_eq!(**cached, Concrete::Int(77));
5969 }
5970
5971 #[test]
5972 fn oncecell_is_evaluated_uses_cache() {
5973 let thunk = Thunk::new_evaluated(Value::Bool(false));
5974 assert!(thunk.is_evaluated());
5976 assert!(thunk.0.cache.get().is_some());
5977 }
5978
5979 #[test]
5980 fn oncecell_already_evaluated_returns_cached_without_repr() {
5981 let thunk = Thunk::new_evaluated(Value::Int(55));
5985 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
5986 assert_eq!(result.unwrap(), Value::Int(55));
5987 }
5988
5989 #[test]
5994 fn with_scope_created_with_empty_cache() {
5995 let thunk = Thunk::new_suspended(
5997 rnix::Root::parse("{}").tree().expr().unwrap(),
5998 Env::new(),
5999 );
6000 let env = Env::new().with_scope(Value::Thunk(thunk));
6001 let scope = &env.0.with_scopes[0];
6002 assert!(scope.cached.borrow().is_none());
6003 }
6004
6005 #[test]
6006 fn with_scope_concrete_pre_populates_cache() {
6007 let mut attrs = NixAttrs::new();
6009 attrs.insert("x".to_string(), Value::Int(1));
6010 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6011 let scope = &env.0.with_scopes[0];
6012 assert!(scope.cached.borrow().is_some());
6013 }
6014
6015 #[test]
6016 fn with_scope_first_lookup_populates_cache() {
6017 let mut attrs = NixAttrs::new();
6018 attrs.insert("x".to_string(), Value::Int(42));
6019 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6020 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6022 let _ = env.lookup("x");
6024 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6025 }
6026
6027 #[test]
6028 fn with_scope_second_lookup_uses_cache() {
6029 let mut attrs = NixAttrs::new();
6030 attrs.insert("x".to_string(), Value::Int(10));
6031 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6032 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6034 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6035 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6037 }
6038
6039 #[test]
6040 fn with_scope_child_shares_cache_via_rc() {
6041 let mut attrs = NixAttrs::new();
6042 attrs.insert("shared".to_string(), Value::Int(7));
6043 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6044 let child = parent.child();
6045 let _ = parent.lookup("shared");
6047 assert!(child.0.with_scopes[0].cached.borrow().is_some());
6050 }
6051
6052 #[test]
6053 fn with_scope_innermost_checked_first() {
6054 let mut outer = NixAttrs::new();
6055 outer.insert("x".to_string(), Value::Int(1));
6056 outer.insert("y".to_string(), Value::Int(100));
6057 let mut inner = NixAttrs::new();
6058 inner.insert("x".to_string(), Value::Int(2));
6059 let env = Env::new()
6060 .with_scope(Value::Attrs(Rc::new(outer)))
6061 .with_scope(Value::Attrs(Rc::new(inner)));
6062 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
6064 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
6066 }
6067
6068 #[test]
6073 fn fxhashmap_nixattrs_new_creates_empty() {
6074 let a = NixAttrs::new();
6075 assert!(a.is_empty());
6076 assert_eq!(a.len(), 0);
6077 assert!(a.inner().is_empty());
6079 }
6080
6081 #[test]
6082 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
6083 let mut a = NixAttrs::new();
6084 a.insert("mykey".to_string(), Value::Int(42));
6085 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
6086 }
6087
6088 #[test]
6089 fn fxhashmap_contains_key_with_interned_keys() {
6090 let mut a = NixAttrs::new();
6091 a.insert("alpha".to_string(), Value::Int(1));
6092 let sym = intern("alpha");
6093 assert!(a.inner().contains_key(&sym));
6094 let missing_sym = intern("beta");
6095 assert!(!a.inner().contains_key(&missing_sym));
6096 }
6097
6098 #[test]
6099 fn fxhashmap_remove_returns_value() {
6100 let mut a = NixAttrs::new();
6101 a.insert("key".to_string(), Value::Int(99));
6102 let removed = a.remove("key");
6103 assert_eq!(removed, Some(Value::Int(99)));
6104 assert!(a.is_empty());
6105 }
6106
6107 #[test]
6108 fn fxhashmap_keys_returns_sorted_strings() {
6109 let mut a = NixAttrs::new();
6110 a.insert("zulu".to_string(), Value::Int(1));
6111 a.insert("alpha".to_string(), Value::Int(2));
6112 a.insert("mike".to_string(), Value::Int(3));
6113 let keys: Vec<String> = a.keys().collect();
6114 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
6115 }
6116
6117 #[test]
6118 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
6119 let mut a = NixAttrs::new();
6120 a.insert("b".to_string(), Value::Int(2));
6121 a.insert("a".to_string(), Value::Int(1));
6122 let pairs: Vec<(String, &Value)> = a.iter().collect();
6123 assert_eq!(pairs.len(), 2);
6124 assert_eq!(pairs[0].0, "a");
6125 assert_eq!(*pairs[0].1, Value::Int(1));
6126 assert_eq!(pairs[1].0, "b");
6127 assert_eq!(*pairs[1].1, Value::Int(2));
6128 }
6129
6130 #[test]
6131 fn fxhashmap_update_merges_correctly() {
6132 let mut left = NixAttrs::new();
6133 left.insert("a".to_string(), Value::Int(1));
6134 left.insert("b".to_string(), Value::Int(2));
6135 let mut right = NixAttrs::new();
6136 right.insert("b".to_string(), Value::Int(20));
6137 right.insert("c".to_string(), Value::Int(3));
6138 let merged = left.update(&right);
6139 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6140 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6142 assert_eq!(merged.len(), 3);
6143 }
6144
6145 #[test]
6146 fn fxhashmap_from_iterator_collects_with_interning() {
6147 let pairs = vec![
6148 ("x".to_string(), Value::Int(10)),
6149 ("y".to_string(), Value::Int(20)),
6150 ("z".to_string(), Value::Int(30)),
6151 ];
6152 let attrs: NixAttrs = pairs.into_iter().collect();
6153 assert_eq!(attrs.len(), 3);
6154 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6155 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6156 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6157 let sym_x = intern("x");
6159 assert!(attrs.inner().contains_key(&sym_x));
6160 }
6161
6162 #[test]
6167 fn smallvec_context_empty() {
6168 let ctx = StringContext::new();
6169 assert!(ctx.is_empty());
6170 assert_eq!(ctx.len(), 0);
6171 assert_eq!(ctx.elements().len(), 0);
6172 }
6173
6174 #[test]
6175 fn smallvec_context_single_element_inline() {
6176 let mut ctx = StringContext::new();
6177 ctx.add_plain("/nix/store/single");
6178 assert_eq!(ctx.len(), 1);
6179 assert!(!ctx.is_empty());
6181 }
6182
6183 #[test]
6184 fn smallvec_context_two_elements_still_inline() {
6185 let mut ctx = StringContext::new();
6186 ctx.add_plain("/nix/store/one");
6187 ctx.add_output("/nix/store/two.drv", "out");
6188 assert_eq!(ctx.len(), 2);
6189 }
6190
6191 #[test]
6192 fn smallvec_context_three_plus_spills_to_heap() {
6193 let mut ctx = StringContext::new();
6194 ctx.add_plain("/nix/store/a");
6195 ctx.add_plain("/nix/store/b");
6196 ctx.add_drv_deep("/nix/store/c.drv");
6197 assert_eq!(ctx.len(), 3);
6198 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6200 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6201 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6202 }
6203
6204 #[test]
6205 fn smallvec_context_merge_deduplicates() {
6206 let mut ctx1 = StringContext::new();
6207 ctx1.add_plain("/nix/store/dup");
6208 ctx1.add_output("/nix/store/x.drv", "out");
6209 let mut ctx2 = StringContext::new();
6210 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6213 assert_eq!(ctx1.len(), 3); }
6215
6216 #[test]
6217 fn smallvec_context_add_plain_output_drv_deep() {
6218 let mut ctx = StringContext::new();
6219 ctx.add_plain("/nix/store/plain");
6220 assert_eq!(ctx.len(), 1);
6221 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6222
6223 ctx.add_output("/nix/store/out.drv", "lib");
6224 assert_eq!(ctx.len(), 2);
6225 assert!(ctx.elements().contains(&ContextElement::Output {
6226 drv: SmolStr::from("/nix/store/out.drv"),
6227 output: SmolStr::from("lib"),
6228 }));
6229
6230 ctx.add_drv_deep("/nix/store/deep.drv");
6231 assert_eq!(ctx.len(), 3);
6232 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6233 }
6234
6235 #[test]
6240 fn rc_list_constructor_wraps_in_rc() {
6241 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6242 match &v {
6243 Value::List(rc) => {
6244 assert_eq!(rc.len(), 2);
6245 assert_eq!(Rc::strong_count(rc), 1);
6246 }
6247 _ => panic!("expected List"),
6248 }
6249 }
6250
6251 #[test]
6252 fn rc_list_clone_is_refcount_bump() {
6253 let v = Value::list(vec![Value::Int(10)]);
6254 let rc1 = match &v {
6255 Value::List(rc) => rc.clone(),
6256 _ => panic!("expected List"),
6257 };
6258 let v2 = v.clone();
6259 let rc2 = match &v2 {
6260 Value::List(rc) => rc.clone(),
6261 _ => panic!("expected List"),
6262 };
6263 assert!(Rc::ptr_eq(&rc1, &rc2));
6265 assert!(Rc::strong_count(&rc1) >= 2);
6268 }
6269
6270 #[test]
6271 fn rc_list_as_list_returns_slice() {
6272 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6273 let slice = v.as_list().unwrap();
6274 assert_eq!(slice.len(), 3);
6275 assert_eq!(slice[0], Value::Int(1));
6276 assert_eq!(slice[1], Value::Int(2));
6277 assert_eq!(slice[2], Value::Int(3));
6278 }
6279
6280 #[test]
6281 fn rc_list_from_vec_wraps_in_rc() {
6282 let items = vec![Value::Bool(true), Value::Bool(false)];
6283 let v: Value = items.into();
6284 match &v {
6285 Value::List(rc) => {
6286 assert_eq!(rc.len(), 2);
6287 assert_eq!(Rc::strong_count(rc), 1);
6288 }
6289 _ => panic!("expected List"),
6290 }
6291 }
6292
6293 #[test]
6298 fn intern_same_string_returns_same_symbol() {
6299 let s1 = intern("hello_intern_test");
6300 let s2 = intern("hello_intern_test");
6301 assert_eq!(s1, s2);
6302 }
6303
6304 #[test]
6305 fn intern_different_strings_returns_different_symbols() {
6306 let s1 = intern("unique_str_a_9182");
6307 let s2 = intern("unique_str_b_9182");
6308 assert_ne!(s1, s2);
6309 }
6310
6311 #[test]
6312 fn resolve_roundtrips_correctly() {
6313 let sym = intern("roundtrip_test_str");
6314 let resolved = resolve(sym);
6315 assert_eq!(resolved, "roundtrip_test_str");
6316 }
6317
6318 #[test]
6319 fn intern_cached_same_offset_returns_cached_symbol() {
6320 let sid = next_source_id();
6321 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6322 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6323 assert_eq!(sym1, sym2);
6324 }
6325
6326 #[test]
6327 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6328 let sid = next_source_id();
6331 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6332 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6333 assert_eq!(sym1, sym2);
6335 }
6336
6337 #[test]
6338 fn clear_ident_cache_clears() {
6339 let sid = next_source_id();
6340 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6341 clear_ident_cache();
6342 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6346 let resolved = resolve(sym2);
6347 assert_eq!(resolved, "to_be_cleared_99");
6348 }
6349
6350 #[test]
6351 fn next_source_id_increments_monotonically() {
6352 let id1 = next_source_id();
6353 let id2 = next_source_id();
6354 let id3 = next_source_id();
6355 assert_eq!(id2, id1 + 1);
6356 assert_eq!(id3, id2 + 1);
6357 }
6358
6359 #[test]
6364 fn env_new_creates_empty_bindings() {
6365 let env = Env::new();
6366 assert!(env.0.bindings.is_empty());
6367 assert!(env.0.with_scopes.is_empty());
6368 assert!(env.eval_file().is_none());
6369 }
6370
6371 #[test]
6372 fn env_bind_lookup_roundtrip() {
6373 let mut env = Env::new();
6374 env.bind("foo".to_string(), Value::Int(42));
6375 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6376 assert_eq!(env.lookup("bar"), None);
6377 }
6378
6379 #[test]
6380 fn env_child_inherits_parent_bindings_flattened() {
6381 let mut parent = Env::new();
6382 parent.bind("a".to_string(), Value::Int(1));
6383 parent.bind("b".to_string(), Value::Int(2));
6384 let child = parent.child();
6385 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6387 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6388 let sym_a = intern("a");
6390 assert!(child.0.bindings.contains_key(&sym_a));
6391 }
6392
6393 #[test]
6394 fn env_child_inherits_with_scopes() {
6395 let mut attrs = NixAttrs::new();
6396 attrs.insert("ws".to_string(), Value::Int(10));
6397 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6398 let child = parent.child();
6399 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6401 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6402 }
6403
6404 #[test]
6405 fn env_lookup_sym_fast_path_matches_lookup() {
6406 let mut env = Env::new();
6407 env.bind("target".to_string(), Value::Int(88));
6408 let sym = intern("target");
6409 let via_lookup = env.lookup("target");
6410 let via_sym = env.lookup_sym(sym);
6411 assert_eq!(via_lookup, via_sym);
6412 assert_eq!(via_sym, Some(Value::Int(88)));
6413 }
6414
6415 #[test]
6416 fn env_lookup_sym_with_scope_fallback() {
6417 let mut attrs = NixAttrs::new();
6418 attrs.insert("sym_ws".to_string(), Value::Int(33));
6419 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6420 let sym = intern("sym_ws");
6421 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6422 }
6423
6424 #[test]
6425 fn env_with_scope_ordering_multiple_innermost_wins() {
6426 let mut a1 = NixAttrs::new();
6427 a1.insert("x".to_string(), Value::Int(1));
6428 let mut a2 = NixAttrs::new();
6429 a2.insert("x".to_string(), Value::Int(2));
6430 let mut a3 = NixAttrs::new();
6431 a3.insert("x".to_string(), Value::Int(3));
6432 let env = Env::new()
6433 .with_scope(Value::Attrs(Rc::new(a1)))
6434 .with_scope(Value::Attrs(Rc::new(a2)))
6435 .with_scope(Value::Attrs(Rc::new(a3)));
6436 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6438 }
6439
6440 #[test]
6441 fn env_lookup_sym_not_found_returns_none() {
6442 let env = Env::new();
6443 let sym = intern("nonexistent_sym_99");
6444 assert_eq!(env.lookup_sym(sym), None);
6445 }
6446
6447 #[test]
6448 fn env_lookup_sym_lexical_wins_over_with_scope() {
6449 let mut attrs = NixAttrs::new();
6450 attrs.insert("priority".to_string(), Value::Int(1));
6451 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6452 env.bind("priority".to_string(), Value::Int(99));
6453 let sym = intern("priority");
6454 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6455 }
6456}