1use std::cell::{Cell, OnceCell, RefCell, UnsafeCell};
8
9use std::fmt;
10pub use std::rc::Rc;
11
12use rustc_hash::FxBuildHasher;
13use smallvec::SmallVec;
14pub use smol_str::SmolStr;
15
16use rowan::ast::AstNode;
17
18use sui_intern::Symbol;
19
20pub type FxHashMap<K, V> = im_rc::HashMap<K, V, FxBuildHasher>;
26
27pub type AttrsMap<K, V> = std::collections::HashMap<K, V, FxBuildHasher>;
42
43pub mod census {
61 use std::sync::atomic::{AtomicI64, Ordering::Relaxed};
62 use std::sync::OnceLock;
63
64 pub static ATTRS_LIVE: AtomicI64 = AtomicI64::new(0);
65 pub static ATTRS_MADE: AtomicI64 = AtomicI64::new(0);
66 pub static THUNK_LIVE: AtomicI64 = AtomicI64::new(0);
67 pub static THUNK_MADE: AtomicI64 = AtomicI64::new(0);
68 pub static THUNK_EVALUATED: AtomicI64 = AtomicI64::new(0);
69 pub static ENV_LIVE: AtomicI64 = AtomicI64::new(0);
70 pub static ENV_MADE: AtomicI64 = AtomicI64::new(0);
71 pub static NIXSTR_LIVE: AtomicI64 = AtomicI64::new(0);
72 pub static NIXSTR_MADE: AtomicI64 = AtomicI64::new(0);
73 pub static LIST_LIVE: AtomicI64 = AtomicI64::new(0);
74 pub static LIST_MADE: AtomicI64 = AtomicI64::new(0);
75
76 pub static SCOPE_THUNKS_NARROWED: AtomicI64 = AtomicI64::new(0);
90 pub static SCOPE_THUNKS_PINNED: AtomicI64 = AtomicI64::new(0);
91
92 #[inline(always)]
94 pub fn scope_narrowed() {
95 if enabled() {
96 SCOPE_THUNKS_NARROWED.fetch_add(1, Relaxed);
97 }
98 }
99
100 #[inline(always)]
102 pub fn scope_pinned() {
103 if enabled() {
104 SCOPE_THUNKS_PINNED.fetch_add(1, Relaxed);
105 }
106 }
107
108 #[inline]
110 pub fn enabled() -> bool {
111 static ON: OnceLock<bool> = OnceLock::new();
112 *ON.get_or_init(|| std::env::var("SUI_LIVE_CENSUS").as_deref() == Ok("1"))
113 }
114
115 #[inline(always)]
116 pub fn made(made: &AtomicI64, live: &AtomicI64) {
117 if enabled() {
118 made.fetch_add(1, Relaxed);
119 live.fetch_add(1, Relaxed);
120 }
121 }
122
123 #[inline(always)]
124 pub fn dropped(live: &AtomicI64) {
125 if enabled() {
126 live.fetch_sub(1, Relaxed);
127 }
128 }
129
130 #[inline(always)]
131 pub fn evaluated() {
132 if enabled() {
133 THUNK_EVALUATED.fetch_add(1, Relaxed);
134 }
135 }
136
137 pub fn rss_bytes() -> u64 {
139 #[cfg(target_os = "macos")]
140 unsafe {
141 let mut info: libc::mach_task_basic_info = std::mem::zeroed();
142 let mut count = (std::mem::size_of::<libc::mach_task_basic_info>()
143 / std::mem::size_of::<libc::natural_t>()) as libc::mach_msg_type_number_t;
144 let kr = libc::task_info(
145 libc::mach_task_self(),
146 libc::MACH_TASK_BASIC_INFO,
147 std::ptr::addr_of_mut!(info).cast(),
148 &mut count,
149 );
150 if kr == libc::KERN_SUCCESS {
151 return info.resident_size;
152 }
153 0
154 }
155 #[cfg(not(target_os = "macos"))]
156 {
157 std::fs::read_to_string("/proc/self/statm")
158 .ok()
159 .and_then(|s| s.split_whitespace().nth(1).map(String::from))
160 .and_then(|pages| pages.parse::<u64>().ok())
161 .map(|pages| pages * 4096)
162 .unwrap_or(0)
163 }
164 }
165
166 pub fn dump(tag: &str) {
179 if !enabled() {
180 return;
181 }
182 let rss = rss_bytes();
183 eprintln!(
184 "[census {tag}] rss={rss_mb:.1}MB \
185attrs_live={al} attrs_made={am} \
186thunk_live={tl} thunk_made={tm} thunk_eval={te} \
187env_live={el} env_made={em} \
188nixstr_live={sl} nixstr_made={sm} \
189list_live={ll} list_made={lm} \
190scope_narrowed={sn} scope_pinned={sp}",
191 rss_mb = rss as f64 / (1024.0 * 1024.0),
192 al = ATTRS_LIVE.load(Relaxed),
193 am = ATTRS_MADE.load(Relaxed),
194 tl = THUNK_LIVE.load(Relaxed),
195 tm = THUNK_MADE.load(Relaxed),
196 te = THUNK_EVALUATED.load(Relaxed),
197 el = ENV_LIVE.load(Relaxed),
198 em = ENV_MADE.load(Relaxed),
199 sl = NIXSTR_LIVE.load(Relaxed),
200 sm = NIXSTR_MADE.load(Relaxed),
201 ll = LIST_LIVE.load(Relaxed),
202 lm = LIST_MADE.load(Relaxed),
203 sn = SCOPE_THUNKS_NARROWED.load(Relaxed),
204 sp = SCOPE_THUNKS_PINNED.load(Relaxed),
205 );
206 let (src_files, src_bytes) = crate::pos::source_text_census();
207 eprintln!(
208 "[census {tag}] src_files={src_files} src_bytes={src_mb:.1}MB",
209 src_mb = src_bytes as f64 / (1024.0 * 1024.0),
210 );
211 }
212
213 pub fn spawn_poller() {
217 if !enabled() {
218 return;
219 }
220 std::thread::spawn(|| loop {
221 std::thread::sleep(std::time::Duration::from_millis(2000));
222 dump("periodic");
223 });
224 }
225}
226
227pub fn intern(s: &str) -> Symbol {
240 sui_intern::intern(s)
241}
242
243pub fn resolve(sym: Symbol) -> String {
248 sui_intern::resolve(sym)
249}
250
251pub fn resolve_rc(sym: Symbol) -> std::rc::Rc<str> {
253 sui_intern::resolve_rc(sym)
254}
255
256pub fn with_resolved<F, R>(sym: Symbol, f: F) -> R
258where
259 F: FnOnce(&str) -> R,
260{
261 sui_intern::with_resolved(sym, f)
262}
263
264thread_local! {
275 static SOURCE_GEN: Cell<u32> = const { Cell::new(1) };
285
286 static IDENT_CACHE: RefCell<rustc_hash::FxHashMap<u64, Symbol>> =
288 RefCell::new(rustc_hash::FxHashMap::default());
289}
290
291pub fn next_source_id() -> u32 {
297 SOURCE_GEN.with(|g| {
298 let id = g.get();
299 g.set(id.wrapping_add(1));
300 id
301 })
302}
303
304pub fn intern_cached(name: &str, source_id: u32, text_offset: u32) -> Symbol {
310 intern_cached_with(source_id, text_offset, || intern(name))
311}
312
313pub fn intern_cached_with<F>(source_id: u32, text_offset: u32, cold: F) -> Symbol
321where
322 F: FnOnce() -> Symbol,
323{
324 let key = (u64::from(source_id) << 32) | u64::from(text_offset);
325 IDENT_CACHE.with(|c| {
326 let mut cache = c.borrow_mut();
327 *cache.entry(key).or_insert_with(cold)
328 })
329}
330
331pub fn clear_ident_cache() {
336 IDENT_CACHE.with(|c| c.borrow_mut().clear());
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
343pub enum ContextElement {
344 Plain(SmolStr),
346 Output { drv: SmolStr, output: SmolStr },
348 DrvDeep(SmolStr),
350}
351
352impl fmt::Display for ContextElement {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 match self {
355 ContextElement::Plain(p) => write!(f, "{p}"),
356 ContextElement::Output { drv, output } => write!(f, "{drv}!{output}"),
357 ContextElement::DrvDeep(d) => write!(f, "={d}"),
358 }
359 }
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Default)]
370pub struct StringContext(SmallVec<[ContextElement; 2]>);
371
372impl StringContext {
373 pub fn new() -> Self {
375 Self(SmallVec::new())
376 }
377
378 pub fn merge(&mut self, other: &StringContext) {
380 for elem in &other.0 {
381 if !self.0.contains(elem) {
382 self.0.push(elem.clone());
383 }
384 }
385 }
386
387 pub fn add_plain(&mut self, path: impl Into<SmolStr>) {
389 let elem = ContextElement::Plain(path.into());
390 if !self.0.contains(&elem) {
391 self.0.push(elem);
392 }
393 }
394
395 pub fn add_output(&mut self, drv: impl Into<SmolStr>, output: impl Into<SmolStr>) {
397 let elem = ContextElement::Output { drv: drv.into(), output: output.into() };
398 if !self.0.contains(&elem) {
399 self.0.push(elem);
400 }
401 }
402
403 pub fn add_drv_deep(&mut self, drv: impl Into<SmolStr>) {
405 let elem = ContextElement::DrvDeep(drv.into());
406 if !self.0.contains(&elem) {
407 self.0.push(elem);
408 }
409 }
410
411 #[must_use]
413 pub fn is_empty(&self) -> bool {
414 self.0.is_empty()
415 }
416
417 #[must_use]
419 pub fn len(&self) -> usize {
420 self.0.len()
421 }
422
423 pub fn iter(&self) -> impl Iterator<Item = &ContextElement> {
425 self.0.iter()
426 }
427
428 pub fn insert(&mut self, elem: ContextElement) {
430 if !self.0.contains(&elem) {
431 self.0.push(elem);
432 }
433 }
434
435 pub fn elements(&self) -> &[ContextElement] {
437 &self.0
438 }
439}
440
441#[derive(Debug, PartialEq, Eq)]
443pub struct NixString {
444 pub chars: SmolStr,
446 pub context: StringContext,
448}
449
450impl Clone for NixString {
454 fn clone(&self) -> Self {
455 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
456 Self {
457 chars: self.chars.clone(),
458 context: self.context.clone(),
459 }
460 }
461}
462
463impl Drop for NixString {
464 fn drop(&mut self) {
465 census::dropped(&census::NIXSTR_LIVE);
466 }
467}
468
469impl NixString {
470 pub fn plain(s: impl Into<SmolStr>) -> Self {
472 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
473 Self {
474 chars: s.into(),
475 context: StringContext::default(),
476 }
477 }
478
479 pub fn with_context(s: impl Into<SmolStr>, ctx: StringContext) -> Self {
481 census::made(&census::NIXSTR_MADE, &census::NIXSTR_LIVE);
482 Self {
483 chars: s.into(),
484 context: ctx,
485 }
486 }
487
488 #[must_use]
490 pub fn as_str(&self) -> &str {
491 &self.chars
492 }
493
494 #[must_use]
496 pub fn has_context(&self) -> bool {
497 !self.context.is_empty()
498 }
499}
500
501impl AsRef<str> for NixString {
502 fn as_ref(&self) -> &str {
503 &self.chars
504 }
505}
506
507#[repr(transparent)]
514#[derive(Debug, PartialEq)]
515pub struct NixList(pub Vec<Value>);
516
517impl NixList {
518 #[inline]
519 pub fn new(v: Vec<Value>) -> Self {
520 census::made(&census::LIST_MADE, &census::LIST_LIVE);
521 NixList(v)
522 }
523
524 #[inline]
528 pub fn into_vec(mut self) -> Vec<Value> {
529 std::mem::take(&mut self.0)
530 }
531}
532
533impl From<Vec<Value>> for NixList {
534 #[inline]
535 fn from(v: Vec<Value>) -> Self {
536 NixList::new(v)
537 }
538}
539
540impl<T: AsRef<[Value]>> PartialEq<T> for NixList {
542 #[inline]
543 fn eq(&self, other: &T) -> bool {
544 self.0.as_slice() == other.as_ref()
545 }
546}
547
548impl Clone for NixList {
549 fn clone(&self) -> Self {
550 census::made(&census::LIST_MADE, &census::LIST_LIVE);
551 NixList(self.0.clone())
552 }
553}
554
555impl Drop for NixList {
556 fn drop(&mut self) {
557 census::dropped(&census::LIST_LIVE);
558 }
559}
560
561impl FromIterator<Value> for NixList {
562 #[inline]
563 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
564 NixList::new(iter.into_iter().collect())
565 }
566}
567
568impl std::ops::Deref for NixList {
569 type Target = Vec<Value>;
570 #[inline]
571 fn deref(&self) -> &Vec<Value> {
572 &self.0
573 }
574}
575
576impl std::ops::DerefMut for NixList {
577 #[inline]
578 fn deref_mut(&mut self) -> &mut Vec<Value> {
579 &mut self.0
580 }
581}
582
583impl<'a> IntoIterator for &'a NixList {
584 type Item = &'a Value;
585 type IntoIter = std::slice::Iter<'a, Value>;
586 #[inline]
587 fn into_iter(self) -> Self::IntoIter {
588 self.0.iter()
589 }
590}
591
592impl IntoIterator for NixList {
593 type Item = Value;
594 type IntoIter = std::vec::IntoIter<Value>;
595 #[inline]
596 fn into_iter(mut self) -> Self::IntoIter {
597 std::mem::take(&mut self.0).into_iter()
601 }
602}
603
604impl std::ops::Deref for NixString {
605 type Target = str;
606
607 fn deref(&self) -> &str {
608 &self.chars
609 }
610}
611
612impl fmt::Display for NixString {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 write!(f, "{}", self.chars)
615 }
616}
617
618#[derive(Debug, Clone)]
626#[derive(Default)]
627pub enum Value {
628 #[default]
629 Null,
630 Bool(bool),
631 Int(i64),
632 Float(f64),
633 String(Rc<NixString>),
634 Path(Box<SmolStr>),
635 List(Rc<NixList>),
636 Attrs(Rc<NixAttrs>),
637 Lambda(Rc<Closure>),
638 Builtin(Box<BuiltinFn>),
639 Thunk(Thunk),
641}
642
643#[derive(Debug, Clone)]
659pub enum Concrete {
660 Null,
661 Bool(bool),
662 Int(i64),
663 Float(f64),
664 String(Rc<NixString>),
665 Path(Box<SmolStr>),
666 List(Rc<NixList>), Attrs(Rc<NixAttrs>), Lambda(Rc<Closure>),
669 Builtin(Box<BuiltinFn>),
670 }
672
673impl Concrete {
674 #[inline]
676 pub fn into_value(self) -> Value {
677 match self {
678 Concrete::Null => Value::Null,
679 Concrete::Bool(b) => Value::Bool(b),
680 Concrete::Int(n) => Value::Int(n),
681 Concrete::Float(f) => Value::Float(f),
682 Concrete::String(s) => Value::String(s),
683 Concrete::Path(p) => Value::Path(p),
684 Concrete::List(l) => Value::List(l),
685 Concrete::Attrs(a) => Value::Attrs(a),
686 Concrete::Lambda(c) => Value::Lambda(c),
687 Concrete::Builtin(b) => Value::Builtin(b),
688 }
689 }
690
691 pub fn to_value(&self) -> Value {
694 self.clone().into_value()
695 }
696
697 pub fn as_bool(&self) -> Result<bool, EvalError> {
699 match self {
700 Concrete::Bool(b) => Ok(*b),
701 other => Err(EvalError::TypeMismatch { expected: "bool", got: other.type_name() }),
702 }
703 }
704
705 pub fn as_int(&self) -> Result<i64, EvalError> {
707 match self {
708 Concrete::Int(n) => Ok(*n),
709 other => Err(EvalError::TypeMismatch { expected: "int", got: other.type_name() }),
710 }
711 }
712
713 pub fn as_str(&self) -> Result<&str, EvalError> {
715 match self {
716 Concrete::String(s) => Ok(&s.chars),
717 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
718 }
719 }
720
721 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
723 match self {
724 Concrete::String(s) => Ok(s),
725 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
726 }
727 }
728
729 pub fn as_list(&self) -> Result<&[Value], EvalError> {
732 match self {
733 Concrete::List(l) => Ok(l.as_slice()),
734 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
735 }
736 }
737
738 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
741 match self {
742 Concrete::Attrs(a) => Ok(a),
743 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
744 }
745 }
746
747 pub fn as_float(&self) -> Result<f64, EvalError> {
749 match self {
750 Concrete::Float(f) => Ok(*f),
751 Concrete::Int(n) => Ok(*n as f64),
752 other => Err(EvalError::TypeMismatch { expected: "float", got: other.type_name() }),
753 }
754 }
755
756 pub fn type_name(&self) -> &'static str {
758 match self {
759 Concrete::Null => "null",
760 Concrete::Bool(_) => "bool",
761 Concrete::Int(_) => "int",
762 Concrete::Float(_) => "float",
763 Concrete::String(_) => "string",
764 Concrete::Path(_) => "path",
765 Concrete::List(_) => "list",
766 Concrete::Attrs(_) => "set",
767 Concrete::Lambda(_) | Concrete::Builtin(_) => "lambda",
768 }
769 }
770
771 pub fn as_string(&self) -> Result<&str, EvalError> {
773 self.as_str()
774 }
775
776 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
778 match self {
779 Concrete::Attrs(a) => Ok((**a).clone()),
780 other => Err(EvalError::TypeMismatch { expected: "set", got: other.type_name() }),
781 }
782 }
783
784 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
786 match self {
787 Concrete::List(l) => Ok((**l).0.clone()),
788 other => Err(EvalError::TypeMismatch { expected: "list", got: other.type_name() }),
789 }
790 }
791
792 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
794 match self {
795 Concrete::Path(p) => Ok(p.to_string()),
796 Concrete::String(ns) => Ok(ns.chars.to_string()),
797 Concrete::Attrs(attrs) => {
798 if let Some(out_path) = attrs.get("outPath") {
799 let forced = crate::eval::force_value(out_path)?;
800 forced.coerce_to_path(context)
801 } else {
802 Err(EvalError::type_error(format!(
803 "{context}: expected path or string, got set without outPath"
804 )))
805 }
806 }
807 other => Err(EvalError::type_error(format!(
808 "{context}: expected path or string, got {}", other.type_name()
809 ))),
810 }
811 }
812
813 pub fn to_str(&self) -> Result<String, EvalError> {
815 match self {
816 Concrete::String(s) => Ok(s.chars.to_string()),
817 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
818 }
819 }
820
821 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
823 match self {
824 Concrete::String(s) => Ok((**s).clone()),
825 other => Err(EvalError::TypeMismatch { expected: "string", got: other.type_name() }),
826 }
827 }
828
829 pub fn is_function(&self) -> bool {
831 matches!(self, Concrete::Lambda(_) | Concrete::Builtin(_))
832 }
833}
834
835impl From<Concrete> for Value {
837 fn from(c: Concrete) -> Value {
838 c.into_value()
839 }
840}
841
842impl PartialEq for Concrete {
843 fn eq(&self, other: &Self) -> bool {
844 match (self, other) {
845 (Concrete::Null, Concrete::Null) => true,
846 (Concrete::Bool(a), Concrete::Bool(b)) => a == b,
847 (Concrete::Int(a), Concrete::Int(b)) => a == b,
848 (Concrete::Float(a), Concrete::Float(b)) => a == b,
849 (Concrete::Int(a), Concrete::Float(b)) | (Concrete::Float(b), Concrete::Int(a)) => (*a as f64) == *b,
850 (Concrete::String(a), Concrete::String(b)) => Rc::ptr_eq(a, b) || a.chars == b.chars,
851 (Concrete::Path(a), Concrete::Path(b)) => a == b,
852 (Concrete::List(a), Concrete::List(b)) => Rc::ptr_eq(a, b) || a == b,
853 (Concrete::Attrs(a), Concrete::Attrs(b)) => {
854 if Rc::ptr_eq(a, b) {
855 return true;
856 }
857 if let (Some(pa), Some(pb)) =
868 (derivation_out_path(a), derivation_out_path(b))
869 {
870 return pa == pb;
871 }
872 let (fa, fb) = (a.as_flat(), b.as_flat());
891 if crate::perf::enabled() {
892 crate::perf::inc(crate::perf::Counter::AttrsEqStructuralCalls);
893 crate::perf::add(
896 crate::perf::Counter::AttrsEqEntriesCloneElided,
897 (fa.len() + fb.len()) as u64,
898 );
899 }
900 fa == fb
901 }
902 (Concrete::Lambda(a), Concrete::Lambda(b)) => Rc::ptr_eq(a, b),
906 _ => false,
907 }
908 }
909}
910
911#[must_use]
947pub fn eq_operator(l: &Value, r: &Value) -> bool {
948 if let (Ok(Concrete::Lambda(_)), Ok(Concrete::Lambda(_))) = (l.demand(), r.demand()) {
949 return false;
950 }
951 l == r
952}
953
954pub fn concat_lists(left: Value, right_elems: &[Value]) -> Result<Value, EvalError> {
970 let mut la = match left {
974 Value::List(rc) => {
975 let reused = Rc::strong_count(&rc) == 1;
976 let vec: Vec<Value> = match Rc::try_unwrap(rc) {
977 Ok(v) => v.into_vec(), Err(rc) => (*rc).0.clone(), };
980 if crate::perf::enabled() {
981 crate::perf::inc(crate::perf::Counter::ListConcatCalls);
982 if reused {
983 crate::perf::add(
985 crate::perf::Counter::ListConcatElemsReused,
986 vec.len() as u64,
987 );
988 } else {
989 crate::perf::add(
991 crate::perf::Counter::ListConcatElemsCopied,
992 vec.len() as u64,
993 );
994 }
995 }
996 vec
997 }
998 other => {
999 return Err(EvalError::TypeMismatch {
1000 expected: "list",
1001 got: other.type_name(),
1002 });
1003 }
1004 };
1005 la.extend_from_slice(right_elems);
1007 Ok(Value::list(la))
1008}
1009
1010fn derivation_out_path(attrs: &NixAttrs) -> Option<String> {
1016 match attrs.get("type")?.demand().ok()? {
1017 Concrete::String(s) if s.chars == "derivation" => {}
1018 _ => return None,
1019 }
1020 match attrs.get("outPath")?.demand().ok()? {
1021 Concrete::String(s) => Some(s.chars.to_string()),
1022 _ => None,
1023 }
1024}
1025
1026fn derivation_drv_and_out(
1040 attrs: &NixAttrs,
1041) -> Result<Option<(String, String)>, EvalError> {
1042 match attrs.get("type") {
1044 Some(t) => match crate::eval::force_value(t)? {
1045 Value::String(s) if s.chars == "derivation" => {}
1046 _ => return Ok(None),
1047 },
1048 None => return Ok(None),
1049 }
1050 let drv_path = match attrs.get("drvPath") {
1053 Some(d) => crate::eval::force_value(d)?.coerce_to_path("drvPath")?,
1054 None => return Ok(None),
1055 };
1056 let out_path = match attrs.get("outPath") {
1057 Some(o) => crate::eval::force_value(o)?.coerce_to_path("outPath")?,
1058 None => return Ok(None),
1059 };
1060 Ok(Some((drv_path, out_path)))
1061}
1062
1063fn out_path_needs_realize(out_path: &str, ctx: &StringContext) -> Option<String> {
1077 if !out_path.starts_with("/nix/store/") {
1079 return None;
1080 }
1081 for elem in ctx.iter() {
1082 if let ContextElement::Output { drv, output } = elem {
1083 let _ = output; return Some(drv.to_string());
1090 }
1091 }
1092 None
1093}
1094
1095impl Value {
1096 pub(crate) fn demand_unchecked(self) -> Concrete {
1099 match self {
1100 Value::Null => Concrete::Null,
1101 Value::Bool(b) => Concrete::Bool(b),
1102 Value::Int(n) => Concrete::Int(n),
1103 Value::Float(f) => Concrete::Float(f),
1104 Value::String(s) => Concrete::String(s),
1105 Value::Path(p) => Concrete::Path(p),
1106 Value::List(l) => Concrete::List(l),
1107 Value::Attrs(a) => Concrete::Attrs(a),
1108 Value::Lambda(c) => Concrete::Lambda(c),
1109 Value::Builtin(b) => Concrete::Builtin(b),
1110 Value::Thunk(_) => panic!("demand_unchecked called on Thunk"),
1111 }
1112 }
1113}
1114
1115impl Value {
1116 pub fn demand(&self) -> Result<Concrete, EvalError> {
1121 let v = match self {
1122 Value::Thunk(_) => crate::eval::force_value(self)?,
1123 other => other.clone(),
1124 };
1125 match v {
1127 Value::Null => Ok(Concrete::Null),
1128 Value::Bool(b) => Ok(Concrete::Bool(b)),
1129 Value::Int(n) => Ok(Concrete::Int(n)),
1130 Value::Float(f) => Ok(Concrete::Float(f)),
1131 Value::String(s) => Ok(Concrete::String(s)),
1132 Value::Path(p) => Ok(Concrete::Path(p)),
1133 Value::List(l) => Ok(Concrete::List(l)),
1134 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1135 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1136 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1137 Value::Thunk(_) => {
1138 let re_forced = crate::eval::force_value(&v)?;
1142 match re_forced {
1143 Value::Null => Ok(Concrete::Null),
1144 Value::Bool(b) => Ok(Concrete::Bool(b)),
1145 Value::Int(n) => Ok(Concrete::Int(n)),
1146 Value::Float(f) => Ok(Concrete::Float(f)),
1147 Value::String(s) => Ok(Concrete::String(s)),
1148 Value::Path(p) => Ok(Concrete::Path(p)),
1149 Value::List(l) => Ok(Concrete::List(l)),
1150 Value::Attrs(a) => Ok(Concrete::Attrs(a)),
1151 Value::Lambda(c) => Ok(Concrete::Lambda(c)),
1152 Value::Builtin(b) => Ok(Concrete::Builtin(b)),
1153 Value::Thunk(_) => Err(EvalError::InfiniteRecursion(
1154 "demand: thunk chain could not be resolved".to_string(),
1155 )),
1156 }
1157 }
1158 }
1159 }
1160}
1161
1162#[cfg(target_pointer_width = "64")]
1163const _: () = assert!(std::mem::size_of::<Value>() <= 16);
1164
1165const FIXPOINT_PROMOTE_NEST_CAP: u32 = 32;
1182
1183const PROMOTION_RUNAWAY_FORCE_DEPTH: usize = 500;
1193
1194thread_local! {
1195 pub(crate) static IN_PROMISE_EVAL: std::cell::Cell<u32> = const { std::cell::Cell::new(0) };
1202
1203 pub(crate) static PROMOTION_OCCURRED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1211}
1212
1213#[inline(always)]
1215pub fn promotion_occurred() -> bool {
1216 PROMOTION_OCCURRED.with(|c| c.get())
1217}
1218
1219#[inline(always)]
1223pub fn in_promise_eval() -> bool {
1224 IN_PROMISE_EVAL.with(|c| c.get() > 0)
1225}
1226
1227pub enum ThunkRepr {
1232 Suspended {
1234 expr: rnix::ast::Expr,
1235 env: Env,
1236 },
1237 InheritSelect {
1253 source_thunk: Thunk,
1254 name: SmolStr,
1255 },
1256 Native(Box<dyn FnOnce() -> Result<Value, EvalError>>),
1261 WithIdent {
1271 name: SmolStr,
1273 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1278 scope_value: Value,
1280 env: Env,
1283 },
1284 Blackhole,
1286 Promise(Rc<RefCell<Value>>),
1299 Failed(EvalError),
1310 Evaluated(Box<Value>),
1314 EvaluatedConcrete,
1325}
1326
1327struct ThunkInner {
1336 cache: OnceCell<Box<Concrete>>,
1340 repr: UnsafeCell<ThunkRepr>,
1342 recursive: bool,
1349}
1350
1351impl Drop for ThunkInner {
1352 fn drop(&mut self) {
1353 census::dropped(&census::THUNK_LIVE);
1354 }
1355}
1356
1357#[derive(Clone)]
1359pub struct Thunk(pub(crate) Rc<ThunkInner>);
1360
1361impl Thunk {
1362 pub fn new_suspended(expr: rnix::ast::Expr, env: Env) -> Self {
1364 crate::trace::inc_thunks_created();
1365 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1366 Self(Rc::new(ThunkInner {
1367 cache: OnceCell::new(),
1368 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1369 recursive: false,
1370 }))
1371 }
1372
1373 pub fn new_suspended_recursive(expr: rnix::ast::Expr, env: Env) -> Self {
1380 crate::trace::inc_thunks_created();
1381 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1382 crate::perf::inc(crate::perf::Counter::ThunkSiteLetForward);
1383 Self(Rc::new(ThunkInner {
1384 cache: OnceCell::new(),
1385 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1386 recursive: true,
1387 }))
1388 }
1389
1390 pub fn new_inherit_select(source_thunk: Thunk, name: impl Into<SmolStr>) -> Self {
1398 crate::trace::inc_thunks_created();
1399 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1400 crate::perf::inc(crate::perf::Counter::ThunkSiteInheritSrc);
1401 Self(Rc::new(ThunkInner {
1402 cache: OnceCell::new(),
1403 repr: UnsafeCell::new(ThunkRepr::InheritSelect {
1404 source_thunk,
1405 name: name.into(),
1406 }),
1407 recursive: false,
1408 }))
1409 }
1410
1411 pub fn new_with_ident(
1415 name: SmolStr,
1416 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1417 scope_value: Value,
1418 env: Env,
1419 ) -> Self {
1420 crate::trace::inc_thunks_created();
1421 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1422 crate::perf::inc(crate::perf::Counter::ThunkSiteOther);
1423 Self(Rc::new(ThunkInner {
1424 cache: OnceCell::new(),
1425 repr: UnsafeCell::new(ThunkRepr::WithIdent {
1426 name,
1427 scope_cache,
1428 scope_value,
1429 env,
1430 }),
1431 recursive: false,
1432 }))
1433 }
1434
1435 pub fn new_native(f: impl FnOnce() -> Result<Value, EvalError> + 'static) -> Self {
1439 crate::trace::inc_thunks_created();
1440 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1441 crate::perf::inc(crate::perf::Counter::ThunkSiteNative);
1442 Self(Rc::new(ThunkInner {
1443 cache: OnceCell::new(),
1444 repr: UnsafeCell::new(ThunkRepr::Native(Box::new(f))),
1445 recursive: false,
1446 }))
1447 }
1448
1449 pub fn new_evaluated(value: Value) -> Self {
1453 crate::trace::inc_thunks_created();
1454 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1455 crate::perf::inc(crate::perf::Counter::ThunkSiteEvaluated);
1456 let cache = OnceCell::new();
1457 let repr = if matches!(value, Value::Thunk(_)) {
1461 ThunkRepr::Evaluated(Box::new(value))
1462 } else {
1463 let _ = cache.set(Box::new(value.demand_unchecked()));
1464 ThunkRepr::EvaluatedConcrete
1465 };
1466 Self(Rc::new(ThunkInner {
1467 cache,
1468 repr: UnsafeCell::new(repr),
1469 recursive: false,
1470 }))
1471 }
1472
1473 pub fn is_evaluated(&self) -> bool {
1476 self.0.cache.get().is_some()
1477 }
1478
1479 pub fn is_native(&self) -> bool {
1485 matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Native(_))
1488 }
1489
1490 pub fn peek(&self) -> Option<&Concrete> {
1496 self.0.cache.get().map(|v| &**v)
1497 }
1498
1499 pub fn update_env(&self, new_env: &Env) {
1504 let repr = unsafe { &mut *self.0.repr.get() };
1507 match repr {
1508 ThunkRepr::Suspended { env, .. } => {
1509 *env = new_env.clone();
1510 }
1511 ThunkRepr::InheritSelect { source_thunk, .. } => {
1512 source_thunk.update_env(new_env);
1513 }
1514 _ => {}
1515 }
1516 }
1517
1518 #[inline]
1541 unsafe fn store_evaluated(&self, value: &Value) {
1542 census::evaluated();
1543 if matches!(value, Value::Thunk(_)) {
1544 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1545 } else {
1546 let _ = self.0.cache.set(Box::new(value.clone().demand_unchecked()));
1547 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1548 }
1549 }
1550
1551 #[inline]
1577 unsafe fn store_evaluated_owned(&self, value: Value) -> Value {
1578 census::evaluated();
1579 let concrete = value.demand_unchecked();
1580 let ret = concrete.clone().into_value();
1581 let _ = self.0.cache.set(Box::new(concrete));
1582 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1583 ret
1584 }
1585
1586 pub fn force(
1595 &self,
1596 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1597 ) -> Result<Value, EvalError> {
1598 if let Some(cached) = self.0.cache.get() {
1602 crate::perf::inc(crate::perf::Counter::ThunkHit);
1603 return Ok((**cached).clone().into_value());
1604 }
1605 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1607 self.force_inner(evaluator)
1608 })
1609 }
1610
1611 fn force_inner(
1614 &self,
1615 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1616 ) -> Result<Value, EvalError> {
1617 if let Some(cached) = self.0.cache.get() {
1626 crate::perf::inc(crate::perf::Counter::ThunkHit);
1627 return Ok((**cached).clone().into_value());
1628 }
1629
1630 let thunk_id = Rc::as_ptr(&self.0) as usize;
1631
1632 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1645 return Ok(cell.borrow().clone());
1646 }
1647
1648 let new_repr_on_force = if self.0.recursive {
1657 ThunkRepr::Promise(Rc::new(RefCell::new(
1658 Value::Attrs(Rc::new(NixAttrs::new())),
1659 )))
1660 } else {
1661 ThunkRepr::Blackhole
1662 };
1663 let is_promise = self.0.recursive;
1664 let repr = std::mem::replace(unsafe { &mut *self.0.repr.get() }, new_repr_on_force);
1665
1666 match repr {
1667 ThunkRepr::Suspended { expr, env } => {
1668 crate::perf::inc(crate::perf::Counter::ThunkForce);
1669 crate::trace::inc_thunks_forced_unique();
1670 let tracing = crate::trace::trace_enabled();
1671 let desc: String = if tracing {
1679 expr.syntax().text().to_string().chars().take(60).collect()
1680 } else {
1681 String::new()
1682 };
1683 crate::trace::push_force(crate::trace::ForceFrame {
1684 defined_in: env.eval_file().cloned(),
1685 description: desc.clone(),
1686 thunk_id,
1687 });
1688 if crate::value::promotion_occurred()
1714 && crate::trace::current_force_depth() as usize
1715 > PROMOTION_RUNAWAY_FORCE_DEPTH
1716 {
1717 crate::trace::pop_force();
1718 *unsafe { &mut *self.0.repr.get() } =
1719 ThunkRepr::Suspended { expr, env };
1720 return Err(EvalError::InfiniteRecursion(
1721 "overlay-fixpoint promotion runaway (force depth exceeded)".into(),
1722 ));
1723 }
1724 if tracing {
1725 crate::trace::trace_force_enter(
1726 env.eval_file().map(|p| p.as_path()),
1727 &desc,
1728 );
1729 if let Err(msg) = crate::trace::check_force_depth() {
1730 crate::trace::dump_trace_on_error();
1731 crate::trace::pop_force();
1732 crate::trace::trace_force_exit();
1733 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended {
1734 expr,
1735 env,
1736 };
1737 return Err(EvalError::InfiniteRecursion(msg));
1738 }
1739 }
1740 let _file_guard = env.eval_file().cloned().map(crate::eval::push_eval_file);
1746 let _srcid_guard = crate::eval::push_source_id(env.source_id());
1755 if is_promise {
1761 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
1762 }
1763 let result = evaluator(&expr, &env);
1764 if is_promise {
1765 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1766 }
1767 let became_promise = !is_promise
1777 && matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Promise(_));
1778 if became_promise {
1779 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1780 }
1781 match result {
1782 Ok(mut value) => {
1783 crate::perf::inc(crate::perf::Counter::ThunkStoreWrites);
1784 if is_promise || became_promise {
1791 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1792 *cell.borrow_mut() = value.clone();
1793 }
1794 }
1795 let was_thunk_before_loop = matches!(value, Value::Thunk(_));
1817 if !was_thunk_before_loop {
1818 crate::perf::inc(crate::perf::Counter::ThunkStoreRedundant);
1823 let ret = unsafe { self.store_evaluated_owned(value) };
1824 crate::trace::pop_force();
1825 if tracing { crate::trace::trace_force_exit(); }
1826 return Ok(ret);
1827 }
1828 unsafe { self.store_evaluated(&value) };
1830 while let Value::Thunk(ref inner) = value {
1835 match inner.peek() {
1836 Some(cached) => value = cached.clone().into_value(),
1837 None => break,
1838 }
1839 }
1840 if !matches!(value, Value::Thunk(_)) {
1841 crate::perf::inc(crate::perf::Counter::ThunkStoreLoopMutated);
1842 }
1843 unsafe { self.store_evaluated(&value) };
1844 crate::trace::pop_force();
1845 if tracing { crate::trace::trace_force_exit(); }
1846 Ok(value)
1847 }
1848 Err(e) => {
1849 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended { expr, env };
1850 if tracing { crate::trace::dump_trace_on_error(); }
1851 crate::trace::pop_force();
1852 if tracing { crate::trace::trace_force_exit(); }
1853 Err(e)
1854 }
1855 }
1856 }
1857 ThunkRepr::InheritSelect { source_thunk, name } => {
1858 let tracing = crate::trace::trace_enabled();
1859 let desc = if tracing { format!("inherit (..) {name}") } else { String::new() };
1860 crate::trace::push_force(crate::trace::ForceFrame {
1861 defined_in: None,
1862 description: desc.clone(),
1863 thunk_id,
1864 });
1865 if tracing {
1866 crate::trace::trace_force_enter(None, &desc);
1867 }
1868 crate::trace::inc_thunks_forced_unique();
1869 if tracing {
1870 if let Err(msg) = crate::trace::check_force_depth() {
1871 crate::trace::dump_trace_on_error();
1872 crate::trace::pop_force();
1873 crate::trace::trace_force_exit();
1874 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect {
1875 source_thunk,
1876 name,
1877 };
1878 return Err(EvalError::InfiniteRecursion(msg));
1879 }
1880 }
1881 let attempt = (|| -> Result<Value, EvalError> {
1882 let mut forced = source_thunk.force(evaluator)?;
1883 while let Value::Thunk(inner) = forced {
1884 forced = inner.force(evaluator)?;
1885 }
1886 let attrs = match &forced {
1887 Value::Attrs(a) => a,
1888 _ => {
1889 return Err(EvalError::TypeError(format!(
1890 "inherit (source) {name}: source is {}, not a set",
1891 forced.type_name()
1892 )))
1893 }
1894 };
1895 attrs
1896 .get(&name)
1897 .cloned()
1898 .ok_or_else(|| EvalError::AttrNotFound(name.to_string()))
1899 })();
1900 match attempt {
1901 Ok(mut value) => {
1902 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1903 while let Value::Thunk(ref inner) = value {
1904 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1905 }
1906 unsafe { self.store_evaluated(&value) };
1907 crate::trace::pop_force();
1908 if tracing { crate::trace::trace_force_exit(); }
1909 Ok(value)
1910 }
1911 Err(e) => {
1912 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect { source_thunk, name };
1913 if tracing { crate::trace::dump_trace_on_error(); }
1914 crate::trace::pop_force();
1915 if tracing { crate::trace::trace_force_exit(); }
1916 Err(e)
1917 }
1918 }
1919 }
1920 ThunkRepr::Native(f) => {
1921 let tracing = crate::trace::trace_enabled();
1922 crate::trace::push_force(crate::trace::ForceFrame {
1923 defined_in: None,
1924 description: if tracing { "<native-thunk>".into() } else { String::new() },
1925 thunk_id,
1926 });
1927 if tracing {
1928 crate::trace::trace_force_enter(None, "<native-thunk>");
1929 }
1930 crate::trace::inc_thunks_forced_unique();
1931 match f() {
1936 Ok(mut value) => {
1937 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1938 while let Value::Thunk(ref inner) = value {
1939 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
1940 }
1941 unsafe { self.store_evaluated(&value) };
1942 crate::trace::pop_force();
1943 if tracing { crate::trace::trace_force_exit(); }
1944 Ok(value)
1945 }
1946 Err(e) => {
1947 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e.clone());
1961 if tracing { crate::trace::dump_trace_on_error(); }
1962 crate::trace::pop_force();
1963 if tracing { crate::trace::trace_force_exit(); }
1964 Err(e)
1965 }
1966 }
1967 }
1968 ThunkRepr::WithIdent { name, scope_cache, scope_value, env } => {
1969 crate::perf::inc(crate::perf::Counter::ThunkForce);
1970 crate::trace::inc_thunks_forced_unique();
1971 {
1976 let cache = scope_cache.borrow();
1977 if let Some(ref attrs) = *cache {
1978 if let Some(v) = attrs.get(&name) {
1979 let value = v.clone();
1980 unsafe { self.store_evaluated(&value) };
1981 return Ok(value);
1982 }
1983 }
1985 }
1986 if let Ok(forced) = crate::eval::force_value(&scope_value) {
1988 if let Value::Attrs(ref attrs) = forced {
1989 *scope_cache.borrow_mut() = Some((**attrs).clone());
1990 if let Some(v) = attrs.get(&name) {
1991 let value = v.clone();
1992 unsafe { self.store_evaluated(&value) };
1993 return Ok(value);
1994 }
1995 }
1996 }
1997 let result = match env.lookup(&name) {
2027 Some(v) => v,
2028 None => match env.lookup_fresh(&name) {
2029 Some(v) => v,
2030 None if in_promise_eval() => Value::Null,
2031 None => return Err(EvalError::UndefinedVar(format!("'{name}'"))),
2032 },
2033 };
2034 unsafe { self.store_evaluated(&result) };
2035 Ok(result)
2036 }
2037 ThunkRepr::Blackhole => {
2038 if std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some() {
2062 return Ok(Value::Null);
2063 }
2064 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_LIST").is_some() {
2065 return Ok(Value::List(Rc::new(NixList::new(Vec::new()))));
2066 }
2067 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some() {
2068 return Ok(Value::Attrs(Rc::new(NixAttrs::new())));
2069 }
2070 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2071 let same = crate::trace::force_stack_contains(thunk_id);
2072 eprintln!(
2073 "[SUI_DEBUG_CYCLE] blackhole re-entry thunk_id={thunk_id:#x} same_thunk_on_stack={same} recursive_flag={}",
2074 self.0.recursive
2075 );
2076 crate::trace::dump_force_stack_ids();
2077 }
2078 if crate::trace::force_stack_contains(thunk_id)
2117 && IN_PROMISE_EVAL.with(|c| c.get()) < FIXPOINT_PROMOTE_NEST_CAP
2118 {
2119 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2120 let chain = crate::trace::capture_cycle(thunk_id);
2121 let nest = IN_PROMISE_EVAL.with(|c| c.get());
2122 let fdepth = crate::trace::current_force_depth();
2123 eprintln!("[SUI_PROMOTE] thunk_id={thunk_id:#x} cycle_len={} nest={nest} fdepth={fdepth}", chain.0.len());
2124 }
2125 let cell = Rc::new(RefCell::new(
2126 Value::Attrs(Rc::new(NixAttrs::new())),
2127 ));
2128 *unsafe { &mut *self.0.repr.get() } =
2131 ThunkRepr::Promise(cell.clone());
2132 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
2136 PROMOTION_OCCURRED.with(|c| c.set(true));
2138 return Ok(cell.borrow().clone());
2139 }
2140 let chain = crate::trace::capture_cycle(thunk_id);
2141 crate::trace::dump_trace_on_error();
2142 Err(EvalError::InfiniteRecursion(chain.to_string()))
2143 }
2144 ThunkRepr::Promise(cell) => {
2145 Ok(cell.borrow().clone())
2154 }
2155 ThunkRepr::Evaluated(v) => {
2156 crate::perf::inc(crate::perf::Counter::ThunkHit);
2160 let cloned = (*v).clone();
2161 if !matches!(cloned, Value::Thunk(_)) {
2162 if !matches!(cloned, Value::Thunk(_)) { let _ = self.0.cache.set(Box::new(cloned.clone().demand_unchecked())); }
2163 }
2164 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(v);
2165 Ok(cloned)
2166 }
2167 ThunkRepr::EvaluatedConcrete => {
2168 crate::perf::inc(crate::perf::Counter::ThunkHit);
2178 let value = self
2179 .0
2180 .cache
2181 .get()
2182 .expect("EvaluatedConcrete implies a populated cache")
2183 .as_ref()
2184 .clone()
2185 .into_value();
2186 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
2187 Ok(value)
2188 }
2189 ThunkRepr::Failed(e) => {
2190 let err = e.clone();
2195 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e);
2196 Err(err)
2197 }
2198 }
2199 }
2200}
2201
2202impl fmt::Debug for Thunk {
2203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2204 match unsafe { &*self.0.repr.get() } {
2206 ThunkRepr::Suspended { .. } => write!(f, "<thunk>"),
2207 ThunkRepr::InheritSelect { name, .. } => write!(f, "<inherit-select {name}>"),
2208 ThunkRepr::Native(_) => write!(f, "<native-thunk>"),
2209 ThunkRepr::WithIdent { name, .. } => write!(f, "<with-ident {name}>"),
2210 ThunkRepr::Blackhole => write!(f, "<blackhole>"),
2211 ThunkRepr::Promise(_) => write!(f, "<promise>"),
2212 ThunkRepr::Failed(e) => write!(f, "<failed-thunk: {e}>"),
2213 ThunkRepr::Evaluated(v) => write!(f, "{v:?}"),
2214 ThunkRepr::EvaluatedConcrete => match self.0.cache.get() {
2215 Some(c) => write!(f, "{:?}", c.as_ref().clone().into_value()),
2216 None => write!(f, "<evaluated-concrete>"),
2217 },
2218 }
2219 }
2220}
2221
2222pub struct NixAttrs(AttrsInner, Option<Rc<crate::pos::AttrPositions>>);
2236
2237impl Clone for NixAttrs {
2242 fn clone(&self) -> Self {
2243 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2244 NixAttrs(self.0.clone(), self.1.clone())
2245 }
2246}
2247
2248impl Drop for NixAttrs {
2249 fn drop(&mut self) {
2250 census::dropped(&census::ATTRS_LIVE);
2251 }
2252}
2253
2254#[derive(Clone)]
2256enum AttrsInner {
2257 Flat(AttrsMap<Symbol, Value>),
2259 Overlay {
2270 left: RefCell<Rc<NixAttrs>>,
2271 right: RefCell<Rc<NixAttrs>>,
2272 cache: Rc<OnceCell<AttrsMap<Symbol, Value>>>,
2273 },
2274}
2275
2276impl fmt::Debug for NixAttrs {
2277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2278 write!(f, "NixAttrs({})", self.len())
2279 }
2280}
2281
2282impl Default for NixAttrs {
2283 fn default() -> Self {
2284 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2285 Self(AttrsInner::Flat(AttrsMap::default()), None)
2286 }
2287}
2288
2289impl NixAttrs {
2290 pub fn new() -> Self {
2291 Self::default()
2292 }
2293
2294 pub fn with_capacity(_capacity: usize) -> Self {
2295 Self::default()
2296 }
2297
2298 pub fn set_positions(&mut self, pos: Rc<crate::pos::AttrPositions>) {
2302 self.1 = Some(pos);
2303 }
2304
2305 #[must_use]
2309 pub fn positions(&self) -> Option<&Rc<crate::pos::AttrPositions>> {
2310 self.1.as_ref()
2311 }
2312
2313 #[must_use]
2318 pub fn pos_for(&self, key: &str) -> Option<crate::pos::ResolvedPos> {
2319 let sym = intern(key);
2320 let (file, offset) = self.pos_entry(sym)?;
2321 crate::pos::resolve(file.as_deref(), offset)
2322 }
2323
2324 fn pos_entry(&self, sym: Symbol) -> Option<(Option<std::path::PathBuf>, u32)> {
2344 if let Some(table) = self.1.as_ref() {
2345 if let Some(offset) = table.keys.get(&sym) {
2346 return Some((table.file.clone(), *offset));
2347 }
2348 }
2349 match &self.0 {
2350 AttrsInner::Overlay { left, right, .. } => {
2351 let r = right.borrow().pos_entry(sym);
2352 if r.is_some() {
2353 return r;
2354 }
2355 let l = left.borrow().pos_entry(sym);
2356 l
2357 }
2358 _ => None,
2359 }
2360 }
2361
2362 #[must_use]
2364 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2365 self.as_flat().clone()
2366 }
2367
2368 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2370 match &self.0 {
2371 AttrsInner::Flat(m) => m,
2372 AttrsInner::Overlay { left, right, cache } => {
2373 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2374 let flat = cache.get_or_init(|| {
2375 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2378 let timed = crate::perf::enabled();
2379 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2380 let mut result = left.borrow().as_flat().clone();
2381 for (k, v) in right.borrow().as_flat().iter() {
2382 result.insert(*k, v.clone());
2383 }
2384 crate::perf::add(
2385 crate::perf::Counter::OverlayFlattenEntries,
2386 result.len() as u64,
2387 );
2388 if let Some(t0) = t0 {
2389 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2390 }
2391 result
2392 });
2393 {
2413 let mut l = left.borrow_mut();
2414 if !l.is_empty() { *l = Rc::new(l.position_husk()); }
2415 }
2416 {
2417 let mut r = right.borrow_mut();
2418 if !r.is_empty() { *r = Rc::new(r.position_husk()); }
2419 }
2420 flat
2421 }
2422 }
2423 }
2424
2425 fn position_husk(&self) -> NixAttrs {
2435 match &self.0 {
2436 AttrsInner::Overlay { left, right, .. } => {
2437 let (l, r) = (left.borrow().position_husk(), right.borrow().position_husk());
2438 if l.1.is_none() && r.1.is_none() && !matches!(l.0, AttrsInner::Overlay { .. })
2439 && !matches!(r.0, AttrsInner::Overlay { .. })
2440 {
2441 return NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone());
2444 }
2445 NixAttrs(
2446 AttrsInner::Overlay {
2447 left: RefCell::new(Rc::new(l)),
2448 right: RefCell::new(Rc::new(r)),
2449 cache: Rc::new(OnceCell::new()),
2450 },
2451 self.1.clone(),
2452 )
2453 }
2454 AttrsInner::Flat(_) => NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone()),
2455 }
2456 }
2457
2458 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2459 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2460 let m = self.as_flat();
2461 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2462 let timed = crate::perf::enabled();
2463 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2464 let mut pairs: Vec<(String, &Value)> = m.iter()
2465 .map(|(sym, v)| (resolve(*sym), v))
2466 .collect();
2467 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2468 if let Some(t0) = t0 {
2469 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2470 }
2471 pairs
2472 }
2473
2474 #[must_use]
2476 pub fn get(&self, key: &str) -> Option<&Value> {
2477 let sym = intern(key);
2478 self.get_sym(&sym)
2479 }
2480
2481 #[must_use]
2495 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2496 match &self.0 {
2497 AttrsInner::Flat(m) => m.get(sym),
2498 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2504 }
2505 }
2506
2507 pub fn insert(&mut self, key: String, value: Value) {
2509 self.ensure_flat();
2510 if let AttrsInner::Flat(ref mut m) = self.0 {
2511 m.insert(intern(&key), value);
2512 }
2513 }
2514
2515 fn ensure_flat(&mut self) {
2517 if matches!(self.0, AttrsInner::Overlay { .. }) {
2518 self.0 = AttrsInner::Flat(self.as_flat().clone());
2519 }
2520 }
2521
2522 #[must_use]
2523 pub fn contains_key(&self, key: &str) -> bool {
2524 let sym = intern(key);
2525 self.contains_key_sym(&sym)
2526 }
2527
2528 #[must_use]
2529 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2530 match &self.0 {
2531 AttrsInner::Flat(m) => m.contains_key(sym),
2532 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2534 }
2535 }
2536
2537 pub fn keys(&self) -> impl Iterator<Item = String> {
2538 self.sorted_entries().into_iter().map(|(k, _)| k)
2539 }
2540
2541 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2542 self.sorted_entries().into_iter()
2543 }
2544
2545 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2546 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2547 }
2548
2549 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2567 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2568 }
2569
2570 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2573 self.ensure_flat();
2574 if let AttrsInner::Flat(ref mut m) = self.0 {
2575 m.insert(sym, value);
2576 }
2577 }
2578
2579 pub fn values(&self) -> impl Iterator<Item = &Value> {
2580 self.sorted_entries().into_iter().map(|(_, v)| v)
2581 }
2582
2583
2584 pub fn remove(&mut self, key: &str) -> Option<Value> {
2585 self.ensure_flat();
2586 if let AttrsInner::Flat(ref mut m) = self.0 {
2587 m.remove(&intern(key))
2588 } else {
2589 None
2590 }
2591 }
2592
2593 #[must_use]
2594 pub fn len(&self) -> usize {
2595 match &self.0 {
2596 AttrsInner::Flat(m) => m.len(),
2597 AttrsInner::Overlay { .. } => {
2598 self.as_flat().len()
2602 }
2603 }
2604 }
2605
2606 #[must_use]
2607 pub fn is_empty(&self) -> bool {
2608 match &self.0 {
2609 AttrsInner::Flat(m) => m.is_empty(),
2610 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2614 }
2615 }
2616
2617 #[must_use]
2619 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2620 if other.is_empty() { return self; }
2621 if self.is_empty() { return other; }
2622 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2623 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2624 NixAttrs(AttrsInner::Overlay {
2625 left: RefCell::new(Rc::new(self)),
2626 right: RefCell::new(Rc::new(other)),
2627 cache: Rc::new(OnceCell::new()),
2628 }, None)
2629 }
2630
2631 #[must_use]
2633 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2634 match (&self.0, &other.0) {
2635 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2636 let mut result = l.clone();
2637 for (k, v) in r.iter() {
2638 result.insert(*k, v.clone());
2639 }
2640 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2641 NixAttrs(AttrsInner::Flat(result), None)
2642 }
2643 _ => {
2644 let mut result = self.as_flat().clone();
2646 let other_flat = other.as_flat();
2647 for (k, v) in other_flat.iter() {
2648 result.insert(*k, v.clone());
2649 }
2650 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2651 NixAttrs(AttrsInner::Flat(result), None)
2652 }
2653 }
2654 }
2655}
2656
2657impl FromIterator<(String, Value)> for NixAttrs {
2658 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2659 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2660 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2661 }
2662}
2663
2664impl IntoIterator for NixAttrs {
2665 type Item = (String, Value);
2666 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2667
2668 fn into_iter(self) -> Self::IntoIter {
2669 let flat = self.as_flat().clone();
2670 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2671 }
2672}
2673
2674#[derive(Debug, Clone)]
2682pub struct Closure {
2683 pub param: rnix::ast::Param,
2684 pub body: rnix::ast::Expr,
2685 pub env: Env,
2686}
2687
2688pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2690
2691#[derive(Clone)]
2696pub struct BuiltinFn {
2697 pub name: &'static str,
2699 pub func: Rc<BuiltinFunc>,
2701}
2702
2703impl fmt::Debug for BuiltinFn {
2704 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2705 write!(f, "<builtin {}>", self.name)
2706 }
2707}
2708
2709#[derive(Clone)]
2719struct WithScope {
2720 value: Value,
2721 cached: Rc<RefCell<Option<NixAttrs>>>,
2724}
2725
2726impl fmt::Debug for WithScope {
2727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2728 f.debug_struct("WithScope")
2729 .field("value", &self.value)
2730 .field("cached", &self.cached.borrow().is_some())
2731 .finish()
2732 }
2733}
2734
2735#[derive(Debug, Clone, Default)]
2745struct EnvInner {
2746 bindings: FxHashMap<Symbol, Value>,
2747 with_scopes: Vec<WithScope>,
2749 eval_file: Option<std::path::PathBuf>,
2753 source_id: u32,
2760}
2761
2762#[derive(Clone, Default)]
2770pub struct Env(Rc<EnvInner>);
2771
2772impl fmt::Debug for Env {
2773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2774 self.0.fmt(f)
2775 }
2776}
2777
2778impl Drop for EnvInner {
2787 fn drop(&mut self) {
2788 census::dropped(&census::ENV_LIVE);
2789 }
2790}
2791
2792impl Env {
2793 #[must_use]
2795 pub fn new() -> Self {
2796 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2797 Self(Rc::new(EnvInner {
2798 bindings: FxHashMap::default(),
2799 with_scopes: Vec::new(),
2800 eval_file: None,
2801 source_id: 0,
2802 }))
2803 }
2804
2805 #[must_use]
2810 pub fn child(&self) -> Self {
2811 crate::perf::inc(crate::perf::Counter::EnvClone);
2812 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2818 Self(Rc::new(EnvInner {
2819 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2821 eval_file: self.0.eval_file.clone(),
2825 source_id: self.0.source_id,
2829 }))
2830 }
2831
2832 #[must_use]
2840 pub fn with_scope(mut self, value: Value) -> Self {
2841 let pre_cached = match &value {
2843 Value::Attrs(attrs) => Some((**attrs).clone()),
2844 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2845 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2846 }),
2847 _ => None,
2848 };
2849 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2850 value,
2851 cached: Rc::new(RefCell::new(pre_cached)),
2852 });
2853 self
2854 }
2855
2856 pub fn bind(&mut self, name: String, value: Value) {
2861 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2862 }
2863
2864 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2874 let inner = Rc::make_mut(&mut self.0);
2875 for (name, value) in pairs {
2876 inner.bindings.insert(intern(&name), value);
2877 }
2878 }
2879
2880 #[must_use]
2882 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2883 self.0.eval_file.as_ref()
2884 }
2885
2886 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2888 Rc::make_mut(&mut self.0).eval_file = file;
2889 }
2890
2891 #[must_use]
2893 pub fn source_id(&self) -> u32 {
2894 self.0.source_id
2895 }
2896
2897 pub fn set_source_id(&mut self, id: u32) {
2900 Rc::make_mut(&mut self.0).source_id = id;
2901 }
2902
2903 #[must_use]
2905 pub fn binding_count(&self) -> usize {
2906 self.0.bindings.len()
2907 }
2908
2909 #[must_use]
2911 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2912 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2913 }
2914
2915 #[must_use]
2917 pub fn with_scope_count(&self) -> usize {
2918 self.0.with_scopes.len()
2919 }
2920
2921 #[must_use]
2925 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2926 let sym = intern(name);
2927 self.0.bindings.get(&sym).cloned()
2928 }
2929
2930 #[must_use]
2941 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
2942 self.0.bindings.get(&sym).cloned()
2943 }
2944
2945 #[must_use]
2949 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
2950 for scope in self.0.with_scopes.iter().rev() {
2951 let cache = scope.cached.borrow();
2952 if let Some(ref attrs) = *cache {
2953 if let Some(v) = attrs.get(name) {
2954 return Some(v.clone());
2955 }
2956 }
2957 drop(cache);
2959 if let Value::Thunk(ref thunk) = scope.value {
2960 if let Some(cached_val) = thunk.peek() {
2961 if let Concrete::Attrs(ref attrs) = *cached_val {
2962 *scope.cached.borrow_mut() = Some((**attrs).clone());
2964 if let Some(v) = attrs.get(name) {
2965 return Some(v.clone());
2966 }
2967 }
2968 }
2969 } else if let Value::Attrs(ref attrs) = scope.value {
2970 *scope.cached.borrow_mut() = Some((**attrs).clone());
2971 if let Some(v) = attrs.get(name) {
2972 return Some(v.clone());
2973 }
2974 }
2975 }
2976 None
2977 }
2978
2979 #[must_use]
2982 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
2983 self.0.with_scopes.last().map(|scope| {
2984 (scope.cached.clone(), scope.value.clone())
2985 })
2986 }
2987
2988 #[must_use]
2997 pub fn lookup(&self, name: &str) -> Option<Value> {
2998 self.lookup_fast(intern(name), name)
2999 }
3000
3001 #[must_use]
3013 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
3014 let sym = intern(name);
3015 if let Some(v) = self.0.bindings.get(&sym) {
3016 return Some(v.clone());
3017 }
3018 for scope in self.0.with_scopes.iter().rev() {
3019 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
3020 if let Some(v) = attrs.get_sym(&sym) {
3021 *scope.cached.borrow_mut() = Some((*attrs).clone());
3024 return Some(v.clone());
3025 }
3026 }
3027 }
3028 None
3029 }
3030
3031 #[must_use]
3033 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
3034 crate::perf::inc(crate::perf::Counter::EnvLookup);
3035 if let Some(v) = self.0.bindings.get(&sym) {
3036 return Some(v.clone());
3037 }
3038 for scope in self.0.with_scopes.iter().rev() {
3040 {
3042 let cache = scope.cached.borrow();
3043 if let Some(ref attrs) = *cache {
3044 if let Some(v) = attrs.get_sym(&sym) {
3045 return Some(v.clone());
3046 }
3047 continue;
3048 }
3049 }
3050 let resolved = match &scope.value {
3055 Value::Attrs(attrs) => {
3056 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3058 *scope.cached.borrow_mut() = Some((**attrs).clone());
3059 Some((**attrs).clone())
3060 }
3061 Value::Thunk(thunk) => {
3062 if let Some(cached_val) = thunk.peek() {
3065 if let Concrete::Attrs(ref attrs) = *cached_val {
3066 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3067 *scope.cached.borrow_mut() = Some((**attrs).clone());
3068 Some((**attrs).clone())
3069 } else {
3070 None
3071 }
3072 } else {
3073 match crate::eval::force_value(&scope.value) {
3084 Ok(forced) => {
3085 if let Value::Attrs(ref attrs) = forced {
3086 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3087 *scope.cached.borrow_mut() = Some((**attrs).clone());
3088 Some((**attrs).clone())
3089 } else {
3090 None
3091 }
3092 }
3093 Err(_) => None, }
3095 }
3096 }
3097 _ => {
3098 match crate::eval::force_value(&scope.value) {
3100 Ok(forced) => {
3101 if let Value::Attrs(ref attrs) = forced {
3102 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3103 *scope.cached.borrow_mut() = Some((**attrs).clone());
3104 Some((**attrs).clone())
3105 } else {
3106 None
3107 }
3108 }
3109 Err(_) => None,
3110 }
3111 }
3112 };
3113 if let Some(ref attrs) = resolved {
3114 if let Some(v) = attrs.get(name) {
3115 return Some(v.clone());
3116 }
3117 }
3118 }
3120 None
3121 }
3122
3123 #[must_use]
3129 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
3130 crate::perf::inc(crate::perf::Counter::EnvLookup);
3131 if let Some(v) = self.0.bindings.get(&sym) {
3133 return Some(v.clone());
3134 }
3135 for scope in self.0.with_scopes.iter().rev() {
3137 {
3139 let cache = scope.cached.borrow();
3140 if let Some(ref attrs) = *cache {
3141 if let Some(v) = attrs.get_sym(&sym) {
3142 return Some(v.clone());
3143 }
3144 continue;
3145 }
3146 }
3147 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
3149 if let Value::Attrs(ref attrs) = forced {
3150 let result = attrs.get_sym(&sym).cloned();
3151 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3152 *scope.cached.borrow_mut() = Some((**attrs).clone());
3153 if result.is_some() {
3154 return result;
3155 }
3156 }
3157 }
3158 }
3160 None
3161 }
3162}
3163
3164#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
3166#[non_exhaustive]
3167pub enum EvalError {
3168 #[error("undefined variable: {0}")]
3170 UndefinedVar(String),
3171 #[error("type error: {0}")]
3173 TypeError(String),
3174 #[error("attribute not found: {0}")]
3176 AttrNotFound(String),
3177 #[error("type error: expected {expected}, got {got}")]
3179 TypeMismatch {
3180 expected: &'static str,
3181 got: &'static str,
3182 },
3183 #[error("assertion failed{0}")]
3185 AssertionFailed(String),
3186 #[error("division by zero")]
3188 DivisionByZero,
3189 #[error("infinite recursion ({0})")]
3191 InfiniteRecursion(String),
3192 #[error("I/O error: {context}: {message}")]
3194 IoError { context: String, message: String },
3195 #[error("{0}")]
3197 Throw(String),
3198 #[error("{0}")]
3202 Abort(String),
3203 #[error("not yet implemented: {0}")]
3205 NotImplemented(String),
3206 #[error("parse error: {0}")]
3208 ParseError(String),
3209 #[error("recursion limit: {0}")]
3211 RecursionLimit(String),
3212}
3213
3214impl EvalError {
3215 #[must_use]
3217 pub fn type_error(msg: impl Into<String>) -> Self {
3218 EvalError::TypeError(msg.into())
3219 }
3220
3221 #[must_use]
3223 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3224 EvalError::TypeMismatch { expected, got }
3225 }
3226
3227 #[must_use]
3229 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3230 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3231 }
3232
3233 #[must_use]
3254 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3255 EvalError::TypeError(format!(
3256 "cannot {op} {lhs} and {rhs}{}",
3257 crate::eval::eval_file_ctx()
3258 ))
3259 }
3260
3261 #[must_use]
3263 pub fn is_throw(&self) -> bool {
3264 matches!(self, EvalError::Throw(_))
3265 }
3266
3267 #[must_use]
3269 pub fn is_infinite_recursion(&self) -> bool {
3270 matches!(self, EvalError::InfiniteRecursion(_))
3271 }
3272}
3273
3274impl Value {
3275 #[must_use]
3277 pub fn string(s: impl Into<SmolStr>) -> Self {
3278 Value::String(Rc::new(NixString::plain(s)))
3279 }
3280
3281 #[must_use]
3284 pub fn list(items: Vec<Value>) -> Self {
3285 Value::List(Rc::new(NixList::new(items)))
3286 }
3287
3288 #[must_use]
3291 pub fn is_uniquely_owned_list(&self) -> bool {
3292 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3293 }
3294
3295 #[must_use]
3297 pub fn to_json(&self) -> serde_json::Value {
3298 match self {
3299 Value::Null => serde_json::Value::Null,
3300 Value::Bool(b) => serde_json::Value::Bool(*b),
3301 Value::Int(n) => serde_json::json!(n),
3302 Value::Float(f) => serde_json::json!(f),
3303 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3304 Value::Path(p) => serde_json::Value::String(p.to_string()),
3305 Value::List(items) => {
3306 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3307 }
3308 Value::Attrs(attrs) => {
3309 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3316 if let Ok((s, _ctx)) = self.coerce_to_string() {
3317 return serde_json::Value::String(s);
3318 }
3319 }
3320 let map: serde_json::Map<String, serde_json::Value> = attrs
3321 .iter()
3322 .map(|(k, v)| (k.clone(), v.to_json()))
3323 .collect();
3324 serde_json::Value::Object(map)
3325 }
3326 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3327 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3328 Value::Thunk(thunk) => {
3329 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3331 Ok(v) => v.to_json(),
3332 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3333 }
3334 }
3335 }
3336 }
3337
3338 pub fn try_to_json(&self) -> Result<serde_json::Value, EvalError> {
3369 Ok(match self {
3370 Value::Null => serde_json::Value::Null,
3371 Value::Bool(b) => serde_json::Value::Bool(*b),
3372 Value::Int(n) => serde_json::json!(n),
3373 Value::Float(f) => serde_json::json!(f),
3374 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3375 Value::Path(p) => serde_json::Value::String(p.to_string()),
3376 Value::List(items) => {
3377 let mut out = Vec::with_capacity(items.len());
3378 for v in items.iter() {
3379 out.push(v.try_to_json()?);
3380 }
3381 serde_json::Value::Array(out)
3382 }
3383 Value::Attrs(attrs) => {
3384 if let Some(v) = attrs.get("outPath").or_else(|| attrs.get("__toString")) {
3390 return v.try_to_json();
3391 }
3392 let mut map = serde_json::Map::new();
3393 for (k, v) in attrs.iter() {
3394 map.insert(k.clone(), v.try_to_json()?);
3395 }
3396 serde_json::Value::Object(map)
3397 }
3398 Value::Lambda(_) => {
3399 return Err(EvalError::TypeError(
3400 "cannot convert a function to JSON".to_string(),
3401 ))
3402 }
3403 Value::Builtin(b) => {
3404 return Err(EvalError::TypeError(format!(
3405 "cannot convert a function to JSON (builtin '{}')",
3406 b.name
3407 )))
3408 }
3409 Value::Thunk(thunk) => {
3410 let forced = thunk.force(&|expr, env| crate::eval::eval_expr(expr, env))?;
3413 forced.try_to_json()?
3414 }
3415 })
3416 }
3417
3418 pub fn to_json_with_context(
3425 &self,
3426 ctx: &mut StringContext,
3427 ) -> Result<serde_json::Value, EvalError> {
3428 Ok(match self {
3429 Value::Null => serde_json::Value::Null,
3430 Value::Bool(b) => serde_json::Value::Bool(*b),
3431 Value::Int(n) => serde_json::json!(n),
3432 Value::Float(f) => serde_json::json!(f),
3433 Value::String(s) => {
3434 ctx.merge(&s.context);
3435 serde_json::Value::String(s.chars.to_string())
3436 }
3437 Value::Path(_) => {
3438 let (str, c) = self.coerce_to_string_copy_to_store()?;
3439 ctx.merge(&c);
3440 serde_json::Value::String(str)
3441 }
3442 Value::List(items) => {
3443 let mut arr = Vec::with_capacity(items.len());
3444 for v in items.iter() {
3445 let fv = crate::eval::force_value(v)?;
3446 arr.push(fv.to_json_with_context(ctx)?);
3447 }
3448 serde_json::Value::Array(arr)
3449 }
3450 Value::Attrs(attrs) => {
3451 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3454 let (s, c) = self.coerce_to_string_copy_to_store()?;
3455 ctx.merge(&c);
3456 return Ok(serde_json::Value::String(s));
3457 }
3458 let mut map = serde_json::Map::new();
3459 for (k, v) in attrs.iter() {
3460 let fv = crate::eval::force_value(v)?;
3461 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3462 }
3463 serde_json::Value::Object(map)
3464 }
3465 Value::Thunk(_) => {
3466 let forced = crate::eval::force_value(self)?;
3467 forced.to_json_with_context(ctx)?
3468 }
3469 other => {
3470 return Err(EvalError::TypeError(format!(
3471 "cannot serialize {} to JSON (__structuredAttrs)",
3472 other.type_name()
3473 )));
3474 }
3475 })
3476 }
3477
3478 #[must_use]
3480 pub fn type_name(&self) -> &'static str {
3481 match self {
3482 Value::Null => "null",
3483 Value::Bool(_) => "bool",
3484 Value::Int(_) => "int",
3485 Value::Float(_) => "float",
3486 Value::String(_) => "string",
3487 Value::Path(_) => "path",
3488 Value::List(_) => "list",
3489 Value::Attrs(_) => "set",
3490 Value::Lambda(_) => "lambda",
3491 Value::Builtin(_) => "lambda",
3492 Value::Thunk(thunk) => {
3493 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3495 Ok(v) => v.type_name(),
3496 Err(_) => "thunk",
3497 }
3498 }
3499 }
3500 }
3501
3502 pub fn as_bool(&self) -> Result<bool, EvalError> {
3523 match self {
3524 Value::Bool(b) => Ok(*b),
3525 Value::Thunk(thunk) => {
3526 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3527 }
3528 _ if in_promise_eval() => Ok(false),
3532 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3533 }
3534 }
3535
3536 pub fn as_int(&self) -> Result<i64, EvalError> {
3538 match self {
3539 Value::Int(n) => Ok(*n),
3540 Value::Thunk(thunk) => {
3541 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3542 }
3543 _ if in_promise_eval() => Ok(0),
3546 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3547 }
3548 }
3549
3550 pub fn as_string(&self) -> Result<&str, EvalError> {
3552 match self {
3553 Value::String(s) => Ok(&s.chars),
3554 Value::Thunk(_) => Err(EvalError::TypeError(
3555 "thunk in as_string: force first via force_value()".into(),
3556 )),
3557 _ if in_promise_eval() => Ok(""),
3558 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3559 }
3560 }
3561
3562 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3564 match self {
3565 Value::String(ns) => Ok(ns),
3566 Value::Thunk(_) => Err(EvalError::TypeError(
3567 "thunk in as_nix_string: force first via force_value()".into(),
3568 )),
3569 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3570 }
3571 }
3572
3573 pub fn to_str(&self) -> Result<String, EvalError> {
3577 match self {
3578 Value::String(s) => Ok(s.chars.to_string()),
3579 Value::Thunk(thunk) => {
3580 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3581 forced.to_str()
3582 }
3583 _ if in_promise_eval() => Ok(String::new()),
3584 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3585 }
3586 }
3587
3588 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3591 match self {
3592 Value::String(s) => Ok((**s).clone()),
3593 Value::Thunk(thunk) => {
3594 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3595 forced.to_nix_string()
3596 }
3597 _ if in_promise_eval() => Ok(NixString::plain("")),
3598 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3599 }
3600 }
3601
3602 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3611 match self {
3612 Value::Attrs(a) => Ok(a),
3613 Value::Thunk(_) => Err(EvalError::TypeError(
3614 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3615 )),
3616 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3617 }
3618 }
3619
3620 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3622 match self {
3623 Value::List(l) => Ok(l.as_slice()),
3624 Value::Thunk(_) => Err(EvalError::TypeError(
3625 "thunk in as_list: force first via force_value()".into(),
3626 )),
3627 _ => Err(crate::eval::attach_trace(
3628 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3629 )),
3630 }
3631 }
3632
3633 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3635 match self {
3636 Value::Attrs(a) => Ok((**a).clone()),
3637 Value::Thunk(thunk) => {
3638 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3639 forced.to_attrs()
3640 }
3641 _ if in_promise_eval() => Ok(NixAttrs::new()),
3647 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3648 }
3649 }
3650
3651 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3653 match self {
3654 Value::List(l) => Ok((**l).0.clone()),
3655 Value::Thunk(thunk) => {
3656 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3657 forced.to_list()
3658 }
3659 _ if in_promise_eval() => Ok(Vec::new()),
3662 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3663 }
3664 }
3665
3666 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3672 match self {
3673 Value::Path(p) => Ok(p.to_string()),
3674 Value::String(ns) => Ok(ns.chars.to_string()),
3675 Value::Attrs(attrs) => {
3676 if let Some(out_path) = attrs.get("outPath") {
3677 let forced = crate::eval::force_value(out_path)?;
3678 forced.coerce_to_path(context)
3679 } else {
3680 Err(EvalError::TypeError(format!(
3681 "{context}: expected path or string, got set without outPath"
3682 )))
3683 }
3684 }
3685 _ => Err(EvalError::TypeError(format!(
3686 "{context}: expected path or string, got {}",
3687 self.type_name()
3688 ))),
3689 }
3690 }
3691
3692 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3716 match self {
3717 Value::Attrs(attrs) => {
3720 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3721 self.realize_if_absent(&drv_path, &out_path, context)?;
3722 return Ok(out_path);
3723 }
3724 }
3725 Value::String(ns) => {
3732 let out_path = ns.chars.to_string();
3733 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3734 self.realize_if_absent(&drv_path, &out_path, context)?;
3735 }
3736 return Ok(out_path);
3737 }
3738 _ => {}
3739 }
3740 self.coerce_to_path(context)
3741 }
3742
3743 fn realize_if_absent(
3748 &self,
3749 drv_path: &str,
3750 out_path: &str,
3751 context: &str,
3752 ) -> Result<(), EvalError> {
3753 let read_path = crate::path::materialize_str(out_path);
3756 if std::path::Path::new(&read_path).exists() {
3757 return Ok(());
3758 }
3759 match crate::realize::realize_output(drv_path, out_path) {
3760 Ok(true) | Ok(false) => Ok(()),
3761 Err(msg) => Err(EvalError::IoError {
3762 context: context.to_string(),
3763 message: format!(
3764 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3765 ),
3766 }),
3767 }
3768 }
3769
3770 pub fn to_float(&self) -> Result<f64, EvalError> {
3772 match self {
3773 Value::Float(f) => Ok(*f),
3774 Value::Int(n) => Ok(*n as f64),
3775 Value::Thunk(thunk) => {
3776 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3777 }
3778 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3779 }
3780 }
3781
3782 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3800 self.coerce_to_string_impl(false)
3801 }
3802
3803 pub fn coerce_to_string_copy_to_store(
3813 &self,
3814 ) -> Result<(String, StringContext), EvalError> {
3815 self.coerce_to_string_impl(true)
3816 }
3817
3818 fn coerce_to_string_impl(
3819 &self,
3820 copy_to_store: bool,
3821 ) -> Result<(String, StringContext), EvalError> {
3822 let mut ctx = StringContext::new();
3823 let s = match self {
3824 Value::String(ns) => {
3825 ctx.merge(&ns.context);
3826 ns.chars.to_string()
3827 }
3828 Value::Path(p) => {
3829 let raw: &str = &**p;
3830 if copy_to_store {
3831 let pb = std::path::Path::new(raw);
3851 let abs = if pb.is_absolute() {
3852 pb.to_path_buf()
3853 } else if let Some(dir) = crate::eval::current_eval_dir() {
3854 dir.join(pb)
3855 } else {
3856 std::env::current_dir()
3857 .map_err(|e| EvalError::IoError {
3858 context: format!("copy-to-store coercion of {raw}"),
3859 message: e.to_string(),
3860 })?
3861 .join(pb)
3862 };
3863 let read_abs = crate::path::materialize(&abs);
3870 let canon = read_abs.canonicalize().map_err(|_| {
3871 EvalError::TypeError(format!(
3872 "path '{}' does not exist",
3873 abs.display()
3874 ))
3875 })?;
3876 let name = crate::path::source_name_for_read_dir(&canon)
3893 .or_else(|| {
3894 canon
3895 .file_name()
3896 .map(|n| sui_compat::source::strip_store_hash_prefix(
3897 &n.to_string_lossy()).to_string())
3898 })
3899 .unwrap_or_else(|| "source".to_string());
3900 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3901 .map_err(|e| {
3902 EvalError::TypeError(format!(
3903 "copy-to-store coercion of '{}': {e}",
3904 canon.display()
3905 ))
3906 })?;
3907 ctx.add_plain(src.store_path.clone());
3908 src.store_path
3909 } else {
3910 ctx.add_plain(raw.to_string());
3911 raw.to_string()
3912 }
3913 }
3914 Value::Int(n) => n.to_string(),
3915 Value::Float(f) => format!("{f:.6}"),
3921 Value::Bool(true) => "1".to_string(),
3922 Value::Bool(false) => String::new(),
3923 Value::Null => String::new(),
3924 Value::Attrs(attrs) => {
3925 if let Some(to_str) = attrs.get("__toString") {
3926 let result =
3927 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3928 let forced = crate::eval::force_value(&result)?;
3929 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3930 ctx.merge(&c);
3931 s
3932 } else if let Some(out_path) = attrs.get("outPath") {
3933 let forced = crate::eval::force_value(out_path)?;
3934 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3935 ctx.merge(&c);
3936 s
3937 } else {
3938 return Err(EvalError::TypeError(
3939 "cannot coerce set to string (no __toString or outPath)".into(),
3940 ));
3941 }
3942 }
3943 Value::List(items) => {
3944 let mut parts = Vec::new();
3945 for item in items.iter() {
3946 let forced = crate::eval::force_value(item)?;
3947 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3948 ctx.merge(&c);
3949 parts.push(s);
3950 }
3951 parts.join(" ")
3952 }
3953 Value::Thunk(_) => {
3954 let forced = crate::eval::force_value(self)?;
3956 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3957 ctx.merge(&c);
3958 s
3959 }
3960 other => {
3961 return Err(EvalError::TypeError(format!(
3962 "cannot coerce {} to string",
3963 other.type_name()
3964 )));
3965 }
3966 };
3967 Ok((s, ctx))
3968 }
3969}
3970
3971impl From<&serde_json::Value> for Value {
3974 fn from(json: &serde_json::Value) -> Self {
3975 match json {
3976 serde_json::Value::Null => Value::Null,
3977 serde_json::Value::Bool(b) => Value::Bool(*b),
3978 serde_json::Value::Number(n) => {
3979 if let Some(i) = n.as_i64() {
3980 Value::Int(i)
3981 } else {
3982 Value::Float(n.as_f64().unwrap_or(0.0))
3983 }
3984 }
3985 serde_json::Value::String(s) => Value::string(s.clone()),
3986 serde_json::Value::Array(arr) => {
3987 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
3988 }
3989 serde_json::Value::Object(obj) => {
3990 let mut attrs = NixAttrs::new();
3991 for (k, v) in obj {
3992 attrs.insert(k.clone(), Value::from(v));
3993 }
3994 Value::Attrs(Rc::new(attrs))
3995 }
3996 }
3997 }
3998}
3999
4000impl From<&toml::Value> for Value {
4001 fn from(v: &toml::Value) -> Self {
4002 match v {
4003 toml::Value::String(s) => Value::string(s.clone()),
4004 toml::Value::Integer(n) => Value::Int(*n),
4005 toml::Value::Float(f) => Value::Float(*f),
4006 toml::Value::Boolean(b) => Value::Bool(*b),
4007 toml::Value::Array(arr) => {
4008 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
4009 }
4010 toml::Value::Table(t) => {
4011 let mut attrs = NixAttrs::new();
4012 for (k, val) in t {
4013 attrs.insert(k.clone(), Value::from(val));
4014 }
4015 Value::Attrs(Rc::new(attrs))
4016 }
4017 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
4018 }
4019 }
4020}
4021
4022
4023impl From<bool> for Value {
4026 fn from(b: bool) -> Self {
4027 Value::Bool(b)
4028 }
4029}
4030
4031impl From<i64> for Value {
4032 fn from(n: i64) -> Self {
4033 Value::Int(n)
4034 }
4035}
4036
4037impl From<f64> for Value {
4038 fn from(f: f64) -> Self {
4039 Value::Float(f)
4040 }
4041}
4042
4043impl From<NixString> for Value {
4044 fn from(s: NixString) -> Self {
4045 Value::String(Rc::new(s))
4046 }
4047}
4048
4049impl From<NixAttrs> for Value {
4050 fn from(attrs: NixAttrs) -> Self {
4051 Value::Attrs(Rc::new(attrs))
4052 }
4053}
4054
4055impl From<Vec<Value>> for Value {
4056 fn from(list: Vec<Value>) -> Self {
4057 Value::List(Rc::new(NixList::new(list)))
4058 }
4059}
4060
4061impl PartialEq for Value {
4062 fn eq(&self, other: &Self) -> bool {
4063 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
4065 if Rc::ptr_eq(&a.0, &b.0) { return true; }
4066 }
4067 let l = self.demand().unwrap_or(Concrete::Null);
4070 let r = other.demand().unwrap_or(Concrete::Null);
4071 l == r
4072 }
4073}
4074
4075impl fmt::Display for Value {
4076 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4077 match self {
4078 Value::Null => write!(f, "null"),
4079 Value::Bool(b) => write!(f, "{b}"),
4080 Value::Int(n) => write!(f, "{n}"),
4081 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
4082 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
4083 Value::Path(p) => write!(f, "{p}"),
4084 Value::List(items) => {
4085 write!(f, "[ ")?;
4086 for item in items.iter() {
4087 write!(f, "{item} ")?;
4088 }
4089 write!(f, "]")
4090 }
4091 Value::Attrs(attrs) => {
4092 write!(f, "{{ ")?;
4093 for (k, v) in attrs.iter() {
4094 write!(f, "{k} = {v}; ")?;
4095 }
4096 write!(f, "}}")
4097 }
4098 Value::Lambda(_) => write!(f, "<<lambda>>"),
4099 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
4100 Value::Thunk(thunk) => {
4101 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
4102 Ok(v) => write!(f, "{v}"),
4103 Err(_) => write!(f, "<<thunk:error>>"),
4104 }
4105 }
4106 }
4107 }
4108}
4109
4110#[cfg(test)]
4111mod tests {
4112 use super::*;
4113 use std::rc::Rc;
4114
4115 #[test]
4121 #[ignore = "measurement, not a gate: run with --ignored --nocapture"]
4122 fn measure_hamt_vs_flat_attrset_cost() {
4123 use crate::value::census::rss_bytes;
4124 const N: usize = 300_000;
4125 const ENTRIES: usize = 4; let syms: Vec<Symbol> = (0..ENTRIES).map(|i| intern(&format!("k{i}"))).collect();
4128
4129 let base = rss_bytes();
4130 let mut hamts: Vec<FxHashMap<Symbol, Value>> = Vec::with_capacity(N);
4131 for _ in 0..N {
4132 let mut m = FxHashMap::default();
4133 for s in &syms { m.insert(*s, Value::Int(1)); }
4134 hamts.push(m);
4135 }
4136 let after_hamt = rss_bytes();
4137
4138 let mut flats: Vec<std::collections::HashMap<Symbol, Value>> = Vec::with_capacity(N);
4139 for _ in 0..N {
4140 let mut m = std::collections::HashMap::with_capacity(ENTRIES);
4141 for s in &syms { m.insert(*s, Value::Int(1)); }
4142 flats.push(m);
4143 }
4144 let after_flat = rss_bytes();
4145
4146 let hamt_cost = after_hamt.saturating_sub(base);
4147 let flat_cost = after_flat.saturating_sub(after_hamt);
4148 eprintln!("N={N} entries={ENTRIES}");
4149 eprintln!(" im_rc HAMT : {} B total, {} B/map", hamt_cost, hamt_cost / N as u64);
4150 eprintln!(" std flat : {} B total, {} B/map", flat_cost, flat_cost / N as u64);
4151 if flat_cost > 0 {
4152 eprintln!(" ratio : {:.2}x", hamt_cost as f64 / flat_cost as f64);
4153 }
4154 std::hint::black_box((&hamts, &flats));
4155 }
4156
4157 #[test]
4158 fn value_is_16_bytes() {
4159 assert_eq!(std::mem::size_of::<Value>(), 16);
4160 }
4161
4162 #[test]
4175 fn overlay_carries_attr_positions_from_both_sides() {
4176 let tbl = |file: &str, key: &str, off: u32| {
4177 let mut t = crate::pos::AttrPositions::new(Some(std::path::PathBuf::from(file)));
4178 t.insert(intern(key), off);
4179 Rc::new(t)
4180 };
4181 let mk = |file: &str, key: &str, off: u32| {
4185 let mut a = NixAttrs::new();
4186 a.insert(key.to_string(), Value::Int(1));
4187 a.set_positions(tbl(file, key, off));
4188 a
4189 };
4190
4191 let left_only = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "other", 22));
4194 assert_eq!(
4195 left_only.pos_entry(intern("modules")),
4196 Some((Some(std::path::PathBuf::from("/l.nix")), 11)),
4197 );
4198
4199 let both = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "modules", 22));
4201 assert_eq!(
4202 both.pos_entry(intern("modules")),
4203 Some((Some(std::path::PathBuf::from("/r.nix")), 22)),
4204 );
4205
4206 assert_eq!(both.pos_entry(intern("nope")), None);
4208 }
4209
4210 #[test]
4213 fn to_json_null() {
4214 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
4215 }
4216
4217 #[test]
4218 fn to_json_bool() {
4219 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
4220 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
4221 }
4222
4223 #[test]
4224 fn to_json_int() {
4225 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
4226 }
4227
4228 #[test]
4229 fn to_json_float() {
4230 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
4231 }
4232
4233 #[test]
4234 fn to_json_string() {
4235 assert_eq!(
4236 Value::string("hello").to_json(),
4237 serde_json::Value::String("hello".to_string()),
4238 );
4239 }
4240
4241 #[test]
4242 fn to_json_path() {
4243 assert_eq!(
4244 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
4245 serde_json::Value::String("/nix/store".to_string()),
4246 );
4247 }
4248
4249 #[test]
4250 fn to_json_list() {
4251 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
4252 assert_eq!(v.to_json(), serde_json::json!([1, true]));
4253 }
4254
4255 #[test]
4256 fn to_json_attrs() {
4257 let mut attrs = NixAttrs::new();
4258 attrs.insert("a".to_string(), Value::Int(1));
4259 let v = Value::Attrs(Rc::new(attrs));
4260 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
4261 }
4262
4263 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
4266 let mut a = NixAttrs::new();
4267 a.insert("type".to_string(), Value::string("derivation"));
4268 a.insert("outPath".to_string(), Value::string(out_path));
4269 a.insert(extra_key.to_string(), Value::Int(extra_val));
4270 Value::Attrs(Rc::new(a))
4271 }
4272
4273 #[test]
4274 fn derivations_same_outpath_differing_attrs_are_equal() {
4275 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4282 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
4283 assert!(a == b, "same-outPath derivations must compare equal");
4284 assert!(!(a != b));
4285 }
4286
4287 #[test]
4288 fn derivations_differing_outpath_are_unequal() {
4289 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4290 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
4291 assert!(a != b, "different-outPath derivations must compare unequal");
4292 }
4293
4294 #[test]
4295 fn non_derivation_attrs_with_outpath_use_structural_eq() {
4296 let mut a = NixAttrs::new();
4299 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
4300 a.insert("foo".to_string(), Value::Int(1));
4301 let mut b = NixAttrs::new();
4302 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
4303 b.insert("foo".to_string(), Value::Int(2));
4304 assert!(
4305 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
4306 "non-derivation attrs with equal outPath but differing foo must be unequal",
4307 );
4308 }
4309
4310 #[test]
4316 fn attrs_eq_borrow_result_matches_multi_key() {
4317 let mk = || {
4320 let mut inner = NixAttrs::new();
4321 inner.insert("n".to_string(), Value::Int(7));
4322 let mut a = NixAttrs::new();
4323 a.insert("a".to_string(), Value::Int(1));
4324 a.insert("b".to_string(), Value::string("two"));
4325 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
4326 Value::Attrs(Rc::new(a))
4327 };
4328 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
4329
4330 let mut b = NixAttrs::new();
4332 b.insert("a".to_string(), Value::Int(1));
4333 b.insert("b".to_string(), Value::string("TWO"));
4334 let mut a2 = NixAttrs::new();
4335 a2.insert("a".to_string(), Value::Int(1));
4336 a2.insert("b".to_string(), Value::string("two"));
4337 assert!(
4338 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
4339 "attrsets differing in one value must be unequal (borrow path)",
4340 );
4341
4342 let mut a3 = NixAttrs::new();
4344 a3.insert("a".to_string(), Value::Int(1));
4345 let mut b3 = NixAttrs::new();
4346 b3.insert("a".to_string(), Value::Int(1));
4347 b3.insert("extra".to_string(), Value::Int(9));
4348 assert!(
4349 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
4350 "attrsets differing in key set must be unequal (borrow path)",
4351 );
4352 }
4353
4354 #[test]
4355 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
4356 let boom = Value::Thunk(Thunk::new_native(|| {
4367 Err(EvalError::Throw("kaboom".to_string()))
4368 }));
4369 let mut a = NixAttrs::new();
4370 a.insert("x".to_string(), Value::Int(1));
4371 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4373 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4375 let va = Value::Attrs(Rc::new(a));
4379 let vb = Value::Attrs(Rc::new(b));
4380 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4381 }
4382
4383 #[test]
4384 fn attrs_eq_borrow_overlay_still_compares() {
4385 let mut base = NixAttrs::new();
4389 base.insert("a".to_string(), Value::Int(1));
4390 let mut over = NixAttrs::new();
4391 over.insert("b".to_string(), Value::Int(2));
4392 let merged = base.overlay(over);
4395 let mut flat = NixAttrs::new();
4396 flat.insert("a".to_string(), Value::Int(1));
4397 flat.insert("b".to_string(), Value::Int(2));
4398 assert!(
4399 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4400 "overlay and equivalent flat attrset must compare equal (borrow path)",
4401 );
4402 }
4403
4404 #[test]
4405 fn to_json_lambda() {
4406 let root = rnix::Root::parse("x: x");
4408 let expr = root.tree().expr().unwrap();
4409 let lambda = match expr {
4410 rnix::ast::Expr::Lambda(l) => l,
4411 _ => panic!("expected lambda"),
4412 };
4413 let closure = Closure {
4414 param: lambda.param().unwrap(),
4415 body: lambda.body().unwrap(),
4416 env: Env::new(),
4417 };
4418 assert_eq!(
4419 Value::Lambda(Rc::new(closure)).to_json(),
4420 serde_json::Value::String("<lambda>".to_string()),
4421 );
4422 }
4423
4424 #[test]
4425 fn to_json_builtin() {
4426 let b = BuiltinFn {
4427 name: "test",
4428 func: Rc::new(|_| Ok(Value::Null)),
4429 };
4430 assert_eq!(
4431 Value::Builtin(Box::new(b)).to_json(),
4432 serde_json::Value::String("<builtin test>".to_string()),
4433 );
4434 }
4435
4436 #[test]
4439 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4440
4441 #[test]
4442 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4443
4444 #[test]
4445 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4446
4447 #[test]
4448 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4449
4450 #[test]
4451 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4452
4453 #[test]
4454 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4455
4456 #[test]
4457 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4458
4459 #[test]
4460 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4461
4462 #[test]
4463 fn type_name_lambda() {
4464 let root = rnix::Root::parse("x: x");
4465 let expr = root.tree().expr().unwrap();
4466 let lambda = match expr {
4467 rnix::ast::Expr::Lambda(l) => l,
4468 _ => panic!("expected lambda"),
4469 };
4470 let closure = Closure {
4471 param: lambda.param().unwrap(),
4472 body: lambda.body().unwrap(),
4473 env: Env::new(),
4474 };
4475 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4476 }
4477
4478 #[test]
4479 fn type_name_builtin() {
4480 let b = BuiltinFn {
4481 name: "t",
4482 func: Rc::new(|_| Ok(Value::Null)),
4483 };
4484 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4485 }
4486
4487 #[test]
4490 fn as_bool_error_on_non_bool() {
4491 assert!(Value::Int(1).as_bool().is_err());
4492 assert!(Value::string("true").as_bool().is_err());
4493 }
4494
4495 #[test]
4496 fn as_int_error_on_non_int() {
4497 assert!(Value::Bool(true).as_int().is_err());
4498 assert!(Value::Float(1.0).as_int().is_err());
4499 }
4500
4501 #[test]
4502 fn as_string_error_on_non_string() {
4503 assert!(Value::Int(42).as_string().is_err());
4504 assert!(Value::Null.as_string().is_err());
4505 }
4506
4507 #[test]
4508 fn as_attrs_error_on_non_attrs() {
4509 assert!(Value::Int(1).as_attrs().is_err());
4510 assert!(Value::list(vec![]).as_attrs().is_err());
4511 }
4512
4513 #[test]
4514 fn as_list_error_on_non_list() {
4515 assert!(Value::Int(1).as_list().is_err());
4516 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4517 }
4518
4519 #[test]
4522 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4523 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4525 assert!(left.is_uniquely_owned_list());
4526 let right = [Value::Int(3), Value::Int(4)];
4527 let out = super::concat_lists(left, &right).unwrap();
4528 assert_eq!(
4529 out.as_list().unwrap(),
4530 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4531 );
4532 }
4533
4534 #[test]
4535 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4536 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4539 let left = Value::List(Rc::clone(&shared));
4540 assert!(!left.is_uniquely_owned_list());
4541 let right = [Value::Int(3)];
4542 let out = super::concat_lists(left, &right).unwrap();
4543 assert_eq!(
4544 out.as_list().unwrap(),
4545 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4546 );
4547 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4549 }
4550
4551 #[test]
4552 fn concat_lists_empty_operands() {
4553 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4554 assert!(out.as_list().unwrap().is_empty());
4555 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4556 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4557 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4558 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4559 }
4560
4561 #[test]
4562 fn concat_lists_non_list_left_errors() {
4563 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4564 }
4565
4566 #[test]
4567 fn concat_lists_preserves_element_identity() {
4568 let inner = Rc::new(NixString::plain("x"));
4570 let a = Value::String(Rc::clone(&inner));
4571 let left = Value::list(vec![a]);
4572 let out = super::concat_lists(left, &[]).unwrap();
4573 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4574 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4575 } else {
4576 panic!("expected string element");
4577 }
4578 }
4579
4580 #[test]
4583 fn to_float_coerces_int() {
4584 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4585 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4586 assert!(Value::string("x").to_float().is_err());
4587 }
4588
4589 #[test]
4592 fn partial_eq_int_float_cross() {
4593 assert_eq!(Value::Int(3), Value::Float(3.0));
4594 assert_eq!(Value::Float(3.0), Value::Int(3));
4595 assert_ne!(Value::Int(3), Value::Float(3.5));
4596 }
4597
4598 #[test]
4599 fn partial_eq_different_types_not_equal() {
4600 assert_ne!(Value::Int(1), Value::string("1"));
4601 assert_ne!(Value::Bool(true), Value::Int(1));
4602 assert_ne!(Value::Null, Value::Bool(false));
4603 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4604 }
4605
4606 #[test]
4609 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4610
4611 #[test]
4612 fn display_bool() {
4613 assert_eq!(format!("{}", Value::Bool(true)), "true");
4614 assert_eq!(format!("{}", Value::Bool(false)), "false");
4615 }
4616
4617 #[test]
4618 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4619
4620 #[test]
4621 fn display_float() {
4622 let s = format!("{}", Value::Float(3.14));
4623 assert!(s.contains("3.14"));
4624 }
4625
4626 #[test]
4627 fn display_string() {
4628 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4629 }
4630
4631 #[test]
4632 fn display_string_with_escapes() {
4633 let v = Value::string("a\"b\\c");
4634 let s = format!("{v}");
4635 assert!(s.contains("\\\""));
4636 assert!(s.contains("\\\\"));
4637 }
4638
4639 #[test]
4640 fn display_path() {
4641 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4642 }
4643
4644 #[test]
4645 fn display_list() {
4646 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4647 assert_eq!(format!("{v}"), "[ 1 2 ]");
4648 }
4649
4650 #[test]
4651 fn display_attrs() {
4652 let mut attrs = NixAttrs::new();
4653 attrs.insert("x".to_string(), Value::Int(1));
4654 let v = Value::Attrs(Rc::new(attrs));
4655 assert_eq!(format!("{v}"), "{ x = 1; }");
4656 }
4657
4658 #[test]
4659 fn display_lambda() {
4660 let root = rnix::Root::parse("x: x");
4661 let expr = root.tree().expr().unwrap();
4662 let lambda = match expr {
4663 rnix::ast::Expr::Lambda(l) => l,
4664 _ => panic!("expected lambda"),
4665 };
4666 let closure = Closure {
4667 param: lambda.param().unwrap(),
4668 body: lambda.body().unwrap(),
4669 env: Env::new(),
4670 };
4671 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4672 }
4673
4674 #[test]
4675 fn display_builtin() {
4676 let b = BuiltinFn {
4677 name: "add",
4678 func: Rc::new(|_| Ok(Value::Null)),
4679 };
4680 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4681 }
4682
4683 #[test]
4686 fn nixattrs_update_merging() {
4687 let mut a = NixAttrs::new();
4688 a.insert("x".to_string(), Value::Int(1));
4689 a.insert("y".to_string(), Value::Int(2));
4690 let mut b = NixAttrs::new();
4691 b.insert("y".to_string(), Value::Int(99));
4692 b.insert("z".to_string(), Value::Int(3));
4693 let merged = a.update(&b);
4694 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4695 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4696 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4697 assert_eq!(merged.len(), 3);
4698 }
4699
4700 #[test]
4701 fn nixattrs_contains_key() {
4702 let mut a = NixAttrs::new();
4703 a.insert("foo".to_string(), Value::Null);
4704 assert!(a.contains_key("foo"));
4705 assert!(!a.contains_key("bar"));
4706 }
4707
4708 #[test]
4711 fn env_lookup_through_parent_chain() {
4712 let mut root = Env::new();
4713 root.bind("a".to_string(), Value::Int(1));
4714 let mut child = root.child();
4715 child.bind("b".to_string(), Value::Int(2));
4716 let grandchild = child.child();
4717 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4719 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4720 assert_eq!(grandchild.lookup("c"), None);
4721 }
4722
4723 #[test]
4724 fn env_with_scope_lookup() {
4725 let mut attrs = NixAttrs::new();
4726 attrs.insert("x".to_string(), Value::Int(42));
4727 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4728 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4729 assert_eq!(env.lookup("y"), None);
4730 }
4731
4732 #[test]
4733 fn env_local_shadows_with_scope() {
4734 let mut attrs = NixAttrs::new();
4735 attrs.insert("x".to_string(), Value::Int(1));
4736 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4737 env.bind("x".to_string(), Value::Int(99));
4738 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4739 }
4740
4741 #[test]
4744 fn string_context_merge_combines_elements() {
4745 let mut ctx_a = StringContext::new();
4746 ctx_a.add_plain("/nix/store/aaa".to_string());
4747 let mut ctx_b = StringContext::new();
4748 ctx_b.add_plain("/nix/store/bbb".to_string());
4749 ctx_a.merge(&ctx_b);
4750 assert_eq!(ctx_a.len(), 2);
4751 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4752 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4753 }
4754
4755 #[test]
4756 fn string_context_merge_deduplicates() {
4757 let mut ctx = StringContext::new();
4758 ctx.add_plain("/nix/store/same".to_string());
4759 ctx.add_plain("/nix/store/same".to_string());
4760 assert_eq!(ctx.len(), 1);
4761 }
4762
4763 #[test]
4764 fn string_context_mixed_element_types() {
4765 let mut ctx = StringContext::new();
4766 ctx.add_plain("/nix/store/foo".to_string());
4767 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4768 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4769 assert_eq!(ctx.len(), 3);
4770 assert!(!ctx.is_empty());
4771 }
4772
4773 #[test]
4774 fn string_context_new_is_empty() {
4775 let ctx = StringContext::new();
4776 assert!(ctx.is_empty());
4777 assert_eq!(ctx.len(), 0);
4778 }
4779
4780 #[test]
4781 fn string_context_merge_zero_elements() {
4782 let mut ctx_a = StringContext::new();
4783 let ctx_b = StringContext::new();
4784 ctx_a.merge(&ctx_b);
4785 assert!(ctx_a.is_empty());
4786 }
4787
4788 #[test]
4789 fn string_context_merge_one_element() {
4790 let mut ctx = StringContext::new();
4791 let mut other = StringContext::new();
4792 other.add_plain("/nix/store/only".to_string());
4793 ctx.merge(&other);
4794 assert_eq!(ctx.len(), 1);
4795 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4796 }
4797
4798 #[test]
4799 fn string_context_merge_two_elements() {
4800 let mut ctx = StringContext::new();
4801 ctx.add_plain("/nix/store/a".to_string());
4802 let mut other = StringContext::new();
4803 other.add_plain("/nix/store/b".to_string());
4804 ctx.merge(&other);
4805 assert_eq!(ctx.len(), 2);
4806 }
4807
4808 #[test]
4809 fn string_context_merge_five_elements() {
4810 let mut ctx = StringContext::new();
4811 for i in 0..5 {
4812 ctx.add_plain(format!("/nix/store/path-{i}"));
4813 }
4814 assert_eq!(ctx.len(), 5);
4815 for i in 0..5 {
4816 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4817 }
4818 }
4819
4820 #[test]
4821 fn string_context_insert_deduplicates() {
4822 let mut ctx = StringContext::new();
4823 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4824 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4825 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4826 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4827 assert_eq!(ctx.len(), 2);
4828 }
4829
4830 #[test]
4831 fn nix_string_plain_has_no_context() {
4832 let s = NixString::plain("hello");
4833 assert!(!s.has_context());
4834 assert_eq!(s.as_str(), "hello");
4835 }
4836
4837 #[test]
4838 fn nix_string_with_context_reports_context() {
4839 let mut ctx = StringContext::new();
4840 ctx.add_plain("/nix/store/xyz".to_string());
4841 let s = NixString::with_context("hello", ctx);
4842 assert!(s.has_context());
4843 assert_eq!(s.as_str(), "hello");
4844 }
4845
4846 #[test]
4847 fn nix_string_display_shows_chars_only() {
4848 let mut ctx = StringContext::new();
4849 ctx.add_plain("/nix/store/abc".to_string());
4850 let s = NixString::with_context("visible", ctx);
4851 assert_eq!(format!("{s}"), "visible");
4852 }
4853
4854 #[test]
4855 fn nix_string_struct_eq_includes_context() {
4856 let plain = NixString::plain("hello");
4857 let mut ctx = StringContext::new();
4858 ctx.add_plain("/nix/store/xxx".to_string());
4859 let with_ctx = NixString::with_context("hello", ctx);
4860 assert_ne!(plain, with_ctx);
4862 }
4863
4864 #[test]
4865 fn value_string_eq_ignores_context() {
4866 let plain = Value::String(Rc::new(NixString::plain("hello")));
4867 let mut ctx = StringContext::new();
4868 ctx.add_plain("/nix/store/xxx".to_string());
4869 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4870 assert_eq!(plain, with_ctx);
4872 }
4873
4874 #[test]
4877 fn env_nested_with_inner_wins() {
4878 let mut outer_attrs = NixAttrs::new();
4879 outer_attrs.insert("x".to_string(), Value::Int(1));
4880 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4881 let mut inner_attrs = NixAttrs::new();
4882 inner_attrs.insert("x".to_string(), Value::Int(2));
4883 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4884 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4885 }
4886
4887 #[test]
4888 fn env_nested_with_fallback_to_outer() {
4889 let mut outer_attrs = NixAttrs::new();
4890 outer_attrs.insert("x".to_string(), Value::Int(1));
4891 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4892 let mut inner_attrs = NixAttrs::new();
4893 inner_attrs.insert("y".to_string(), Value::Int(2));
4894 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4895 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4896 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4897 }
4898
4899 #[test]
4900 fn env_lexical_binding_wins_over_all_with_scopes() {
4901 let mut outer_attrs = NixAttrs::new();
4902 outer_attrs.insert("x".to_string(), Value::Int(1));
4903 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4904 let mut inner_attrs = NixAttrs::new();
4905 inner_attrs.insert("x".to_string(), Value::Int(2));
4906 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4907 inner.bind("x".to_string(), Value::Int(99));
4908 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4909 }
4910
4911 #[test]
4912 fn env_parent_lexical_wins_over_child_with_scope() {
4913 let mut root = Env::new();
4914 root.bind("x".to_string(), Value::Int(10));
4915 let mut child_attrs = NixAttrs::new();
4916 child_attrs.insert("x".to_string(), Value::Int(20));
4917 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4918 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4919 }
4920
4921 #[test]
4922 fn env_deeply_nested_with_scopes_three_levels() {
4923 let mut a = NixAttrs::new();
4924 a.insert("x".to_string(), Value::Int(1));
4925 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4926
4927 let mut b = NixAttrs::new();
4928 b.insert("y".to_string(), Value::Int(2));
4929 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4930
4931 let mut c = NixAttrs::new();
4932 c.insert("z".to_string(), Value::Int(3));
4933 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
4934
4935 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
4936 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
4937 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
4938 assert_eq!(env3.lookup("w"), None);
4939 }
4940
4941 #[test]
4942 fn env_with_scope_does_not_pollute_bindings() {
4943 let mut attrs = NixAttrs::new();
4946 attrs.insert("x".to_string(), Value::Int(42));
4947 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4948 assert!(env.0.bindings.get(&intern("x")).is_none());
4950 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4952 }
4953
4954 #[test]
4955 fn env_lexical_binding_not_in_with_scopes() {
4956 let mut env = Env::new();
4958 env.bind("x".to_string(), Value::Int(42));
4959 assert!(env.0.with_scopes.is_empty());
4961 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4963 }
4964
4965 #[test]
4966 fn env_child_inherits_eval_file() {
4967 let mut env = Env::new();
4968 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
4969 let child = env.child();
4970 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
4971 }
4972
4973 #[test]
4974 fn env_new_has_no_parent_no_with() {
4975 let env = Env::new();
4976 assert_eq!(env.lookup("anything"), None);
4977 assert!(env.eval_file().is_none());
4978 }
4979
4980 #[test]
4983 fn thunk_new_suspended_is_not_evaluated() {
4984 let root = rnix::Root::parse("42");
4985 let expr = root.tree().expr().unwrap();
4986 let thunk = Thunk::new_suspended(expr, Env::new());
4987 assert!(!thunk.is_evaluated());
4988 }
4989
4990 #[test]
4991 fn thunk_new_evaluated_is_evaluated() {
4992 let thunk = Thunk::new_evaluated(Value::Int(42));
4993 assert!(thunk.is_evaluated());
4994 }
4995
4996 #[test]
4997 fn thunk_force_evaluates_suspended() {
4998 let root = rnix::Root::parse("42");
4999 let expr = root.tree().expr().unwrap();
5000 let thunk = Thunk::new_suspended(expr, Env::new());
5001 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5002 assert!(result.is_ok());
5003 assert_eq!(result.unwrap(), Value::Int(42));
5004 assert!(thunk.is_evaluated());
5005 }
5006
5007 #[test]
5008 fn thunk_force_memoizes_result() {
5009 let root = rnix::Root::parse("1 + 2");
5010 let expr = root.tree().expr().unwrap();
5011 let thunk = Thunk::new_suspended(expr, Env::new());
5012 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5013 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5014 assert_eq!(r1, Value::Int(3));
5015 assert_eq!(r2, Value::Int(3));
5016 }
5017
5018 #[test]
5019 fn thunk_force_already_evaluated_returns_value() {
5020 let thunk = Thunk::new_evaluated(Value::Bool(true));
5021 let result = thunk.force(&|_, _| panic!("should not be called"));
5022 assert_eq!(result.unwrap(), Value::Bool(true));
5023 }
5024
5025 #[test]
5034 fn thunk_force_concrete_skips_redundant_store_but_caches() {
5035 let root = rnix::Root::parse("1 + 2");
5038 let expr = root.tree().expr().unwrap();
5039 let thunk = Thunk::new_suspended(expr, Env::new());
5040
5041 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5042 assert_eq!(r1, Value::Int(3));
5043 assert!(thunk.is_evaluated());
5044
5045 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
5048
5049 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
5051 assert_eq!(r2, Value::Int(3));
5052 }
5053
5054 #[test]
5055 fn thunk_blackhole_detects_infinite_recursion() {
5056 let root = rnix::Root::parse("42");
5057 let expr = root.tree().expr().unwrap();
5058 let thunk = Thunk::new_suspended(expr, Env::new());
5059
5060 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5063
5064 let result = thunk.force(&|_, _| Ok(Value::Null));
5065 assert!(result.is_err());
5066 let err_msg = format!("{}", result.unwrap_err());
5067 assert!(err_msg.contains("infinite recursion"));
5068 }
5069
5070 #[test]
5071 fn thunk_update_env_replaces_suspended_env() {
5072 let root = rnix::Root::parse("x");
5073 let expr = root.tree().expr().unwrap();
5074 let thunk = Thunk::new_suspended(expr, Env::new());
5075
5076 let mut new_env = Env::new();
5077 new_env.bind("x".to_string(), Value::Int(99));
5078 thunk.update_env(&new_env);
5079
5080 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5081 assert_eq!(result.unwrap(), Value::Int(99));
5082 }
5083
5084 #[test]
5085 fn thunk_update_env_noop_when_evaluated() {
5086 let thunk = Thunk::new_evaluated(Value::Int(1));
5087 let mut new_env = Env::new();
5088 new_env.bind("x".to_string(), Value::Int(99));
5089 thunk.update_env(&new_env);
5090 assert_eq!(
5091 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
5092 Value::Int(1),
5093 );
5094 }
5095
5096 #[test]
5097 fn thunk_debug_suspended() {
5098 let root = rnix::Root::parse("42");
5099 let expr = root.tree().expr().unwrap();
5100 let thunk = Thunk::new_suspended(expr, Env::new());
5101 assert_eq!(format!("{thunk:?}"), "<thunk>");
5102 }
5103
5104 #[test]
5105 fn thunk_debug_evaluated() {
5106 let thunk = Thunk::new_evaluated(Value::Int(42));
5107 let dbg = format!("{thunk:?}");
5108 assert!(dbg.contains("42"));
5109 }
5110
5111 #[test]
5112 fn thunk_error_restores_suspended_state() {
5113 let root = rnix::Root::parse("nonexistent_var");
5114 let expr = root.tree().expr().unwrap();
5115 let thunk = Thunk::new_suspended(expr, Env::new());
5116
5117 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5118 assert!(result.is_err());
5119 assert!(!thunk.is_evaluated());
5121 let dbg = format!("{thunk:?}");
5122 assert_eq!(dbg, "<thunk>");
5123 }
5124
5125 #[test]
5126 fn thunk_inherit_select_forces_and_selects() {
5127 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5128 let expr = root.tree().expr().unwrap();
5129 let source = Thunk::new_suspended(expr, Env::new());
5130 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5131 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5132 assert_eq!(result.unwrap(), Value::Int(42));
5133 assert!(thunk.is_evaluated());
5134 }
5135
5136 #[test]
5137 fn thunk_inherit_select_missing_attr_errors() {
5138 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5139 let expr = root.tree().expr().unwrap();
5140 let source = Thunk::new_suspended(expr, Env::new());
5141 let thunk = Thunk::new_inherit_select(source, "y".to_string());
5142 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5143 assert!(result.is_err());
5144 assert!(!thunk.is_evaluated());
5146 }
5147
5148 #[test]
5149 fn thunk_inherit_select_non_attrs_source_errors() {
5150 let root = rnix::Root::parse("42");
5151 let expr = root.tree().expr().unwrap();
5152 let source = Thunk::new_suspended(expr, Env::new());
5153 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5154 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5155 assert!(result.is_err());
5156 let msg = format!("{}", result.unwrap_err());
5157 assert!(msg.contains("not a set"));
5158 }
5159
5160 #[test]
5161 fn thunk_inherit_select_shares_source_thunk() {
5162 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
5166 let expr = root.tree().expr().unwrap();
5167 let source = Thunk::new_suspended(expr, Env::new());
5168 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
5169 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
5170 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
5171 assert_eq!(result_a.unwrap(), Value::Int(1));
5172 assert!(source.is_evaluated());
5174 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
5176 assert_eq!(result_b.unwrap(), Value::Int(2));
5177 }
5178
5179 #[test]
5182 fn nixattrs_empty_operations() {
5183 let a = NixAttrs::new();
5184 assert!(a.is_empty());
5185 assert_eq!(a.len(), 0);
5186 assert_eq!(a.get("x"), None);
5187 assert!(!a.contains_key("x"));
5188 assert_eq!(a.keys().count(), 0);
5189 assert_eq!(a.iter().count(), 0);
5190 }
5191
5192 #[test]
5193 fn nixattrs_update_with_empty() {
5194 let mut a = NixAttrs::new();
5195 a.insert("x".to_string(), Value::Int(1));
5196 let b = NixAttrs::new();
5197 let merged = a.update(&b);
5198 assert_eq!(merged.len(), 1);
5199 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5200 }
5201
5202 #[test]
5203 fn nixattrs_update_empty_with_nonempty() {
5204 let a = NixAttrs::new();
5205 let mut b = NixAttrs::new();
5206 b.insert("x".to_string(), Value::Int(1));
5207 let merged = a.update(&b);
5208 assert_eq!(merged.len(), 1);
5209 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5210 }
5211
5212 #[test]
5213 fn nixattrs_keys_sorted_order() {
5214 let mut a = NixAttrs::new();
5215 a.insert("c".to_string(), Value::Int(3));
5216 a.insert("a".to_string(), Value::Int(1));
5217 a.insert("b".to_string(), Value::Int(2));
5218 let keys: Vec<String> = a.keys().collect();
5219 assert_eq!(keys, vec!["a", "b", "c"]);
5220 }
5221
5222 #[test]
5225 fn value_to_str_forces_thunks() {
5226 let root = rnix::Root::parse(r#""hello""#);
5227 let expr = root.tree().expr().unwrap();
5228 let thunk = Thunk::new_suspended(expr, Env::new());
5229 let val = Value::Thunk(thunk);
5230 assert_eq!(val.to_str().unwrap(), "hello");
5231 }
5232
5233 #[test]
5234 fn value_to_nix_string_forces_thunks() {
5235 let root = rnix::Root::parse(r#""world""#);
5236 let expr = root.tree().expr().unwrap();
5237 let thunk = Thunk::new_suspended(expr, Env::new());
5238 let val = Value::Thunk(thunk);
5239 let ns = val.to_nix_string().unwrap();
5240 assert_eq!(ns.as_str(), "world");
5241 assert!(!ns.has_context());
5242 }
5243
5244 #[test]
5245 fn value_to_attrs_forces_thunks() {
5246 let root = rnix::Root::parse("{ x = 1; }");
5247 let expr = root.tree().expr().unwrap();
5248 let thunk = Thunk::new_suspended(expr, Env::new());
5249 let val = Value::Thunk(thunk);
5250 let attrs = val.to_attrs().unwrap();
5251 assert_eq!(attrs.len(), 1);
5252 }
5253
5254 #[test]
5255 fn value_to_list_forces_thunks() {
5256 let root = rnix::Root::parse("[1 2 3]");
5257 let expr = root.tree().expr().unwrap();
5258 let thunk = Thunk::new_suspended(expr, Env::new());
5259 let val = Value::Thunk(thunk);
5260 let list = val.to_list().unwrap();
5261 assert_eq!(list.len(), 3);
5262 }
5263
5264 #[test]
5265 fn value_to_float_on_thunk() {
5266 let root = rnix::Root::parse("3.14");
5267 let expr = root.tree().expr().unwrap();
5268 let thunk = Thunk::new_suspended(expr, Env::new());
5269 let val = Value::Thunk(thunk);
5270 let f = val.to_float().unwrap();
5271 assert!((f - 3.14).abs() < f64::EPSILON);
5272 }
5273
5274 #[test]
5275 fn value_as_bool_on_thunk() {
5276 let root = rnix::Root::parse("true");
5277 let expr = root.tree().expr().unwrap();
5278 let thunk = Thunk::new_suspended(expr, Env::new());
5279 let val = Value::Thunk(thunk);
5280 assert!(val.as_bool().unwrap());
5281 }
5282
5283 #[test]
5284 fn value_as_int_on_thunk() {
5285 let root = rnix::Root::parse("42");
5286 let expr = root.tree().expr().unwrap();
5287 let thunk = Thunk::new_suspended(expr, Env::new());
5288 let val = Value::Thunk(thunk);
5289 assert_eq!(val.as_int().unwrap(), 42);
5290 }
5291
5292 #[test]
5293 fn value_string_constructor() {
5294 let v = Value::string("test");
5295 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
5296 }
5297
5298 #[test]
5299 fn value_partial_eq_null_null() {
5300 assert_eq!(Value::Null, Value::Null);
5301 }
5302
5303 #[test]
5304 fn value_partial_eq_lists_deep() {
5305 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5306 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5307 assert_eq!(a, b);
5308 }
5309
5310 #[test]
5311 fn value_partial_eq_attrs_deep() {
5312 let mut a = NixAttrs::new();
5313 a.insert("x".to_string(), Value::Int(1));
5314 let mut b = NixAttrs::new();
5315 b.insert("x".to_string(), Value::Int(1));
5316 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
5317 }
5318
5319 #[test]
5322 fn eval_error_type_error_constructor() {
5323 let e = EvalError::type_error("oops");
5324 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
5325 }
5326
5327 #[test]
5328 fn eval_error_type_mismatch_constructor() {
5329 let e = EvalError::type_mismatch("int", "string");
5330 match e {
5331 EvalError::TypeMismatch { expected, got } => {
5332 assert_eq!(expected, "int");
5333 assert_eq!(got, "string");
5334 }
5335 _ => panic!("expected TypeMismatch"),
5336 }
5337 }
5338
5339 #[test]
5340 fn eval_error_is_throw_yes_no() {
5341 assert!(EvalError::Throw("oops".into()).is_throw());
5342 assert!(!EvalError::TypeError("oops".into()).is_throw());
5343 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
5344 }
5345
5346 #[test]
5347 fn eval_error_is_infinite_recursion_yes_no() {
5348 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
5349 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
5350 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
5351 }
5352
5353 #[test]
5354 fn eval_error_display_undefined_var() {
5355 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
5356 assert!(s.contains("undefined variable"));
5357 assert!(s.contains("foo"));
5358 }
5359
5360 #[test]
5361 fn eval_error_display_type_error() {
5362 let s = format!("{}", EvalError::TypeError("bad".into()));
5363 assert!(s.contains("type error"));
5364 assert!(s.contains("bad"));
5365 }
5366
5367 #[test]
5368 fn eval_error_display_attr_not_found() {
5369 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5370 assert!(s.contains("attribute not found"));
5371 assert!(s.contains("x"));
5372 }
5373
5374 #[test]
5375 fn eval_error_display_type_mismatch() {
5376 let s = format!(
5377 "{}",
5378 EvalError::TypeMismatch { expected: "int", got: "string" }
5379 );
5380 assert!(s.contains("expected int"));
5381 assert!(s.contains("got string"));
5382 }
5383
5384 #[test]
5385 fn eval_error_display_assertion_failed() {
5386 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5387 assert!(s.contains("assertion"));
5388 }
5389
5390 #[test]
5391 fn eval_error_display_division_by_zero() {
5392 let s = format!("{}", EvalError::DivisionByZero);
5393 assert!(s.contains("division by zero"));
5394 }
5395
5396 #[test]
5397 fn eval_error_display_infinite_recursion() {
5398 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5399 assert!(s.contains("infinite recursion"));
5400 assert!(s.contains("loop"));
5401 }
5402
5403 #[test]
5404 fn eval_error_display_io_error() {
5405 let s = format!(
5406 "{}",
5407 EvalError::IoError {
5408 context: "ctx".into(),
5409 message: "no such file".into(),
5410 }
5411 );
5412 assert!(s.contains("I/O"));
5413 assert!(s.contains("ctx"));
5414 assert!(s.contains("no such file"));
5415 }
5416
5417 #[test]
5418 fn eval_error_display_throw() {
5419 let s = format!("{}", EvalError::Throw("boom".into()));
5420 assert_eq!(s, "boom");
5421 }
5422
5423 #[test]
5424 fn eval_error_display_not_implemented() {
5425 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5426 assert!(s.contains("not yet implemented"));
5427 assert!(s.contains("frob"));
5428 }
5429
5430 #[test]
5431 fn eval_error_display_parse_error() {
5432 let s = format!("{}", EvalError::ParseError("syntax".into()));
5433 assert!(s.contains("parse error"));
5434 assert!(s.contains("syntax"));
5435 }
5436
5437 #[test]
5438 fn eval_error_display_recursion_limit() {
5439 let s = format!(
5440 "{}",
5441 EvalError::RecursionLimit("max depth exceeded".into())
5442 );
5443 assert!(s.contains("recursion limit"));
5444 assert!(s.contains("max depth exceeded"));
5445 }
5446
5447 #[test]
5448 fn eval_error_partial_eq_same_variant() {
5449 assert_eq!(
5450 EvalError::UndefinedVar("x".into()),
5451 EvalError::UndefinedVar("x".into()),
5452 );
5453 assert_ne!(
5454 EvalError::UndefinedVar("x".into()),
5455 EvalError::UndefinedVar("y".into()),
5456 );
5457 assert_ne!(
5458 EvalError::UndefinedVar("x".into()),
5459 EvalError::AttrNotFound("x".into()),
5460 );
5461 }
5462
5463 #[test]
5466 fn context_element_display_plain() {
5467 let e = ContextElement::Plain("/nix/store/xyz".into());
5468 assert_eq!(format!("{e}"), "/nix/store/xyz");
5469 }
5470
5471 #[test]
5472 fn context_element_display_output() {
5473 let e = ContextElement::Output {
5474 drv: "/nix/store/abc.drv".into(),
5475 output: "out".into(),
5476 };
5477 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5478 }
5479
5480 #[test]
5481 fn context_element_display_drv_deep() {
5482 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5483 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5484 }
5485
5486 #[test]
5489 fn string_context_iter_yields_all() {
5490 let mut ctx = StringContext::new();
5491 ctx.add_plain("/nix/store/aaa");
5492 ctx.add_plain("/nix/store/bbb");
5493 let count = ctx.iter().count();
5494 assert_eq!(count, 2);
5495 }
5496
5497 #[test]
5498 fn string_context_len_matches_set_size() {
5499 let mut ctx = StringContext::new();
5500 assert_eq!(ctx.len(), 0);
5501 ctx.add_plain("/nix/store/x");
5502 assert_eq!(ctx.len(), 1);
5503 ctx.add_output("/nix/store/y.drv", "out");
5504 assert_eq!(ctx.len(), 2);
5505 }
5506
5507 #[test]
5508 fn string_context_insert_raw_element() {
5509 let mut ctx = StringContext::new();
5510 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5511 assert_eq!(ctx.len(), 1);
5512 }
5513
5514 #[test]
5515 fn string_context_default_is_empty() {
5516 let ctx = StringContext::default();
5517 assert!(ctx.is_empty());
5518 }
5519
5520 #[test]
5523 fn nix_string_as_ref_str() {
5524 let s = NixString::plain("hello");
5525 let r: &str = s.as_ref();
5526 assert_eq!(r, "hello");
5527 }
5528
5529 #[test]
5530 fn nix_string_deref_to_str_methods() {
5531 let s = NixString::plain("Hello World");
5532 assert_eq!(s.len(), 11);
5533 assert!(s.starts_with("Hello"));
5534 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5536 }
5537
5538 #[test]
5541 fn nixattrs_remove_returns_value() {
5542 let mut a = NixAttrs::new();
5543 a.insert("x".into(), Value::Int(1));
5544 let removed = a.remove("x");
5545 assert_eq!(removed, Some(Value::Int(1)));
5546 assert!(!a.contains_key("x"));
5547 assert_eq!(a.remove("y"), None);
5548 }
5549
5550 #[test]
5551 fn nixattrs_values_iter() {
5552 let mut a = NixAttrs::new();
5553 a.insert("a".into(), Value::Int(1));
5554 a.insert("b".into(), Value::Int(2));
5555 let mut vs: Vec<&Value> = a.values().collect();
5556 vs.sort_by_key(|v| match v {
5557 Value::Int(n) => *n,
5558 _ => 0,
5559 });
5560 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5561 }
5562
5563 #[test]
5564 fn nixattrs_iter_returns_sorted_pairs() {
5565 let mut a = NixAttrs::new();
5566 a.insert("zeta".into(), Value::Int(3));
5567 a.insert("alpha".into(), Value::Int(1));
5568 a.insert("mu".into(), Value::Int(2));
5569 let pairs: Vec<(String, &Value)> = a.iter().collect();
5570 assert_eq!(pairs[0].0, "alpha");
5571 assert_eq!(pairs[1].0, "mu");
5572 assert_eq!(pairs[2].0, "zeta");
5573 }
5574
5575 #[test]
5576 fn nixattrs_from_iterator() {
5577 let pairs = vec![
5578 ("a".to_string(), Value::Int(1)),
5579 ("b".to_string(), Value::Int(2)),
5580 ];
5581 let attrs: NixAttrs = pairs.into_iter().collect();
5582 assert_eq!(attrs.len(), 2);
5583 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5584 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5585 }
5586
5587 #[test]
5588 fn nixattrs_into_iterator_yields_owned() {
5589 let mut a = NixAttrs::new();
5590 a.insert("x".into(), Value::Int(42));
5591 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5592 assert_eq!(pairs.len(), 1);
5593 assert_eq!(pairs[0].0, "x");
5594 assert_eq!(pairs[0].1, Value::Int(42));
5595 }
5596
5597 #[test]
5598 fn nixattrs_default_is_empty() {
5599 let a = NixAttrs::default();
5600 assert!(a.is_empty());
5601 }
5602
5603 #[test]
5606 fn value_from_bool() {
5607 assert_eq!(Value::from(true), Value::Bool(true));
5608 assert_eq!(Value::from(false), Value::Bool(false));
5609 }
5610
5611 #[test]
5612 fn value_from_i64() {
5613 assert_eq!(Value::from(42_i64), Value::Int(42));
5614 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5615 }
5616
5617 #[test]
5618 fn value_from_f64() {
5619 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5620 }
5621
5622 #[test]
5623 fn value_from_nix_string() {
5624 let v: Value = NixString::plain("hi").into();
5625 assert_eq!(v, Value::string("hi"));
5626 }
5627
5628 #[test]
5629 fn value_from_nix_attrs() {
5630 let mut a = NixAttrs::new();
5631 a.insert("x".into(), Value::Int(1));
5632 let v: Value = a.into();
5633 match v {
5634 Value::Attrs(_) => {}
5635 _ => panic!("expected Attrs"),
5636 }
5637 }
5638
5639 #[test]
5640 fn value_from_vec() {
5641 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5642 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5643 }
5644
5645 #[test]
5646 fn value_default_is_null() {
5647 let v: Value = Value::default();
5648 assert_eq!(v, Value::Null);
5649 }
5650
5651 #[test]
5654 fn value_from_json_null() {
5655 let v = Value::from(&serde_json::Value::Null);
5656 assert_eq!(v, Value::Null);
5657 }
5658
5659 #[test]
5660 fn value_from_json_bool() {
5661 let v = Value::from(&serde_json::Value::Bool(true));
5662 assert_eq!(v, Value::Bool(true));
5663 }
5664
5665 #[test]
5666 fn value_from_json_int() {
5667 let v = Value::from(&serde_json::json!(42));
5668 assert_eq!(v, Value::Int(42));
5669 }
5670
5671 #[test]
5672 fn value_from_json_float() {
5673 let v = Value::from(&serde_json::json!(3.14));
5674 match v {
5675 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5676 _ => panic!("expected Float"),
5677 }
5678 }
5679
5680 #[test]
5681 fn value_from_json_string() {
5682 let v = Value::from(&serde_json::Value::String("hi".into()));
5683 assert_eq!(v, Value::string("hi"));
5684 }
5685
5686 #[test]
5687 fn value_from_json_array() {
5688 let v = Value::from(&serde_json::json!([1, true, "x"]));
5689 match v {
5690 Value::List(items) => {
5691 assert_eq!(items.len(), 3);
5692 assert_eq!(items[0], Value::Int(1));
5693 assert_eq!(items[1], Value::Bool(true));
5694 assert_eq!(items[2], Value::string("x"));
5695 }
5696 _ => panic!("expected List"),
5697 }
5698 }
5699
5700 #[test]
5701 fn value_from_json_object() {
5702 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5703 match v {
5704 Value::Attrs(attrs) => {
5705 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5706 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5707 }
5708 _ => panic!("expected Attrs"),
5709 }
5710 }
5711
5712 #[test]
5713 fn value_from_json_nested() {
5714 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5715 let json_back = v.to_json();
5716 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5717 }
5718
5719 #[test]
5722 fn value_from_toml_string() {
5723 let t = toml::Value::String("hi".into());
5724 assert_eq!(Value::from(&t), Value::string("hi"));
5725 }
5726
5727 #[test]
5728 fn value_from_toml_int() {
5729 let t = toml::Value::Integer(42);
5730 assert_eq!(Value::from(&t), Value::Int(42));
5731 }
5732
5733 #[test]
5734 fn value_from_toml_float() {
5735 let t = toml::Value::Float(3.14);
5736 match Value::from(&t) {
5737 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5738 _ => panic!("expected Float"),
5739 }
5740 }
5741
5742 #[test]
5743 fn value_from_toml_bool() {
5744 let t = toml::Value::Boolean(true);
5745 assert_eq!(Value::from(&t), Value::Bool(true));
5746 }
5747
5748 #[test]
5749 fn value_from_toml_array() {
5750 let t = toml::Value::Array(vec![
5751 toml::Value::Integer(1),
5752 toml::Value::Integer(2),
5753 ]);
5754 assert_eq!(
5755 Value::from(&t),
5756 Value::list(vec![Value::Int(1), Value::Int(2)]),
5757 );
5758 }
5759
5760 #[test]
5761 fn value_from_toml_table() {
5762 let mut tbl = toml::map::Map::new();
5763 tbl.insert("k".into(), toml::Value::Integer(7));
5764 let t = toml::Value::Table(tbl);
5765 match Value::from(&t) {
5766 Value::Attrs(attrs) => {
5767 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5768 }
5769 _ => panic!("expected Attrs"),
5770 }
5771 }
5772
5773 #[test]
5774 fn value_from_toml_datetime_becomes_string() {
5775 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5777 let t = toml::Value::Datetime(dt);
5778 match Value::from(&t) {
5779 Value::String(_) => {}
5780 other => panic!("expected String, got {other:?}"),
5781 }
5782 }
5783
5784 #[test]
5787 fn coerce_to_path_from_path() {
5788 let v = Value::Path(Box::new("/foo".into()));
5789 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5790 }
5791
5792 #[test]
5793 fn coerce_to_path_from_string() {
5794 let v = Value::string("/bar");
5795 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5796 }
5797
5798 #[test]
5806 fn out_path_needs_realize_matches_output_context() {
5807 let mut ctx = StringContext::new();
5810 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5811 assert_eq!(
5812 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5813 Some("/nix/store/aaa-thing.drv".to_string()),
5814 );
5815 }
5816
5817 #[test]
5818 fn out_path_needs_realize_ignores_plain_context() {
5819 let mut ctx = StringContext::new();
5822 ctx.add_plain("/nix/store/ccc-plain");
5823 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5824 }
5825
5826 #[test]
5827 fn out_path_needs_realize_ignores_non_store_path() {
5828 let mut ctx = StringContext::new();
5831 ctx.add_output("/nix/store/ddd.drv", "out");
5832 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5833 }
5834
5835 #[test]
5836 fn out_path_needs_realize_empty_context_is_none() {
5837 let ctx = StringContext::new();
5839 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5840 }
5841
5842 #[test]
5843 fn coerce_to_realized_path_present_output_is_passthrough() {
5844 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5848 std::fs::create_dir_all(&dir).unwrap();
5849 let file = dir.join("out");
5850 std::fs::write(&file, b"present").unwrap();
5851 let present = file.to_string_lossy().to_string();
5852
5853 let mut ctx = StringContext::new();
5854 ctx.add_plain(&present);
5859 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5860 present.as_str(),
5861 ctx,
5862 )));
5863 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5864 }
5865
5866 #[test]
5867 fn coerce_to_realized_path_absent_output_invokes_hook() {
5868 use std::sync::{Arc, Mutex};
5873 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5874 let seen2 = seen.clone();
5875 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5876 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5877 Ok(())
5878 }));
5879
5880 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5883 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5884 let mut ctx = StringContext::new();
5885 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5886 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5887
5888 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5890 let s = seen.lock().unwrap();
5891 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5892 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5893 assert_eq!(s[0].1, out);
5894 }
5895
5896 #[test]
5897 fn coerce_to_path_errors_on_int() {
5898 let v = Value::Int(1);
5899 let e = v.coerce_to_path("readFile").unwrap_err();
5900 match e {
5901 EvalError::TypeError(ref msg) => {
5902 assert!(msg.contains("readFile"));
5903 assert!(msg.contains("path or string"));
5904 assert!(msg.contains("int"));
5905 }
5906 _ => panic!("expected TypeError"),
5907 }
5908 }
5909
5910 #[test]
5911 fn coerce_to_path_errors_on_null() {
5912 let v = Value::Null;
5913 assert!(v.coerce_to_path("ctx").is_err());
5914 }
5915
5916 #[test]
5917 fn coerce_to_path_attrs_with_outpath() {
5918 let mut attrs = NixAttrs::new();
5919 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5920 let val = Value::Attrs(Rc::new(attrs));
5921 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5922 }
5923
5924 #[test]
5925 fn coerce_to_path_attrs_without_outpath_fails() {
5926 let attrs = NixAttrs::new();
5927 let val = Value::Attrs(Rc::new(attrs));
5928 assert!(val.coerce_to_path("test").is_err());
5929 }
5930
5931 #[test]
5934 fn coerce_to_string_string() {
5935 let v = Value::string("hello");
5936 let (s, _ctx) = v.coerce_to_string().unwrap();
5937 assert_eq!(s, "hello");
5938 }
5939
5940 #[test]
5941 fn coerce_to_string_path() {
5942 let v = Value::Path(Box::new("/foo".into()));
5943 let (s, ctx) = v.coerce_to_string().unwrap();
5944 assert_eq!(s, "/foo");
5945 assert!(!ctx.is_empty()); }
5947
5948 #[test]
5949 fn coerce_to_string_int() {
5950 let v = Value::Int(42);
5951 let (s, _ctx) = v.coerce_to_string().unwrap();
5952 assert_eq!(s, "42");
5953 }
5954
5955 #[test]
5956 fn coerce_to_string_float() {
5957 let v = Value::Float(3.14);
5959 let (s, _ctx) = v.coerce_to_string().unwrap();
5960 assert_eq!(s, "3.140000");
5961 }
5962
5963 #[test]
5964 fn coerce_to_string_bool_true() {
5965 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
5966 assert_eq!(s, "1");
5967 }
5968
5969 #[test]
5970 fn coerce_to_string_bool_false() {
5971 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
5972 assert_eq!(s, "");
5973 }
5974
5975 #[test]
5976 fn coerce_to_string_null() {
5977 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
5978 assert_eq!(s, "");
5979 }
5980
5981 #[test]
5982 fn coerce_to_string_attrs_with_outpath() {
5983 let mut attrs = NixAttrs::new();
5984 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
5985 let val = Value::Attrs(Rc::new(attrs));
5986 let (s, _ctx) = val.coerce_to_string().unwrap();
5987 assert_eq!(s, "/nix/store/abc");
5988 }
5989
5990 #[test]
5991 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
5992 let attrs = NixAttrs::new();
5993 let val = Value::Attrs(Rc::new(attrs));
5994 assert!(val.coerce_to_string().is_err());
5995 }
5996
5997 #[test]
5998 fn coerce_to_string_lambda_fails() {
5999 let root = rnix::Root::parse("x: x");
6000 let expr = root.tree().expr().unwrap();
6001 let closure = Closure {
6002 param: match expr {
6003 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
6004 _ => panic!("expected lambda"),
6005 },
6006 body: match expr {
6007 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
6008 _ => panic!("expected lambda"),
6009 },
6010 env: Env::new(),
6011 };
6012 let val = Value::Lambda(Rc::new(closure));
6013 assert!(val.coerce_to_string().is_err());
6014 }
6015
6016 #[test]
6019 fn builtin_fn_debug_includes_name() {
6020 let b = BuiltinFn {
6021 name: "myFunc",
6022 func: Rc::new(|_| Ok(Value::Null)),
6023 };
6024 let s = format!("{b:?}");
6025 assert!(s.contains("myFunc"));
6026 assert!(s.contains("builtin"));
6027 }
6028
6029 #[test]
6032 fn thunk_force_chains_through_inner_thunks() {
6033 let inner_root = rnix::Root::parse("99");
6035 let inner_expr = inner_root.tree().expr().unwrap();
6036 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
6037 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
6038 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
6039 match result.unwrap() {
6044 Value::Thunk(_) | Value::Int(99) => {}
6045 other => panic!("unexpected: {other:?}"),
6046 }
6047 }
6048
6049 #[test]
6050 fn thunk_inherit_select_debug_format() {
6051 let root = rnix::Root::parse("{ x = 1; }");
6052 let expr = root.tree().expr().unwrap();
6053 let source = Thunk::new_suspended(expr, Env::new());
6054 let thunk = Thunk::new_inherit_select(source, "x");
6055 let s = format!("{thunk:?}");
6056 assert!(s.contains("inherit-select"));
6057 assert!(s.contains("x"));
6058 }
6059
6060 #[test]
6061 fn thunk_blackhole_debug_format() {
6062 let root = rnix::Root::parse("1");
6063 let expr = root.tree().expr().unwrap();
6064 let thunk = Thunk::new_suspended(expr, Env::new());
6065 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
6067 assert_eq!(format!("{thunk:?}"), "<blackhole>");
6068 }
6069
6070 #[test]
6073 fn value_display_thunk_evaluates() {
6074 let root = rnix::Root::parse("42");
6075 let expr = root.tree().expr().unwrap();
6076 let thunk = Thunk::new_suspended(expr, Env::new());
6077 let val = Value::Thunk(thunk);
6078 assert_eq!(format!("{val}"), "42");
6079 }
6080
6081 #[test]
6082 fn value_to_json_thunk_forces() {
6083 let root = rnix::Root::parse(r#""world""#);
6084 let expr = root.tree().expr().unwrap();
6085 let thunk = Thunk::new_suspended(expr, Env::new());
6086 let val = Value::Thunk(thunk);
6087 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
6088 }
6089
6090 #[test]
6091 fn value_type_name_thunk_forces() {
6092 let root = rnix::Root::parse("42");
6093 let expr = root.tree().expr().unwrap();
6094 let thunk = Thunk::new_suspended(expr, Env::new());
6095 let val = Value::Thunk(thunk);
6096 assert_eq!(val.type_name(), "int");
6097 }
6098
6099 #[test]
6102 fn as_string_errors_on_thunk() {
6103 let root = rnix::Root::parse(r#""x""#);
6104 let expr = root.tree().expr().unwrap();
6105 let thunk = Thunk::new_suspended(expr, Env::new());
6106 let val = Value::Thunk(thunk);
6107 let err = val.as_string().unwrap_err();
6108 match err {
6109 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
6110 _ => panic!("expected TypeError"),
6111 }
6112 }
6113
6114 #[test]
6115 fn as_nix_string_errors_on_thunk() {
6116 let root = rnix::Root::parse(r#""x""#);
6117 let expr = root.tree().expr().unwrap();
6118 let thunk = Thunk::new_suspended(expr, Env::new());
6119 let val = Value::Thunk(thunk);
6120 assert!(val.as_nix_string().is_err());
6121 }
6122
6123 #[test]
6124 fn as_attrs_errors_on_thunk() {
6125 let root = rnix::Root::parse("{}");
6126 let expr = root.tree().expr().unwrap();
6127 let thunk = Thunk::new_suspended(expr, Env::new());
6128 let val = Value::Thunk(thunk);
6129 assert!(val.as_attrs().is_err());
6130 }
6131
6132 #[test]
6133 fn as_list_errors_on_thunk() {
6134 let root = rnix::Root::parse("[]");
6135 let expr = root.tree().expr().unwrap();
6136 let thunk = Thunk::new_suspended(expr, Env::new());
6137 let val = Value::Thunk(thunk);
6138 assert!(val.as_list().is_err());
6139 }
6140
6141 #[test]
6144 fn as_nix_string_ok_on_string() {
6145 let v = Value::string("hi");
6146 let ns = v.as_nix_string().unwrap();
6147 assert_eq!(ns.as_str(), "hi");
6148 }
6149
6150 #[test]
6151 fn as_nix_string_errors_on_int() {
6152 let v = Value::Int(1);
6153 match v.as_nix_string() {
6154 Err(EvalError::TypeMismatch { expected, got }) => {
6155 assert_eq!(expected, "string");
6156 assert_eq!(got, "int");
6157 }
6158 _ => panic!("expected TypeMismatch"),
6159 }
6160 }
6161
6162 #[test]
6167 fn oncecell_cache_populated_after_force() {
6168 let root = rnix::Root::parse("42");
6169 let expr = root.tree().expr().unwrap();
6170 let thunk = Thunk::new_suspended(expr, Env::new());
6171 assert!(thunk.0.cache.get().is_none());
6173 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6174 assert!(thunk.0.cache.get().is_some());
6176 }
6177
6178 #[test]
6179 fn oncecell_cache_matches_force_result() {
6180 let root = rnix::Root::parse("1 + 2");
6181 let expr = root.tree().expr().unwrap();
6182 let thunk = Thunk::new_suspended(expr, Env::new());
6183 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6184 let cached = thunk.0.cache.get().unwrap();
6185 assert_eq!((**cached).clone().into_value(), forced);
6188 }
6189
6190 #[test]
6191 fn oncecell_new_evaluated_prepopulates_cache() {
6192 let thunk = Thunk::new_evaluated(Value::Int(77));
6193 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
6195 assert_eq!(**cached, Concrete::Int(77));
6196 }
6197
6198 #[test]
6199 fn oncecell_is_evaluated_uses_cache() {
6200 let thunk = Thunk::new_evaluated(Value::Bool(false));
6201 assert!(thunk.is_evaluated());
6203 assert!(thunk.0.cache.get().is_some());
6204 }
6205
6206 #[test]
6207 fn oncecell_already_evaluated_returns_cached_without_repr() {
6208 let thunk = Thunk::new_evaluated(Value::Int(55));
6212 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
6213 assert_eq!(result.unwrap(), Value::Int(55));
6214 }
6215
6216 #[test]
6221 fn with_scope_created_with_empty_cache() {
6222 let thunk = Thunk::new_suspended(
6224 rnix::Root::parse("{}").tree().expr().unwrap(),
6225 Env::new(),
6226 );
6227 let env = Env::new().with_scope(Value::Thunk(thunk));
6228 let scope = &env.0.with_scopes[0];
6229 assert!(scope.cached.borrow().is_none());
6230 }
6231
6232 #[test]
6233 fn with_scope_concrete_pre_populates_cache() {
6234 let mut attrs = NixAttrs::new();
6236 attrs.insert("x".to_string(), Value::Int(1));
6237 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6238 let scope = &env.0.with_scopes[0];
6239 assert!(scope.cached.borrow().is_some());
6240 }
6241
6242 #[test]
6243 fn with_scope_first_lookup_populates_cache() {
6244 let mut attrs = NixAttrs::new();
6245 attrs.insert("x".to_string(), Value::Int(42));
6246 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6247 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6249 let _ = env.lookup("x");
6251 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6252 }
6253
6254 #[test]
6255 fn with_scope_second_lookup_uses_cache() {
6256 let mut attrs = NixAttrs::new();
6257 attrs.insert("x".to_string(), Value::Int(10));
6258 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6259 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6261 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6262 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6264 }
6265
6266 #[test]
6267 fn with_scope_child_shares_cache_via_rc() {
6268 let mut attrs = NixAttrs::new();
6269 attrs.insert("shared".to_string(), Value::Int(7));
6270 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6271 let child = parent.child();
6272 let _ = parent.lookup("shared");
6274 assert!(child.0.with_scopes[0].cached.borrow().is_some());
6277 }
6278
6279 #[test]
6280 fn with_scope_innermost_checked_first() {
6281 let mut outer = NixAttrs::new();
6282 outer.insert("x".to_string(), Value::Int(1));
6283 outer.insert("y".to_string(), Value::Int(100));
6284 let mut inner = NixAttrs::new();
6285 inner.insert("x".to_string(), Value::Int(2));
6286 let env = Env::new()
6287 .with_scope(Value::Attrs(Rc::new(outer)))
6288 .with_scope(Value::Attrs(Rc::new(inner)));
6289 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
6291 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
6293 }
6294
6295 #[test]
6300 fn fxhashmap_nixattrs_new_creates_empty() {
6301 let a = NixAttrs::new();
6302 assert!(a.is_empty());
6303 assert_eq!(a.len(), 0);
6304 assert!(a.inner().is_empty());
6306 }
6307
6308 #[test]
6309 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
6310 let mut a = NixAttrs::new();
6311 a.insert("mykey".to_string(), Value::Int(42));
6312 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
6313 }
6314
6315 #[test]
6316 fn fxhashmap_contains_key_with_interned_keys() {
6317 let mut a = NixAttrs::new();
6318 a.insert("alpha".to_string(), Value::Int(1));
6319 let sym = intern("alpha");
6320 assert!(a.inner().contains_key(&sym));
6321 let missing_sym = intern("beta");
6322 assert!(!a.inner().contains_key(&missing_sym));
6323 }
6324
6325 #[test]
6326 fn fxhashmap_remove_returns_value() {
6327 let mut a = NixAttrs::new();
6328 a.insert("key".to_string(), Value::Int(99));
6329 let removed = a.remove("key");
6330 assert_eq!(removed, Some(Value::Int(99)));
6331 assert!(a.is_empty());
6332 }
6333
6334 #[test]
6335 fn fxhashmap_keys_returns_sorted_strings() {
6336 let mut a = NixAttrs::new();
6337 a.insert("zulu".to_string(), Value::Int(1));
6338 a.insert("alpha".to_string(), Value::Int(2));
6339 a.insert("mike".to_string(), Value::Int(3));
6340 let keys: Vec<String> = a.keys().collect();
6341 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
6342 }
6343
6344 #[test]
6345 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
6346 let mut a = NixAttrs::new();
6347 a.insert("b".to_string(), Value::Int(2));
6348 a.insert("a".to_string(), Value::Int(1));
6349 let pairs: Vec<(String, &Value)> = a.iter().collect();
6350 assert_eq!(pairs.len(), 2);
6351 assert_eq!(pairs[0].0, "a");
6352 assert_eq!(*pairs[0].1, Value::Int(1));
6353 assert_eq!(pairs[1].0, "b");
6354 assert_eq!(*pairs[1].1, Value::Int(2));
6355 }
6356
6357 #[test]
6358 fn fxhashmap_update_merges_correctly() {
6359 let mut left = NixAttrs::new();
6360 left.insert("a".to_string(), Value::Int(1));
6361 left.insert("b".to_string(), Value::Int(2));
6362 let mut right = NixAttrs::new();
6363 right.insert("b".to_string(), Value::Int(20));
6364 right.insert("c".to_string(), Value::Int(3));
6365 let merged = left.update(&right);
6366 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6367 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6369 assert_eq!(merged.len(), 3);
6370 }
6371
6372 #[test]
6373 fn fxhashmap_from_iterator_collects_with_interning() {
6374 let pairs = vec![
6375 ("x".to_string(), Value::Int(10)),
6376 ("y".to_string(), Value::Int(20)),
6377 ("z".to_string(), Value::Int(30)),
6378 ];
6379 let attrs: NixAttrs = pairs.into_iter().collect();
6380 assert_eq!(attrs.len(), 3);
6381 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6382 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6383 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6384 let sym_x = intern("x");
6386 assert!(attrs.inner().contains_key(&sym_x));
6387 }
6388
6389 #[test]
6394 fn smallvec_context_empty() {
6395 let ctx = StringContext::new();
6396 assert!(ctx.is_empty());
6397 assert_eq!(ctx.len(), 0);
6398 assert_eq!(ctx.elements().len(), 0);
6399 }
6400
6401 #[test]
6402 fn smallvec_context_single_element_inline() {
6403 let mut ctx = StringContext::new();
6404 ctx.add_plain("/nix/store/single");
6405 assert_eq!(ctx.len(), 1);
6406 assert!(!ctx.is_empty());
6408 }
6409
6410 #[test]
6411 fn smallvec_context_two_elements_still_inline() {
6412 let mut ctx = StringContext::new();
6413 ctx.add_plain("/nix/store/one");
6414 ctx.add_output("/nix/store/two.drv", "out");
6415 assert_eq!(ctx.len(), 2);
6416 }
6417
6418 #[test]
6419 fn smallvec_context_three_plus_spills_to_heap() {
6420 let mut ctx = StringContext::new();
6421 ctx.add_plain("/nix/store/a");
6422 ctx.add_plain("/nix/store/b");
6423 ctx.add_drv_deep("/nix/store/c.drv");
6424 assert_eq!(ctx.len(), 3);
6425 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6427 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6428 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6429 }
6430
6431 #[test]
6432 fn smallvec_context_merge_deduplicates() {
6433 let mut ctx1 = StringContext::new();
6434 ctx1.add_plain("/nix/store/dup");
6435 ctx1.add_output("/nix/store/x.drv", "out");
6436 let mut ctx2 = StringContext::new();
6437 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6440 assert_eq!(ctx1.len(), 3); }
6442
6443 #[test]
6444 fn smallvec_context_add_plain_output_drv_deep() {
6445 let mut ctx = StringContext::new();
6446 ctx.add_plain("/nix/store/plain");
6447 assert_eq!(ctx.len(), 1);
6448 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6449
6450 ctx.add_output("/nix/store/out.drv", "lib");
6451 assert_eq!(ctx.len(), 2);
6452 assert!(ctx.elements().contains(&ContextElement::Output {
6453 drv: SmolStr::from("/nix/store/out.drv"),
6454 output: SmolStr::from("lib"),
6455 }));
6456
6457 ctx.add_drv_deep("/nix/store/deep.drv");
6458 assert_eq!(ctx.len(), 3);
6459 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6460 }
6461
6462 #[test]
6467 fn rc_list_constructor_wraps_in_rc() {
6468 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6469 match &v {
6470 Value::List(rc) => {
6471 assert_eq!(rc.len(), 2);
6472 assert_eq!(Rc::strong_count(rc), 1);
6473 }
6474 _ => panic!("expected List"),
6475 }
6476 }
6477
6478 #[test]
6479 fn rc_list_clone_is_refcount_bump() {
6480 let v = Value::list(vec![Value::Int(10)]);
6481 let rc1 = match &v {
6482 Value::List(rc) => rc.clone(),
6483 _ => panic!("expected List"),
6484 };
6485 let v2 = v.clone();
6486 let rc2 = match &v2 {
6487 Value::List(rc) => rc.clone(),
6488 _ => panic!("expected List"),
6489 };
6490 assert!(Rc::ptr_eq(&rc1, &rc2));
6492 assert!(Rc::strong_count(&rc1) >= 2);
6495 }
6496
6497 #[test]
6498 fn rc_list_as_list_returns_slice() {
6499 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6500 let slice = v.as_list().unwrap();
6501 assert_eq!(slice.len(), 3);
6502 assert_eq!(slice[0], Value::Int(1));
6503 assert_eq!(slice[1], Value::Int(2));
6504 assert_eq!(slice[2], Value::Int(3));
6505 }
6506
6507 #[test]
6508 fn rc_list_from_vec_wraps_in_rc() {
6509 let items = vec![Value::Bool(true), Value::Bool(false)];
6510 let v: Value = items.into();
6511 match &v {
6512 Value::List(rc) => {
6513 assert_eq!(rc.len(), 2);
6514 assert_eq!(Rc::strong_count(rc), 1);
6515 }
6516 _ => panic!("expected List"),
6517 }
6518 }
6519
6520 #[test]
6525 fn intern_same_string_returns_same_symbol() {
6526 let s1 = intern("hello_intern_test");
6527 let s2 = intern("hello_intern_test");
6528 assert_eq!(s1, s2);
6529 }
6530
6531 #[test]
6532 fn intern_different_strings_returns_different_symbols() {
6533 let s1 = intern("unique_str_a_9182");
6534 let s2 = intern("unique_str_b_9182");
6535 assert_ne!(s1, s2);
6536 }
6537
6538 #[test]
6539 fn resolve_roundtrips_correctly() {
6540 let sym = intern("roundtrip_test_str");
6541 let resolved = resolve(sym);
6542 assert_eq!(resolved, "roundtrip_test_str");
6543 }
6544
6545 #[test]
6546 fn intern_cached_same_offset_returns_cached_symbol() {
6547 let sid = next_source_id();
6548 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6549 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6550 assert_eq!(sym1, sym2);
6551 }
6552
6553 #[test]
6554 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6555 let sid = next_source_id();
6558 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6559 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6560 assert_eq!(sym1, sym2);
6562 }
6563
6564 #[test]
6565 fn clear_ident_cache_clears() {
6566 let sid = next_source_id();
6567 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6568 clear_ident_cache();
6569 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6573 let resolved = resolve(sym2);
6574 assert_eq!(resolved, "to_be_cleared_99");
6575 }
6576
6577 #[test]
6578 fn next_source_id_increments_monotonically() {
6579 let id1 = next_source_id();
6580 let id2 = next_source_id();
6581 let id3 = next_source_id();
6582 assert_eq!(id2, id1 + 1);
6583 assert_eq!(id3, id2 + 1);
6584 }
6585
6586 #[test]
6591 fn env_new_creates_empty_bindings() {
6592 let env = Env::new();
6593 assert!(env.0.bindings.is_empty());
6594 assert!(env.0.with_scopes.is_empty());
6595 assert!(env.eval_file().is_none());
6596 }
6597
6598 #[test]
6599 fn env_bind_lookup_roundtrip() {
6600 let mut env = Env::new();
6601 env.bind("foo".to_string(), Value::Int(42));
6602 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6603 assert_eq!(env.lookup("bar"), None);
6604 }
6605
6606 #[test]
6607 fn env_child_inherits_parent_bindings_flattened() {
6608 let mut parent = Env::new();
6609 parent.bind("a".to_string(), Value::Int(1));
6610 parent.bind("b".to_string(), Value::Int(2));
6611 let child = parent.child();
6612 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6614 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6615 let sym_a = intern("a");
6617 assert!(child.0.bindings.contains_key(&sym_a));
6618 }
6619
6620 #[test]
6621 fn env_child_inherits_with_scopes() {
6622 let mut attrs = NixAttrs::new();
6623 attrs.insert("ws".to_string(), Value::Int(10));
6624 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6625 let child = parent.child();
6626 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6628 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6629 }
6630
6631 #[test]
6632 fn env_lookup_sym_fast_path_matches_lookup() {
6633 let mut env = Env::new();
6634 env.bind("target".to_string(), Value::Int(88));
6635 let sym = intern("target");
6636 let via_lookup = env.lookup("target");
6637 let via_sym = env.lookup_sym(sym);
6638 assert_eq!(via_lookup, via_sym);
6639 assert_eq!(via_sym, Some(Value::Int(88)));
6640 }
6641
6642 #[test]
6643 fn env_lookup_sym_with_scope_fallback() {
6644 let mut attrs = NixAttrs::new();
6645 attrs.insert("sym_ws".to_string(), Value::Int(33));
6646 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6647 let sym = intern("sym_ws");
6648 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6649 }
6650
6651 #[test]
6652 fn env_with_scope_ordering_multiple_innermost_wins() {
6653 let mut a1 = NixAttrs::new();
6654 a1.insert("x".to_string(), Value::Int(1));
6655 let mut a2 = NixAttrs::new();
6656 a2.insert("x".to_string(), Value::Int(2));
6657 let mut a3 = NixAttrs::new();
6658 a3.insert("x".to_string(), Value::Int(3));
6659 let env = Env::new()
6660 .with_scope(Value::Attrs(Rc::new(a1)))
6661 .with_scope(Value::Attrs(Rc::new(a2)))
6662 .with_scope(Value::Attrs(Rc::new(a3)));
6663 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6665 }
6666
6667 #[test]
6668 fn env_lookup_sym_not_found_returns_none() {
6669 let env = Env::new();
6670 let sym = intern("nonexistent_sym_99");
6671 assert_eq!(env.lookup_sym(sym), None);
6672 }
6673
6674 #[test]
6675 fn env_lookup_sym_lexical_wins_over_with_scope() {
6676 let mut attrs = NixAttrs::new();
6677 attrs.insert("priority".to_string(), Value::Int(1));
6678 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6679 env.bind("priority".to_string(), Value::Int(99));
6680 let sym = intern("priority");
6681 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6682 }
6683}