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 PlanGroup {
1268 plan: sui_normalize::GroupPlan,
1269 env: Env,
1270 },
1271 Native(Box<dyn FnOnce() -> Result<Value, EvalError>>),
1276 WithIdent {
1286 name: SmolStr,
1288 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1293 scope_value: Value,
1295 env: Env,
1298 },
1299 Blackhole,
1301 Promise(Rc<RefCell<Value>>),
1314 Failed(EvalError),
1325 Evaluated(Box<Value>),
1329 EvaluatedConcrete,
1340}
1341
1342struct ThunkInner {
1351 cache: OnceCell<Box<Concrete>>,
1355 repr: UnsafeCell<ThunkRepr>,
1357 recursive: bool,
1364}
1365
1366impl Drop for ThunkInner {
1367 fn drop(&mut self) {
1368 census::dropped(&census::THUNK_LIVE);
1369 }
1370}
1371
1372#[derive(Clone)]
1374pub struct Thunk(pub(crate) Rc<ThunkInner>);
1375
1376impl Thunk {
1377 #[must_use]
1384 pub fn new_plan_group(plan: sui_normalize::GroupPlan, env: Env) -> Self {
1385 crate::trace::inc_thunks_created();
1386 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1387 Self(Rc::new(ThunkInner {
1388 cache: OnceCell::new(),
1389 repr: UnsafeCell::new(ThunkRepr::PlanGroup { plan, env }),
1390 recursive: false,
1391 }))
1392 }
1393
1394 pub fn new_suspended(expr: rnix::ast::Expr, env: Env) -> Self {
1395 crate::trace::inc_thunks_created();
1396 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1397 Self(Rc::new(ThunkInner {
1398 cache: OnceCell::new(),
1399 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1400 recursive: false,
1401 }))
1402 }
1403
1404 pub fn new_suspended_recursive(expr: rnix::ast::Expr, env: Env) -> Self {
1411 crate::trace::inc_thunks_created();
1412 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1413 crate::perf::inc(crate::perf::Counter::ThunkSiteLetForward);
1414 Self(Rc::new(ThunkInner {
1415 cache: OnceCell::new(),
1416 repr: UnsafeCell::new(ThunkRepr::Suspended { expr, env }),
1417 recursive: true,
1418 }))
1419 }
1420
1421 pub fn new_inherit_select(source_thunk: Thunk, name: impl Into<SmolStr>) -> Self {
1429 crate::trace::inc_thunks_created();
1430 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1431 crate::perf::inc(crate::perf::Counter::ThunkSiteInheritSrc);
1432 Self(Rc::new(ThunkInner {
1433 cache: OnceCell::new(),
1434 repr: UnsafeCell::new(ThunkRepr::InheritSelect {
1435 source_thunk,
1436 name: name.into(),
1437 }),
1438 recursive: false,
1439 }))
1440 }
1441
1442 pub fn new_with_ident(
1446 name: SmolStr,
1447 scope_cache: Rc<RefCell<Option<NixAttrs>>>,
1448 scope_value: Value,
1449 env: Env,
1450 ) -> Self {
1451 crate::trace::inc_thunks_created();
1452 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1453 crate::perf::inc(crate::perf::Counter::ThunkSiteOther);
1454 Self(Rc::new(ThunkInner {
1455 cache: OnceCell::new(),
1456 repr: UnsafeCell::new(ThunkRepr::WithIdent {
1457 name,
1458 scope_cache,
1459 scope_value,
1460 env,
1461 }),
1462 recursive: false,
1463 }))
1464 }
1465
1466 pub fn new_native(f: impl FnOnce() -> Result<Value, EvalError> + 'static) -> Self {
1470 crate::trace::inc_thunks_created();
1471 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1472 crate::perf::inc(crate::perf::Counter::ThunkSiteNative);
1473 Self(Rc::new(ThunkInner {
1474 cache: OnceCell::new(),
1475 repr: UnsafeCell::new(ThunkRepr::Native(Box::new(f))),
1476 recursive: false,
1477 }))
1478 }
1479
1480 pub fn new_evaluated(value: Value) -> Self {
1484 crate::trace::inc_thunks_created();
1485 census::made(&census::THUNK_MADE, &census::THUNK_LIVE);
1486 crate::perf::inc(crate::perf::Counter::ThunkSiteEvaluated);
1487 let cache = OnceCell::new();
1488 let repr = if matches!(value, Value::Thunk(_)) {
1492 ThunkRepr::Evaluated(Box::new(value))
1493 } else {
1494 let _ = cache.set(Box::new(value.demand_unchecked()));
1495 ThunkRepr::EvaluatedConcrete
1496 };
1497 Self(Rc::new(ThunkInner {
1498 cache,
1499 repr: UnsafeCell::new(repr),
1500 recursive: false,
1501 }))
1502 }
1503
1504 pub fn is_evaluated(&self) -> bool {
1507 self.0.cache.get().is_some()
1508 }
1509
1510 pub fn is_native(&self) -> bool {
1516 matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Native(_))
1519 }
1520
1521 pub fn peek(&self) -> Option<&Concrete> {
1527 self.0.cache.get().map(|v| &**v)
1528 }
1529
1530 pub fn update_env(&self, new_env: &Env) {
1535 let repr = unsafe { &mut *self.0.repr.get() };
1538 match repr {
1539 ThunkRepr::Suspended { env, .. } => {
1540 *env = new_env.clone();
1541 }
1542 ThunkRepr::InheritSelect { source_thunk, .. } => {
1543 source_thunk.update_env(new_env);
1544 }
1545 ThunkRepr::PlanGroup { env, .. } => {
1547 *env = new_env.clone();
1548 }
1549 _ => {}
1550 }
1551 }
1552
1553 #[inline]
1576 unsafe fn store_evaluated(&self, value: &Value) {
1577 census::evaluated();
1578 if matches!(value, Value::Thunk(_)) {
1579 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
1580 } else {
1581 let _ = self.0.cache.set(Box::new(value.clone().demand_unchecked()));
1582 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1583 }
1584 }
1585
1586 #[inline]
1612 unsafe fn store_evaluated_owned(&self, value: Value) -> Value {
1613 census::evaluated();
1614 let concrete = value.demand_unchecked();
1615 let ret = concrete.clone().into_value();
1616 let _ = self.0.cache.set(Box::new(concrete));
1617 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
1618 ret
1619 }
1620
1621 pub fn force(
1630 &self,
1631 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1632 ) -> Result<Value, EvalError> {
1633 if let Some(cached) = self.0.cache.get() {
1637 crate::perf::inc(crate::perf::Counter::ThunkHit);
1638 return Ok((**cached).clone().into_value());
1639 }
1640 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1642 self.force_inner(evaluator)
1643 })
1644 }
1645
1646 fn force_inner(
1649 &self,
1650 evaluator: &dyn Fn(&rnix::ast::Expr, &Env) -> Result<Value, EvalError>,
1651 ) -> Result<Value, EvalError> {
1652 if let Some(cached) = self.0.cache.get() {
1661 crate::perf::inc(crate::perf::Counter::ThunkHit);
1662 return Ok((**cached).clone().into_value());
1663 }
1664
1665 let thunk_id = Rc::as_ptr(&self.0) as usize;
1666
1667 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1680 return Ok(cell.borrow().clone());
1681 }
1682
1683 let new_repr_on_force = if self.0.recursive {
1692 ThunkRepr::Promise(Rc::new(RefCell::new(
1693 Value::Attrs(Rc::new(NixAttrs::new())),
1694 )))
1695 } else {
1696 ThunkRepr::Blackhole
1697 };
1698 let is_promise = self.0.recursive;
1699 let repr = std::mem::replace(unsafe { &mut *self.0.repr.get() }, new_repr_on_force);
1700
1701 match repr {
1702 ThunkRepr::Suspended { expr, env } => {
1703 crate::perf::inc(crate::perf::Counter::ThunkForce);
1704 crate::trace::inc_thunks_forced_unique();
1705 let tracing = crate::trace::trace_enabled();
1706 let desc: String = if tracing {
1714 expr.syntax().text().to_string().chars().take(60).collect()
1715 } else {
1716 String::new()
1717 };
1718 crate::trace::push_force(crate::trace::ForceFrame {
1719 defined_in: env.eval_file().cloned(),
1720 description: desc.clone(),
1721 thunk_id,
1722 });
1723 if crate::value::promotion_occurred()
1749 && crate::trace::current_force_depth() as usize
1750 > PROMOTION_RUNAWAY_FORCE_DEPTH
1751 {
1752 crate::trace::pop_force();
1753 *unsafe { &mut *self.0.repr.get() } =
1754 ThunkRepr::Suspended { expr, env };
1755 return Err(EvalError::InfiniteRecursion(
1756 "overlay-fixpoint promotion runaway (force depth exceeded)".into(),
1757 ));
1758 }
1759 if tracing {
1760 crate::trace::trace_force_enter(
1761 env.eval_file().map(|p| p.as_path()),
1762 &desc,
1763 );
1764 if let Err(msg) = crate::trace::check_force_depth() {
1765 crate::trace::dump_trace_on_error();
1766 crate::trace::pop_force();
1767 crate::trace::trace_force_exit();
1768 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended {
1769 expr,
1770 env,
1771 };
1772 return Err(EvalError::InfiniteRecursion(msg));
1773 }
1774 }
1775 let _file_guard = env.eval_file().cloned().map(crate::eval::push_eval_file);
1781 let _srcid_guard = crate::eval::push_source_id(env.source_id());
1790 if is_promise {
1796 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
1797 }
1798 let result = evaluator(&expr, &env);
1799 if is_promise {
1800 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1801 }
1802 let became_promise = !is_promise
1812 && matches!(unsafe { &*self.0.repr.get() }, ThunkRepr::Promise(_));
1813 if became_promise {
1814 IN_PROMISE_EVAL.with(|c| c.set(c.get().saturating_sub(1)));
1815 }
1816 match result {
1817 Ok(mut value) => {
1818 crate::perf::inc(crate::perf::Counter::ThunkStoreWrites);
1819 if is_promise || became_promise {
1826 if let ThunkRepr::Promise(cell) = unsafe { &*self.0.repr.get() } {
1827 *cell.borrow_mut() = value.clone();
1828 }
1829 }
1830 let was_thunk_before_loop = matches!(value, Value::Thunk(_));
1852 if !was_thunk_before_loop {
1853 crate::perf::inc(crate::perf::Counter::ThunkStoreRedundant);
1858 let ret = unsafe { self.store_evaluated_owned(value) };
1859 crate::trace::pop_force();
1860 if tracing { crate::trace::trace_force_exit(); }
1861 return Ok(ret);
1862 }
1863 unsafe { self.store_evaluated(&value) };
1865 while let Value::Thunk(ref inner) = value {
1870 match inner.peek() {
1871 Some(cached) => value = cached.clone().into_value(),
1872 None => break,
1873 }
1874 }
1875 if !matches!(value, Value::Thunk(_)) {
1876 crate::perf::inc(crate::perf::Counter::ThunkStoreLoopMutated);
1877 }
1878 unsafe { self.store_evaluated(&value) };
1879 crate::trace::pop_force();
1880 if tracing { crate::trace::trace_force_exit(); }
1881 Ok(value)
1882 }
1883 Err(e) => {
1884 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Suspended { expr, env };
1885 if tracing { crate::trace::dump_trace_on_error(); }
1886 crate::trace::pop_force();
1887 if tracing { crate::trace::trace_force_exit(); }
1888 Err(e)
1889 }
1890 }
1891 }
1892 ThunkRepr::InheritSelect { source_thunk, name } => {
1893 let tracing = crate::trace::trace_enabled();
1894 let desc = if tracing { format!("inherit (..) {name}") } else { String::new() };
1895 crate::trace::push_force(crate::trace::ForceFrame {
1896 defined_in: None,
1897 description: desc.clone(),
1898 thunk_id,
1899 });
1900 if tracing {
1901 crate::trace::trace_force_enter(None, &desc);
1902 }
1903 crate::trace::inc_thunks_forced_unique();
1904 if tracing {
1905 if let Err(msg) = crate::trace::check_force_depth() {
1906 crate::trace::dump_trace_on_error();
1907 crate::trace::pop_force();
1908 crate::trace::trace_force_exit();
1909 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::InheritSelect {
1910 source_thunk,
1911 name,
1912 };
1913 return Err(EvalError::InfiniteRecursion(msg));
1914 }
1915 }
1916 let attempt = (|| -> Result<Value, EvalError> {
1917 let mut forced = source_thunk.force(evaluator)?;
1918 while let Value::Thunk(inner) = forced {
1919 forced = inner.force(evaluator)?;
1920 }
1921 let attrs = match &forced {
1922 Value::Attrs(a) => a,
1923 _ => {
1924 return Err(EvalError::TypeError(format!(
1925 "inherit (source) {name}: source is {}, not a set",
1926 forced.type_name()
1927 )))
1928 }
1929 };
1930 attrs
1931 .get(&name)
1932 .cloned()
1933 .ok_or_else(|| EvalError::AttrNotFound(name.to_string()))
1934 })();
1935 match attempt {
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::InheritSelect { source_thunk, name };
1948 if tracing { crate::trace::dump_trace_on_error(); }
1949 crate::trace::pop_force();
1950 if tracing { crate::trace::trace_force_exit(); }
1951 Err(e)
1952 }
1953 }
1954 }
1955 ThunkRepr::PlanGroup { plan, env } => {
1956 let tracing = crate::trace::trace_enabled();
1957 crate::trace::push_force(crate::trace::ForceFrame {
1958 defined_in: env.eval_file().cloned(),
1959 description: if tracing { "<plan-group>".into() } else { String::new() },
1960 thunk_id,
1961 });
1962 if tracing {
1963 crate::trace::trace_force_enter(None, "<plan-group>");
1964 }
1965 crate::trace::inc_thunks_forced_unique();
1966 match crate::eval::eval_plan_group(&plan, &env) {
1972 Ok(value) => {
1973 *unsafe { &mut *self.0.repr.get() } =
1974 ThunkRepr::Evaluated(Box::new(value.clone()));
1975 crate::trace::pop_force();
1976 crate::trace::trace_force_exit();
1977 Ok(value)
1978 }
1979 Err(e) => {
1980 *unsafe { &mut *self.0.repr.get() } =
1981 ThunkRepr::PlanGroup { plan, env };
1982 crate::trace::pop_force();
1983 crate::trace::trace_force_exit();
1984 Err(e)
1985 }
1986 }
1987 }
1988 ThunkRepr::Native(f) => {
1989 let tracing = crate::trace::trace_enabled();
1990 crate::trace::push_force(crate::trace::ForceFrame {
1991 defined_in: None,
1992 description: if tracing { "<native-thunk>".into() } else { String::new() },
1993 thunk_id,
1994 });
1995 if tracing {
1996 crate::trace::trace_force_enter(None, "<native-thunk>");
1997 }
1998 crate::trace::inc_thunks_forced_unique();
1999 match f() {
2004 Ok(mut value) => {
2005 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(Box::new(value.clone()));
2006 while let Value::Thunk(ref inner) = value {
2007 match inner.peek() { Some(c) => value = c.clone().into_value(), None => break }
2008 }
2009 unsafe { self.store_evaluated(&value) };
2010 crate::trace::pop_force();
2011 if tracing { crate::trace::trace_force_exit(); }
2012 Ok(value)
2013 }
2014 Err(e) => {
2015 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e.clone());
2029 if tracing { crate::trace::dump_trace_on_error(); }
2030 crate::trace::pop_force();
2031 if tracing { crate::trace::trace_force_exit(); }
2032 Err(e)
2033 }
2034 }
2035 }
2036 ThunkRepr::WithIdent { name, scope_cache, scope_value, env } => {
2037 crate::perf::inc(crate::perf::Counter::ThunkForce);
2038 crate::trace::inc_thunks_forced_unique();
2039 {
2044 let cache = scope_cache.borrow();
2045 if let Some(ref attrs) = *cache {
2046 if let Some(v) = attrs.get(&name) {
2047 let value = v.clone();
2048 unsafe { self.store_evaluated(&value) };
2049 return Ok(value);
2050 }
2051 }
2053 }
2054 if let Ok(forced) = crate::eval::force_value(&scope_value) {
2056 if let Value::Attrs(ref attrs) = forced {
2057 *scope_cache.borrow_mut() = Some((**attrs).clone());
2058 if let Some(v) = attrs.get(&name) {
2059 let value = v.clone();
2060 unsafe { self.store_evaluated(&value) };
2061 return Ok(value);
2062 }
2063 }
2064 }
2065 let result = match env.lookup(&name) {
2095 Some(v) => v,
2096 None => match env.lookup_fresh(&name) {
2097 Some(v) => v,
2098 None if in_promise_eval() => Value::Null,
2099 None => return Err(EvalError::UndefinedVar(format!("'{name}'"))),
2100 },
2101 };
2102 unsafe { self.store_evaluated(&result) };
2103 Ok(result)
2104 }
2105 ThunkRepr::Blackhole => {
2106 if std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some() {
2130 return Ok(Value::Null);
2131 }
2132 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_LIST").is_some() {
2133 return Ok(Value::List(Rc::new(NixList::new(Vec::new()))));
2134 }
2135 if std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some() {
2136 return Ok(Value::Attrs(Rc::new(NixAttrs::new())));
2137 }
2138 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2139 let same = crate::trace::force_stack_contains(thunk_id);
2140 eprintln!(
2141 "[SUI_DEBUG_CYCLE] blackhole re-entry thunk_id={thunk_id:#x} same_thunk_on_stack={same} recursive_flag={}",
2142 self.0.recursive
2143 );
2144 crate::trace::dump_force_stack_ids();
2145 }
2146 if crate::trace::force_stack_contains(thunk_id)
2185 && IN_PROMISE_EVAL.with(|c| c.get()) < FIXPOINT_PROMOTE_NEST_CAP
2186 {
2187 if std::env::var_os("SUI_DEBUG_CYCLE").is_some() {
2188 let chain = crate::trace::capture_cycle(thunk_id);
2189 let nest = IN_PROMISE_EVAL.with(|c| c.get());
2190 let fdepth = crate::trace::current_force_depth();
2191 eprintln!("[SUI_PROMOTE] thunk_id={thunk_id:#x} cycle_len={} nest={nest} fdepth={fdepth}", chain.0.len());
2192 }
2193 let cell = Rc::new(RefCell::new(
2194 Value::Attrs(Rc::new(NixAttrs::new())),
2195 ));
2196 *unsafe { &mut *self.0.repr.get() } =
2199 ThunkRepr::Promise(cell.clone());
2200 IN_PROMISE_EVAL.with(|c| c.set(c.get() + 1));
2204 PROMOTION_OCCURRED.with(|c| c.set(true));
2206 return Ok(cell.borrow().clone());
2207 }
2208 let chain = crate::trace::capture_cycle(thunk_id);
2209 crate::trace::dump_trace_on_error();
2210 Err(EvalError::InfiniteRecursion(chain.to_string()))
2211 }
2212 ThunkRepr::Promise(cell) => {
2213 Ok(cell.borrow().clone())
2222 }
2223 ThunkRepr::Evaluated(v) => {
2224 crate::perf::inc(crate::perf::Counter::ThunkHit);
2228 let cloned = (*v).clone();
2229 if !matches!(cloned, Value::Thunk(_)) {
2230 if !matches!(cloned, Value::Thunk(_)) { let _ = self.0.cache.set(Box::new(cloned.clone().demand_unchecked())); }
2231 }
2232 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Evaluated(v);
2233 Ok(cloned)
2234 }
2235 ThunkRepr::EvaluatedConcrete => {
2236 crate::perf::inc(crate::perf::Counter::ThunkHit);
2246 let value = self
2247 .0
2248 .cache
2249 .get()
2250 .expect("EvaluatedConcrete implies a populated cache")
2251 .as_ref()
2252 .clone()
2253 .into_value();
2254 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::EvaluatedConcrete;
2255 Ok(value)
2256 }
2257 ThunkRepr::Failed(e) => {
2258 let err = e.clone();
2263 *unsafe { &mut *self.0.repr.get() } = ThunkRepr::Failed(e);
2264 Err(err)
2265 }
2266 }
2267 }
2268}
2269
2270impl fmt::Debug for Thunk {
2271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2272 match unsafe { &*self.0.repr.get() } {
2274 ThunkRepr::Suspended { .. } => write!(f, "<thunk>"),
2275 ThunkRepr::InheritSelect { name, .. } => write!(f, "<inherit-select {name}>"),
2276 ThunkRepr::Native(_) => write!(f, "<native-thunk>"),
2277 ThunkRepr::PlanGroup { .. } => write!(f, "<plan-group>"),
2278 ThunkRepr::WithIdent { name, .. } => write!(f, "<with-ident {name}>"),
2279 ThunkRepr::Blackhole => write!(f, "<blackhole>"),
2280 ThunkRepr::Promise(_) => write!(f, "<promise>"),
2281 ThunkRepr::Failed(e) => write!(f, "<failed-thunk: {e}>"),
2282 ThunkRepr::Evaluated(v) => write!(f, "{v:?}"),
2283 ThunkRepr::EvaluatedConcrete => match self.0.cache.get() {
2284 Some(c) => write!(f, "{:?}", c.as_ref().clone().into_value()),
2285 None => write!(f, "<evaluated-concrete>"),
2286 },
2287 }
2288 }
2289}
2290
2291pub struct NixAttrs(AttrsInner, Option<Rc<crate::pos::AttrPositions>>);
2305
2306impl Clone for NixAttrs {
2311 fn clone(&self) -> Self {
2312 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2313 NixAttrs(self.0.clone(), self.1.clone())
2314 }
2315}
2316
2317impl Drop for NixAttrs {
2318 fn drop(&mut self) {
2319 census::dropped(&census::ATTRS_LIVE);
2320 }
2321}
2322
2323#[derive(Clone)]
2325enum AttrsInner {
2326 Flat(AttrsMap<Symbol, Value>),
2328 Overlay {
2339 left: RefCell<Rc<NixAttrs>>,
2340 right: RefCell<Rc<NixAttrs>>,
2341 cache: Rc<OnceCell<AttrsMap<Symbol, Value>>>,
2342 },
2343}
2344
2345impl fmt::Debug for NixAttrs {
2346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2347 write!(f, "NixAttrs({})", self.len())
2348 }
2349}
2350
2351impl Default for NixAttrs {
2352 fn default() -> Self {
2353 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2354 Self(AttrsInner::Flat(AttrsMap::default()), None)
2355 }
2356}
2357
2358impl NixAttrs {
2359 pub fn new() -> Self {
2360 Self::default()
2361 }
2362
2363 pub fn with_capacity(_capacity: usize) -> Self {
2364 Self::default()
2365 }
2366
2367 pub fn set_positions(&mut self, pos: Rc<crate::pos::AttrPositions>) {
2371 self.1 = Some(pos);
2372 }
2373
2374 #[must_use]
2378 pub fn positions(&self) -> Option<&Rc<crate::pos::AttrPositions>> {
2379 self.1.as_ref()
2380 }
2381
2382 #[must_use]
2387 pub fn pos_for(&self, key: &str) -> Option<crate::pos::ResolvedPos> {
2388 let sym = intern(key);
2389 let (file, offset) = self.pos_entry(sym)?;
2390 crate::pos::resolve(file.as_deref(), offset)
2391 }
2392
2393 fn pos_entry(&self, sym: Symbol) -> Option<(Option<std::path::PathBuf>, u32)> {
2413 if let Some(table) = self.1.as_ref() {
2414 if let Some(offset) = table.keys.get(&sym) {
2415 return Some((table.file.clone(), *offset));
2416 }
2417 }
2418 match &self.0 {
2419 AttrsInner::Overlay { left, right, .. } => {
2420 let r = right.borrow().pos_entry(sym);
2421 if r.is_some() {
2422 return r;
2423 }
2424 let l = left.borrow().pos_entry(sym);
2425 l
2426 }
2427 _ => None,
2428 }
2429 }
2430
2431 #[must_use]
2433 pub fn inner(&self) -> AttrsMap<Symbol, Value> {
2434 self.as_flat().clone()
2435 }
2436
2437 fn as_flat(&self) -> &AttrsMap<Symbol, Value> {
2439 match &self.0 {
2440 AttrsInner::Flat(m) => m,
2441 AttrsInner::Overlay { left, right, cache } => {
2442 crate::perf::inc(crate::perf::Counter::OverlayFlattenAttempt);
2443 let flat = cache.get_or_init(|| {
2444 crate::perf::inc(crate::perf::Counter::OverlayFlattenBuild);
2447 let timed = crate::perf::enabled();
2448 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2449 let mut result = left.borrow().as_flat().clone();
2450 for (k, v) in right.borrow().as_flat().iter() {
2451 result.insert(*k, v.clone());
2452 }
2453 crate::perf::add(
2454 crate::perf::Counter::OverlayFlattenEntries,
2455 result.len() as u64,
2456 );
2457 if let Some(t0) = t0 {
2458 crate::trace::add_overlay_flatten_nanos(t0.elapsed().as_nanos());
2459 }
2460 result
2461 });
2462 {
2482 let mut l = left.borrow_mut();
2483 if !l.is_empty() { *l = Rc::new(l.position_husk()); }
2484 }
2485 {
2486 let mut r = right.borrow_mut();
2487 if !r.is_empty() { *r = Rc::new(r.position_husk()); }
2488 }
2489 flat
2490 }
2491 }
2492 }
2493
2494 fn position_husk(&self) -> NixAttrs {
2504 match &self.0 {
2505 AttrsInner::Overlay { left, right, .. } => {
2506 let (l, r) = (left.borrow().position_husk(), right.borrow().position_husk());
2507 if l.1.is_none() && r.1.is_none() && !matches!(l.0, AttrsInner::Overlay { .. })
2508 && !matches!(r.0, AttrsInner::Overlay { .. })
2509 {
2510 return NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone());
2513 }
2514 NixAttrs(
2515 AttrsInner::Overlay {
2516 left: RefCell::new(Rc::new(l)),
2517 right: RefCell::new(Rc::new(r)),
2518 cache: Rc::new(OnceCell::new()),
2519 },
2520 self.1.clone(),
2521 )
2522 }
2523 AttrsInner::Flat(_) => NixAttrs(AttrsInner::Flat(AttrsMap::default()), self.1.clone()),
2524 }
2525 }
2526
2527 fn sorted_entries(&self) -> Vec<(String, &Value)> {
2528 crate::perf::inc(crate::perf::Counter::SortedEntriesCalls);
2529 let m = self.as_flat();
2530 crate::perf::add(crate::perf::Counter::SortedEntriesRows, m.len() as u64);
2531 let timed = crate::perf::enabled();
2532 let t0 = if timed { Some(std::time::Instant::now()) } else { None };
2533 let mut pairs: Vec<(String, &Value)> = m.iter()
2534 .map(|(sym, v)| (resolve(*sym), v))
2535 .collect();
2536 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
2537 if let Some(t0) = t0 {
2538 crate::trace::add_sorted_entries_nanos(t0.elapsed().as_nanos());
2539 }
2540 pairs
2541 }
2542
2543 #[must_use]
2545 pub fn get(&self, key: &str) -> Option<&Value> {
2546 let sym = intern(key);
2547 self.get_sym(&sym)
2548 }
2549
2550 #[must_use]
2564 pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
2565 match &self.0 {
2566 AttrsInner::Flat(m) => m.get(sym),
2567 AttrsInner::Overlay { .. } => self.as_flat().get(sym),
2573 }
2574 }
2575
2576 pub fn insert(&mut self, key: String, value: Value) {
2578 self.ensure_flat();
2579 if let AttrsInner::Flat(ref mut m) = self.0 {
2580 m.insert(intern(&key), value);
2581 }
2582 }
2583
2584 fn ensure_flat(&mut self) {
2586 if matches!(self.0, AttrsInner::Overlay { .. }) {
2587 self.0 = AttrsInner::Flat(self.as_flat().clone());
2588 }
2589 }
2590
2591 #[must_use]
2592 pub fn contains_key(&self, key: &str) -> bool {
2593 let sym = intern(key);
2594 self.contains_key_sym(&sym)
2595 }
2596
2597 #[must_use]
2598 pub fn contains_key_sym(&self, sym: &Symbol) -> bool {
2599 match &self.0 {
2600 AttrsInner::Flat(m) => m.contains_key(sym),
2601 AttrsInner::Overlay { .. } => self.as_flat().contains_key(sym),
2603 }
2604 }
2605
2606 pub fn keys(&self) -> impl Iterator<Item = String> {
2607 self.sorted_entries().into_iter().map(|(k, _)| k)
2608 }
2609
2610 pub fn iter(&self) -> impl Iterator<Item = (String, &Value)> {
2611 self.sorted_entries().into_iter()
2612 }
2613
2614 pub fn iter_unsorted(&self) -> impl Iterator<Item = (String, &Value)> {
2615 self.as_flat().iter().map(|(sym, v)| (resolve(*sym), v)).collect::<Vec<_>>().into_iter()
2616 }
2617
2618 pub fn iter_syms(&self) -> impl Iterator<Item = (Symbol, &Value)> {
2636 self.as_flat().iter().map(|(sym, v)| (*sym, v))
2637 }
2638
2639 pub fn insert_sym(&mut self, sym: Symbol, value: Value) {
2642 self.ensure_flat();
2643 if let AttrsInner::Flat(ref mut m) = self.0 {
2644 m.insert(sym, value);
2645 }
2646 }
2647
2648 pub fn values(&self) -> impl Iterator<Item = &Value> {
2649 self.sorted_entries().into_iter().map(|(_, v)| v)
2650 }
2651
2652
2653 pub fn remove(&mut self, key: &str) -> Option<Value> {
2654 self.ensure_flat();
2655 if let AttrsInner::Flat(ref mut m) = self.0 {
2656 m.remove(&intern(key))
2657 } else {
2658 None
2659 }
2660 }
2661
2662 #[must_use]
2663 pub fn len(&self) -> usize {
2664 match &self.0 {
2665 AttrsInner::Flat(m) => m.len(),
2666 AttrsInner::Overlay { .. } => {
2667 self.as_flat().len()
2671 }
2672 }
2673 }
2674
2675 #[must_use]
2676 pub fn is_empty(&self) -> bool {
2677 match &self.0 {
2678 AttrsInner::Flat(m) => m.is_empty(),
2679 AttrsInner::Overlay { .. } => self.as_flat().is_empty(),
2683 }
2684 }
2685
2686 #[must_use]
2688 pub fn overlay(self, other: NixAttrs) -> NixAttrs {
2689 if other.is_empty() { return self; }
2690 if self.is_empty() { return other; }
2691 crate::perf::inc(crate::perf::Counter::OverlayCreated);
2692 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2693 NixAttrs(AttrsInner::Overlay {
2694 left: RefCell::new(Rc::new(self)),
2695 right: RefCell::new(Rc::new(other)),
2696 cache: Rc::new(OnceCell::new()),
2697 }, None)
2698 }
2699
2700 #[must_use]
2702 pub fn update(&self, other: &NixAttrs) -> NixAttrs {
2703 match (&self.0, &other.0) {
2704 (AttrsInner::Flat(l), AttrsInner::Flat(r)) => {
2705 let mut result = l.clone();
2706 for (k, v) in r.iter() {
2707 result.insert(*k, v.clone());
2708 }
2709 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2710 NixAttrs(AttrsInner::Flat(result), None)
2711 }
2712 _ => {
2713 let mut result = self.as_flat().clone();
2715 let other_flat = other.as_flat();
2716 for (k, v) in other_flat.iter() {
2717 result.insert(*k, v.clone());
2718 }
2719 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2720 NixAttrs(AttrsInner::Flat(result), None)
2721 }
2722 }
2723 }
2724}
2725
2726impl FromIterator<(String, Value)> for NixAttrs {
2727 fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
2728 census::made(&census::ATTRS_MADE, &census::ATTRS_LIVE);
2729 NixAttrs(AttrsInner::Flat(iter.into_iter().map(|(k, v)| (intern(&k), v)).collect()), None)
2730 }
2731}
2732
2733impl IntoIterator for NixAttrs {
2734 type Item = (String, Value);
2735 type IntoIter = Box<dyn Iterator<Item = (String, Value)>>;
2736
2737 fn into_iter(self) -> Self::IntoIter {
2738 let flat = self.as_flat().clone();
2739 Box::new(flat.into_iter().map(|(sym, v)| (resolve(sym), v)))
2740 }
2741}
2742
2743#[derive(Debug, Clone)]
2751pub struct Closure {
2752 pub param: rnix::ast::Param,
2753 pub body: rnix::ast::Expr,
2754 pub env: Env,
2755}
2756
2757pub type BuiltinFunc = dyn Fn(&[Value]) -> Result<Value, EvalError>;
2759
2760#[derive(Clone)]
2765pub struct BuiltinFn {
2766 pub name: &'static str,
2768 pub func: Rc<BuiltinFunc>,
2770}
2771
2772impl fmt::Debug for BuiltinFn {
2773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2774 write!(f, "<builtin {}>", self.name)
2775 }
2776}
2777
2778#[derive(Clone)]
2788struct WithScope {
2789 value: Value,
2790 cached: Rc<RefCell<Option<NixAttrs>>>,
2793}
2794
2795impl fmt::Debug for WithScope {
2796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2797 f.debug_struct("WithScope")
2798 .field("value", &self.value)
2799 .field("cached", &self.cached.borrow().is_some())
2800 .finish()
2801 }
2802}
2803
2804#[derive(Debug, Clone, Default)]
2814struct EnvInner {
2815 bindings: FxHashMap<Symbol, Value>,
2816 with_scopes: Vec<WithScope>,
2818 eval_file: Option<std::path::PathBuf>,
2822 source_id: u32,
2829}
2830
2831#[derive(Clone, Default)]
2839pub struct Env(Rc<EnvInner>);
2840
2841impl fmt::Debug for Env {
2842 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2843 self.0.fmt(f)
2844 }
2845}
2846
2847impl Drop for EnvInner {
2856 fn drop(&mut self) {
2857 census::dropped(&census::ENV_LIVE);
2858 }
2859}
2860
2861impl Env {
2862 #[must_use]
2864 pub fn new() -> Self {
2865 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2866 Self(Rc::new(EnvInner {
2867 bindings: FxHashMap::default(),
2868 with_scopes: Vec::new(),
2869 eval_file: None,
2870 source_id: 0,
2871 }))
2872 }
2873
2874 #[must_use]
2879 pub fn child(&self) -> Self {
2880 crate::perf::inc(crate::perf::Counter::EnvClone);
2881 census::made(&census::ENV_MADE, &census::ENV_LIVE);
2887 Self(Rc::new(EnvInner {
2888 bindings: self.0.bindings.clone(), with_scopes: self.0.with_scopes.clone(),
2890 eval_file: self.0.eval_file.clone(),
2894 source_id: self.0.source_id,
2898 }))
2899 }
2900
2901 #[must_use]
2909 pub fn with_scope(mut self, value: Value) -> Self {
2910 let pre_cached = match &value {
2912 Value::Attrs(attrs) => Some((**attrs).clone()),
2913 Value::Thunk(thunk) => thunk.peek().and_then(|v| {
2914 if let Concrete::Attrs(attrs) = v { Some((**attrs).clone()) } else { None }
2915 }),
2916 _ => None,
2917 };
2918 Rc::make_mut(&mut self.0).with_scopes.push(WithScope {
2919 value,
2920 cached: Rc::new(RefCell::new(pre_cached)),
2921 });
2922 self
2923 }
2924
2925 pub fn bind(&mut self, name: String, value: Value) {
2930 Rc::make_mut(&mut self.0).bindings.insert(intern(&name), value);
2931 }
2932
2933 pub fn bind_many(&mut self, pairs: impl IntoIterator<Item = (String, Value)>) {
2943 let inner = Rc::make_mut(&mut self.0);
2944 for (name, value) in pairs {
2945 inner.bindings.insert(intern(&name), value);
2946 }
2947 }
2948
2949 #[must_use]
2951 pub fn eval_file(&self) -> Option<&std::path::PathBuf> {
2952 self.0.eval_file.as_ref()
2953 }
2954
2955 pub fn set_eval_file(&mut self, file: Option<std::path::PathBuf>) {
2957 Rc::make_mut(&mut self.0).eval_file = file;
2958 }
2959
2960 #[must_use]
2962 pub fn source_id(&self) -> u32 {
2963 self.0.source_id
2964 }
2965
2966 pub fn set_source_id(&mut self, id: u32) {
2969 Rc::make_mut(&mut self.0).source_id = id;
2970 }
2971
2972 #[must_use]
2974 pub fn binding_count(&self) -> usize {
2975 self.0.bindings.len()
2976 }
2977
2978 #[must_use]
2980 pub fn binding_names_preview(&self, n: usize) -> Vec<String> {
2981 self.0.bindings.keys().take(n).map(|s| resolve(*s)).collect()
2982 }
2983
2984 #[must_use]
2986 pub fn with_scope_count(&self) -> usize {
2987 self.0.with_scopes.len()
2988 }
2989
2990 #[must_use]
2994 pub fn lookup_lexical(&self, name: &str) -> Option<Value> {
2995 let sym = intern(name);
2996 self.0.bindings.get(&sym).cloned()
2997 }
2998
2999 #[must_use]
3010 pub fn lookup_lexical_sym(&self, sym: Symbol) -> Option<Value> {
3011 self.0.bindings.get(&sym).cloned()
3012 }
3013
3014 #[must_use]
3018 pub fn lookup_with_cache_only(&self, name: &str) -> Option<Value> {
3019 for scope in self.0.with_scopes.iter().rev() {
3020 let cache = scope.cached.borrow();
3021 if let Some(ref attrs) = *cache {
3022 if let Some(v) = attrs.get(name) {
3023 return Some(v.clone());
3024 }
3025 }
3026 drop(cache);
3028 if let Value::Thunk(ref thunk) = scope.value {
3029 if let Some(cached_val) = thunk.peek() {
3030 if let Concrete::Attrs(ref attrs) = *cached_val {
3031 *scope.cached.borrow_mut() = Some((**attrs).clone());
3033 if let Some(v) = attrs.get(name) {
3034 return Some(v.clone());
3035 }
3036 }
3037 }
3038 } else if let Value::Attrs(ref attrs) = scope.value {
3039 *scope.cached.borrow_mut() = Some((**attrs).clone());
3040 if let Some(v) = attrs.get(name) {
3041 return Some(v.clone());
3042 }
3043 }
3044 }
3045 None
3046 }
3047
3048 #[must_use]
3051 pub fn innermost_with_scope(&self) -> Option<(Rc<RefCell<Option<NixAttrs>>>, Value)> {
3052 self.0.with_scopes.last().map(|scope| {
3053 (scope.cached.clone(), scope.value.clone())
3054 })
3055 }
3056
3057 #[must_use]
3066 pub fn lookup(&self, name: &str) -> Option<Value> {
3067 self.lookup_fast(intern(name), name)
3068 }
3069
3070 #[must_use]
3082 pub fn lookup_fresh(&self, name: &str) -> Option<Value> {
3083 let sym = intern(name);
3084 if let Some(v) = self.0.bindings.get(&sym) {
3085 return Some(v.clone());
3086 }
3087 for scope in self.0.with_scopes.iter().rev() {
3088 if let Ok(Value::Attrs(attrs)) = crate::eval::force_value(&scope.value) {
3089 if let Some(v) = attrs.get_sym(&sym) {
3090 *scope.cached.borrow_mut() = Some((*attrs).clone());
3093 return Some(v.clone());
3094 }
3095 }
3096 }
3097 None
3098 }
3099
3100 #[must_use]
3102 pub fn lookup_fast(&self, sym: Symbol, name: &str) -> Option<Value> {
3103 crate::perf::inc(crate::perf::Counter::EnvLookup);
3104 if let Some(v) = self.0.bindings.get(&sym) {
3105 return Some(v.clone());
3106 }
3107 for scope in self.0.with_scopes.iter().rev() {
3109 {
3111 let cache = scope.cached.borrow();
3112 if let Some(ref attrs) = *cache {
3113 if let Some(v) = attrs.get_sym(&sym) {
3114 return Some(v.clone());
3115 }
3116 continue;
3117 }
3118 }
3119 let resolved = match &scope.value {
3124 Value::Attrs(attrs) => {
3125 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3127 *scope.cached.borrow_mut() = Some((**attrs).clone());
3128 Some((**attrs).clone())
3129 }
3130 Value::Thunk(thunk) => {
3131 if let Some(cached_val) = thunk.peek() {
3134 if let Concrete::Attrs(ref attrs) = *cached_val {
3135 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3136 *scope.cached.borrow_mut() = Some((**attrs).clone());
3137 Some((**attrs).clone())
3138 } else {
3139 None
3140 }
3141 } else {
3142 match crate::eval::force_value(&scope.value) {
3153 Ok(forced) => {
3154 if let Value::Attrs(ref attrs) = forced {
3155 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3156 *scope.cached.borrow_mut() = Some((**attrs).clone());
3157 Some((**attrs).clone())
3158 } else {
3159 None
3160 }
3161 }
3162 Err(_) => None, }
3164 }
3165 }
3166 _ => {
3167 match crate::eval::force_value(&scope.value) {
3169 Ok(forced) => {
3170 if let Value::Attrs(ref attrs) = forced {
3171 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3172 *scope.cached.borrow_mut() = Some((**attrs).clone());
3173 Some((**attrs).clone())
3174 } else {
3175 None
3176 }
3177 }
3178 Err(_) => None,
3179 }
3180 }
3181 };
3182 if let Some(ref attrs) = resolved {
3183 if let Some(v) = attrs.get(name) {
3184 return Some(v.clone());
3185 }
3186 }
3187 }
3189 None
3190 }
3191
3192 #[must_use]
3198 pub fn lookup_sym(&self, sym: Symbol) -> Option<Value> {
3199 crate::perf::inc(crate::perf::Counter::EnvLookup);
3200 if let Some(v) = self.0.bindings.get(&sym) {
3202 return Some(v.clone());
3203 }
3204 for scope in self.0.with_scopes.iter().rev() {
3206 {
3208 let cache = scope.cached.borrow();
3209 if let Some(ref attrs) = *cache {
3210 if let Some(v) = attrs.get_sym(&sym) {
3211 return Some(v.clone());
3212 }
3213 continue;
3214 }
3215 }
3216 if let Ok(forced) = crate::eval::force_value_tracked(&scope.value, "with_scope") {
3218 if let Value::Attrs(ref attrs) = forced {
3219 let result = attrs.get_sym(&sym).cloned();
3220 crate::perf::inc(crate::perf::Counter::WithScopeCacheClone);
3221 *scope.cached.borrow_mut() = Some((**attrs).clone());
3222 if result.is_some() {
3223 return result;
3224 }
3225 }
3226 }
3227 }
3229 None
3230 }
3231}
3232
3233#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
3235#[non_exhaustive]
3236pub enum EvalError {
3237 #[error("undefined variable: {0}")]
3239 UndefinedVar(String),
3240 #[error("type error: {0}")]
3242 TypeError(String),
3243 #[error("attribute not found: {0}")]
3245 AttrNotFound(String),
3246 #[error("type error: expected {expected}, got {got}")]
3248 TypeMismatch {
3249 expected: &'static str,
3250 got: &'static str,
3251 },
3252 #[error("assertion failed{0}")]
3254 AssertionFailed(String),
3255 #[error("division by zero")]
3257 DivisionByZero,
3258 #[error("infinite recursion ({0})")]
3260 InfiniteRecursion(String),
3261 #[error("I/O error: {context}: {message}")]
3263 IoError { context: String, message: String },
3264 #[error("{0}")]
3266 Throw(String),
3267 #[error("{0}")]
3271 Abort(String),
3272 #[error("not yet implemented: {0}")]
3274 NotImplemented(String),
3275 #[error("parse error: {0}")]
3277 ParseError(String),
3278 #[error("recursion limit: {0}")]
3280 RecursionLimit(String),
3281}
3282
3283impl EvalError {
3284 #[must_use]
3286 pub fn type_error(msg: impl Into<String>) -> Self {
3287 EvalError::TypeError(msg.into())
3288 }
3289
3290 #[must_use]
3292 pub fn type_mismatch(expected: &'static str, got: &'static str) -> Self {
3293 EvalError::TypeMismatch { expected, got }
3294 }
3295
3296 #[must_use]
3298 pub fn builtin_type(builtin: &str, expected: &str, got: &str) -> Self {
3299 EvalError::TypeError(format!("{builtin}: expected {expected}, got {got}"))
3300 }
3301
3302 #[must_use]
3323 pub fn op_type(op: &str, lhs: &str, rhs: &str) -> Self {
3324 EvalError::TypeError(format!(
3325 "cannot {op} {lhs} and {rhs}{}",
3326 crate::eval::eval_file_ctx()
3327 ))
3328 }
3329
3330 #[must_use]
3332 pub fn is_throw(&self) -> bool {
3333 matches!(self, EvalError::Throw(_))
3334 }
3335
3336 #[must_use]
3338 pub fn is_infinite_recursion(&self) -> bool {
3339 matches!(self, EvalError::InfiniteRecursion(_))
3340 }
3341}
3342
3343impl Value {
3344 #[must_use]
3346 pub fn string(s: impl Into<SmolStr>) -> Self {
3347 Value::String(Rc::new(NixString::plain(s)))
3348 }
3349
3350 #[must_use]
3353 pub fn list(items: Vec<Value>) -> Self {
3354 Value::List(Rc::new(NixList::new(items)))
3355 }
3356
3357 #[must_use]
3360 pub fn is_uniquely_owned_list(&self) -> bool {
3361 matches!(self, Value::List(rc) if Rc::strong_count(rc) == 1)
3362 }
3363
3364 #[must_use]
3366 pub fn to_json(&self) -> serde_json::Value {
3367 match self {
3368 Value::Null => serde_json::Value::Null,
3369 Value::Bool(b) => serde_json::Value::Bool(*b),
3370 Value::Int(n) => serde_json::json!(n),
3371 Value::Float(f) => serde_json::json!(f),
3372 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3373 Value::Path(p) => serde_json::Value::String(p.to_string()),
3374 Value::List(items) => {
3375 serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
3376 }
3377 Value::Attrs(attrs) => {
3378 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3385 if let Ok((s, _ctx)) = self.coerce_to_string() {
3386 return serde_json::Value::String(s);
3387 }
3388 }
3389 let map: serde_json::Map<String, serde_json::Value> = attrs
3390 .iter()
3391 .map(|(k, v)| (k.clone(), v.to_json()))
3392 .collect();
3393 serde_json::Value::Object(map)
3394 }
3395 Value::Lambda(_) => serde_json::Value::String("<lambda>".to_string()),
3396 Value::Builtin(b) => serde_json::Value::String(format!("<builtin {}>", b.name)),
3397 Value::Thunk(thunk) => {
3398 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3400 Ok(v) => v.to_json(),
3401 Err(_) => serde_json::Value::String("<thunk:error>".to_string()),
3402 }
3403 }
3404 }
3405 }
3406
3407 pub fn try_to_json(&self) -> Result<serde_json::Value, EvalError> {
3438 Ok(match self {
3439 Value::Null => serde_json::Value::Null,
3440 Value::Bool(b) => serde_json::Value::Bool(*b),
3441 Value::Int(n) => serde_json::json!(n),
3442 Value::Float(f) => serde_json::json!(f),
3443 Value::String(s) => serde_json::Value::String(s.chars.to_string()),
3444 Value::Path(p) => serde_json::Value::String(p.to_string()),
3445 Value::List(items) => {
3446 let mut out = Vec::with_capacity(items.len());
3447 for v in items.iter() {
3448 out.push(v.try_to_json()?);
3449 }
3450 serde_json::Value::Array(out)
3451 }
3452 Value::Attrs(attrs) => {
3453 if let Some(v) = attrs.get("outPath").or_else(|| attrs.get("__toString")) {
3459 return v.try_to_json();
3460 }
3461 let mut map = serde_json::Map::new();
3462 for (k, v) in attrs.iter() {
3463 map.insert(k.clone(), v.try_to_json()?);
3464 }
3465 serde_json::Value::Object(map)
3466 }
3467 Value::Lambda(_) => {
3468 return Err(EvalError::TypeError(
3469 "cannot convert a function to JSON".to_string(),
3470 ))
3471 }
3472 Value::Builtin(b) => {
3473 return Err(EvalError::TypeError(format!(
3474 "cannot convert a function to JSON (builtin '{}')",
3475 b.name
3476 )))
3477 }
3478 Value::Thunk(thunk) => {
3479 let forced = thunk.force(&|expr, env| crate::eval::eval_expr(expr, env))?;
3482 forced.try_to_json()?
3483 }
3484 })
3485 }
3486
3487 pub fn to_json_with_context(
3494 &self,
3495 ctx: &mut StringContext,
3496 ) -> Result<serde_json::Value, EvalError> {
3497 Ok(match self {
3498 Value::Null => serde_json::Value::Null,
3499 Value::Bool(b) => serde_json::Value::Bool(*b),
3500 Value::Int(n) => serde_json::json!(n),
3501 Value::Float(f) => serde_json::json!(f),
3502 Value::String(s) => {
3503 ctx.merge(&s.context);
3504 serde_json::Value::String(s.chars.to_string())
3505 }
3506 Value::Path(_) => {
3507 let (str, c) = self.coerce_to_string_copy_to_store()?;
3508 ctx.merge(&c);
3509 serde_json::Value::String(str)
3510 }
3511 Value::List(items) => {
3512 let mut arr = Vec::with_capacity(items.len());
3513 for v in items.iter() {
3514 let fv = crate::eval::force_value(v)?;
3515 arr.push(fv.to_json_with_context(ctx)?);
3516 }
3517 serde_json::Value::Array(arr)
3518 }
3519 Value::Attrs(attrs) => {
3520 if attrs.get("__toString").is_some() || attrs.get("outPath").is_some() {
3523 let (s, c) = self.coerce_to_string_copy_to_store()?;
3524 ctx.merge(&c);
3525 return Ok(serde_json::Value::String(s));
3526 }
3527 let mut map = serde_json::Map::new();
3528 for (k, v) in attrs.iter() {
3529 let fv = crate::eval::force_value(v)?;
3530 map.insert(k.clone(), fv.to_json_with_context(ctx)?);
3531 }
3532 serde_json::Value::Object(map)
3533 }
3534 Value::Thunk(_) => {
3535 let forced = crate::eval::force_value(self)?;
3536 forced.to_json_with_context(ctx)?
3537 }
3538 other => {
3539 return Err(EvalError::TypeError(format!(
3540 "cannot serialize {} to JSON (__structuredAttrs)",
3541 other.type_name()
3542 )));
3543 }
3544 })
3545 }
3546
3547 #[must_use]
3549 pub fn type_name(&self) -> &'static str {
3550 match self {
3551 Value::Null => "null",
3552 Value::Bool(_) => "bool",
3553 Value::Int(_) => "int",
3554 Value::Float(_) => "float",
3555 Value::String(_) => "string",
3556 Value::Path(_) => "path",
3557 Value::List(_) => "list",
3558 Value::Attrs(_) => "set",
3559 Value::Lambda(_) => "lambda",
3560 Value::Builtin(_) => "lambda",
3561 Value::Thunk(thunk) => {
3562 match thunk.force(&|expr, env| crate::eval::eval_expr(expr, env)) {
3564 Ok(v) => v.type_name(),
3565 Err(_) => "thunk",
3566 }
3567 }
3568 }
3569 }
3570
3571 pub fn as_bool(&self) -> Result<bool, EvalError> {
3592 match self {
3593 Value::Bool(b) => Ok(*b),
3594 Value::Thunk(thunk) => {
3595 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_bool()
3596 }
3597 _ if in_promise_eval() => Ok(false),
3601 _ => Err(EvalError::TypeMismatch { expected: "bool", got: self.type_name() }),
3602 }
3603 }
3604
3605 pub fn as_int(&self) -> Result<i64, EvalError> {
3607 match self {
3608 Value::Int(n) => Ok(*n),
3609 Value::Thunk(thunk) => {
3610 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.as_int()
3611 }
3612 _ if in_promise_eval() => Ok(0),
3615 _ => Err(EvalError::TypeMismatch { expected: "int", got: self.type_name() }),
3616 }
3617 }
3618
3619 pub fn as_string(&self) -> Result<&str, EvalError> {
3621 match self {
3622 Value::String(s) => Ok(&s.chars),
3623 Value::Thunk(_) => Err(EvalError::TypeError(
3624 "thunk in as_string: force first via force_value()".into(),
3625 )),
3626 _ if in_promise_eval() => Ok(""),
3627 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3628 }
3629 }
3630
3631 pub fn as_nix_string(&self) -> Result<&NixString, EvalError> {
3633 match self {
3634 Value::String(ns) => Ok(ns),
3635 Value::Thunk(_) => Err(EvalError::TypeError(
3636 "thunk in as_nix_string: force first via force_value()".into(),
3637 )),
3638 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3639 }
3640 }
3641
3642 pub fn to_str(&self) -> Result<String, EvalError> {
3646 match self {
3647 Value::String(s) => Ok(s.chars.to_string()),
3648 Value::Thunk(thunk) => {
3649 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3650 forced.to_str()
3651 }
3652 _ if in_promise_eval() => Ok(String::new()),
3653 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3654 }
3655 }
3656
3657 pub fn to_nix_string(&self) -> Result<NixString, EvalError> {
3660 match self {
3661 Value::String(s) => Ok((**s).clone()),
3662 Value::Thunk(thunk) => {
3663 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3664 forced.to_nix_string()
3665 }
3666 _ if in_promise_eval() => Ok(NixString::plain("")),
3667 _ => Err(EvalError::TypeMismatch { expected: "string", got: self.type_name() }),
3668 }
3669 }
3670
3671 pub fn as_attrs(&self) -> Result<&NixAttrs, EvalError> {
3680 match self {
3681 Value::Attrs(a) => Ok(a),
3682 Value::Thunk(_) => Err(EvalError::TypeError(
3683 "thunk in as_attrs: force first via force_value() or use to_attrs()".into(),
3684 )),
3685 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3686 }
3687 }
3688
3689 pub fn as_list(&self) -> Result<&[Value], EvalError> {
3691 match self {
3692 Value::List(l) => Ok(l.as_slice()),
3693 Value::Thunk(_) => Err(EvalError::TypeError(
3694 "thunk in as_list: force first via force_value()".into(),
3695 )),
3696 _ => Err(crate::eval::attach_trace(
3697 EvalError::TypeMismatch { expected: "list", got: self.type_name() }
3698 )),
3699 }
3700 }
3701
3702 pub fn to_attrs(&self) -> Result<NixAttrs, EvalError> {
3704 match self {
3705 Value::Attrs(a) => Ok((**a).clone()),
3706 Value::Thunk(thunk) => {
3707 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3708 forced.to_attrs()
3709 }
3710 _ if in_promise_eval() => Ok(NixAttrs::new()),
3716 _ => Err(EvalError::TypeMismatch { expected: "set", got: self.type_name() }),
3717 }
3718 }
3719
3720 pub fn to_list(&self) -> Result<Vec<Value>, EvalError> {
3722 match self {
3723 Value::List(l) => Ok((**l).0.clone()),
3724 Value::Thunk(thunk) => {
3725 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env))?;
3726 forced.to_list()
3727 }
3728 _ if in_promise_eval() => Ok(Vec::new()),
3731 _ => Err(EvalError::TypeMismatch { expected: "list", got: self.type_name() }),
3732 }
3733 }
3734
3735 pub fn coerce_to_path(&self, context: &str) -> Result<String, EvalError> {
3741 match self {
3742 Value::Path(p) => Ok(p.to_string()),
3743 Value::String(ns) => Ok(ns.chars.to_string()),
3744 Value::Attrs(attrs) => {
3745 if let Some(out_path) = attrs.get("outPath") {
3746 let forced = crate::eval::force_value(out_path)?;
3747 forced.coerce_to_path(context)
3748 } else {
3749 Err(EvalError::TypeError(format!(
3750 "{context}: expected path or string, got set without outPath"
3751 )))
3752 }
3753 }
3754 _ => Err(EvalError::TypeError(format!(
3755 "{context}: expected path or string, got {}",
3756 self.type_name()
3757 ))),
3758 }
3759 }
3760
3761 pub fn coerce_to_realized_path(&self, context: &str) -> Result<String, EvalError> {
3785 match self {
3786 Value::Attrs(attrs) => {
3789 if let Some((drv_path, out_path)) = derivation_drv_and_out(attrs)? {
3790 self.realize_if_absent(&drv_path, &out_path, context)?;
3791 return Ok(out_path);
3792 }
3793 }
3794 Value::String(ns) => {
3801 let out_path = ns.chars.to_string();
3802 if let Some(drv_path) = out_path_needs_realize(&out_path, &ns.context) {
3803 self.realize_if_absent(&drv_path, &out_path, context)?;
3804 }
3805 return Ok(out_path);
3806 }
3807 _ => {}
3808 }
3809 self.coerce_to_path(context)
3810 }
3811
3812 fn realize_if_absent(
3817 &self,
3818 drv_path: &str,
3819 out_path: &str,
3820 context: &str,
3821 ) -> Result<(), EvalError> {
3822 let read_path = crate::path::materialize_str(out_path);
3825 if std::path::Path::new(&read_path).exists() {
3826 return Ok(());
3827 }
3828 match crate::realize::realize_output(drv_path, out_path) {
3829 Ok(true) | Ok(false) => Ok(()),
3830 Err(msg) => Err(EvalError::IoError {
3831 context: context.to_string(),
3832 message: format!(
3833 "import-from-derivation: realizing {drv_path} -> {out_path}: {msg}"
3834 ),
3835 }),
3836 }
3837 }
3838
3839 pub fn to_float(&self) -> Result<f64, EvalError> {
3841 match self {
3842 Value::Float(f) => Ok(*f),
3843 Value::Int(n) => Ok(*n as f64),
3844 Value::Thunk(thunk) => {
3845 thunk.force(&|e, env| crate::eval::eval_expr(e, env))?.to_float()
3846 }
3847 _ => Err(EvalError::TypeMismatch { expected: "number", got: self.type_name() }),
3848 }
3849 }
3850
3851 pub fn coerce_to_string(&self) -> Result<(String, StringContext), EvalError> {
3869 self.coerce_to_string_impl(false)
3870 }
3871
3872 pub fn coerce_to_string_copy_to_store(
3882 &self,
3883 ) -> Result<(String, StringContext), EvalError> {
3884 self.coerce_to_string_impl(true)
3885 }
3886
3887 fn coerce_to_string_impl(
3888 &self,
3889 copy_to_store: bool,
3890 ) -> Result<(String, StringContext), EvalError> {
3891 let mut ctx = StringContext::new();
3892 let s = match self {
3893 Value::String(ns) => {
3894 ctx.merge(&ns.context);
3895 ns.chars.to_string()
3896 }
3897 Value::Path(p) => {
3898 let raw: &str = &**p;
3899 if copy_to_store {
3900 let pb = std::path::Path::new(raw);
3920 let abs = if pb.is_absolute() {
3921 pb.to_path_buf()
3922 } else if let Some(dir) = crate::eval::current_eval_dir() {
3923 dir.join(pb)
3924 } else {
3925 std::env::current_dir()
3926 .map_err(|e| EvalError::IoError {
3927 context: format!("copy-to-store coercion of {raw}"),
3928 message: e.to_string(),
3929 })?
3930 .join(pb)
3931 };
3932 let read_abs = crate::path::materialize(&abs);
3939 let canon = read_abs.canonicalize().map_err(|_| {
3940 EvalError::TypeError(format!(
3941 "path '{}' does not exist",
3942 abs.display()
3943 ))
3944 })?;
3945 let name = crate::path::source_name_for_read_dir(&canon)
3962 .or_else(|| {
3963 canon
3964 .file_name()
3965 .map(|n| sui_compat::source::strip_store_hash_prefix(
3966 &n.to_string_lossy()).to_string())
3967 })
3968 .unwrap_or_else(|| "source".to_string());
3969 let src = sui_compat::source::nar_hash_source_tree(&canon, &name)
3970 .map_err(|e| {
3971 EvalError::TypeError(format!(
3972 "copy-to-store coercion of '{}': {e}",
3973 canon.display()
3974 ))
3975 })?;
3976 ctx.add_plain(src.store_path.clone());
3977 src.store_path
3978 } else {
3979 ctx.add_plain(raw.to_string());
3980 raw.to_string()
3981 }
3982 }
3983 Value::Int(n) => n.to_string(),
3984 Value::Float(f) => format!("{f:.6}"),
3990 Value::Bool(true) => "1".to_string(),
3991 Value::Bool(false) => String::new(),
3992 Value::Null => String::new(),
3993 Value::Attrs(attrs) => {
3994 if let Some(to_str) = attrs.get("__toString") {
3995 let result =
3996 crate::eval::apply(to_str.clone(), Value::Attrs(attrs.clone()))?;
3997 let forced = crate::eval::force_value(&result)?;
3998 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
3999 ctx.merge(&c);
4000 s
4001 } else if let Some(out_path) = attrs.get("outPath") {
4002 let forced = crate::eval::force_value(out_path)?;
4003 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
4004 ctx.merge(&c);
4005 s
4006 } else {
4007 return Err(EvalError::TypeError(
4008 "cannot coerce set to string (no __toString or outPath)".into(),
4009 ));
4010 }
4011 }
4012 Value::List(items) => {
4013 let mut parts = Vec::new();
4014 for item in items.iter() {
4015 let forced = crate::eval::force_value(item)?;
4016 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
4017 ctx.merge(&c);
4018 parts.push(s);
4019 }
4020 parts.join(" ")
4021 }
4022 Value::Thunk(_) => {
4023 let forced = crate::eval::force_value(self)?;
4025 let (s, c) = forced.coerce_to_string_impl(copy_to_store)?;
4026 ctx.merge(&c);
4027 s
4028 }
4029 other => {
4030 return Err(EvalError::TypeError(format!(
4031 "cannot coerce {} to string",
4032 other.type_name()
4033 )));
4034 }
4035 };
4036 Ok((s, ctx))
4037 }
4038}
4039
4040impl From<&serde_json::Value> for Value {
4043 fn from(json: &serde_json::Value) -> Self {
4044 match json {
4045 serde_json::Value::Null => Value::Null,
4046 serde_json::Value::Bool(b) => Value::Bool(*b),
4047 serde_json::Value::Number(n) => {
4048 if let Some(i) = n.as_i64() {
4049 Value::Int(i)
4050 } else {
4051 Value::Float(n.as_f64().unwrap_or(0.0))
4052 }
4053 }
4054 serde_json::Value::String(s) => Value::string(s.clone()),
4055 serde_json::Value::Array(arr) => {
4056 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
4057 }
4058 serde_json::Value::Object(obj) => {
4059 let mut attrs = NixAttrs::new();
4060 for (k, v) in obj {
4061 attrs.insert(k.clone(), Value::from(v));
4062 }
4063 Value::Attrs(Rc::new(attrs))
4064 }
4065 }
4066 }
4067}
4068
4069impl From<&toml::Value> for Value {
4070 fn from(v: &toml::Value) -> Self {
4071 match v {
4072 toml::Value::String(s) => Value::string(s.clone()),
4073 toml::Value::Integer(n) => Value::Int(*n),
4074 toml::Value::Float(f) => Value::Float(*f),
4075 toml::Value::Boolean(b) => Value::Bool(*b),
4076 toml::Value::Array(arr) => {
4077 Value::List(Rc::new(NixList::new(arr.iter().map(Value::from).collect())))
4078 }
4079 toml::Value::Table(t) => {
4080 let mut attrs = NixAttrs::new();
4081 for (k, val) in t {
4082 attrs.insert(k.clone(), Value::from(val));
4083 }
4084 Value::Attrs(Rc::new(attrs))
4085 }
4086 toml::Value::Datetime(dt) => Value::string(dt.to_string()),
4087 }
4088 }
4089}
4090
4091
4092impl From<bool> for Value {
4095 fn from(b: bool) -> Self {
4096 Value::Bool(b)
4097 }
4098}
4099
4100impl From<i64> for Value {
4101 fn from(n: i64) -> Self {
4102 Value::Int(n)
4103 }
4104}
4105
4106impl From<f64> for Value {
4107 fn from(f: f64) -> Self {
4108 Value::Float(f)
4109 }
4110}
4111
4112impl From<NixString> for Value {
4113 fn from(s: NixString) -> Self {
4114 Value::String(Rc::new(s))
4115 }
4116}
4117
4118impl From<NixAttrs> for Value {
4119 fn from(attrs: NixAttrs) -> Self {
4120 Value::Attrs(Rc::new(attrs))
4121 }
4122}
4123
4124impl From<Vec<Value>> for Value {
4125 fn from(list: Vec<Value>) -> Self {
4126 Value::List(Rc::new(NixList::new(list)))
4127 }
4128}
4129
4130impl PartialEq for Value {
4131 fn eq(&self, other: &Self) -> bool {
4132 if let (Value::Thunk(a), Value::Thunk(b)) = (self, other) {
4134 if Rc::ptr_eq(&a.0, &b.0) { return true; }
4135 }
4136 let l = self.demand().unwrap_or(Concrete::Null);
4139 let r = other.demand().unwrap_or(Concrete::Null);
4140 l == r
4141 }
4142}
4143
4144impl fmt::Display for Value {
4145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4146 match self {
4147 Value::Null => write!(f, "null"),
4148 Value::Bool(b) => write!(f, "{b}"),
4149 Value::Int(n) => write!(f, "{n}"),
4150 Value::Float(n) => write!(f, "{}", sui_compat::versions::cppnix_format_float(*n)),
4151 Value::String(s) => write!(f, "\"{}\"", s.chars.replace('\\', "\\\\").replace('"', "\\\"")),
4152 Value::Path(p) => write!(f, "{p}"),
4153 Value::List(items) => {
4154 write!(f, "[ ")?;
4155 for item in items.iter() {
4156 write!(f, "{item} ")?;
4157 }
4158 write!(f, "]")
4159 }
4160 Value::Attrs(attrs) => {
4161 write!(f, "{{ ")?;
4162 for (k, v) in attrs.iter() {
4163 write!(f, "{k} = {v}; ")?;
4164 }
4165 write!(f, "}}")
4166 }
4167 Value::Lambda(_) => write!(f, "<<lambda>>"),
4168 Value::Builtin(b) => write!(f, "<<builtin {}>>" , b.name),
4169 Value::Thunk(thunk) => {
4170 match thunk.force(&|e, env| crate::eval::eval_expr(e, env)) {
4171 Ok(v) => write!(f, "{v}"),
4172 Err(_) => write!(f, "<<thunk:error>>"),
4173 }
4174 }
4175 }
4176 }
4177}
4178
4179#[cfg(test)]
4180mod tests {
4181 use super::*;
4182 use std::rc::Rc;
4183
4184 #[test]
4190 #[ignore = "measurement, not a gate: run with --ignored --nocapture"]
4191 fn measure_hamt_vs_flat_attrset_cost() {
4192 use crate::value::census::rss_bytes;
4193 const N: usize = 300_000;
4194 const ENTRIES: usize = 4; let syms: Vec<Symbol> = (0..ENTRIES).map(|i| intern(&format!("k{i}"))).collect();
4197
4198 let base = rss_bytes();
4199 let mut hamts: Vec<FxHashMap<Symbol, Value>> = Vec::with_capacity(N);
4200 for _ in 0..N {
4201 let mut m = FxHashMap::default();
4202 for s in &syms { m.insert(*s, Value::Int(1)); }
4203 hamts.push(m);
4204 }
4205 let after_hamt = rss_bytes();
4206
4207 let mut flats: Vec<std::collections::HashMap<Symbol, Value>> = Vec::with_capacity(N);
4208 for _ in 0..N {
4209 let mut m = std::collections::HashMap::with_capacity(ENTRIES);
4210 for s in &syms { m.insert(*s, Value::Int(1)); }
4211 flats.push(m);
4212 }
4213 let after_flat = rss_bytes();
4214
4215 let hamt_cost = after_hamt.saturating_sub(base);
4216 let flat_cost = after_flat.saturating_sub(after_hamt);
4217 eprintln!("N={N} entries={ENTRIES}");
4218 eprintln!(" im_rc HAMT : {} B total, {} B/map", hamt_cost, hamt_cost / N as u64);
4219 eprintln!(" std flat : {} B total, {} B/map", flat_cost, flat_cost / N as u64);
4220 if flat_cost > 0 {
4221 eprintln!(" ratio : {:.2}x", hamt_cost as f64 / flat_cost as f64);
4222 }
4223 std::hint::black_box((&hamts, &flats));
4224 }
4225
4226 #[test]
4227 fn value_is_16_bytes() {
4228 assert_eq!(std::mem::size_of::<Value>(), 16);
4229 }
4230
4231 #[test]
4244 fn overlay_carries_attr_positions_from_both_sides() {
4245 let tbl = |file: &str, key: &str, off: u32| {
4246 let mut t = crate::pos::AttrPositions::new(Some(std::path::PathBuf::from(file)));
4247 t.insert(intern(key), off);
4248 Rc::new(t)
4249 };
4250 let mk = |file: &str, key: &str, off: u32| {
4254 let mut a = NixAttrs::new();
4255 a.insert(key.to_string(), Value::Int(1));
4256 a.set_positions(tbl(file, key, off));
4257 a
4258 };
4259
4260 let left_only = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "other", 22));
4263 assert_eq!(
4264 left_only.pos_entry(intern("modules")),
4265 Some((Some(std::path::PathBuf::from("/l.nix")), 11)),
4266 );
4267
4268 let both = mk("/l.nix", "modules", 11).overlay(mk("/r.nix", "modules", 22));
4270 assert_eq!(
4271 both.pos_entry(intern("modules")),
4272 Some((Some(std::path::PathBuf::from("/r.nix")), 22)),
4273 );
4274
4275 assert_eq!(both.pos_entry(intern("nope")), None);
4277 }
4278
4279 #[test]
4282 fn to_json_null() {
4283 assert_eq!(Value::Null.to_json(), serde_json::Value::Null);
4284 }
4285
4286 #[test]
4287 fn to_json_bool() {
4288 assert_eq!(Value::Bool(true).to_json(), serde_json::Value::Bool(true));
4289 assert_eq!(Value::Bool(false).to_json(), serde_json::Value::Bool(false));
4290 }
4291
4292 #[test]
4293 fn to_json_int() {
4294 assert_eq!(Value::Int(42).to_json(), serde_json::json!(42));
4295 }
4296
4297 #[test]
4298 fn to_json_float() {
4299 assert_eq!(Value::Float(3.14).to_json(), serde_json::json!(3.14));
4300 }
4301
4302 #[test]
4303 fn to_json_string() {
4304 assert_eq!(
4305 Value::string("hello").to_json(),
4306 serde_json::Value::String("hello".to_string()),
4307 );
4308 }
4309
4310 #[test]
4311 fn to_json_path() {
4312 assert_eq!(
4313 Value::Path(Box::new(SmolStr::from("/nix/store"))).to_json(),
4314 serde_json::Value::String("/nix/store".to_string()),
4315 );
4316 }
4317
4318 #[test]
4319 fn to_json_list() {
4320 let v = Value::list(vec![Value::Int(1), Value::Bool(true)]);
4321 assert_eq!(v.to_json(), serde_json::json!([1, true]));
4322 }
4323
4324 #[test]
4325 fn to_json_attrs() {
4326 let mut attrs = NixAttrs::new();
4327 attrs.insert("a".to_string(), Value::Int(1));
4328 let v = Value::Attrs(Rc::new(attrs));
4329 assert_eq!(v.to_json(), serde_json::json!({"a": 1}));
4330 }
4331
4332 fn mk_drv_attrs(out_path: &str, extra_key: &str, extra_val: i64) -> Value {
4335 let mut a = NixAttrs::new();
4336 a.insert("type".to_string(), Value::string("derivation"));
4337 a.insert("outPath".to_string(), Value::string(out_path));
4338 a.insert(extra_key.to_string(), Value::Int(extra_val));
4339 Value::Attrs(Rc::new(a))
4340 }
4341
4342 #[test]
4343 fn derivations_same_outpath_differing_attrs_are_equal() {
4344 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4351 let b = mk_drv_attrs("/nix/store/x-foo", "bar", 2);
4352 assert!(a == b, "same-outPath derivations must compare equal");
4353 assert!(!(a != b));
4354 }
4355
4356 #[test]
4357 fn derivations_differing_outpath_are_unequal() {
4358 let a = mk_drv_attrs("/nix/store/x-foo", "foo", 1);
4359 let b = mk_drv_attrs("/nix/store/y-foo", "foo", 1);
4360 assert!(a != b, "different-outPath derivations must compare unequal");
4361 }
4362
4363 #[test]
4364 fn non_derivation_attrs_with_outpath_use_structural_eq() {
4365 let mut a = NixAttrs::new();
4368 a.insert("outPath".to_string(), Value::string("/nix/store/x"));
4369 a.insert("foo".to_string(), Value::Int(1));
4370 let mut b = NixAttrs::new();
4371 b.insert("outPath".to_string(), Value::string("/nix/store/x"));
4372 b.insert("foo".to_string(), Value::Int(2));
4373 assert!(
4374 Value::Attrs(Rc::new(a)) != Value::Attrs(Rc::new(b)),
4375 "non-derivation attrs with equal outPath but differing foo must be unequal",
4376 );
4377 }
4378
4379 #[test]
4385 fn attrs_eq_borrow_result_matches_multi_key() {
4386 let mk = || {
4389 let mut inner = NixAttrs::new();
4390 inner.insert("n".to_string(), Value::Int(7));
4391 let mut a = NixAttrs::new();
4392 a.insert("a".to_string(), Value::Int(1));
4393 a.insert("b".to_string(), Value::string("two"));
4394 a.insert("c".to_string(), Value::Attrs(Rc::new(inner)));
4395 Value::Attrs(Rc::new(a))
4396 };
4397 assert!(mk() == mk(), "equal multi-key attrsets must compare equal (borrow path)");
4398
4399 let mut b = NixAttrs::new();
4401 b.insert("a".to_string(), Value::Int(1));
4402 b.insert("b".to_string(), Value::string("TWO"));
4403 let mut a2 = NixAttrs::new();
4404 a2.insert("a".to_string(), Value::Int(1));
4405 a2.insert("b".to_string(), Value::string("two"));
4406 assert!(
4407 Value::Attrs(Rc::new(a2)) != Value::Attrs(Rc::new(b)),
4408 "attrsets differing in one value must be unequal (borrow path)",
4409 );
4410
4411 let mut a3 = NixAttrs::new();
4413 a3.insert("a".to_string(), Value::Int(1));
4414 let mut b3 = NixAttrs::new();
4415 b3.insert("a".to_string(), Value::Int(1));
4416 b3.insert("extra".to_string(), Value::Int(9));
4417 assert!(
4418 Value::Attrs(Rc::new(a3)) != Value::Attrs(Rc::new(b3)),
4419 "attrsets differing in key set must be unequal (borrow path)",
4420 );
4421 }
4422
4423 #[test]
4424 fn attrs_eq_borrow_does_not_force_or_throw_on_shared_thunk() {
4425 let boom = Value::Thunk(Thunk::new_native(|| {
4436 Err(EvalError::Throw("kaboom".to_string()))
4437 }));
4438 let mut a = NixAttrs::new();
4439 a.insert("x".to_string(), Value::Int(1));
4440 a.insert("t".to_string(), boom.clone()); let mut b = NixAttrs::new();
4442 b.insert("x".to_string(), Value::Int(2)); b.insert("t".to_string(), boom);
4444 let va = Value::Attrs(Rc::new(a));
4448 let vb = Value::Attrs(Rc::new(b));
4449 assert!(va != vb, "differ on x → unequal, throwing thunk must not abort eq");
4450 }
4451
4452 #[test]
4453 fn attrs_eq_borrow_overlay_still_compares() {
4454 let mut base = NixAttrs::new();
4458 base.insert("a".to_string(), Value::Int(1));
4459 let mut over = NixAttrs::new();
4460 over.insert("b".to_string(), Value::Int(2));
4461 let merged = base.overlay(over);
4464 let mut flat = NixAttrs::new();
4465 flat.insert("a".to_string(), Value::Int(1));
4466 flat.insert("b".to_string(), Value::Int(2));
4467 assert!(
4468 Value::Attrs(Rc::new(merged)) == Value::Attrs(Rc::new(flat)),
4469 "overlay and equivalent flat attrset must compare equal (borrow path)",
4470 );
4471 }
4472
4473 #[test]
4474 fn to_json_lambda() {
4475 let root = rnix::Root::parse("x: x");
4477 let expr = root.tree().expr().unwrap();
4478 let lambda = match expr {
4479 rnix::ast::Expr::Lambda(l) => l,
4480 _ => panic!("expected lambda"),
4481 };
4482 let closure = Closure {
4483 param: lambda.param().unwrap(),
4484 body: lambda.body().unwrap(),
4485 env: Env::new(),
4486 };
4487 assert_eq!(
4488 Value::Lambda(Rc::new(closure)).to_json(),
4489 serde_json::Value::String("<lambda>".to_string()),
4490 );
4491 }
4492
4493 #[test]
4494 fn to_json_builtin() {
4495 let b = BuiltinFn {
4496 name: "test",
4497 func: Rc::new(|_| Ok(Value::Null)),
4498 };
4499 assert_eq!(
4500 Value::Builtin(Box::new(b)).to_json(),
4501 serde_json::Value::String("<builtin test>".to_string()),
4502 );
4503 }
4504
4505 #[test]
4508 fn type_name_null() { assert_eq!(Value::Null.type_name(), "null"); }
4509
4510 #[test]
4511 fn type_name_bool() { assert_eq!(Value::Bool(false).type_name(), "bool"); }
4512
4513 #[test]
4514 fn type_name_int() { assert_eq!(Value::Int(0).type_name(), "int"); }
4515
4516 #[test]
4517 fn type_name_float() { assert_eq!(Value::Float(0.0).type_name(), "float"); }
4518
4519 #[test]
4520 fn type_name_string() { assert_eq!(Value::string("").type_name(), "string"); }
4521
4522 #[test]
4523 fn type_name_path() { assert_eq!(Value::Path(Box::new(SmolStr::from(""))).type_name(), "path"); }
4524
4525 #[test]
4526 fn type_name_list() { assert_eq!(Value::list(vec![]).type_name(), "list"); }
4527
4528 #[test]
4529 fn type_name_set() { assert_eq!(Value::Attrs(Rc::new(NixAttrs::new())).type_name(), "set"); }
4530
4531 #[test]
4532 fn type_name_lambda() {
4533 let root = rnix::Root::parse("x: x");
4534 let expr = root.tree().expr().unwrap();
4535 let lambda = match expr {
4536 rnix::ast::Expr::Lambda(l) => l,
4537 _ => panic!("expected lambda"),
4538 };
4539 let closure = Closure {
4540 param: lambda.param().unwrap(),
4541 body: lambda.body().unwrap(),
4542 env: Env::new(),
4543 };
4544 assert_eq!(Value::Lambda(Rc::new(closure)).type_name(), "lambda");
4545 }
4546
4547 #[test]
4548 fn type_name_builtin() {
4549 let b = BuiltinFn {
4550 name: "t",
4551 func: Rc::new(|_| Ok(Value::Null)),
4552 };
4553 assert_eq!(Value::Builtin(Box::new(b)).type_name(), "lambda");
4554 }
4555
4556 #[test]
4559 fn as_bool_error_on_non_bool() {
4560 assert!(Value::Int(1).as_bool().is_err());
4561 assert!(Value::string("true").as_bool().is_err());
4562 }
4563
4564 #[test]
4565 fn as_int_error_on_non_int() {
4566 assert!(Value::Bool(true).as_int().is_err());
4567 assert!(Value::Float(1.0).as_int().is_err());
4568 }
4569
4570 #[test]
4571 fn as_string_error_on_non_string() {
4572 assert!(Value::Int(42).as_string().is_err());
4573 assert!(Value::Null.as_string().is_err());
4574 }
4575
4576 #[test]
4577 fn as_attrs_error_on_non_attrs() {
4578 assert!(Value::Int(1).as_attrs().is_err());
4579 assert!(Value::list(vec![]).as_attrs().is_err());
4580 }
4581
4582 #[test]
4583 fn as_list_error_on_non_list() {
4584 assert!(Value::Int(1).as_list().is_err());
4585 assert!(Value::Attrs(Rc::new(NixAttrs::new())).as_list().is_err());
4586 }
4587
4588 #[test]
4591 fn concat_lists_uniquely_owned_reuses_and_is_correct() {
4592 let left = Value::list(vec![Value::Int(1), Value::Int(2)]);
4594 assert!(left.is_uniquely_owned_list());
4595 let right = [Value::Int(3), Value::Int(4)];
4596 let out = super::concat_lists(left, &right).unwrap();
4597 assert_eq!(
4598 out.as_list().unwrap(),
4599 &[Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
4600 );
4601 }
4602
4603 #[test]
4604 fn concat_lists_shared_left_is_left_untouched_and_correct() {
4605 let shared = Rc::new(NixList::new(vec![Value::Int(1), Value::Int(2)]));
4608 let left = Value::List(Rc::clone(&shared));
4609 assert!(!left.is_uniquely_owned_list());
4610 let right = [Value::Int(3)];
4611 let out = super::concat_lists(left, &right).unwrap();
4612 assert_eq!(
4613 out.as_list().unwrap(),
4614 &[Value::Int(1), Value::Int(2), Value::Int(3)]
4615 );
4616 assert_eq!(&*shared, &[Value::Int(1), Value::Int(2)]);
4618 }
4619
4620 #[test]
4621 fn concat_lists_empty_operands() {
4622 let out = super::concat_lists(Value::list(vec![]), &[]).unwrap();
4623 assert!(out.as_list().unwrap().is_empty());
4624 let out2 = super::concat_lists(Value::list(vec![Value::Int(9)]), &[]).unwrap();
4625 assert_eq!(out2.as_list().unwrap(), &[Value::Int(9)]);
4626 let out3 = super::concat_lists(Value::list(vec![]), &[Value::Int(9)]).unwrap();
4627 assert_eq!(out3.as_list().unwrap(), &[Value::Int(9)]);
4628 }
4629
4630 #[test]
4631 fn concat_lists_non_list_left_errors() {
4632 assert!(super::concat_lists(Value::Int(1), &[]).is_err());
4633 }
4634
4635 #[test]
4636 fn concat_lists_preserves_element_identity() {
4637 let inner = Rc::new(NixString::plain("x"));
4639 let a = Value::String(Rc::clone(&inner));
4640 let left = Value::list(vec![a]);
4641 let out = super::concat_lists(left, &[]).unwrap();
4642 if let Value::String(rc) = &out.as_list().unwrap()[0] {
4643 assert!(Rc::ptr_eq(rc, &inner), "element Rc identity preserved");
4644 } else {
4645 panic!("expected string element");
4646 }
4647 }
4648
4649 #[test]
4652 fn to_float_coerces_int() {
4653 assert_eq!(Value::Int(5).to_float().unwrap(), 5.0);
4654 assert_eq!(Value::Float(2.5).to_float().unwrap(), 2.5);
4655 assert!(Value::string("x").to_float().is_err());
4656 }
4657
4658 #[test]
4661 fn partial_eq_int_float_cross() {
4662 assert_eq!(Value::Int(3), Value::Float(3.0));
4663 assert_eq!(Value::Float(3.0), Value::Int(3));
4664 assert_ne!(Value::Int(3), Value::Float(3.5));
4665 }
4666
4667 #[test]
4668 fn partial_eq_different_types_not_equal() {
4669 assert_ne!(Value::Int(1), Value::string("1"));
4670 assert_ne!(Value::Bool(true), Value::Int(1));
4671 assert_ne!(Value::Null, Value::Bool(false));
4672 assert_ne!(Value::list(vec![]), Value::Attrs(Rc::new(NixAttrs::new())));
4673 }
4674
4675 #[test]
4678 fn display_null() { assert_eq!(format!("{}", Value::Null), "null"); }
4679
4680 #[test]
4681 fn display_bool() {
4682 assert_eq!(format!("{}", Value::Bool(true)), "true");
4683 assert_eq!(format!("{}", Value::Bool(false)), "false");
4684 }
4685
4686 #[test]
4687 fn display_int() { assert_eq!(format!("{}", Value::Int(42)), "42"); }
4688
4689 #[test]
4690 fn display_float() {
4691 let s = format!("{}", Value::Float(3.14));
4692 assert!(s.contains("3.14"));
4693 }
4694
4695 #[test]
4696 fn display_string() {
4697 assert_eq!(format!("{}", Value::string("hi")), "\"hi\"");
4698 }
4699
4700 #[test]
4701 fn display_string_with_escapes() {
4702 let v = Value::string("a\"b\\c");
4703 let s = format!("{v}");
4704 assert!(s.contains("\\\""));
4705 assert!(s.contains("\\\\"));
4706 }
4707
4708 #[test]
4709 fn display_path() {
4710 assert_eq!(format!("{}", Value::Path(Box::new(SmolStr::from("/foo")))), "/foo");
4711 }
4712
4713 #[test]
4714 fn display_list() {
4715 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
4716 assert_eq!(format!("{v}"), "[ 1 2 ]");
4717 }
4718
4719 #[test]
4720 fn display_attrs() {
4721 let mut attrs = NixAttrs::new();
4722 attrs.insert("x".to_string(), Value::Int(1));
4723 let v = Value::Attrs(Rc::new(attrs));
4724 assert_eq!(format!("{v}"), "{ x = 1; }");
4725 }
4726
4727 #[test]
4728 fn display_lambda() {
4729 let root = rnix::Root::parse("x: x");
4730 let expr = root.tree().expr().unwrap();
4731 let lambda = match expr {
4732 rnix::ast::Expr::Lambda(l) => l,
4733 _ => panic!("expected lambda"),
4734 };
4735 let closure = Closure {
4736 param: lambda.param().unwrap(),
4737 body: lambda.body().unwrap(),
4738 env: Env::new(),
4739 };
4740 assert_eq!(format!("{}", Value::Lambda(Rc::new(closure))), "<<lambda>>");
4741 }
4742
4743 #[test]
4744 fn display_builtin() {
4745 let b = BuiltinFn {
4746 name: "add",
4747 func: Rc::new(|_| Ok(Value::Null)),
4748 };
4749 assert_eq!(format!("{}", Value::Builtin(Box::new(b))), "<<builtin add>>");
4750 }
4751
4752 #[test]
4755 fn nixattrs_update_merging() {
4756 let mut a = NixAttrs::new();
4757 a.insert("x".to_string(), Value::Int(1));
4758 a.insert("y".to_string(), Value::Int(2));
4759 let mut b = NixAttrs::new();
4760 b.insert("y".to_string(), Value::Int(99));
4761 b.insert("z".to_string(), Value::Int(3));
4762 let merged = a.update(&b);
4763 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
4764 assert_eq!(merged.get("y"), Some(&Value::Int(99)));
4765 assert_eq!(merged.get("z"), Some(&Value::Int(3)));
4766 assert_eq!(merged.len(), 3);
4767 }
4768
4769 #[test]
4770 fn nixattrs_contains_key() {
4771 let mut a = NixAttrs::new();
4772 a.insert("foo".to_string(), Value::Null);
4773 assert!(a.contains_key("foo"));
4774 assert!(!a.contains_key("bar"));
4775 }
4776
4777 #[test]
4780 fn env_lookup_through_parent_chain() {
4781 let mut root = Env::new();
4782 root.bind("a".to_string(), Value::Int(1));
4783 let mut child = root.child();
4784 child.bind("b".to_string(), Value::Int(2));
4785 let grandchild = child.child();
4786 assert_eq!(grandchild.lookup("a"), Some(Value::Int(1)));
4788 assert_eq!(grandchild.lookup("b"), Some(Value::Int(2)));
4789 assert_eq!(grandchild.lookup("c"), None);
4790 }
4791
4792 #[test]
4793 fn env_with_scope_lookup() {
4794 let mut attrs = NixAttrs::new();
4795 attrs.insert("x".to_string(), Value::Int(42));
4796 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4797 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
4798 assert_eq!(env.lookup("y"), None);
4799 }
4800
4801 #[test]
4802 fn env_local_shadows_with_scope() {
4803 let mut attrs = NixAttrs::new();
4804 attrs.insert("x".to_string(), Value::Int(1));
4805 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
4806 env.bind("x".to_string(), Value::Int(99));
4807 assert_eq!(env.lookup("x"), Some(Value::Int(99)));
4808 }
4809
4810 #[test]
4813 fn string_context_merge_combines_elements() {
4814 let mut ctx_a = StringContext::new();
4815 ctx_a.add_plain("/nix/store/aaa".to_string());
4816 let mut ctx_b = StringContext::new();
4817 ctx_b.add_plain("/nix/store/bbb".to_string());
4818 ctx_a.merge(&ctx_b);
4819 assert_eq!(ctx_a.len(), 2);
4820 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/aaa"))));
4821 assert!(ctx_a.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/bbb"))));
4822 }
4823
4824 #[test]
4825 fn string_context_merge_deduplicates() {
4826 let mut ctx = StringContext::new();
4827 ctx.add_plain("/nix/store/same".to_string());
4828 ctx.add_plain("/nix/store/same".to_string());
4829 assert_eq!(ctx.len(), 1);
4830 }
4831
4832 #[test]
4833 fn string_context_mixed_element_types() {
4834 let mut ctx = StringContext::new();
4835 ctx.add_plain("/nix/store/foo".to_string());
4836 ctx.add_output("/nix/store/bar.drv".to_string(), "out".to_string());
4837 ctx.add_drv_deep("/nix/store/baz.drv".to_string());
4838 assert_eq!(ctx.len(), 3);
4839 assert!(!ctx.is_empty());
4840 }
4841
4842 #[test]
4843 fn string_context_new_is_empty() {
4844 let ctx = StringContext::new();
4845 assert!(ctx.is_empty());
4846 assert_eq!(ctx.len(), 0);
4847 }
4848
4849 #[test]
4850 fn string_context_merge_zero_elements() {
4851 let mut ctx_a = StringContext::new();
4852 let ctx_b = StringContext::new();
4853 ctx_a.merge(&ctx_b);
4854 assert!(ctx_a.is_empty());
4855 }
4856
4857 #[test]
4858 fn string_context_merge_one_element() {
4859 let mut ctx = StringContext::new();
4860 let mut other = StringContext::new();
4861 other.add_plain("/nix/store/only".to_string());
4862 ctx.merge(&other);
4863 assert_eq!(ctx.len(), 1);
4864 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/only"))));
4865 }
4866
4867 #[test]
4868 fn string_context_merge_two_elements() {
4869 let mut ctx = StringContext::new();
4870 ctx.add_plain("/nix/store/a".to_string());
4871 let mut other = StringContext::new();
4872 other.add_plain("/nix/store/b".to_string());
4873 ctx.merge(&other);
4874 assert_eq!(ctx.len(), 2);
4875 }
4876
4877 #[test]
4878 fn string_context_merge_five_elements() {
4879 let mut ctx = StringContext::new();
4880 for i in 0..5 {
4881 ctx.add_plain(format!("/nix/store/path-{i}"));
4882 }
4883 assert_eq!(ctx.len(), 5);
4884 for i in 0..5 {
4885 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from(format!("/nix/store/path-{i}").as_str()))));
4886 }
4887 }
4888
4889 #[test]
4890 fn string_context_insert_deduplicates() {
4891 let mut ctx = StringContext::new();
4892 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4893 ctx.insert(ContextElement::Plain(SmolStr::from("/nix/store/dup")));
4894 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4895 ctx.insert(ContextElement::Output { drv: SmolStr::from("/nix/store/x.drv"), output: SmolStr::from("out") });
4896 assert_eq!(ctx.len(), 2);
4897 }
4898
4899 #[test]
4900 fn nix_string_plain_has_no_context() {
4901 let s = NixString::plain("hello");
4902 assert!(!s.has_context());
4903 assert_eq!(s.as_str(), "hello");
4904 }
4905
4906 #[test]
4907 fn nix_string_with_context_reports_context() {
4908 let mut ctx = StringContext::new();
4909 ctx.add_plain("/nix/store/xyz".to_string());
4910 let s = NixString::with_context("hello", ctx);
4911 assert!(s.has_context());
4912 assert_eq!(s.as_str(), "hello");
4913 }
4914
4915 #[test]
4916 fn nix_string_display_shows_chars_only() {
4917 let mut ctx = StringContext::new();
4918 ctx.add_plain("/nix/store/abc".to_string());
4919 let s = NixString::with_context("visible", ctx);
4920 assert_eq!(format!("{s}"), "visible");
4921 }
4922
4923 #[test]
4924 fn nix_string_struct_eq_includes_context() {
4925 let plain = NixString::plain("hello");
4926 let mut ctx = StringContext::new();
4927 ctx.add_plain("/nix/store/xxx".to_string());
4928 let with_ctx = NixString::with_context("hello", ctx);
4929 assert_ne!(plain, with_ctx);
4931 }
4932
4933 #[test]
4934 fn value_string_eq_ignores_context() {
4935 let plain = Value::String(Rc::new(NixString::plain("hello")));
4936 let mut ctx = StringContext::new();
4937 ctx.add_plain("/nix/store/xxx".to_string());
4938 let with_ctx = Value::String(Rc::new(NixString::with_context("hello", ctx)));
4939 assert_eq!(plain, with_ctx);
4941 }
4942
4943 #[test]
4946 fn env_nested_with_inner_wins() {
4947 let mut outer_attrs = NixAttrs::new();
4948 outer_attrs.insert("x".to_string(), Value::Int(1));
4949 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4950 let mut inner_attrs = NixAttrs::new();
4951 inner_attrs.insert("x".to_string(), Value::Int(2));
4952 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4953 assert_eq!(inner.lookup("x"), Some(Value::Int(2)));
4954 }
4955
4956 #[test]
4957 fn env_nested_with_fallback_to_outer() {
4958 let mut outer_attrs = NixAttrs::new();
4959 outer_attrs.insert("x".to_string(), Value::Int(1));
4960 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4961 let mut inner_attrs = NixAttrs::new();
4962 inner_attrs.insert("y".to_string(), Value::Int(2));
4963 let inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4964 assert_eq!(inner.lookup("x"), Some(Value::Int(1)));
4965 assert_eq!(inner.lookup("y"), Some(Value::Int(2)));
4966 }
4967
4968 #[test]
4969 fn env_lexical_binding_wins_over_all_with_scopes() {
4970 let mut outer_attrs = NixAttrs::new();
4971 outer_attrs.insert("x".to_string(), Value::Int(1));
4972 let outer = Env::new().with_scope(Value::Attrs(Rc::new(outer_attrs)));
4973 let mut inner_attrs = NixAttrs::new();
4974 inner_attrs.insert("x".to_string(), Value::Int(2));
4975 let mut inner = outer.child().with_scope(Value::Attrs(Rc::new(inner_attrs)));
4976 inner.bind("x".to_string(), Value::Int(99));
4977 assert_eq!(inner.lookup("x"), Some(Value::Int(99)));
4978 }
4979
4980 #[test]
4981 fn env_parent_lexical_wins_over_child_with_scope() {
4982 let mut root = Env::new();
4983 root.bind("x".to_string(), Value::Int(10));
4984 let mut child_attrs = NixAttrs::new();
4985 child_attrs.insert("x".to_string(), Value::Int(20));
4986 let child = root.child().with_scope(Value::Attrs(Rc::new(child_attrs)));
4987 assert_eq!(child.lookup("x"), Some(Value::Int(10)));
4988 }
4989
4990 #[test]
4991 fn env_deeply_nested_with_scopes_three_levels() {
4992 let mut a = NixAttrs::new();
4993 a.insert("x".to_string(), Value::Int(1));
4994 let env1 = Env::new().with_scope(Value::Attrs(Rc::new(a)));
4995
4996 let mut b = NixAttrs::new();
4997 b.insert("y".to_string(), Value::Int(2));
4998 let env2 = env1.child().with_scope(Value::Attrs(Rc::new(b)));
4999
5000 let mut c = NixAttrs::new();
5001 c.insert("z".to_string(), Value::Int(3));
5002 let env3 = env2.child().with_scope(Value::Attrs(Rc::new(c)));
5003
5004 assert_eq!(env3.lookup("x"), Some(Value::Int(1)));
5005 assert_eq!(env3.lookup("y"), Some(Value::Int(2)));
5006 assert_eq!(env3.lookup("z"), Some(Value::Int(3)));
5007 assert_eq!(env3.lookup("w"), None);
5008 }
5009
5010 #[test]
5011 fn env_with_scope_does_not_pollute_bindings() {
5012 let mut attrs = NixAttrs::new();
5015 attrs.insert("x".to_string(), Value::Int(42));
5016 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
5017 assert!(env.0.bindings.get(&intern("x")).is_none());
5019 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
5021 }
5022
5023 #[test]
5024 fn env_lexical_binding_not_in_with_scopes() {
5025 let mut env = Env::new();
5027 env.bind("x".to_string(), Value::Int(42));
5028 assert!(env.0.with_scopes.is_empty());
5030 assert_eq!(env.lookup("x"), Some(Value::Int(42)));
5032 }
5033
5034 #[test]
5035 fn env_child_inherits_eval_file() {
5036 let mut env = Env::new();
5037 env.set_eval_file(Some(std::path::PathBuf::from("/foo/bar.nix")));
5038 let child = env.child();
5039 assert_eq!(child.eval_file().cloned(), Some(std::path::PathBuf::from("/foo/bar.nix")));
5040 }
5041
5042 #[test]
5043 fn env_new_has_no_parent_no_with() {
5044 let env = Env::new();
5045 assert_eq!(env.lookup("anything"), None);
5046 assert!(env.eval_file().is_none());
5047 }
5048
5049 #[test]
5052 fn thunk_new_suspended_is_not_evaluated() {
5053 let root = rnix::Root::parse("42");
5054 let expr = root.tree().expr().unwrap();
5055 let thunk = Thunk::new_suspended(expr, Env::new());
5056 assert!(!thunk.is_evaluated());
5057 }
5058
5059 #[test]
5060 fn thunk_new_evaluated_is_evaluated() {
5061 let thunk = Thunk::new_evaluated(Value::Int(42));
5062 assert!(thunk.is_evaluated());
5063 }
5064
5065 #[test]
5066 fn thunk_force_evaluates_suspended() {
5067 let root = rnix::Root::parse("42");
5068 let expr = root.tree().expr().unwrap();
5069 let thunk = Thunk::new_suspended(expr, Env::new());
5070 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5071 assert!(result.is_ok());
5072 assert_eq!(result.unwrap(), Value::Int(42));
5073 assert!(thunk.is_evaluated());
5074 }
5075
5076 #[test]
5077 fn thunk_force_memoizes_result() {
5078 let root = rnix::Root::parse("1 + 2");
5079 let expr = root.tree().expr().unwrap();
5080 let thunk = Thunk::new_suspended(expr, Env::new());
5081 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5082 let r2 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5083 assert_eq!(r1, Value::Int(3));
5084 assert_eq!(r2, Value::Int(3));
5085 }
5086
5087 #[test]
5088 fn thunk_force_already_evaluated_returns_value() {
5089 let thunk = Thunk::new_evaluated(Value::Bool(true));
5090 let result = thunk.force(&|_, _| panic!("should not be called"));
5091 assert_eq!(result.unwrap(), Value::Bool(true));
5092 }
5093
5094 #[test]
5103 fn thunk_force_concrete_skips_redundant_store_but_caches() {
5104 let root = rnix::Root::parse("1 + 2");
5107 let expr = root.tree().expr().unwrap();
5108 let thunk = Thunk::new_suspended(expr, Env::new());
5109
5110 let r1 = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
5111 assert_eq!(r1, Value::Int(3));
5112 assert!(thunk.is_evaluated());
5113
5114 assert_eq!(thunk.peek().map(|c| c.clone().into_value()), Some(Value::Int(3)));
5117
5118 let r2 = thunk.force(&|_, _| panic!("re-force must hit the cache, not re-eval")).unwrap();
5120 assert_eq!(r2, Value::Int(3));
5121 }
5122
5123 #[test]
5124 fn thunk_blackhole_detects_infinite_recursion() {
5125 let root = rnix::Root::parse("42");
5126 let expr = root.tree().expr().unwrap();
5127 let thunk = Thunk::new_suspended(expr, Env::new());
5128
5129 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
5132
5133 let result = thunk.force(&|_, _| Ok(Value::Null));
5134 assert!(result.is_err());
5135 let err_msg = format!("{}", result.unwrap_err());
5136 assert!(err_msg.contains("infinite recursion"));
5137 }
5138
5139 #[test]
5140 fn thunk_update_env_replaces_suspended_env() {
5141 let root = rnix::Root::parse("x");
5142 let expr = root.tree().expr().unwrap();
5143 let thunk = Thunk::new_suspended(expr, Env::new());
5144
5145 let mut new_env = Env::new();
5146 new_env.bind("x".to_string(), Value::Int(99));
5147 thunk.update_env(&new_env);
5148
5149 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5150 assert_eq!(result.unwrap(), Value::Int(99));
5151 }
5152
5153 #[test]
5154 fn thunk_update_env_noop_when_evaluated() {
5155 let thunk = Thunk::new_evaluated(Value::Int(1));
5156 let mut new_env = Env::new();
5157 new_env.bind("x".to_string(), Value::Int(99));
5158 thunk.update_env(&new_env);
5159 assert_eq!(
5160 thunk.force(&|_, _| panic!("should not be called")).unwrap(),
5161 Value::Int(1),
5162 );
5163 }
5164
5165 #[test]
5166 fn thunk_debug_suspended() {
5167 let root = rnix::Root::parse("42");
5168 let expr = root.tree().expr().unwrap();
5169 let thunk = Thunk::new_suspended(expr, Env::new());
5170 assert_eq!(format!("{thunk:?}"), "<thunk>");
5171 }
5172
5173 #[test]
5174 fn thunk_debug_evaluated() {
5175 let thunk = Thunk::new_evaluated(Value::Int(42));
5176 let dbg = format!("{thunk:?}");
5177 assert!(dbg.contains("42"));
5178 }
5179
5180 #[test]
5181 fn thunk_error_restores_suspended_state() {
5182 let root = rnix::Root::parse("nonexistent_var");
5183 let expr = root.tree().expr().unwrap();
5184 let thunk = Thunk::new_suspended(expr, Env::new());
5185
5186 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5187 assert!(result.is_err());
5188 assert!(!thunk.is_evaluated());
5190 let dbg = format!("{thunk:?}");
5191 assert_eq!(dbg, "<thunk>");
5192 }
5193
5194 #[test]
5195 fn thunk_inherit_select_forces_and_selects() {
5196 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5197 let expr = root.tree().expr().unwrap();
5198 let source = Thunk::new_suspended(expr, Env::new());
5199 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5200 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5201 assert_eq!(result.unwrap(), Value::Int(42));
5202 assert!(thunk.is_evaluated());
5203 }
5204
5205 #[test]
5206 fn thunk_inherit_select_missing_attr_errors() {
5207 let root = rnix::Root::parse(r#"{ x = 42; }"#);
5208 let expr = root.tree().expr().unwrap();
5209 let source = Thunk::new_suspended(expr, Env::new());
5210 let thunk = Thunk::new_inherit_select(source, "y".to_string());
5211 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5212 assert!(result.is_err());
5213 assert!(!thunk.is_evaluated());
5215 }
5216
5217 #[test]
5218 fn thunk_inherit_select_non_attrs_source_errors() {
5219 let root = rnix::Root::parse("42");
5220 let expr = root.tree().expr().unwrap();
5221 let source = Thunk::new_suspended(expr, Env::new());
5222 let thunk = Thunk::new_inherit_select(source, "x".to_string());
5223 let result = thunk.force(&|e, env| crate::eval::eval_expr(e, env));
5224 assert!(result.is_err());
5225 let msg = format!("{}", result.unwrap_err());
5226 assert!(msg.contains("not a set"));
5227 }
5228
5229 #[test]
5230 fn thunk_inherit_select_shares_source_thunk() {
5231 let root = rnix::Root::parse(r#"{ a = 1; b = 2; }"#);
5235 let expr = root.tree().expr().unwrap();
5236 let source = Thunk::new_suspended(expr, Env::new());
5237 let thunk_a = Thunk::new_inherit_select(source.clone(), "a".to_string());
5238 let thunk_b = Thunk::new_inherit_select(source.clone(), "b".to_string());
5239 let result_a = thunk_a.force(&|e, env| crate::eval::eval_expr(e, env));
5240 assert_eq!(result_a.unwrap(), Value::Int(1));
5241 assert!(source.is_evaluated());
5243 let result_b = thunk_b.force(&|e, env| crate::eval::eval_expr(e, env));
5245 assert_eq!(result_b.unwrap(), Value::Int(2));
5246 }
5247
5248 #[test]
5251 fn nixattrs_empty_operations() {
5252 let a = NixAttrs::new();
5253 assert!(a.is_empty());
5254 assert_eq!(a.len(), 0);
5255 assert_eq!(a.get("x"), None);
5256 assert!(!a.contains_key("x"));
5257 assert_eq!(a.keys().count(), 0);
5258 assert_eq!(a.iter().count(), 0);
5259 }
5260
5261 #[test]
5262 fn nixattrs_update_with_empty() {
5263 let mut a = NixAttrs::new();
5264 a.insert("x".to_string(), Value::Int(1));
5265 let b = NixAttrs::new();
5266 let merged = a.update(&b);
5267 assert_eq!(merged.len(), 1);
5268 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5269 }
5270
5271 #[test]
5272 fn nixattrs_update_empty_with_nonempty() {
5273 let a = NixAttrs::new();
5274 let mut b = NixAttrs::new();
5275 b.insert("x".to_string(), Value::Int(1));
5276 let merged = a.update(&b);
5277 assert_eq!(merged.len(), 1);
5278 assert_eq!(merged.get("x"), Some(&Value::Int(1)));
5279 }
5280
5281 #[test]
5282 fn nixattrs_keys_sorted_order() {
5283 let mut a = NixAttrs::new();
5284 a.insert("c".to_string(), Value::Int(3));
5285 a.insert("a".to_string(), Value::Int(1));
5286 a.insert("b".to_string(), Value::Int(2));
5287 let keys: Vec<String> = a.keys().collect();
5288 assert_eq!(keys, vec!["a", "b", "c"]);
5289 }
5290
5291 #[test]
5294 fn value_to_str_forces_thunks() {
5295 let root = rnix::Root::parse(r#""hello""#);
5296 let expr = root.tree().expr().unwrap();
5297 let thunk = Thunk::new_suspended(expr, Env::new());
5298 let val = Value::Thunk(thunk);
5299 assert_eq!(val.to_str().unwrap(), "hello");
5300 }
5301
5302 #[test]
5303 fn value_to_nix_string_forces_thunks() {
5304 let root = rnix::Root::parse(r#""world""#);
5305 let expr = root.tree().expr().unwrap();
5306 let thunk = Thunk::new_suspended(expr, Env::new());
5307 let val = Value::Thunk(thunk);
5308 let ns = val.to_nix_string().unwrap();
5309 assert_eq!(ns.as_str(), "world");
5310 assert!(!ns.has_context());
5311 }
5312
5313 #[test]
5314 fn value_to_attrs_forces_thunks() {
5315 let root = rnix::Root::parse("{ x = 1; }");
5316 let expr = root.tree().expr().unwrap();
5317 let thunk = Thunk::new_suspended(expr, Env::new());
5318 let val = Value::Thunk(thunk);
5319 let attrs = val.to_attrs().unwrap();
5320 assert_eq!(attrs.len(), 1);
5321 }
5322
5323 #[test]
5324 fn value_to_list_forces_thunks() {
5325 let root = rnix::Root::parse("[1 2 3]");
5326 let expr = root.tree().expr().unwrap();
5327 let thunk = Thunk::new_suspended(expr, Env::new());
5328 let val = Value::Thunk(thunk);
5329 let list = val.to_list().unwrap();
5330 assert_eq!(list.len(), 3);
5331 }
5332
5333 #[test]
5334 fn value_to_float_on_thunk() {
5335 let root = rnix::Root::parse("3.14");
5336 let expr = root.tree().expr().unwrap();
5337 let thunk = Thunk::new_suspended(expr, Env::new());
5338 let val = Value::Thunk(thunk);
5339 let f = val.to_float().unwrap();
5340 assert!((f - 3.14).abs() < f64::EPSILON);
5341 }
5342
5343 #[test]
5344 fn value_as_bool_on_thunk() {
5345 let root = rnix::Root::parse("true");
5346 let expr = root.tree().expr().unwrap();
5347 let thunk = Thunk::new_suspended(expr, Env::new());
5348 let val = Value::Thunk(thunk);
5349 assert!(val.as_bool().unwrap());
5350 }
5351
5352 #[test]
5353 fn value_as_int_on_thunk() {
5354 let root = rnix::Root::parse("42");
5355 let expr = root.tree().expr().unwrap();
5356 let thunk = Thunk::new_suspended(expr, Env::new());
5357 let val = Value::Thunk(thunk);
5358 assert_eq!(val.as_int().unwrap(), 42);
5359 }
5360
5361 #[test]
5362 fn value_string_constructor() {
5363 let v = Value::string("test");
5364 assert_eq!(v, Value::String(Rc::new(NixString::plain("test"))));
5365 }
5366
5367 #[test]
5368 fn value_partial_eq_null_null() {
5369 assert_eq!(Value::Null, Value::Null);
5370 }
5371
5372 #[test]
5373 fn value_partial_eq_lists_deep() {
5374 let a = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5375 let b = Value::list(vec![Value::Int(1), Value::list(vec![Value::Int(2)])]);
5376 assert_eq!(a, b);
5377 }
5378
5379 #[test]
5380 fn value_partial_eq_attrs_deep() {
5381 let mut a = NixAttrs::new();
5382 a.insert("x".to_string(), Value::Int(1));
5383 let mut b = NixAttrs::new();
5384 b.insert("x".to_string(), Value::Int(1));
5385 assert_eq!(Value::Attrs(Rc::new(a)), Value::Attrs(Rc::new(b)));
5386 }
5387
5388 #[test]
5391 fn eval_error_type_error_constructor() {
5392 let e = EvalError::type_error("oops");
5393 assert!(matches!(e, EvalError::TypeError(ref s) if s == "oops"));
5394 }
5395
5396 #[test]
5397 fn eval_error_type_mismatch_constructor() {
5398 let e = EvalError::type_mismatch("int", "string");
5399 match e {
5400 EvalError::TypeMismatch { expected, got } => {
5401 assert_eq!(expected, "int");
5402 assert_eq!(got, "string");
5403 }
5404 _ => panic!("expected TypeMismatch"),
5405 }
5406 }
5407
5408 #[test]
5409 fn eval_error_is_throw_yes_no() {
5410 assert!(EvalError::Throw("oops".into()).is_throw());
5411 assert!(!EvalError::TypeError("oops".into()).is_throw());
5412 assert!(!EvalError::AssertionFailed(String::new()).is_throw());
5413 }
5414
5415 #[test]
5416 fn eval_error_is_infinite_recursion_yes_no() {
5417 assert!(EvalError::InfiniteRecursion("loop".into()).is_infinite_recursion());
5418 assert!(!EvalError::DivisionByZero.is_infinite_recursion());
5419 assert!(!EvalError::Throw("x".into()).is_infinite_recursion());
5420 }
5421
5422 #[test]
5423 fn eval_error_display_undefined_var() {
5424 let s = format!("{}", EvalError::UndefinedVar("foo".into()));
5425 assert!(s.contains("undefined variable"));
5426 assert!(s.contains("foo"));
5427 }
5428
5429 #[test]
5430 fn eval_error_display_type_error() {
5431 let s = format!("{}", EvalError::TypeError("bad".into()));
5432 assert!(s.contains("type error"));
5433 assert!(s.contains("bad"));
5434 }
5435
5436 #[test]
5437 fn eval_error_display_attr_not_found() {
5438 let s = format!("{}", EvalError::AttrNotFound("x".into()));
5439 assert!(s.contains("attribute not found"));
5440 assert!(s.contains("x"));
5441 }
5442
5443 #[test]
5444 fn eval_error_display_type_mismatch() {
5445 let s = format!(
5446 "{}",
5447 EvalError::TypeMismatch { expected: "int", got: "string" }
5448 );
5449 assert!(s.contains("expected int"));
5450 assert!(s.contains("got string"));
5451 }
5452
5453 #[test]
5454 fn eval_error_display_assertion_failed() {
5455 let s = format!("{}", EvalError::AssertionFailed(String::new()));
5456 assert!(s.contains("assertion"));
5457 }
5458
5459 #[test]
5460 fn eval_error_display_division_by_zero() {
5461 let s = format!("{}", EvalError::DivisionByZero);
5462 assert!(s.contains("division by zero"));
5463 }
5464
5465 #[test]
5466 fn eval_error_display_infinite_recursion() {
5467 let s = format!("{}", EvalError::InfiniteRecursion("loop".into()));
5468 assert!(s.contains("infinite recursion"));
5469 assert!(s.contains("loop"));
5470 }
5471
5472 #[test]
5473 fn eval_error_display_io_error() {
5474 let s = format!(
5475 "{}",
5476 EvalError::IoError {
5477 context: "ctx".into(),
5478 message: "no such file".into(),
5479 }
5480 );
5481 assert!(s.contains("I/O"));
5482 assert!(s.contains("ctx"));
5483 assert!(s.contains("no such file"));
5484 }
5485
5486 #[test]
5487 fn eval_error_display_throw() {
5488 let s = format!("{}", EvalError::Throw("boom".into()));
5489 assert_eq!(s, "boom");
5490 }
5491
5492 #[test]
5493 fn eval_error_display_not_implemented() {
5494 let s = format!("{}", EvalError::NotImplemented("frob".into()));
5495 assert!(s.contains("not yet implemented"));
5496 assert!(s.contains("frob"));
5497 }
5498
5499 #[test]
5500 fn eval_error_display_parse_error() {
5501 let s = format!("{}", EvalError::ParseError("syntax".into()));
5502 assert!(s.contains("parse error"));
5503 assert!(s.contains("syntax"));
5504 }
5505
5506 #[test]
5507 fn eval_error_display_recursion_limit() {
5508 let s = format!(
5509 "{}",
5510 EvalError::RecursionLimit("max depth exceeded".into())
5511 );
5512 assert!(s.contains("recursion limit"));
5513 assert!(s.contains("max depth exceeded"));
5514 }
5515
5516 #[test]
5517 fn eval_error_partial_eq_same_variant() {
5518 assert_eq!(
5519 EvalError::UndefinedVar("x".into()),
5520 EvalError::UndefinedVar("x".into()),
5521 );
5522 assert_ne!(
5523 EvalError::UndefinedVar("x".into()),
5524 EvalError::UndefinedVar("y".into()),
5525 );
5526 assert_ne!(
5527 EvalError::UndefinedVar("x".into()),
5528 EvalError::AttrNotFound("x".into()),
5529 );
5530 }
5531
5532 #[test]
5535 fn context_element_display_plain() {
5536 let e = ContextElement::Plain("/nix/store/xyz".into());
5537 assert_eq!(format!("{e}"), "/nix/store/xyz");
5538 }
5539
5540 #[test]
5541 fn context_element_display_output() {
5542 let e = ContextElement::Output {
5543 drv: "/nix/store/abc.drv".into(),
5544 output: "out".into(),
5545 };
5546 assert_eq!(format!("{e}"), "/nix/store/abc.drv!out");
5547 }
5548
5549 #[test]
5550 fn context_element_display_drv_deep() {
5551 let e = ContextElement::DrvDeep("/nix/store/abc.drv".into());
5552 assert_eq!(format!("{e}"), "=/nix/store/abc.drv");
5553 }
5554
5555 #[test]
5558 fn string_context_iter_yields_all() {
5559 let mut ctx = StringContext::new();
5560 ctx.add_plain("/nix/store/aaa");
5561 ctx.add_plain("/nix/store/bbb");
5562 let count = ctx.iter().count();
5563 assert_eq!(count, 2);
5564 }
5565
5566 #[test]
5567 fn string_context_len_matches_set_size() {
5568 let mut ctx = StringContext::new();
5569 assert_eq!(ctx.len(), 0);
5570 ctx.add_plain("/nix/store/x");
5571 assert_eq!(ctx.len(), 1);
5572 ctx.add_output("/nix/store/y.drv", "out");
5573 assert_eq!(ctx.len(), 2);
5574 }
5575
5576 #[test]
5577 fn string_context_insert_raw_element() {
5578 let mut ctx = StringContext::new();
5579 ctx.insert(ContextElement::Plain("/nix/store/foo".into()));
5580 assert_eq!(ctx.len(), 1);
5581 }
5582
5583 #[test]
5584 fn string_context_default_is_empty() {
5585 let ctx = StringContext::default();
5586 assert!(ctx.is_empty());
5587 }
5588
5589 #[test]
5592 fn nix_string_as_ref_str() {
5593 let s = NixString::plain("hello");
5594 let r: &str = s.as_ref();
5595 assert_eq!(r, "hello");
5596 }
5597
5598 #[test]
5599 fn nix_string_deref_to_str_methods() {
5600 let s = NixString::plain("Hello World");
5601 assert_eq!(s.len(), 11);
5602 assert!(s.starts_with("Hello"));
5603 assert_eq!(s.to_uppercase(), "HELLO WORLD");
5605 }
5606
5607 #[test]
5610 fn nixattrs_remove_returns_value() {
5611 let mut a = NixAttrs::new();
5612 a.insert("x".into(), Value::Int(1));
5613 let removed = a.remove("x");
5614 assert_eq!(removed, Some(Value::Int(1)));
5615 assert!(!a.contains_key("x"));
5616 assert_eq!(a.remove("y"), None);
5617 }
5618
5619 #[test]
5620 fn nixattrs_values_iter() {
5621 let mut a = NixAttrs::new();
5622 a.insert("a".into(), Value::Int(1));
5623 a.insert("b".into(), Value::Int(2));
5624 let mut vs: Vec<&Value> = a.values().collect();
5625 vs.sort_by_key(|v| match v {
5626 Value::Int(n) => *n,
5627 _ => 0,
5628 });
5629 assert_eq!(vs, vec![&Value::Int(1), &Value::Int(2)]);
5630 }
5631
5632 #[test]
5633 fn nixattrs_iter_returns_sorted_pairs() {
5634 let mut a = NixAttrs::new();
5635 a.insert("zeta".into(), Value::Int(3));
5636 a.insert("alpha".into(), Value::Int(1));
5637 a.insert("mu".into(), Value::Int(2));
5638 let pairs: Vec<(String, &Value)> = a.iter().collect();
5639 assert_eq!(pairs[0].0, "alpha");
5640 assert_eq!(pairs[1].0, "mu");
5641 assert_eq!(pairs[2].0, "zeta");
5642 }
5643
5644 #[test]
5645 fn nixattrs_from_iterator() {
5646 let pairs = vec![
5647 ("a".to_string(), Value::Int(1)),
5648 ("b".to_string(), Value::Int(2)),
5649 ];
5650 let attrs: NixAttrs = pairs.into_iter().collect();
5651 assert_eq!(attrs.len(), 2);
5652 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5653 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5654 }
5655
5656 #[test]
5657 fn nixattrs_into_iterator_yields_owned() {
5658 let mut a = NixAttrs::new();
5659 a.insert("x".into(), Value::Int(42));
5660 let pairs: Vec<(String, Value)> = a.into_iter().collect();
5661 assert_eq!(pairs.len(), 1);
5662 assert_eq!(pairs[0].0, "x");
5663 assert_eq!(pairs[0].1, Value::Int(42));
5664 }
5665
5666 #[test]
5667 fn nixattrs_default_is_empty() {
5668 let a = NixAttrs::default();
5669 assert!(a.is_empty());
5670 }
5671
5672 #[test]
5675 fn value_from_bool() {
5676 assert_eq!(Value::from(true), Value::Bool(true));
5677 assert_eq!(Value::from(false), Value::Bool(false));
5678 }
5679
5680 #[test]
5681 fn value_from_i64() {
5682 assert_eq!(Value::from(42_i64), Value::Int(42));
5683 assert_eq!(Value::from(-1_i64), Value::Int(-1));
5684 }
5685
5686 #[test]
5687 fn value_from_f64() {
5688 assert_eq!(Value::from(2.5_f64), Value::Float(2.5));
5689 }
5690
5691 #[test]
5692 fn value_from_nix_string() {
5693 let v: Value = NixString::plain("hi").into();
5694 assert_eq!(v, Value::string("hi"));
5695 }
5696
5697 #[test]
5698 fn value_from_nix_attrs() {
5699 let mut a = NixAttrs::new();
5700 a.insert("x".into(), Value::Int(1));
5701 let v: Value = a.into();
5702 match v {
5703 Value::Attrs(_) => {}
5704 _ => panic!("expected Attrs"),
5705 }
5706 }
5707
5708 #[test]
5709 fn value_from_vec() {
5710 let v: Value = vec![Value::Int(1), Value::Int(2)].into();
5711 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
5712 }
5713
5714 #[test]
5715 fn value_default_is_null() {
5716 let v: Value = Value::default();
5717 assert_eq!(v, Value::Null);
5718 }
5719
5720 #[test]
5723 fn value_from_json_null() {
5724 let v = Value::from(&serde_json::Value::Null);
5725 assert_eq!(v, Value::Null);
5726 }
5727
5728 #[test]
5729 fn value_from_json_bool() {
5730 let v = Value::from(&serde_json::Value::Bool(true));
5731 assert_eq!(v, Value::Bool(true));
5732 }
5733
5734 #[test]
5735 fn value_from_json_int() {
5736 let v = Value::from(&serde_json::json!(42));
5737 assert_eq!(v, Value::Int(42));
5738 }
5739
5740 #[test]
5741 fn value_from_json_float() {
5742 let v = Value::from(&serde_json::json!(3.14));
5743 match v {
5744 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5745 _ => panic!("expected Float"),
5746 }
5747 }
5748
5749 #[test]
5750 fn value_from_json_string() {
5751 let v = Value::from(&serde_json::Value::String("hi".into()));
5752 assert_eq!(v, Value::string("hi"));
5753 }
5754
5755 #[test]
5756 fn value_from_json_array() {
5757 let v = Value::from(&serde_json::json!([1, true, "x"]));
5758 match v {
5759 Value::List(items) => {
5760 assert_eq!(items.len(), 3);
5761 assert_eq!(items[0], Value::Int(1));
5762 assert_eq!(items[1], Value::Bool(true));
5763 assert_eq!(items[2], Value::string("x"));
5764 }
5765 _ => panic!("expected List"),
5766 }
5767 }
5768
5769 #[test]
5770 fn value_from_json_object() {
5771 let v = Value::from(&serde_json::json!({"a": 1, "b": "x"}));
5772 match v {
5773 Value::Attrs(attrs) => {
5774 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5775 assert_eq!(attrs.get("b"), Some(&Value::string("x")));
5776 }
5777 _ => panic!("expected Attrs"),
5778 }
5779 }
5780
5781 #[test]
5782 fn value_from_json_nested() {
5783 let v = Value::from(&serde_json::json!({"outer": {"inner": [1, 2]}}));
5784 let json_back = v.to_json();
5785 assert_eq!(json_back, serde_json::json!({"outer": {"inner": [1, 2]}}));
5786 }
5787
5788 #[test]
5791 fn value_from_toml_string() {
5792 let t = toml::Value::String("hi".into());
5793 assert_eq!(Value::from(&t), Value::string("hi"));
5794 }
5795
5796 #[test]
5797 fn value_from_toml_int() {
5798 let t = toml::Value::Integer(42);
5799 assert_eq!(Value::from(&t), Value::Int(42));
5800 }
5801
5802 #[test]
5803 fn value_from_toml_float() {
5804 let t = toml::Value::Float(3.14);
5805 match Value::from(&t) {
5806 Value::Float(f) => assert!((f - 3.14).abs() < f64::EPSILON),
5807 _ => panic!("expected Float"),
5808 }
5809 }
5810
5811 #[test]
5812 fn value_from_toml_bool() {
5813 let t = toml::Value::Boolean(true);
5814 assert_eq!(Value::from(&t), Value::Bool(true));
5815 }
5816
5817 #[test]
5818 fn value_from_toml_array() {
5819 let t = toml::Value::Array(vec![
5820 toml::Value::Integer(1),
5821 toml::Value::Integer(2),
5822 ]);
5823 assert_eq!(
5824 Value::from(&t),
5825 Value::list(vec![Value::Int(1), Value::Int(2)]),
5826 );
5827 }
5828
5829 #[test]
5830 fn value_from_toml_table() {
5831 let mut tbl = toml::map::Map::new();
5832 tbl.insert("k".into(), toml::Value::Integer(7));
5833 let t = toml::Value::Table(tbl);
5834 match Value::from(&t) {
5835 Value::Attrs(attrs) => {
5836 assert_eq!(attrs.get("k"), Some(&Value::Int(7)));
5837 }
5838 _ => panic!("expected Attrs"),
5839 }
5840 }
5841
5842 #[test]
5843 fn value_from_toml_datetime_becomes_string() {
5844 let dt: toml::value::Datetime = "2024-01-01T00:00:00Z".parse().unwrap();
5846 let t = toml::Value::Datetime(dt);
5847 match Value::from(&t) {
5848 Value::String(_) => {}
5849 other => panic!("expected String, got {other:?}"),
5850 }
5851 }
5852
5853 #[test]
5856 fn coerce_to_path_from_path() {
5857 let v = Value::Path(Box::new("/foo".into()));
5858 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/foo");
5859 }
5860
5861 #[test]
5862 fn coerce_to_path_from_string() {
5863 let v = Value::string("/bar");
5864 assert_eq!(v.coerce_to_path("ctx").unwrap(), "/bar");
5865 }
5866
5867 #[test]
5875 fn out_path_needs_realize_matches_output_context() {
5876 let mut ctx = StringContext::new();
5879 ctx.add_output("/nix/store/aaa-thing.drv", "out");
5880 assert_eq!(
5881 super::out_path_needs_realize("/nix/store/bbb-thing", &ctx),
5882 Some("/nix/store/aaa-thing.drv".to_string()),
5883 );
5884 }
5885
5886 #[test]
5887 fn out_path_needs_realize_ignores_plain_context() {
5888 let mut ctx = StringContext::new();
5891 ctx.add_plain("/nix/store/ccc-plain");
5892 assert_eq!(super::out_path_needs_realize("/nix/store/ccc-plain", &ctx), None);
5893 }
5894
5895 #[test]
5896 fn out_path_needs_realize_ignores_non_store_path() {
5897 let mut ctx = StringContext::new();
5900 ctx.add_output("/nix/store/ddd.drv", "out");
5901 assert_eq!(super::out_path_needs_realize("/etc/passwd", &ctx), None);
5902 }
5903
5904 #[test]
5905 fn out_path_needs_realize_empty_context_is_none() {
5906 let ctx = StringContext::new();
5908 assert_eq!(super::out_path_needs_realize("/nix/store/eee-lit", &ctx), None);
5909 }
5910
5911 #[test]
5912 fn coerce_to_realized_path_present_output_is_passthrough() {
5913 let dir = std::env::temp_dir().join("sui-ifd-present-test");
5917 std::fs::create_dir_all(&dir).unwrap();
5918 let file = dir.join("out");
5919 std::fs::write(&file, b"present").unwrap();
5920 let present = file.to_string_lossy().to_string();
5921
5922 let mut ctx = StringContext::new();
5923 ctx.add_plain(&present);
5928 let v = Value::String(std::rc::Rc::new(NixString::with_context(
5929 present.as_str(),
5930 ctx,
5931 )));
5932 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), present);
5933 }
5934
5935 #[test]
5936 fn coerce_to_realized_path_absent_output_invokes_hook() {
5937 use std::sync::{Arc, Mutex};
5942 let seen: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
5943 let seen2 = seen.clone();
5944 let _guard = crate::realize::install_realize_hook(Box::new(move |drv, out| {
5945 seen2.lock().unwrap().push((drv.to_string(), out.to_string()));
5946 Ok(())
5947 }));
5948
5949 let out = "/nix/store/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz-ifd-absent";
5952 assert!(!std::path::Path::new(out).exists(), "test store path must be absent");
5953 let mut ctx = StringContext::new();
5954 ctx.add_output("/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv", "out");
5955 let v = Value::String(std::rc::Rc::new(NixString::with_context(out, ctx)));
5956
5957 assert_eq!(v.coerce_to_realized_path("readFile").unwrap(), out);
5959 let s = seen.lock().unwrap();
5960 assert_eq!(s.len(), 1, "realize hook should fire once for an absent output");
5961 assert_eq!(s[0].0, "/nix/store/qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq-ifd-absent.drv");
5962 assert_eq!(s[0].1, out);
5963 }
5964
5965 #[test]
5966 fn coerce_to_path_errors_on_int() {
5967 let v = Value::Int(1);
5968 let e = v.coerce_to_path("readFile").unwrap_err();
5969 match e {
5970 EvalError::TypeError(ref msg) => {
5971 assert!(msg.contains("readFile"));
5972 assert!(msg.contains("path or string"));
5973 assert!(msg.contains("int"));
5974 }
5975 _ => panic!("expected TypeError"),
5976 }
5977 }
5978
5979 #[test]
5980 fn coerce_to_path_errors_on_null() {
5981 let v = Value::Null;
5982 assert!(v.coerce_to_path("ctx").is_err());
5983 }
5984
5985 #[test]
5986 fn coerce_to_path_attrs_with_outpath() {
5987 let mut attrs = NixAttrs::new();
5988 attrs.insert("outPath".to_string(), Value::string("/nix/store/test"));
5989 let val = Value::Attrs(Rc::new(attrs));
5990 assert_eq!(val.coerce_to_path("test").unwrap(), "/nix/store/test");
5991 }
5992
5993 #[test]
5994 fn coerce_to_path_attrs_without_outpath_fails() {
5995 let attrs = NixAttrs::new();
5996 let val = Value::Attrs(Rc::new(attrs));
5997 assert!(val.coerce_to_path("test").is_err());
5998 }
5999
6000 #[test]
6003 fn coerce_to_string_string() {
6004 let v = Value::string("hello");
6005 let (s, _ctx) = v.coerce_to_string().unwrap();
6006 assert_eq!(s, "hello");
6007 }
6008
6009 #[test]
6010 fn coerce_to_string_path() {
6011 let v = Value::Path(Box::new("/foo".into()));
6012 let (s, ctx) = v.coerce_to_string().unwrap();
6013 assert_eq!(s, "/foo");
6014 assert!(!ctx.is_empty()); }
6016
6017 #[test]
6018 fn coerce_to_string_int() {
6019 let v = Value::Int(42);
6020 let (s, _ctx) = v.coerce_to_string().unwrap();
6021 assert_eq!(s, "42");
6022 }
6023
6024 #[test]
6025 fn coerce_to_string_float() {
6026 let v = Value::Float(3.14);
6028 let (s, _ctx) = v.coerce_to_string().unwrap();
6029 assert_eq!(s, "3.140000");
6030 }
6031
6032 #[test]
6033 fn coerce_to_string_bool_true() {
6034 let (s, _ctx) = Value::Bool(true).coerce_to_string().unwrap();
6035 assert_eq!(s, "1");
6036 }
6037
6038 #[test]
6039 fn coerce_to_string_bool_false() {
6040 let (s, _ctx) = Value::Bool(false).coerce_to_string().unwrap();
6041 assert_eq!(s, "");
6042 }
6043
6044 #[test]
6045 fn coerce_to_string_null() {
6046 let (s, _ctx) = Value::Null.coerce_to_string().unwrap();
6047 assert_eq!(s, "");
6048 }
6049
6050 #[test]
6051 fn coerce_to_string_attrs_with_outpath() {
6052 let mut attrs = NixAttrs::new();
6053 attrs.insert("outPath".to_string(), Value::string("/nix/store/abc"));
6054 let val = Value::Attrs(Rc::new(attrs));
6055 let (s, _ctx) = val.coerce_to_string().unwrap();
6056 assert_eq!(s, "/nix/store/abc");
6057 }
6058
6059 #[test]
6060 fn coerce_to_string_attrs_without_outpath_or_tostring_fails() {
6061 let attrs = NixAttrs::new();
6062 let val = Value::Attrs(Rc::new(attrs));
6063 assert!(val.coerce_to_string().is_err());
6064 }
6065
6066 #[test]
6067 fn coerce_to_string_lambda_fails() {
6068 let root = rnix::Root::parse("x: x");
6069 let expr = root.tree().expr().unwrap();
6070 let closure = Closure {
6071 param: match expr {
6072 rnix::ast::Expr::Lambda(ref l) => l.param().unwrap(),
6073 _ => panic!("expected lambda"),
6074 },
6075 body: match expr {
6076 rnix::ast::Expr::Lambda(ref l) => l.body().unwrap(),
6077 _ => panic!("expected lambda"),
6078 },
6079 env: Env::new(),
6080 };
6081 let val = Value::Lambda(Rc::new(closure));
6082 assert!(val.coerce_to_string().is_err());
6083 }
6084
6085 #[test]
6088 fn builtin_fn_debug_includes_name() {
6089 let b = BuiltinFn {
6090 name: "myFunc",
6091 func: Rc::new(|_| Ok(Value::Null)),
6092 };
6093 let s = format!("{b:?}");
6094 assert!(s.contains("myFunc"));
6095 assert!(s.contains("builtin"));
6096 }
6097
6098 #[test]
6101 fn thunk_force_chains_through_inner_thunks() {
6102 let inner_root = rnix::Root::parse("99");
6104 let inner_expr = inner_root.tree().expr().unwrap();
6105 let inner_thunk = Thunk::new_suspended(inner_expr, Env::new());
6106 let outer = Thunk::new_evaluated(Value::Thunk(inner_thunk));
6107 let result = outer.force(&|e, env| crate::eval::eval_expr(e, env));
6108 match result.unwrap() {
6113 Value::Thunk(_) | Value::Int(99) => {}
6114 other => panic!("unexpected: {other:?}"),
6115 }
6116 }
6117
6118 #[test]
6119 fn thunk_inherit_select_debug_format() {
6120 let root = rnix::Root::parse("{ x = 1; }");
6121 let expr = root.tree().expr().unwrap();
6122 let source = Thunk::new_suspended(expr, Env::new());
6123 let thunk = Thunk::new_inherit_select(source, "x");
6124 let s = format!("{thunk:?}");
6125 assert!(s.contains("inherit-select"));
6126 assert!(s.contains("x"));
6127 }
6128
6129 #[test]
6130 fn thunk_blackhole_debug_format() {
6131 let root = rnix::Root::parse("1");
6132 let expr = root.tree().expr().unwrap();
6133 let thunk = Thunk::new_suspended(expr, Env::new());
6134 *unsafe { &mut *thunk.0.repr.get() } = ThunkRepr::Blackhole;
6136 assert_eq!(format!("{thunk:?}"), "<blackhole>");
6137 }
6138
6139 #[test]
6142 fn value_display_thunk_evaluates() {
6143 let root = rnix::Root::parse("42");
6144 let expr = root.tree().expr().unwrap();
6145 let thunk = Thunk::new_suspended(expr, Env::new());
6146 let val = Value::Thunk(thunk);
6147 assert_eq!(format!("{val}"), "42");
6148 }
6149
6150 #[test]
6151 fn value_to_json_thunk_forces() {
6152 let root = rnix::Root::parse(r#""world""#);
6153 let expr = root.tree().expr().unwrap();
6154 let thunk = Thunk::new_suspended(expr, Env::new());
6155 let val = Value::Thunk(thunk);
6156 assert_eq!(val.to_json(), serde_json::Value::String("world".into()));
6157 }
6158
6159 #[test]
6160 fn value_type_name_thunk_forces() {
6161 let root = rnix::Root::parse("42");
6162 let expr = root.tree().expr().unwrap();
6163 let thunk = Thunk::new_suspended(expr, Env::new());
6164 let val = Value::Thunk(thunk);
6165 assert_eq!(val.type_name(), "int");
6166 }
6167
6168 #[test]
6171 fn as_string_errors_on_thunk() {
6172 let root = rnix::Root::parse(r#""x""#);
6173 let expr = root.tree().expr().unwrap();
6174 let thunk = Thunk::new_suspended(expr, Env::new());
6175 let val = Value::Thunk(thunk);
6176 let err = val.as_string().unwrap_err();
6177 match err {
6178 EvalError::TypeError(msg) => assert!(msg.contains("thunk")),
6179 _ => panic!("expected TypeError"),
6180 }
6181 }
6182
6183 #[test]
6184 fn as_nix_string_errors_on_thunk() {
6185 let root = rnix::Root::parse(r#""x""#);
6186 let expr = root.tree().expr().unwrap();
6187 let thunk = Thunk::new_suspended(expr, Env::new());
6188 let val = Value::Thunk(thunk);
6189 assert!(val.as_nix_string().is_err());
6190 }
6191
6192 #[test]
6193 fn as_attrs_errors_on_thunk() {
6194 let root = rnix::Root::parse("{}");
6195 let expr = root.tree().expr().unwrap();
6196 let thunk = Thunk::new_suspended(expr, Env::new());
6197 let val = Value::Thunk(thunk);
6198 assert!(val.as_attrs().is_err());
6199 }
6200
6201 #[test]
6202 fn as_list_errors_on_thunk() {
6203 let root = rnix::Root::parse("[]");
6204 let expr = root.tree().expr().unwrap();
6205 let thunk = Thunk::new_suspended(expr, Env::new());
6206 let val = Value::Thunk(thunk);
6207 assert!(val.as_list().is_err());
6208 }
6209
6210 #[test]
6213 fn as_nix_string_ok_on_string() {
6214 let v = Value::string("hi");
6215 let ns = v.as_nix_string().unwrap();
6216 assert_eq!(ns.as_str(), "hi");
6217 }
6218
6219 #[test]
6220 fn as_nix_string_errors_on_int() {
6221 let v = Value::Int(1);
6222 match v.as_nix_string() {
6223 Err(EvalError::TypeMismatch { expected, got }) => {
6224 assert_eq!(expected, "string");
6225 assert_eq!(got, "int");
6226 }
6227 _ => panic!("expected TypeMismatch"),
6228 }
6229 }
6230
6231 #[test]
6236 fn oncecell_cache_populated_after_force() {
6237 let root = rnix::Root::parse("42");
6238 let expr = root.tree().expr().unwrap();
6239 let thunk = Thunk::new_suspended(expr, Env::new());
6240 assert!(thunk.0.cache.get().is_none());
6242 let _ = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6243 assert!(thunk.0.cache.get().is_some());
6245 }
6246
6247 #[test]
6248 fn oncecell_cache_matches_force_result() {
6249 let root = rnix::Root::parse("1 + 2");
6250 let expr = root.tree().expr().unwrap();
6251 let thunk = Thunk::new_suspended(expr, Env::new());
6252 let forced = thunk.force(&|e, env| crate::eval::eval_expr(e, env)).unwrap();
6253 let cached = thunk.0.cache.get().unwrap();
6254 assert_eq!((**cached).clone().into_value(), forced);
6257 }
6258
6259 #[test]
6260 fn oncecell_new_evaluated_prepopulates_cache() {
6261 let thunk = Thunk::new_evaluated(Value::Int(77));
6262 let cached = thunk.0.cache.get().expect("cache should be pre-populated");
6264 assert_eq!(**cached, Concrete::Int(77));
6265 }
6266
6267 #[test]
6268 fn oncecell_is_evaluated_uses_cache() {
6269 let thunk = Thunk::new_evaluated(Value::Bool(false));
6270 assert!(thunk.is_evaluated());
6272 assert!(thunk.0.cache.get().is_some());
6273 }
6274
6275 #[test]
6276 fn oncecell_already_evaluated_returns_cached_without_repr() {
6277 let thunk = Thunk::new_evaluated(Value::Int(55));
6281 let result = thunk.force(&|_, _| panic!("evaluator should not be called"));
6282 assert_eq!(result.unwrap(), Value::Int(55));
6283 }
6284
6285 #[test]
6290 fn with_scope_created_with_empty_cache() {
6291 let thunk = Thunk::new_suspended(
6293 rnix::Root::parse("{}").tree().expr().unwrap(),
6294 Env::new(),
6295 );
6296 let env = Env::new().with_scope(Value::Thunk(thunk));
6297 let scope = &env.0.with_scopes[0];
6298 assert!(scope.cached.borrow().is_none());
6299 }
6300
6301 #[test]
6302 fn with_scope_concrete_pre_populates_cache() {
6303 let mut attrs = NixAttrs::new();
6305 attrs.insert("x".to_string(), Value::Int(1));
6306 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6307 let scope = &env.0.with_scopes[0];
6308 assert!(scope.cached.borrow().is_some());
6309 }
6310
6311 #[test]
6312 fn with_scope_first_lookup_populates_cache() {
6313 let mut attrs = NixAttrs::new();
6314 attrs.insert("x".to_string(), Value::Int(42));
6315 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6316 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6318 let _ = env.lookup("x");
6320 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6321 }
6322
6323 #[test]
6324 fn with_scope_second_lookup_uses_cache() {
6325 let mut attrs = NixAttrs::new();
6326 attrs.insert("x".to_string(), Value::Int(10));
6327 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6328 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6330 assert!(env.0.with_scopes[0].cached.borrow().is_some());
6331 assert_eq!(env.lookup("x"), Some(Value::Int(10)));
6333 }
6334
6335 #[test]
6336 fn with_scope_child_shares_cache_via_rc() {
6337 let mut attrs = NixAttrs::new();
6338 attrs.insert("shared".to_string(), Value::Int(7));
6339 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6340 let child = parent.child();
6341 let _ = parent.lookup("shared");
6343 assert!(child.0.with_scopes[0].cached.borrow().is_some());
6346 }
6347
6348 #[test]
6349 fn with_scope_innermost_checked_first() {
6350 let mut outer = NixAttrs::new();
6351 outer.insert("x".to_string(), Value::Int(1));
6352 outer.insert("y".to_string(), Value::Int(100));
6353 let mut inner = NixAttrs::new();
6354 inner.insert("x".to_string(), Value::Int(2));
6355 let env = Env::new()
6356 .with_scope(Value::Attrs(Rc::new(outer)))
6357 .with_scope(Value::Attrs(Rc::new(inner)));
6358 assert_eq!(env.lookup("x"), Some(Value::Int(2)));
6360 assert_eq!(env.lookup("y"), Some(Value::Int(100)));
6362 }
6363
6364 #[test]
6369 fn fxhashmap_nixattrs_new_creates_empty() {
6370 let a = NixAttrs::new();
6371 assert!(a.is_empty());
6372 assert_eq!(a.len(), 0);
6373 assert!(a.inner().is_empty());
6375 }
6376
6377 #[test]
6378 fn fxhashmap_insert_get_roundtrip_with_symbol_keys() {
6379 let mut a = NixAttrs::new();
6380 a.insert("mykey".to_string(), Value::Int(42));
6381 assert_eq!(a.get("mykey"), Some(&Value::Int(42)));
6382 }
6383
6384 #[test]
6385 fn fxhashmap_contains_key_with_interned_keys() {
6386 let mut a = NixAttrs::new();
6387 a.insert("alpha".to_string(), Value::Int(1));
6388 let sym = intern("alpha");
6389 assert!(a.inner().contains_key(&sym));
6390 let missing_sym = intern("beta");
6391 assert!(!a.inner().contains_key(&missing_sym));
6392 }
6393
6394 #[test]
6395 fn fxhashmap_remove_returns_value() {
6396 let mut a = NixAttrs::new();
6397 a.insert("key".to_string(), Value::Int(99));
6398 let removed = a.remove("key");
6399 assert_eq!(removed, Some(Value::Int(99)));
6400 assert!(a.is_empty());
6401 }
6402
6403 #[test]
6404 fn fxhashmap_keys_returns_sorted_strings() {
6405 let mut a = NixAttrs::new();
6406 a.insert("zulu".to_string(), Value::Int(1));
6407 a.insert("alpha".to_string(), Value::Int(2));
6408 a.insert("mike".to_string(), Value::Int(3));
6409 let keys: Vec<String> = a.keys().collect();
6410 assert_eq!(keys, vec!["alpha", "mike", "zulu"]);
6411 }
6412
6413 #[test]
6414 fn fxhashmap_iter_returns_sorted_string_value_pairs() {
6415 let mut a = NixAttrs::new();
6416 a.insert("b".to_string(), Value::Int(2));
6417 a.insert("a".to_string(), Value::Int(1));
6418 let pairs: Vec<(String, &Value)> = a.iter().collect();
6419 assert_eq!(pairs.len(), 2);
6420 assert_eq!(pairs[0].0, "a");
6421 assert_eq!(*pairs[0].1, Value::Int(1));
6422 assert_eq!(pairs[1].0, "b");
6423 assert_eq!(*pairs[1].1, Value::Int(2));
6424 }
6425
6426 #[test]
6427 fn fxhashmap_update_merges_correctly() {
6428 let mut left = NixAttrs::new();
6429 left.insert("a".to_string(), Value::Int(1));
6430 left.insert("b".to_string(), Value::Int(2));
6431 let mut right = NixAttrs::new();
6432 right.insert("b".to_string(), Value::Int(20));
6433 right.insert("c".to_string(), Value::Int(3));
6434 let merged = left.update(&right);
6435 assert_eq!(merged.get("a"), Some(&Value::Int(1)));
6436 assert_eq!(merged.get("b"), Some(&Value::Int(20))); assert_eq!(merged.get("c"), Some(&Value::Int(3)));
6438 assert_eq!(merged.len(), 3);
6439 }
6440
6441 #[test]
6442 fn fxhashmap_from_iterator_collects_with_interning() {
6443 let pairs = vec![
6444 ("x".to_string(), Value::Int(10)),
6445 ("y".to_string(), Value::Int(20)),
6446 ("z".to_string(), Value::Int(30)),
6447 ];
6448 let attrs: NixAttrs = pairs.into_iter().collect();
6449 assert_eq!(attrs.len(), 3);
6450 assert_eq!(attrs.get("x"), Some(&Value::Int(10)));
6451 assert_eq!(attrs.get("y"), Some(&Value::Int(20)));
6452 assert_eq!(attrs.get("z"), Some(&Value::Int(30)));
6453 let sym_x = intern("x");
6455 assert!(attrs.inner().contains_key(&sym_x));
6456 }
6457
6458 #[test]
6463 fn smallvec_context_empty() {
6464 let ctx = StringContext::new();
6465 assert!(ctx.is_empty());
6466 assert_eq!(ctx.len(), 0);
6467 assert_eq!(ctx.elements().len(), 0);
6468 }
6469
6470 #[test]
6471 fn smallvec_context_single_element_inline() {
6472 let mut ctx = StringContext::new();
6473 ctx.add_plain("/nix/store/single");
6474 assert_eq!(ctx.len(), 1);
6475 assert!(!ctx.is_empty());
6477 }
6478
6479 #[test]
6480 fn smallvec_context_two_elements_still_inline() {
6481 let mut ctx = StringContext::new();
6482 ctx.add_plain("/nix/store/one");
6483 ctx.add_output("/nix/store/two.drv", "out");
6484 assert_eq!(ctx.len(), 2);
6485 }
6486
6487 #[test]
6488 fn smallvec_context_three_plus_spills_to_heap() {
6489 let mut ctx = StringContext::new();
6490 ctx.add_plain("/nix/store/a");
6491 ctx.add_plain("/nix/store/b");
6492 ctx.add_drv_deep("/nix/store/c.drv");
6493 assert_eq!(ctx.len(), 3);
6494 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/a"))));
6496 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/b"))));
6497 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/c.drv"))));
6498 }
6499
6500 #[test]
6501 fn smallvec_context_merge_deduplicates() {
6502 let mut ctx1 = StringContext::new();
6503 ctx1.add_plain("/nix/store/dup");
6504 ctx1.add_output("/nix/store/x.drv", "out");
6505 let mut ctx2 = StringContext::new();
6506 ctx2.add_plain("/nix/store/dup"); ctx2.add_plain("/nix/store/unique"); ctx1.merge(&ctx2);
6509 assert_eq!(ctx1.len(), 3); }
6511
6512 #[test]
6513 fn smallvec_context_add_plain_output_drv_deep() {
6514 let mut ctx = StringContext::new();
6515 ctx.add_plain("/nix/store/plain");
6516 assert_eq!(ctx.len(), 1);
6517 assert!(ctx.elements().contains(&ContextElement::Plain(SmolStr::from("/nix/store/plain"))));
6518
6519 ctx.add_output("/nix/store/out.drv", "lib");
6520 assert_eq!(ctx.len(), 2);
6521 assert!(ctx.elements().contains(&ContextElement::Output {
6522 drv: SmolStr::from("/nix/store/out.drv"),
6523 output: SmolStr::from("lib"),
6524 }));
6525
6526 ctx.add_drv_deep("/nix/store/deep.drv");
6527 assert_eq!(ctx.len(), 3);
6528 assert!(ctx.elements().contains(&ContextElement::DrvDeep(SmolStr::from("/nix/store/deep.drv"))));
6529 }
6530
6531 #[test]
6536 fn rc_list_constructor_wraps_in_rc() {
6537 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
6538 match &v {
6539 Value::List(rc) => {
6540 assert_eq!(rc.len(), 2);
6541 assert_eq!(Rc::strong_count(rc), 1);
6542 }
6543 _ => panic!("expected List"),
6544 }
6545 }
6546
6547 #[test]
6548 fn rc_list_clone_is_refcount_bump() {
6549 let v = Value::list(vec![Value::Int(10)]);
6550 let rc1 = match &v {
6551 Value::List(rc) => rc.clone(),
6552 _ => panic!("expected List"),
6553 };
6554 let v2 = v.clone();
6555 let rc2 = match &v2 {
6556 Value::List(rc) => rc.clone(),
6557 _ => panic!("expected List"),
6558 };
6559 assert!(Rc::ptr_eq(&rc1, &rc2));
6561 assert!(Rc::strong_count(&rc1) >= 2);
6564 }
6565
6566 #[test]
6567 fn rc_list_as_list_returns_slice() {
6568 let v = Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
6569 let slice = v.as_list().unwrap();
6570 assert_eq!(slice.len(), 3);
6571 assert_eq!(slice[0], Value::Int(1));
6572 assert_eq!(slice[1], Value::Int(2));
6573 assert_eq!(slice[2], Value::Int(3));
6574 }
6575
6576 #[test]
6577 fn rc_list_from_vec_wraps_in_rc() {
6578 let items = vec![Value::Bool(true), Value::Bool(false)];
6579 let v: Value = items.into();
6580 match &v {
6581 Value::List(rc) => {
6582 assert_eq!(rc.len(), 2);
6583 assert_eq!(Rc::strong_count(rc), 1);
6584 }
6585 _ => panic!("expected List"),
6586 }
6587 }
6588
6589 #[test]
6594 fn intern_same_string_returns_same_symbol() {
6595 let s1 = intern("hello_intern_test");
6596 let s2 = intern("hello_intern_test");
6597 assert_eq!(s1, s2);
6598 }
6599
6600 #[test]
6601 fn intern_different_strings_returns_different_symbols() {
6602 let s1 = intern("unique_str_a_9182");
6603 let s2 = intern("unique_str_b_9182");
6604 assert_ne!(s1, s2);
6605 }
6606
6607 #[test]
6608 fn resolve_roundtrips_correctly() {
6609 let sym = intern("roundtrip_test_str");
6610 let resolved = resolve(sym);
6611 assert_eq!(resolved, "roundtrip_test_str");
6612 }
6613
6614 #[test]
6615 fn intern_cached_same_offset_returns_cached_symbol() {
6616 let sid = next_source_id();
6617 let sym1 = intern_cached("cached_ident_aa", sid, 100);
6618 let sym2 = intern_cached("cached_ident_aa", sid, 100);
6619 assert_eq!(sym1, sym2);
6620 }
6621
6622 #[test]
6623 fn intern_cached_different_offset_same_string_returns_same_symbol() {
6624 let sid = next_source_id();
6627 let sym1 = intern_cached("dedup_test_str_77", sid, 200);
6628 let sym2 = intern_cached("dedup_test_str_77", sid, 300);
6629 assert_eq!(sym1, sym2);
6631 }
6632
6633 #[test]
6634 fn clear_ident_cache_clears() {
6635 let sid = next_source_id();
6636 let _sym = intern_cached("to_be_cleared_99", sid, 500);
6637 clear_ident_cache();
6638 let sym2 = intern_cached("to_be_cleared_99", sid, 500);
6642 let resolved = resolve(sym2);
6643 assert_eq!(resolved, "to_be_cleared_99");
6644 }
6645
6646 #[test]
6647 fn next_source_id_increments_monotonically() {
6648 let id1 = next_source_id();
6649 let id2 = next_source_id();
6650 let id3 = next_source_id();
6651 assert_eq!(id2, id1 + 1);
6652 assert_eq!(id3, id2 + 1);
6653 }
6654
6655 #[test]
6660 fn env_new_creates_empty_bindings() {
6661 let env = Env::new();
6662 assert!(env.0.bindings.is_empty());
6663 assert!(env.0.with_scopes.is_empty());
6664 assert!(env.eval_file().is_none());
6665 }
6666
6667 #[test]
6668 fn env_bind_lookup_roundtrip() {
6669 let mut env = Env::new();
6670 env.bind("foo".to_string(), Value::Int(42));
6671 assert_eq!(env.lookup("foo"), Some(Value::Int(42)));
6672 assert_eq!(env.lookup("bar"), None);
6673 }
6674
6675 #[test]
6676 fn env_child_inherits_parent_bindings_flattened() {
6677 let mut parent = Env::new();
6678 parent.bind("a".to_string(), Value::Int(1));
6679 parent.bind("b".to_string(), Value::Int(2));
6680 let child = parent.child();
6681 assert_eq!(child.lookup("a"), Some(Value::Int(1)));
6683 assert_eq!(child.lookup("b"), Some(Value::Int(2)));
6684 let sym_a = intern("a");
6686 assert!(child.0.bindings.contains_key(&sym_a));
6687 }
6688
6689 #[test]
6690 fn env_child_inherits_with_scopes() {
6691 let mut attrs = NixAttrs::new();
6692 attrs.insert("ws".to_string(), Value::Int(10));
6693 let parent = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6694 let child = parent.child();
6695 assert_eq!(child.0.with_scopes.len(), parent.0.with_scopes.len());
6697 assert_eq!(child.lookup("ws"), Some(Value::Int(10)));
6698 }
6699
6700 #[test]
6701 fn env_lookup_sym_fast_path_matches_lookup() {
6702 let mut env = Env::new();
6703 env.bind("target".to_string(), Value::Int(88));
6704 let sym = intern("target");
6705 let via_lookup = env.lookup("target");
6706 let via_sym = env.lookup_sym(sym);
6707 assert_eq!(via_lookup, via_sym);
6708 assert_eq!(via_sym, Some(Value::Int(88)));
6709 }
6710
6711 #[test]
6712 fn env_lookup_sym_with_scope_fallback() {
6713 let mut attrs = NixAttrs::new();
6714 attrs.insert("sym_ws".to_string(), Value::Int(33));
6715 let env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6716 let sym = intern("sym_ws");
6717 assert_eq!(env.lookup_sym(sym), Some(Value::Int(33)));
6718 }
6719
6720 #[test]
6721 fn env_with_scope_ordering_multiple_innermost_wins() {
6722 let mut a1 = NixAttrs::new();
6723 a1.insert("x".to_string(), Value::Int(1));
6724 let mut a2 = NixAttrs::new();
6725 a2.insert("x".to_string(), Value::Int(2));
6726 let mut a3 = NixAttrs::new();
6727 a3.insert("x".to_string(), Value::Int(3));
6728 let env = Env::new()
6729 .with_scope(Value::Attrs(Rc::new(a1)))
6730 .with_scope(Value::Attrs(Rc::new(a2)))
6731 .with_scope(Value::Attrs(Rc::new(a3)));
6732 assert_eq!(env.lookup("x"), Some(Value::Int(3)));
6734 }
6735
6736 #[test]
6737 fn env_lookup_sym_not_found_returns_none() {
6738 let env = Env::new();
6739 let sym = intern("nonexistent_sym_99");
6740 assert_eq!(env.lookup_sym(sym), None);
6741 }
6742
6743 #[test]
6744 fn env_lookup_sym_lexical_wins_over_with_scope() {
6745 let mut attrs = NixAttrs::new();
6746 attrs.insert("priority".to_string(), Value::Int(1));
6747 let mut env = Env::new().with_scope(Value::Attrs(Rc::new(attrs)));
6748 env.bind("priority".to_string(), Value::Int(99));
6749 let sym = intern("priority");
6750 assert_eq!(env.lookup_sym(sym), Some(Value::Int(99)));
6751 }
6752}