1use std::cell::{Cell, RefCell};
8use std::collections::{HashSet, HashMap, VecDeque};
9use std::path::PathBuf;
10
11use rnix::ast::{self, AstToken, HasEntry, InterpolPart};
12use rowan::ast::AstNode;
13
14use crate::builtins;
15use crate::value::*;
16
17thread_local! { static EVAL_DEPTH: Cell<usize> = const { Cell::new(0) }; }
18
19
20thread_local! {
29 static CURRENT_SOURCE_ID: Cell<u32> = const { Cell::new(0) };
30}
31
32thread_local! {
40 static EVAL_FILE_STACK: RefCell<Vec<Option<PathBuf>>> = const { RefCell::new(Vec::new()) };
49 static NIX_TRACE_STACK: RefCell<Vec<NixTraceFrame>> = const { RefCell::new(Vec::new()) };
53}
54
55#[derive(Debug, Clone)]
65pub enum NixTraceFrame {
66 Eager {
70 file: Option<String>,
71 description: String,
72 },
73 Lambda {
83 closure_env: Env,
84 current_file: Option<PathBuf>,
85 },
86}
87
88fn strip_source_prefix(p: &std::path::Path) -> String {
91 let s = p.display().to_string();
92 s.rsplit_once("-source/")
93 .map_or_else(|| p.display().to_string(), |(_, tail)| tail.to_string())
94}
95
96impl NixTraceFrame {
97 fn file(&self) -> Option<String> {
100 match self {
101 NixTraceFrame::Eager { file, .. } => file.clone(),
102 NixTraceFrame::Lambda { current_file, .. } => {
103 current_file.as_deref().map(strip_source_prefix)
104 }
105 }
106 }
107
108 fn description(&self) -> String {
113 self.to_string()
114 }
115}
116
117impl std::fmt::Display for NixTraceFrame {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 NixTraceFrame::Eager { description, .. } => f.write_str(description),
124 NixTraceFrame::Lambda { closure_env, .. } => {
125 let file = closure_env.eval_file().map(|p| strip_source_prefix(p));
126 write!(
127 f,
128 "while calling function defined in {}",
129 file.as_deref().unwrap_or("<eval>")
130 )
131 }
132 }
133 }
134}
135
136fn push_nix_trace(desc: impl Into<String>) -> NixTraceGuard {
138 let frame = NixTraceFrame::Eager {
139 file: current_eval_file().map(|p| {
140 p.display().to_string()
141 .rsplit_once("-source/")
142 .map_or_else(|| p.display().to_string(), |(_, s)| s.to_string())
143 }),
144 description: desc.into(),
145 };
146 NIX_TRACE_STACK.with(|s| s.borrow_mut().push(frame));
147 NixTraceGuard
148}
149
150fn push_nix_trace_lambda(closure_env: &Env) -> NixTraceGuard {
156 let frame = NixTraceFrame::Lambda {
157 closure_env: closure_env.clone(),
158 current_file: current_eval_file(),
159 };
160 NIX_TRACE_STACK.with(|s| s.borrow_mut().push(frame));
161 NixTraceGuard
162}
163
164struct NixTraceGuard;
165impl Drop for NixTraceGuard {
166 fn drop(&mut self) {
167 NIX_TRACE_STACK.with(|s| s.borrow_mut().pop());
168 }
169}
170
171pub fn attach_trace(err: EvalError) -> EvalError {
173 NIX_TRACE_STACK.with(|s| {
174 let stack = s.borrow();
175 if stack.is_empty() {
176 return err;
177 }
178 let max_frames = std::env::var("SUI_M26_MAXFRAMES").ok()
179 .and_then(|s| s.parse::<usize>().ok()).unwrap_or(15);
180 let mut trace = format!("{err}");
181 for (i, frame) in stack.iter().rev().take(max_frames).enumerate() {
182 let file = frame.file();
183 let loc = file.as_deref().unwrap_or("<eval>");
184 trace.push_str(&format!("\n {} ({loc})", frame.description()));
185 if i + 1 >= max_frames && stack.len() > max_frames {
186 trace.push_str(&format!("\n ... ({} more frames)", stack.len() - max_frames));
187 }
188 }
189 match err {
192 EvalError::Throw(_) => EvalError::Throw(trace),
193 EvalError::AssertionFailed(_) => EvalError::AssertionFailed(trace),
194 _ => EvalError::TypeError(trace),
195 }
196 })
197}
198
199#[must_use]
202pub fn current_eval_dir() -> Option<PathBuf> {
203 EVAL_FILE_STACK
204 .with(|s| s.borrow().last().cloned())
205 .flatten()
206 .and_then(|p| p.parent().map(PathBuf::from))
207}
208
209pub fn push_eval_file(file: PathBuf) -> EvalFileGuard {
213 push_eval_frame(Some(file))
214}
215
216pub fn push_eval_frame(file: Option<PathBuf>) -> EvalFileGuard {
221 EVAL_FILE_STACK.with(|s| s.borrow_mut().push(file));
222 EvalFileGuard
223}
224
225#[must_use]
228pub fn current_eval_file() -> Option<PathBuf> {
229 EVAL_FILE_STACK.with(|s| s.borrow().last().cloned()).flatten()
230}
231
232
233pub fn eval_file_stack_snapshot() -> Vec<String> {
235 EVAL_FILE_STACK.with(|s| {
236 s.borrow().iter().map(|p| {
237 let Some(p) = p else { return "<no-file>".to_string() };
238 let s = p.display().to_string();
239 s.rsplit_once("-source/").map_or(s.clone(), |(_, r)| r.to_string())
240 }).collect()
241 })
242}
243
244pub(crate) fn eval_file_ctx() -> String {
247 current_eval_file()
248 .map(|p| format!(", in '{}'", p.display()))
249 .unwrap_or_default()
250}
251
252pub struct EvalFileGuard;
254
255impl Drop for EvalFileGuard {
256 fn drop(&mut self) {
257 EVAL_FILE_STACK.with(|s| {
258 s.borrow_mut().pop();
259 });
260 }
261}
262
263pub fn push_source_id(id: u32) -> SourceIdGuard {
269 let prev = CURRENT_SOURCE_ID.with(|s| {
270 let old = s.get();
271 s.set(id);
272 old
273 });
274 SourceIdGuard(prev)
275}
276
277pub struct SourceIdGuard(u32);
279
280impl Drop for SourceIdGuard {
281 fn drop(&mut self) {
282 CURRENT_SOURCE_ID.with(|s| s.set(self.0));
283 }
284}
285
286pub fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
299 crate::path::normalize(path)
300}
301
302thread_local! {
310 static PURE_MODE: Cell<bool> = const { Cell::new(false) };
311}
312
313pub fn set_pure_mode(pure: bool) {
315 PURE_MODE.with(|p| p.set(pure));
316}
317
318#[must_use]
320pub fn is_pure_mode() -> bool {
321 PURE_MODE.with(Cell::get)
322}
323
324#[cfg(test)]
340const MAX_EVAL_DEPTH: usize = 2_048;
341#[cfg(not(test))]
342const MAX_EVAL_DEPTH: usize = usize::MAX;
343
344struct DepthGuard;
350
351const PROMOTION_RUNAWAY_EVAL_DEPTH: usize = 500;
368
369impl DepthGuard {
370 #[inline(always)]
371 fn enter() -> Result<Self, EvalError> {
372 EVAL_DEPTH.with(|d| {
373 let depth = d.get();
374 if MAX_EVAL_DEPTH != usize::MAX && depth > MAX_EVAL_DEPTH {
375 return Err(EvalError::InfiniteRecursion(
376 "eval depth exceeded".into(),
377 ));
378 }
379 if depth > PROMOTION_RUNAWAY_EVAL_DEPTH
380 && crate::value::promotion_occurred()
381 {
382 return Err(EvalError::InfiniteRecursion(
383 "overlay-fixpoint promotion runaway (eval depth exceeded)".into(),
384 ));
385 }
386 d.set(depth + 1);
387 Ok(DepthGuard)
388 })
389 }
390}
391
392impl Drop for DepthGuard {
393 #[inline(always)]
394 fn drop(&mut self) {
395 EVAL_DEPTH.with(|d| d.set(d.get().saturating_sub(1)));
396 }
397}
398
399fn collect_referenced_names(expr: &ast::Expr) -> HashSet<String> {
416 let mut names = HashSet::new();
417 for node in expr.syntax().descendants() {
418 if let Some(ident) = ast::Ident::cast(node) {
419 names.insert(ident_text(&ident));
420 }
421 }
422 names
423}
424
425fn compute_needed_bindings(
439 body: &ast::Expr,
440 binding_info: &[(String, Option<ast::Expr>)], ) -> HashSet<String> {
442 let body_refs = collect_referenced_names(body);
444
445 let mut all_names: HashSet<String> = HashSet::with_capacity(binding_info.len());
447 let mut deps: HashMap<String, HashSet<String>> = HashMap::with_capacity(binding_info.len());
448
449 for (name, value_expr) in binding_info {
450 all_names.insert(name.clone());
451 if let Some(expr) = value_expr {
452 deps.insert(name.clone(), collect_referenced_names(expr));
453 }
454 }
455
456 let mut needed: HashSet<String> = body_refs.intersection(&all_names).cloned().collect();
458 let mut queue: VecDeque<String> = needed.iter().cloned().collect();
459
460 while let Some(name) = queue.pop_front() {
461 if let Some(name_deps) = deps.get(&name) {
462 for dep in name_deps {
463 if all_names.contains(dep) && needed.insert(dep.clone()) {
464 queue.push_back(dep.clone());
465 }
466 }
467 }
468 }
469
470 needed
471}
472
473#[must_use = "evaluation result should be used"]
475pub fn eval(input: &str) -> Result<Value, EvalError> {
476 eval_with_file(input, None)
477}
478
479thread_local! {
481 static EVAL_NESTING: Cell<usize> = const { Cell::new(0) };
482}
483
484pub fn eval_with_file(input: &str, file: Option<std::path::PathBuf>) -> Result<Value, EvalError> {
491 let nesting = EVAL_NESTING.with(|n| {
492 let v = n.get();
493 n.set(v + 1);
494 v
495 });
496 if nesting == 0 {
497 crate::perf::init();
498 crate::perf::start();
499 crate::trace::init_trace();
500 clear_ident_cache();
503 crate::resolve_env::clear();
507 }
526 let parse = rnix::Root::parse(input);
527 if !parse.errors().is_empty() {
528 let msgs: Vec<String> = parse.errors().iter().map(|e| e.to_string()).collect();
529 EVAL_NESTING.with(|n| n.set(n.get().saturating_sub(1)));
530 return Err(EvalError::ParseError(msgs.join("; ")));
531 }
532
533 let src_id = next_source_id();
537 if crate::resolve_env::enabled() {
544 let table = sui_resolve::resolve(&parse.tree());
545 crate::resolve_env::populate(src_id, &table);
546 }
547 crate::pos::register_source(file.as_deref(), input);
553 let prev_src_id = CURRENT_SOURCE_ID.with(|s| {
554 let old = s.get();
555 s.set(src_id);
556 old
557 });
558
559 let root = parse.tree();
560 let expr = match root.expr() {
561 Some(e) => e,
562 None => {
563 CURRENT_SOURCE_ID.with(|s| s.set(prev_src_id));
564 EVAL_NESTING.with(|n| n.set(n.get().saturating_sub(1)));
565 return Err(EvalError::ParseError("empty expression".to_string()));
566 }
567 };
568 let mut env = Env::new();
569 env.set_eval_file(file);
570 env.set_source_id(src_id);
575 builtins::register(&mut env);
576 let result = eval_expr(&expr, &env).map_err(|e| attach_trace(e))?;
577 let final_result = force_value(&result).map_err(|e| attach_trace(e));
579 CURRENT_SOURCE_ID.with(|s| s.set(prev_src_id));
581 EVAL_NESTING.with(|n| n.set(n.get().saturating_sub(1)));
582 if nesting == 0 {
583 crate::perf::report();
584 }
585 final_result
586}
587
588#[inline(always)]
596pub fn force_concrete(value: &Value) -> Result<Concrete, EvalError> {
601 value.demand()
602}
603
604pub fn force_value(value: &Value) -> Result<Value, EvalError> {
608 crate::perf::inc(crate::perf::Counter::ForceValue);
609 if !matches!(value, Value::Thunk(_)) {
612 return Ok(value.clone());
613 }
614 let mut v = value.clone();
629 let mut depth = 0u32;
630 loop {
631 match v {
632 Value::Thunk(ref thunk) => {
633 v = force_thunk(thunk)?;
634 depth += 1;
635 if depth > 100 {
636 return Err(EvalError::InfiniteRecursion(
637 "force_value: thunk chain exceeded depth 100 (cycle or runaway lazy wrap)".into(),
638 ));
639 }
640 }
641 _ => return Ok(v),
642 }
643 }
644}
645
646pub fn force_value_tracked(value: &Value, site: &str) -> Result<Value, EvalError> {
648 crate::perf::inc(crate::perf::Counter::ForceValue);
649 if let Value::Thunk(thunk) = value {
650 FORCE_SITES.with(|sites| {
651 *sites.borrow_mut().entry(site.to_string()).or_insert(0) += 1;
652 });
653 force_thunk(thunk)
654 } else {
655 Ok(value.clone())
656 }
657}
658
659thread_local! {
660 static FORCE_SITES: std::cell::RefCell<std::collections::HashMap<String, u64>> =
661 std::cell::RefCell::new(std::collections::HashMap::new());
662 static APPLY_SITES: std::cell::RefCell<std::collections::HashMap<String, u64>> =
663 std::cell::RefCell::new(std::collections::HashMap::new());
664}
665
666pub fn dump_force_sites() {
668 FORCE_SITES.with(|sites| {
669 let sites = sites.borrow();
670 let mut sorted: Vec<_> = sites.iter().collect();
671 sorted.sort_by(|a, b| b.1.cmp(a.1));
672 eprintln!("[force-sites] top thunk force call sites:");
673 for (site, count) in sorted.iter().take(10) {
674 eprintln!(" {count:>8} {site}");
675 }
676 });
677 APPLY_SITES.with(|sites| {
678 let sites = sites.borrow();
679 let mut sorted: Vec<_> = sites.iter().collect();
680 sorted.sort_by(|a, b| b.1.cmp(a.1));
681 eprintln!("[apply-sites] top lambda call sites by source file:");
682 for (site, count) in sorted.iter().take(15) {
683 let short = site.rsplit_once("-source/").map_or(site.as_str(), |(_,s)| s);
685 eprintln!(" {count:>8} {short}");
686 }
687 });
688}
689
690fn force_thunk(thunk: &Thunk) -> Result<Value, EvalError> {
694 if let Some(cached) = thunk.peek() {
696 crate::perf::inc(crate::perf::Counter::ThunkHit);
697 return Ok(cached.clone().into_value());
698 }
699 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
700 thunk.force(&|expr, env| eval_expr(expr, env))
706 })
707}
708
709fn referenced_idents(value_expr: &ast::Expr) -> HashSet<SmolStr> {
776 use rnix::SyntaxKind;
777 let perf_on = crate::perf::enabled();
783 let t0 = if perf_on {
784 Some(std::time::Instant::now())
785 } else {
786 None
787 };
788 crate::perf::inc(crate::perf::Counter::SelfRecWalkCalls);
789 let mut nodes_walked: u64 = 0;
790 let mut set: HashSet<SmolStr> = HashSet::new();
791 for node in value_expr.syntax().descendants() {
792 nodes_walked += 1;
793 if node.kind() == SyntaxKind::NODE_IDENT
794 && node
795 .parent()
796 .is_none_or(|p| p.kind() != SyntaxKind::NODE_ATTRPATH)
797 && let Some(i) = ast::Ident::cast(node)
798 {
799 set.insert(SmolStr::from(ident_text(&i).as_str()));
800 }
801 }
802 crate::perf::add(crate::perf::Counter::SelfRecWalkNodes, nodes_walked);
803 if let Some(t0) = t0 {
804 crate::trace::add_self_rec_walk_nanos(t0.elapsed().as_nanos());
805 }
806 set
807}
808
809fn is_self_recursive_binding(value_expr: &ast::Expr, name: &str) -> bool {
813 referenced_idents(value_expr).contains(name)
814}
815
816fn maybe_thunk(
817 expr: &ast::Expr,
818 env: &Env,
819 is_rec: bool,
820 defined_so_far: Option<&HashSet<String>>,
821) -> Value {
822 match expr {
823 ast::Expr::Literal(lit) => eval_literal(lit).unwrap_or_else(|_| {
825 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
826 }),
827 ast::Expr::Ident(ident) if !is_rec => {
834 let sym = {
844 let src_id = env.source_id();
845 let offset = u32::from(ident.syntax().text_range().start());
846 crate::value::intern_cached_with(src_id, offset, || {
847 crate::value::intern(&ident_text(ident))
848 })
849 };
850 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
852 "true" => Some(Value::Bool(true)),
853 "false" => Some(Value::Bool(false)),
854 "null" => Some(Value::Null),
855 _ => None,
856 }) {
857 return kw;
858 }
859 {
860 {
861 if let Some(v) = env.lookup_fast(sym, "") {
865 return v;
866 }
867 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
870 return Value::Thunk(Thunk::new_with_ident(
871 SmolStr::from(ident_text(ident).as_str()),
872 scope_cache,
873 scope_value,
874 env.clone(),
875 ));
876 }
877 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
878 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
879 }
880 }
881 }
882 ast::Expr::Ident(ident) if is_rec => {
886 let name = ident_text(ident);
887 match name.as_str() {
888 "true" => Value::Bool(true),
889 "false" => Value::Bool(false),
890 "null" => Value::Null,
891 _ => {
892 if defined_so_far.map_or(false, |d| d.contains(&name)) {
895 env.lookup(&name).unwrap_or_else(|| {
896 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
897 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
898 })
899 } else {
900 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
902 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
903 }
904 }
905 }
906 }
907 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
912 let text = crate::path::canon_abs(&p.syntax().text().to_string());
918 Value::Path(Box::new(SmolStr::from(text.as_str())))
919 }
920 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
921 let text = p.syntax().text().to_string();
922 Value::Path(Box::new(SmolStr::from(text.as_str())))
923 }
924 ast::Expr::Str(st) if !str_has_interpolation(st) => {
937 eval_str(st, env).unwrap_or_else(|_| {
938 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
939 })
940 }
941 ast::Expr::Lambda(lam) if !is_rec => {
945 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
946 Value::Lambda(Rc::new(Closure {
947 param,
948 body,
949 env: env.clone(),
950 }))
951 } else {
952 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
953 }
954 }
955 _ => {
966 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeOther);
967 if crate::perf::enabled() {
968 let kind = match expr {
969 ast::Expr::Select(_) => "Select",
970 ast::Expr::Apply(_) => "Apply",
971 ast::Expr::BinOp(_) => "BinOp",
972 ast::Expr::IfElse(_) => "IfElse",
973 ast::Expr::Str(_) => "Str",
974 ast::Expr::List(_) => "List",
975 ast::Expr::With(_) => "With",
976 ast::Expr::Assert(_) => "Assert",
977 ast::Expr::HasAttr(_) => "HasAttr",
978 ast::Expr::UnaryOp(_) => "UnaryOp",
979 ast::Expr::Paren(_) => "Paren",
980 ast::Expr::LetIn(_) => "LetIn",
981 ast::Expr::AttrSet(_) => "AttrSet",
982 ast::Expr::Ident(_) => "Ident(rec)",
983 ast::Expr::Lambda(_) => "Lambda(rec)",
984 ast::Expr::LegacyLet(_) => "LegacyLet",
985 ast::Expr::PathAbs(_)
986 | ast::Expr::PathHome(_)
987 | ast::Expr::PathRel(_)
988 | ast::Expr::PathSearch(_) => "Path(interp)",
989 _ => "Other",
990 };
991 crate::trace::inc_maybe_other_kind(kind);
992 }
993 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
994 }
995 }
996}
997
998#[inline(always)]
1009pub fn eval_expr(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1010 match expr {
1013 ast::Expr::Ident(ident) => {
1014 crate::perf::inc(crate::perf::Counter::EvalExpr);
1015 if crate::perf::enabled() {
1016 crate::perf::inc(crate::perf::Counter::ExprIdent);
1017 }
1018 if crate::resolve_env::enabled() {
1031 let src_id = CURRENT_SOURCE_ID.with(std::cell::Cell::get);
1032 let offset = u32::from(ident.syntax().text_range().start());
1033 if let sui_resolve::Resolution::Lexical { sym } =
1034 crate::resolve_env::resolution_for(src_id, offset)
1035 {
1036 if let Some(v) = env.lookup_lexical_sym(sym) {
1037 return Ok(v);
1038 }
1039 }
1040 }
1042 let sym = {
1076 let src_id = env.source_id();
1077 let offset = u32::from(ident.syntax().text_range().start());
1078 crate::value::intern_cached_with(src_id, offset, || {
1079 crate::value::intern(&ident_text(ident))
1080 })
1081 };
1082 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
1086 "true" => Some(Value::Bool(true)),
1087 "false" => Some(Value::Bool(false)),
1088 "null" => Some(Value::Null),
1089 _ => None,
1090 }) {
1091 return Ok(kw);
1092 }
1093 return {
1094 {
1095 if let Some(v) = env.lookup_fast(sym, "") {
1099 Ok(v)
1100 } else {
1101 let name = ident_text(ident);
1102 let fresh = crate::value::intern(name.as_str());
1121 if fresh != sym {
1122 if let Some(v) = env.lookup_fast(fresh, name.as_str()) {
1123 return Ok(v);
1124 }
1125 }
1126 if env.with_scope_count() > 0 {
1127 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
1131 Ok(Value::Thunk(Thunk::new_with_ident(
1132 SmolStr::from(name.as_str()),
1133 scope_cache,
1134 scope_value,
1135 env.clone(),
1136 )))
1137 } else if crate::value::in_promise_eval() {
1138 Ok(Value::Null)
1148 } else {
1149 Err(EvalError::UndefinedVar(
1150 format!("'{name}'{}", eval_file_ctx()),
1151 ))
1152 }
1153 } else {
1154 if let Ok(dbg_var) = std::env::var("SUI_DEBUG_VAR") {
1155 if dbg_var == name || dbg_var == "*" {
1156 eprintln!(
1157 "[sui-debug] UndefinedVar '{name}' in {}\n\
1158 [sui-debug] env bindings ({} total): {:?}\n\
1159 [sui-debug] with_scopes: {}",
1160 eval_file_ctx(),
1161 env.binding_count(),
1162 env.binding_names_preview(20),
1163 env.with_scope_count(),
1164 );
1165 }
1166 }
1167 if crate::value::in_promise_eval() {
1168 return Ok(Value::Null);
1171 }
1172 Err(EvalError::UndefinedVar(
1173 format!("'{name}'{}", eval_file_ctx()),
1174 ))
1175 }
1176 }
1177 }
1178 };
1179 }
1180 ast::Expr::Literal(lit) => {
1181 crate::perf::inc(crate::perf::Counter::EvalExpr);
1182 if crate::perf::enabled() {
1183 crate::perf::inc(crate::perf::Counter::ExprLiteral);
1184 }
1185 return eval_literal(lit);
1186 }
1187 ast::Expr::Paren(p) => {
1188 if let Some(inner) = p.expr() {
1189 return eval_expr(&inner, env);
1190 }
1191 }
1192 ast::Expr::Root(r) => {
1193 if let Some(inner) = r.expr() {
1194 return eval_expr(&inner, env);
1195 }
1196 }
1197 ast::Expr::Lambda(lam) => {
1199 crate::perf::inc(crate::perf::Counter::EvalExpr);
1200 if crate::perf::enabled() {
1201 crate::perf::inc(crate::perf::Counter::ExprLambda);
1202 }
1203 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
1204 return Ok(Value::Lambda(Rc::new(Closure {
1205 param,
1206 body,
1207 env: env.clone(),
1208 })));
1209 }
1210 }
1211 _ => {}
1212 }
1213 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1215 eval_expr_inner(expr, env)
1216 })
1217}
1218
1219fn eval_expr_inner(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1227 let mut cur_expr = expr.clone();
1230 let mut cur_env = env.clone();
1231
1232 loop {
1233 crate::perf::inc(crate::perf::Counter::EvalExpr);
1234 if crate::perf::enabled() {
1236 use crate::perf::Counter;
1237 let c = match &cur_expr {
1238 ast::Expr::Ident(_) => Counter::ExprIdent,
1239 ast::Expr::Literal(_) => Counter::ExprLiteral,
1240 ast::Expr::Str(_) => Counter::ExprStr,
1241 ast::Expr::List(_) => Counter::ExprList,
1242 ast::Expr::AttrSet(_) => Counter::ExprAttrs,
1243 ast::Expr::Select(_) => Counter::ExprSelect,
1244 ast::Expr::Apply(_) => Counter::ExprApply,
1245 ast::Expr::LetIn(_) => Counter::ExprLetIn,
1246 ast::Expr::IfElse(_) => Counter::ExprIfElse,
1247 ast::Expr::With(_) => Counter::ExprWith,
1248 ast::Expr::Lambda(_) => Counter::ExprLambda,
1249 ast::Expr::BinOp(_) => Counter::ExprBinOp,
1250 ast::Expr::HasAttr(_) => Counter::ExprHasAttr,
1251 ast::Expr::UnaryOp(_) => Counter::ExprUnaryOp,
1252 ast::Expr::Assert(_) => Counter::ExprAssert,
1253 ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
1254 | ast::Expr::PathHome(_) | ast::Expr::PathSearch(_) => Counter::ExprPath,
1255 _ => Counter::ExprOther,
1256 };
1257 crate::perf::inc(c);
1258 }
1259 let _guard = DepthGuard::enter()?;
1260 let env = &cur_env;
1261 match &cur_expr {
1262 ast::Expr::Literal(lit) => return eval_literal(lit),
1263
1264 ast::Expr::Str(s) => return eval_str(s, env),
1265
1266 ast::Expr::PathAbs(p) => {
1267 let parts = p.parts();
1270 if parts_have_interpolation(&parts) {
1271 return eval_interpol_path_parts(&parts, PathKind::Abs, env);
1272 }
1273 let text = crate::path::canon_abs(&p.syntax().text().to_string());
1276 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1277 }
1278 ast::Expr::PathRel(p) => {
1279 let parts = p.parts();
1290 if parts_have_interpolation(&parts) {
1291 return eval_interpol_path_parts(&parts, PathKind::Rel, env);
1292 }
1293 let text = p.syntax().text().to_string();
1294 let resolved = if let Some(dir) = current_eval_dir() {
1295 let joined = dir.join(&text);
1296 let norm = normalize_path(&joined);
1300 crate::path::dematerialize(&norm)
1310 .to_string_lossy()
1311 .into_owned()
1312 } else {
1313 text.clone()
1314 };
1315 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1316 }
1317 ast::Expr::PathHome(p) => {
1318 let parts = p.parts();
1319 if parts_have_interpolation(&parts) {
1320 return eval_interpol_path_parts(&parts, PathKind::Home, env);
1321 }
1322 let text = p.syntax().text().to_string();
1323 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1324 }
1325 ast::Expr::PathSearch(p) => {
1326 let text = p.syntax().text().to_string();
1331 let inner = text
1332 .strip_prefix('<')
1333 .and_then(|s| s.strip_suffix('>'))
1334 .unwrap_or(&text);
1335 if let Some(resolved) = crate::builtins::resolve_search_path(inner) {
1336 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1337 }
1338 return Err(EvalError::Throw(
1342 format!("search path '{text}' not in NIX_PATH"),
1343 ));
1344 }
1345
1346 ast::Expr::Ident(ident) => {
1347 let name = ident_text(ident);
1348 return match name.as_str() {
1349 "true" => Ok(Value::Bool(true)),
1350 "false" => Ok(Value::Bool(false)),
1351 "null" => Ok(Value::Null),
1352 _ => {
1353 env.lookup(&name)
1354 .ok_or_else(|| EvalError::UndefinedVar(
1355 format!("'{name}'{}", eval_file_ctx()),
1356 ))
1357 }
1358 };
1359 }
1360
1361 ast::Expr::List(list) => {
1362 let values: Vec<Value> = list.items()
1367 .map(|e| maybe_thunk(&e, env, false, None))
1368 .collect();
1369 return Ok(Value::list(values));
1370 }
1371
1372 ast::Expr::AttrSet(set) => return eval_attrset(set, env),
1373
1374 ast::Expr::Select(sel) => return eval_select(sel, env),
1375
1376 ast::Expr::HasAttr(ha) => return eval_has_attr(ha, env),
1377
1378 ast::Expr::UnaryOp(op) => return eval_unary_op(op, env),
1379
1380 ast::Expr::BinOp(binop) => {
1381 let lhs_expr = binop
1382 .lhs()
1383 .ok_or_else(|| EvalError::ParseError("binop missing lhs".to_string()))?;
1384 let rhs_expr = binop
1385 .rhs()
1386 .ok_or_else(|| EvalError::ParseError("binop missing rhs".to_string()))?;
1387 let kind = binop
1388 .operator()
1389 .ok_or_else(|| EvalError::ParseError("binop missing operator".to_string()))?;
1390 return eval_binop(kind, &lhs_expr, &rhs_expr, env);
1391 }
1392
1393 ast::Expr::Apply(app) => return eval_apply(app, env),
1394
1395 ast::Expr::IfElse(ie) => {
1396 let cond = ie
1397 .condition()
1398 .ok_or_else(|| EvalError::ParseError("if missing condition".to_string()))?;
1399 let body = ie
1400 .body()
1401 .ok_or_else(|| EvalError::ParseError("if missing then body".to_string()))?;
1402 let else_body = ie
1403 .else_body()
1404 .ok_or_else(|| EvalError::ParseError("if missing else body".to_string()))?;
1405 if force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1406 cur_expr = body;
1407 } else {
1408 cur_expr = else_body;
1409 }
1410 continue;
1412 }
1413
1414 ast::Expr::Assert(assert) => {
1415 let cond = assert
1416 .condition()
1417 .ok_or_else(|| EvalError::ParseError("assert missing condition".to_string()))?;
1418 let body = assert
1419 .body()
1420 .ok_or_else(|| EvalError::ParseError("assert missing body".to_string()))?;
1421 if !force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1422 return Err(EvalError::AssertionFailed(eval_file_ctx()));
1423 }
1424 cur_expr = body;
1425 continue;
1426 }
1427
1428 ast::Expr::With(with) => {
1429 let ns = with
1430 .namespace()
1431 .ok_or_else(|| EvalError::ParseError("with missing namespace".to_string()))?;
1432 let body = with
1433 .body()
1434 .ok_or_else(|| EvalError::ParseError("with missing body".to_string()))?;
1435 let scope_val = maybe_thunk(&ns, env, false, None);
1461 let new_env = env.child().with_scope(scope_val);
1462 cur_expr = body;
1463 cur_env = new_env;
1464 continue;
1465 }
1466
1467 ast::Expr::LetIn(letin) => {
1468 let mut new_env = env.child();
1469
1470 let mut thunks: Vec<(String, Thunk)> = Vec::new();
1473
1474 let mut defined_so_far: HashSet<String> = HashSet::new();
1478
1479 let mut dotted_attrs: NixAttrs = NixAttrs::new();
1483
1484 let let_scope_names: HashSet<String> = {
1490 let mut s = HashSet::new();
1491 for entry in letin.entries() {
1492 match entry {
1493 ast::Entry::AttrpathValue(apv) => {
1494 if let Some(attrpath) = apv.attrpath() {
1495 if let Some(first) = attrpath.attrs().next() {
1496 if let Ok(name) = eval_attr(&first, env) {
1497 s.insert(name);
1498 }
1499 }
1500 }
1501 }
1502 ast::Entry::Inherit(inherit) => {
1503 for attr in inherit.attrs() {
1504 if let Ok(name) = eval_attr(&attr, env) {
1505 s.insert(name);
1506 }
1507 }
1508 }
1509 }
1510 }
1511 s
1512 };
1513
1514 for entry in letin.entries() {
1515 match entry {
1516 ast::Entry::AttrpathValue(ref apv) => {
1517 let attrpath = apv.attrpath().ok_or_else(|| {
1518 EvalError::ParseError("binding missing attrpath".to_string())
1519 })?;
1520 let value_expr = apv.value().ok_or_else(|| {
1521 EvalError::ParseError("binding missing value".to_string())
1522 })?;
1523 let mut path_keys: Vec<String> = attrpath
1524 .attrs()
1525 .map(|a| eval_attr(&a, env))
1526 .collect::<Result<_, _>>()?;
1527 if path_keys.len() == 1 {
1528 let key = path_keys.pop().unwrap();
1529 let referenced = referenced_idents(&value_expr);
1552 let in_mutual_cycle = std::iter::once(&key)
1553 .chain(let_scope_names.iter())
1554 .any(|n| referenced.contains(n.as_str()));
1555 let value = if in_mutual_cycle {
1556 Value::Thunk(Thunk::new_suspended_recursive(
1557 value_expr.clone(),
1558 env.clone(),
1559 ))
1560 } else {
1561 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
1562 };
1563 new_env.bind(key.clone(), value.clone());
1564 if let Value::Thunk(t) = &value {
1565 thunks.push((key.clone(), t.clone()));
1566 }
1567 defined_so_far.insert(key);
1568 } else if path_keys.len() > 1 {
1569 let key = path_keys[0].clone();
1574 let value = build_nested_attr_thunk(
1575 &path_keys[1..],
1576 &value_expr,
1577 env,
1578 &mut thunks,
1579 );
1580 merge_nested_insert(&mut dotted_attrs, key, value);
1581 }
1582 }
1583 ast::Entry::Inherit(ref inherit) => {
1584 if let Some(from) = inherit.from() {
1585 let source_expr = from.expr().ok_or_else(|| {
1586 EvalError::ParseError(
1587 "inherit from missing expr".to_string(),
1588 )
1589 })?;
1590 let source_thunk = Thunk::new_suspended(
1595 source_expr, env.clone(),
1596 );
1597 for attr in inherit.attrs() {
1598 let name = eval_attr(&attr, env)?;
1599 let thunk = Thunk::new_inherit_select(
1600 source_thunk.clone(),
1601 name.clone(),
1602 );
1603 new_env.bind(name.clone(), Value::Thunk(thunk.clone()));
1604 thunks.push((name, thunk));
1605 }
1606 } else {
1607 for attr in inherit.attrs() {
1612 let name = eval_attr(&attr, env)?;
1613 let value = env.lookup(&name).ok_or_else(|| {
1614 EvalError::UndefinedVar(
1615 format!("'{name}'{}", eval_file_ctx()),
1616 )
1617 })?;
1618 new_env.bind(name, value);
1619 }
1620 }
1621 }
1622 }
1623 }
1624
1625 for (key, value) in dotted_attrs.iter() {
1630 new_env.bind(key.clone(), value.clone());
1631 }
1632
1633 for (_key, thunk) in &thunks {
1636 thunk.update_env(&new_env);
1637 }
1638
1639 let body = letin
1640 .body()
1641 .ok_or_else(|| EvalError::ParseError("let missing body".to_string()))?;
1642 cur_expr = body;
1643 cur_env = new_env;
1644 continue;
1645 }
1646
1647 ast::Expr::Lambda(lam) => {
1648 let param = lam
1649 .param()
1650 .ok_or_else(|| EvalError::ParseError("lambda missing param".to_string()))?;
1651 let body = lam
1652 .body()
1653 .ok_or_else(|| EvalError::ParseError("lambda missing body".to_string()))?;
1654 return Ok(Value::Lambda(Rc::new(Closure {
1655 param,
1656 body,
1657 env: env.clone(),
1658 })));
1659 }
1660
1661 ast::Expr::Paren(p) => {
1662 let inner = p
1663 .expr()
1664 .ok_or_else(|| EvalError::ParseError("paren missing expr".to_string()))?;
1665 cur_expr = inner;
1666 continue;
1667 }
1668
1669 ast::Expr::Root(r) => {
1670 let inner = r
1671 .expr()
1672 .ok_or_else(|| EvalError::ParseError("root missing expr".to_string()))?;
1673 cur_expr = inner;
1674 continue;
1675 }
1676
1677 ast::Expr::LegacyLet(ll) => {
1678 let mut new_env = env.child();
1679 eval_entries(ll, &mut new_env)?;
1680 return new_env
1682 .lookup("body")
1683 .ok_or_else(|| EvalError::AttrNotFound(
1684 format!("'body' in legacy let{}", eval_file_ctx()),
1685 ));
1686 }
1687
1688 ast::Expr::CurPos(_) => return Err(EvalError::NotImplemented("__curPos".to_string())),
1689 ast::Expr::Error(_) => return Err(EvalError::ParseError("parse error node".to_string())),
1690 } } }
1693
1694fn eval_literal(lit: &ast::Literal) -> Result<Value, EvalError> {
1695 use ast::LiteralKind;
1696 match lit.kind() {
1697 LiteralKind::Integer(tok) => {
1698 let n = tok
1699 .value()
1700 .map_err(|e| EvalError::ParseError(format!("invalid integer: {e}")))?;
1701 Ok(Value::Int(n))
1702 }
1703 LiteralKind::Float(tok) => {
1704 let f = tok
1705 .value()
1706 .map_err(|e| EvalError::ParseError(format!("invalid float: {e}")))?;
1707 Ok(Value::Float(f))
1708 }
1709 LiteralKind::Uri(tok) => Ok(Value::string(tok.syntax().text().to_string())),
1710 }
1711}
1712
1713enum TraverseResult {
1715 Found(Value),
1717 Missing(String),
1719 NotAttrs(Value),
1721}
1722
1723fn traverse_attrpath(
1728 base: Value,
1729 attrpath: &rnix::ast::Attrpath,
1730 env: &Env,
1731) -> Result<TraverseResult, EvalError> {
1732 let attrs: Vec<_> = attrpath.attrs().collect();
1733 let mut value = base;
1734 for (i, attr) in attrs.iter().enumerate() {
1735 let key = eval_attr(attr, env)?;
1736 let forced = force_value(&value)?;
1738 match forced {
1739 Value::Attrs(ref a) => match a.get(&key) {
1740 Some(v) => {
1741 if i < attrs.len() - 1 {
1742 value = force_value(v)?;
1744 } else {
1745 value = v.clone();
1748 }
1749 }
1750 None => return Ok(TraverseResult::Missing(key)),
1751 },
1752 _ => return Ok(TraverseResult::NotAttrs(forced)),
1753 }
1754 }
1755 Ok(TraverseResult::Found(value))
1756}
1757
1758fn eval_select(sel: &ast::Select, env: &Env) -> Result<Value, EvalError> {
1759 crate::perf::inc(crate::perf::Counter::Select);
1760 let base_expr = sel.expr().ok_or_else(|| {
1761 EvalError::ParseError("select missing expression".to_string())
1762 })?;
1763 let base_result = eval_expr(&base_expr, env)
1772 .and_then(|v| force_concrete(&v).map(Concrete::into_value));
1773 let base = match base_result {
1774 Ok(v) => v,
1775 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
1776 return eval_expr(&sel.default_expr().expect("checked"), env);
1777 }
1778 Err(e) => return Err(e),
1779 };
1780 let base_type = base.type_name();
1781 let attrpath = sel.attrpath().ok_or_else(|| {
1782 EvalError::ParseError("select missing attrpath".to_string())
1783 })?;
1784 let bridge_active = std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some()
1806 || std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some();
1807 let traversal = traverse_attrpath(base, &attrpath, env);
1808 match traversal {
1809 Ok(TraverseResult::Found(v)) => Ok(v),
1810 Ok(TraverseResult::Missing(key)) => {
1811 if let Some(def) = sel.default_expr() {
1812 eval_expr(&def, env)
1813 } else if bridge_active {
1814 if std::env::var_os("SUI_M26_SELTRACE").is_some() {
1815 let path: Vec<String> = sel.attrpath().map(|ap|
1816 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
1817 ).unwrap_or_default();
1818 eprintln!("[M26 SEL-MISS→null] base_type={base_type} path={path:?} missing-key={key}{}", eval_file_ctx());
1819 }
1820 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
1821 let path: Vec<String> = sel.attrpath().map(|ap|
1822 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
1823 ).unwrap_or_default();
1824 if path.iter().any(|p| p.contains(&filt)) {
1825 return Err(EvalError::type_error(format!(
1826 "M26-HARDSOFTEN path={path:?} key={key}"
1827 )));
1828 }
1829 }
1830 Ok(Value::Null)
1831 } else {
1832 Err(EvalError::AttrNotFound(
1833 format!("'{key}'{}", eval_file_ctx()),
1834 ))
1835 }
1836 }
1837 Ok(TraverseResult::NotAttrs(forced)) => {
1838 if let Some(def) = sel.default_expr() {
1844 eval_expr(&def, env)
1845 } else if bridge_active {
1846 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
1847 let path: Vec<String> = sel.attrpath().map(|ap|
1848 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
1849 ).unwrap_or_default();
1850 if path.iter().any(|p| p.contains(&filt)) {
1851 return Err(EvalError::type_error(format!(
1852 "M26-HARDSOFTEN-NOTATTRS path={path:?} base_type={base_type}"
1853 )));
1854 }
1855 }
1856 return Ok(Value::Null);
1857 } else {
1858 if std::env::var("SUI_DEBUG_SELECT").is_ok() {
1859 let path: Vec<String> = sel.attrpath().map(|ap|
1860 ap.attrs().filter_map(|a| match a {
1861 ast::Attr::Ident(i) => Some(i.to_string()),
1862 ast::Attr::Str(s) => Some(format!("\"{}\"", s.syntax().text())),
1863 ast::Attr::Dynamic(_) => Some("<dyn>".into()),
1864 }).collect()
1865 ).unwrap_or_default();
1866 let dbg = format!("{:?}", forced);
1867 let truncated = if dbg.len() > 200 { format!("{}…", &dbg[..200]) } else { dbg };
1868 eprintln!("[SUI_DEBUG_SELECT] base_type={base_type} path={path:?} base={truncated}{}", eval_file_ctx());
1869 }
1870 Err(attach_trace(EvalError::type_error(
1871 format!("cannot select from {base_type}"),
1872 )))
1873 }
1874 }
1875 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
1880 eval_expr(&sel.default_expr().expect("checked"), env)
1881 }
1882 Err(e) => Err(e),
1883 }
1884}
1885
1886fn eval_has_attr(ha: &ast::HasAttr, env: &Env) -> Result<Value, EvalError> {
1888 let base_expr = ha.expr().ok_or_else(|| {
1889 EvalError::ParseError("hasattr missing expression".to_string())
1890 })?;
1891 let base = force_concrete(&eval_expr(&base_expr, env)?)?.into_value();
1892 let attrpath = ha.attrpath().ok_or_else(|| {
1893 EvalError::ParseError("hasattr missing attrpath".to_string())
1894 })?;
1895 match traverse_attrpath(base, &attrpath, env)? {
1896 TraverseResult::Found(_) => Ok(Value::Bool(true)),
1897 TraverseResult::Missing(_) | TraverseResult::NotAttrs(_) => Ok(Value::Bool(false)),
1898 }
1899}
1900
1901fn eval_unary_op(op: &ast::UnaryOp, env: &Env) -> Result<Value, EvalError> {
1902 let inner = op
1903 .expr()
1904 .ok_or_else(|| EvalError::ParseError("unary op missing expr".to_string()))?;
1905 let val = force_value(&eval_expr(&inner, env)?)?;
1906 let kind = op
1907 .operator()
1908 .ok_or_else(|| EvalError::ParseError("unary op missing operator".to_string()))?;
1909 match kind {
1910 ast::UnaryOpKind::Negate => match val {
1911 Value::Int(n) => Ok(Value::Int(-n)),
1912 Value::Float(f) => Ok(Value::Float(-f)),
1913 _ => Err(EvalError::type_error(
1914 format!("cannot negate {}", val.type_name()),
1915 )),
1916 },
1917 ast::UnaryOpKind::Invert => Ok(Value::Bool(!val.as_bool()?)),
1918 }
1919}
1920
1921#[inline]
1932pub(crate) fn builtin_takes_lazy_arg(name: &str) -> bool {
1933 matches!(
1934 name,
1935 "tryEval" | "addErrorContext<partial>" | "seq<partial>" | "deepSeq<partial>" | "foldl'<p1>"
1936 )
1937}
1938
1939fn eval_apply(app: &ast::Apply, env: &Env) -> Result<Value, EvalError> {
1940 let func_expr = app
1941 .lambda()
1942 .ok_or_else(|| EvalError::ParseError("apply missing function".to_string()))?;
1943 let arg_expr = app
1944 .argument()
1945 .ok_or_else(|| EvalError::ParseError("apply missing argument".to_string()))?;
1946 let func = force_value(&eval_expr(&func_expr, env)?)?;
1947 let arg = match &func {
1955 Value::Lambda(_) => {
1956 if let Some(v) = eval_pure_constant_arg(&arg_expr) {
1965 v
1966 } else {
1967 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
1968 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
1969 }
1970 }
1971 Value::Builtin(b) if builtin_takes_lazy_arg(&b.name) => {
1972 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
1977 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
1978 }
1979 _ => eval_expr(&arg_expr, env)?,
1980 };
1981 apply(func, arg)
1982}
1983
1984fn eval_pure_constant_arg(arg_expr: &ast::Expr) -> Option<Value> {
1999 match arg_expr {
2000 ast::Expr::Literal(lit) => eval_literal(lit).ok(),
2001 ast::Expr::Str(st) if !str_has_interpolation(st) => {
2002 eval_str(st, &Env::new()).ok()
2004 }
2005 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
2006 let text = crate::path::canon_abs(&p.syntax().text().to_string());
2007 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2008 }
2009 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
2010 let text = p.syntax().text().to_string();
2011 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2012 }
2013 _ => None,
2014 }
2015}
2016
2017fn eval_str(s: &ast::Str, env: &Env) -> Result<Value, EvalError> {
2018 let mut result = String::new();
2019 let mut ctx = StringContext::new();
2020 for part in s.normalized_parts() {
2021 match part {
2022 InterpolPart::Literal(text) => result.push_str(&text),
2023 InterpolPart::Interpolation(interpol) => {
2024 let expr = interpol.expr().ok_or_else(|| {
2025 EvalError::ParseError("interpolation missing expr".to_string())
2026 })?;
2027 let val = force_value(&eval_expr(&expr, env)?)?;
2028 let (s, c) = val.coerce_to_string_copy_to_store()?;
2033 result.push_str(&s);
2034 ctx.merge(&c);
2035 }
2036 }
2037 }
2038 Ok(Value::String(Rc::new(NixString::with_context(result, ctx))))
2039}
2040
2041fn parts_have_interpolation(parts: &[InterpolPart<rnix::ast::PathContent>]) -> bool {
2045 parts
2046 .iter()
2047 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2048}
2049
2050fn str_has_interpolation(s: &ast::Str) -> bool {
2054 s.normalized_parts()
2055 .iter()
2056 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2057}
2058
2059fn eval_interpol_path_parts(
2074 parts: &[InterpolPart<rnix::ast::PathContent>],
2075 kind: PathKind,
2076 env: &Env,
2077) -> Result<Value, EvalError> {
2078 let mut text = String::new();
2079 for part in parts {
2080 match part {
2081 InterpolPart::Literal(content) => text.push_str(content.text()),
2082 InterpolPart::Interpolation(interpol) => {
2083 let expr = interpol.expr().ok_or_else(|| {
2084 EvalError::ParseError("path interpolation missing expr".to_string())
2085 })?;
2086 let val = force_value(&eval_expr(&expr, env)?)?;
2087 let (s, _ctx) = val.coerce_to_string()?;
2091 text.push_str(&s);
2092 }
2093 }
2094 }
2095 let resolved = match kind {
2096 PathKind::Rel => {
2099 if let Some(dir) = current_eval_dir() {
2100 let norm = normalize_path(&dir.join(&text));
2101 crate::path::dematerialize(&norm).to_string_lossy().into_owned()
2110 } else {
2111 text
2115 }
2116 }
2117 PathKind::Abs => crate::path::canon_abs(&text),
2125 PathKind::Home => normalize_path(std::path::Path::new(&text))
2128 .to_string_lossy()
2129 .into_owned(),
2130 };
2131 Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))))
2132}
2133
2134#[derive(Clone, Copy)]
2137enum PathKind {
2138 Abs,
2139 Rel,
2140 Home,
2141}
2142
2143fn eval_attr(attr: &ast::Attr, env: &Env) -> Result<String, EvalError> {
2146 eval_attr_maybe_null(attr, env)?
2147 .ok_or_else(|| EvalError::TypeError("null dynamic attribute name".into()))
2148}
2149
2150fn eval_attr_maybe_null(attr: &ast::Attr, env: &Env) -> Result<Option<String>, EvalError> {
2153 match attr {
2154 ast::Attr::Ident(ident) => Ok(Some(ident_text(ident))),
2155 ast::Attr::Dynamic(dyn_) => {
2156 let expr = dyn_
2157 .expr()
2158 .ok_or_else(|| EvalError::ParseError("dynamic attr missing expr".to_string()))?;
2159 let val = force_value(&eval_expr(&expr, env)?)?;
2160 if val == Value::Null {
2163 return Ok(None);
2164 }
2165 Ok(Some(val.as_string()?.to_string()))
2166 }
2167 ast::Attr::Str(s) => {
2168 let val = eval_str(s, env)?;
2169 Ok(Some(val.as_string()?.to_string()))
2170 }
2171 }
2172}
2173
2174fn ident_text(ident: &ast::Ident) -> String {
2176 match ident.ident_token() {
2184 Some(tok) => tok.text().to_string(),
2185 None => ident.syntax().text().to_string(),
2186 }
2187}
2188
2189fn static_attr_offset(attr: &ast::Attr) -> Option<u32> {
2196 let node = match attr {
2197 ast::Attr::Ident(i) => i.syntax(),
2198 ast::Attr::Str(s) => s.syntax(),
2199 ast::Attr::Dynamic(_) => return None,
2200 };
2201 Some(u32::from(node.text_range().start()))
2202}
2203
2204fn attach_attrset_positions(set: &ast::AttrSet, attrs: &mut NixAttrs, env: &Env) {
2211 let mut table = crate::pos::AttrPositions::new(current_eval_file());
2218 for entry in set.entries() {
2219 if let ast::Entry::AttrpathValue(apv) = entry {
2220 let Some(attrpath) = apv.attrpath() else { continue };
2221 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2222 let Some(head) = path_attrs.first() else { continue };
2230 let Some(offset) = static_attr_offset(head) else { continue };
2231 if let Ok(Some(name)) = eval_attr_maybe_null(&path_attrs[0], env) {
2234 table.insert(intern(&name), offset);
2235 }
2236 } else if let ast::Entry::Inherit(inh) = entry {
2237 for attr in inh.attrs() {
2251 let Some(offset) = static_attr_offset(&attr) else { continue };
2252 if let Ok(Some(name)) = eval_attr_maybe_null(&attr, env) {
2253 table.insert(intern(&name), offset);
2254 }
2255 }
2256 }
2257 }
2258 if !table.is_empty() {
2259 attrs.set_positions(std::rc::Rc::new(table));
2260 }
2261}
2262
2263fn eval_attrset(set: &ast::AttrSet, env: &Env) -> Result<Value, EvalError> {
2264 crate::perf::inc(crate::perf::Counter::Attrset);
2265 let mut attrs = NixAttrs::new();
2266 let is_rec = set.rec_token().is_some();
2267
2268 if is_rec {
2269 let mut rec_env = env.child();
2270 let mut thunks: Vec<(String, Thunk)> = Vec::new();
2271
2272 let mut defined_so_far: HashSet<String> = HashSet::new();
2276
2277 let mut dotted_attrs: NixAttrs = NixAttrs::new();
2283
2284 for entry in set.entries() {
2286 match entry {
2287 ast::Entry::AttrpathValue(apv) => {
2288 let attrpath = apv.attrpath().ok_or_else(|| {
2289 EvalError::ParseError("binding missing attrpath".to_string())
2290 })?;
2291 let value_expr = apv.value().ok_or_else(|| {
2292 EvalError::ParseError("binding missing value".to_string())
2293 })?;
2294 let mut path_keys: Vec<String> = attrpath
2295 .attrs()
2296 .filter_map(|a| eval_attr_maybe_null(&a, env).transpose())
2297 .collect::<Result<_, _>>()?;
2298 if path_keys.is_empty() { continue; }
2300 if path_keys.len() == 1 {
2301 let key = path_keys.pop().unwrap();
2302 let referenced = referenced_idents(&value_expr);
2319 let is_recursive_binding = referenced.contains(key.as_str())
2320 || defined_so_far
2321 .iter()
2322 .any(|n| referenced.contains(n.as_str()));
2323 let value = if is_recursive_binding {
2324 Value::Thunk(Thunk::new_suspended_recursive(
2325 value_expr.clone(),
2326 env.clone(),
2327 ))
2328 } else {
2329 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
2335 };
2336 rec_env.bind(key.clone(), value.clone());
2337 attrs.insert(key.clone(), value.clone());
2338 if let Value::Thunk(t) = &value {
2339 thunks.push((key.clone(), t.clone()));
2340 }
2341 defined_so_far.insert(key);
2342 } else {
2343 let key = path_keys[0].clone();
2347 let value =
2348 build_nested_attr_thunk(&path_keys[1..], &value_expr, env, &mut thunks);
2349 merge_nested_insert(&mut dotted_attrs, key, value);
2350 }
2351 }
2352 ast::Entry::Inherit(inherit) => {
2353 eval_inherit(&inherit, env, &mut attrs, Some(&mut rec_env), Some(&mut thunks))?;
2354 }
2355 }
2356 }
2357
2358 for (key, value) in dotted_attrs.iter() {
2363 attrs.insert(key.clone(), value.clone());
2364 rec_env.bind(key.clone(), value.clone());
2365 }
2366
2367 for (_key, thunk) in &thunks {
2370 thunk.update_env(&rec_env);
2371 }
2372 } else {
2373 for entry in set.entries() {
2374 match entry {
2375 ast::Entry::AttrpathValue(apv) => {
2376 let attrpath = apv.attrpath().ok_or_else(|| {
2377 EvalError::ParseError("binding missing attrpath".to_string())
2378 })?;
2379 let value_expr = apv.value().ok_or_else(|| {
2380 EvalError::ParseError("binding missing value".to_string())
2381 })?;
2382 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2383 let tail_is_dynamic =
2393 path_attrs.len() > 1 && attrs_have_dynamic(&path_attrs[1..]);
2394 let head_key = match eval_attr_maybe_null(&path_attrs[0], env)? {
2395 Some(k) => k,
2396 None => continue,
2398 };
2399 if tail_is_dynamic && attrs.get(&head_key).is_none() {
2400 let value =
2401 build_deferred_tail_attr(&path_attrs[1..], &value_expr, env);
2402 attrs.insert(head_key, value);
2403 continue;
2404 }
2405 if tail_is_dynamic {
2419 if let Some(existing) = attrs.get(&head_key).cloned() {
2420 let merged = merge_deferred_dynamic_tail(
2421 existing,
2422 &path_attrs[1..],
2423 &value_expr,
2424 env,
2425 )?;
2426 attrs.insert(head_key, merged);
2427 continue;
2428 }
2429 }
2430 let mut path_keys: Vec<String> = {
2433 let mut v = Vec::with_capacity(path_attrs.len());
2434 v.push(head_key);
2435 let mut skip = false;
2436 for a in &path_attrs[1..] {
2437 match eval_attr_maybe_null(a, env)? {
2438 Some(k) => v.push(k),
2439 None => { skip = true; break; }
2440 }
2441 }
2442 if skip { v.clear(); }
2443 v
2444 };
2445 if path_keys.is_empty() { continue; }
2447 if path_keys.len() == 1 {
2448 let key = path_keys.pop().unwrap();
2449 let value = maybe_thunk(&value_expr, env, false, None);
2452 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2477 let existing = attrs.get(&key).cloned().unwrap();
2478 let forced_existing = force_value(&existing)?;
2479 attrs.insert(key.clone(), forced_existing);
2480 }
2481 if matches!(attrs.get(&key), Some(Value::Attrs(_))) {
2482 let forced = force_value(&value)?;
2483 merge_nested_insert(&mut attrs, key, forced);
2484 } else {
2485 attrs.insert(key, value);
2486 }
2487 } else {
2488 let key = path_keys[0].clone();
2489 let value = build_nested_attr(&path_keys[1..], &value_expr, env)?;
2490 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2504 let existing = attrs.get(&key).cloned().unwrap();
2505 let forced = force_value(&existing)?;
2506 attrs.insert(key.clone(), forced);
2507 }
2508 merge_nested_insert(&mut attrs, key, value);
2509 }
2510 }
2511 ast::Entry::Inherit(inherit) => {
2512 eval_inherit(&inherit, env, &mut attrs, None, None)?;
2513 }
2514 }
2515 }
2516 }
2517
2518 attach_attrset_positions(set, &mut attrs, env);
2524
2525 Ok(Value::Attrs(Rc::new(attrs)))
2526}
2527
2528fn eval_inherit(
2529 inherit: &ast::Inherit,
2530 env: &Env,
2531 attrs: &mut NixAttrs,
2532 bind_env: Option<&mut Env>,
2533 mut thunks: Option<&mut Vec<(String, Thunk)>>,
2534) -> Result<(), EvalError> {
2535 if let Some(from) = inherit.from() {
2536 let source_expr = from
2556 .expr()
2557 .ok_or_else(|| EvalError::ParseError("inherit from missing expr".to_string()))?;
2558 let source_thunk = Thunk::new_suspended(source_expr, env.clone());
2562 let mut be = bind_env;
2563 for attr in inherit.attrs() {
2564 let name = eval_attr(&attr, env)?;
2565 let thunk = Thunk::new_inherit_select(source_thunk.clone(), name.clone());
2566 let value = Value::Thunk(thunk.clone());
2567 attrs.insert(name.clone(), value.clone());
2568 if let Some(ref mut e) = be {
2569 e.bind(name.clone(), value);
2570 }
2571 if let Some(ref mut t) = thunks {
2572 t.push((name, thunk));
2573 }
2574 }
2575 } else {
2576 let mut be = bind_env;
2592 for attr in inherit.attrs() {
2593 let name = eval_attr(&attr, env)?;
2594 let sym = crate::value::intern(&name);
2595 let value = if let Some(v) = env.lookup_fast(sym, &name) {
2596 v
2597 } else if let Some((scope_cache, scope_value)) =
2598 env.innermost_with_scope()
2599 {
2600 Value::Thunk(Thunk::new_with_ident(
2601 SmolStr::from(name.as_str()),
2602 scope_cache,
2603 scope_value,
2604 env.clone(),
2605 ))
2606 } else {
2607 return Err(EvalError::UndefinedVar(format!(
2608 "'{name}'{}",
2609 eval_file_ctx()
2610 )));
2611 };
2612 attrs.insert(name.clone(), value.clone());
2613 if let Some(ref mut e) = be {
2614 e.bind(name, value);
2615 }
2616 }
2617 }
2618 Ok(())
2619}
2620
2621fn build_nested_attr(
2622 path: &[String],
2623 expr: &ast::Expr,
2624 env: &Env,
2625) -> Result<Value, EvalError> {
2626 if path.is_empty() {
2627 return Ok(maybe_thunk(expr, env, false, None));
2632 }
2633 let key = path[0].clone();
2634 let inner = build_nested_attr(&path[1..], expr, env)?;
2635 let mut attrs = NixAttrs::new();
2636 attrs.insert(key, inner);
2637 Ok(Value::Attrs(Rc::new(attrs)))
2638}
2639
2640fn attr_is_dynamic(attr: &ast::Attr) -> bool {
2661 match attr {
2662 ast::Attr::Dynamic(_) => true,
2663 ast::Attr::Str(s) => s
2666 .normalized_parts()
2667 .iter()
2668 .any(|p| matches!(p, InterpolPart::Interpolation(_))),
2669 ast::Attr::Ident(_) => false,
2670 }
2671}
2672
2673fn attrs_have_dynamic(attrs: &[ast::Attr]) -> bool {
2681 attrs.iter().any(attr_is_dynamic)
2682}
2683
2684fn build_deferred_tail_attr(
2697 tail: &[ast::Attr],
2698 value_expr: &ast::Expr,
2699 env: &Env,
2700) -> Value {
2701 let tail: Vec<ast::Attr> = tail.to_vec();
2702 let value_expr = value_expr.clone();
2703 let env = env.clone();
2704 Value::Thunk(Thunk::new_native(move || {
2705 build_tail_attrs_now(&tail, &value_expr, &env)
2706 }))
2707}
2708
2709fn build_tail_attrs_now(
2730 tail: &[ast::Attr],
2731 value_expr: &ast::Expr,
2732 env: &Env,
2733) -> Result<Value, EvalError> {
2734 if tail.is_empty() {
2735 return Ok(maybe_thunk(value_expr, env, false, None));
2736 }
2737 if std::env::var_os("SUI_M26_TAILTRACE").is_some() {
2738 let t: String = tail[0].syntax().text().to_string().chars().take(40).collect();
2739 eprintln!("[M26 TAIL-RESOLVE] forcing dynamic tail key `{t}`");
2740 if attrs_have_dynamic(&tail[..1]) {
2741 crate::trace::dump_force_stack_ids();
2742 }
2743 }
2744 let key = match eval_attr_maybe_null(&tail[0], env)? {
2745 Some(k) => k,
2746 None => return Ok(Value::Attrs(Rc::new(NixAttrs::new()))),
2749 };
2750 let inner = if tail.len() == 1 {
2756 maybe_thunk(value_expr, env, false, None)
2757 } else {
2758 build_deferred_tail_attr(&tail[1..], value_expr, env)
2759 };
2760 let mut attrs = NixAttrs::new();
2761 attrs.insert(key, inner);
2762 Ok(Value::Attrs(Rc::new(attrs)))
2763}
2764
2765fn merge_deferred_dynamic_tail(
2783 existing: Value,
2784 tail: &[ast::Attr],
2785 value_expr: &ast::Expr,
2786 env: &Env,
2787) -> Result<Value, EvalError> {
2788 debug_assert!(!tail.is_empty());
2791
2792 if attr_is_dynamic(&tail[0]) {
2797 let deferred = build_deferred_tail_attr(tail, value_expr, env);
2798 return Ok(lazy_overlay_merge(existing, deferred));
2799 }
2800
2801 let key = match eval_attr_maybe_null(&tail[0], env)? {
2804 Some(k) => k,
2805 None => return Ok(existing),
2806 };
2807
2808 let existing_forced = force_value(&existing)?;
2812 let mut base = match existing_forced {
2813 Value::Attrs(a) => (*a).clone(),
2814 _ => {
2819 let deferred = build_deferred_tail_attr(tail, value_expr, env);
2820 return Ok(deferred);
2821 }
2822 };
2823
2824 let child_existing = base.get(&key).cloned();
2826 let new_child = match child_existing {
2827 Some(child) if tail.len() > 1 => {
2828 merge_deferred_dynamic_tail(child, &tail[1..], value_expr, env)?
2830 }
2831 Some(child) => {
2832 let leaf = maybe_thunk(value_expr, env, false, None);
2835 lazy_overlay_merge(child, leaf)
2836 }
2837 None if tail.len() > 1 => {
2838 build_deferred_tail_attr(&tail[1..], value_expr, env)
2842 }
2843 None => maybe_thunk(value_expr, env, false, None),
2844 };
2845 base.insert(key, new_child);
2846 Ok(Value::Attrs(Rc::new(base)))
2847}
2848
2849fn lazy_overlay_merge(left: Value, right: Value) -> Value {
2856 match (&left, &right) {
2857 (Value::Attrs(la), Value::Attrs(_)) => {
2858 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
2859 let mut merged = (**la).clone();
2860 if let Value::Attrs(ra) = &right {
2861 for (k, v) in ra.iter_unsorted() {
2865 merge_nested_insert(&mut merged, k.clone(), v.clone());
2866 }
2867 }
2868 Value::Attrs(Rc::new(merged))
2869 }
2870 _ => {
2871 Value::Thunk(Thunk::new_native(move || {
2875 let lf = force_value(&left)?;
2876 let rf = force_value(&right)?;
2877 let la = lf.as_attrs()?;
2878 let ra = rf.as_attrs()?;
2879 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
2880 let mut merged = (*la).clone();
2881 for (k, v) in ra.iter_unsorted() {
2882 merge_nested_insert(&mut merged, k.clone(), v.clone());
2883 }
2884 Ok(Value::Attrs(Rc::new(merged)))
2885 }))
2886 }
2887 }
2888}
2889
2890fn build_nested_attr_thunk(
2898 path: &[String],
2899 expr: &ast::Expr,
2900 env: &Env,
2901 thunks: &mut Vec<(String, Thunk)>,
2902) -> Value {
2903 if path.is_empty() {
2904 let thunk = Thunk::new_suspended(expr.clone(), env.clone());
2905 let val = Value::Thunk(thunk.clone());
2906 thunks.push((String::new(), thunk));
2907 return val;
2908 }
2909 let key = path[0].clone();
2910 let inner = build_nested_attr_thunk(&path[1..], expr, env, thunks);
2911 let mut attrs = NixAttrs::new();
2912 attrs.insert(key, inner);
2913 Value::Attrs(Rc::new(attrs))
2914}
2915
2916fn merge_nested_insert(target: &mut NixAttrs, key: String, value: Value) {
2923 let existing = match target.get(&key) {
2927 Some(e) => e.clone(),
2928 None => {
2929 target.insert(key, value);
2930 return;
2931 }
2932 };
2933 let value = match value {
2957 Value::Thunk(_) => match force_value(&value) {
2958 Ok(v @ Value::Attrs(_)) => v,
2959 _ => value,
2960 },
2961 other => other,
2962 };
2963 if !matches!(value, Value::Attrs(_)) {
2964 target.insert(key, value);
2965 return;
2966 }
2967 let existing_concrete = match &existing {
2970 Value::Attrs(_) => existing.clone(),
2971 Value::Thunk(_) => match force_value(&existing) {
2972 Ok(v @ Value::Attrs(_)) => v,
2973 _ => {
2974 target.insert(key, value);
2975 return;
2976 }
2977 },
2978 _ => {
2979 target.insert(key, value);
2980 return;
2981 }
2982 };
2983 let mut existing_attrs = match existing_concrete {
2987 Value::Attrs(a) => (*a).clone(),
2988 _ => unreachable!(),
2989 };
2990 let new_attrs = match value {
2991 Value::Attrs(ref a) => a,
2992 _ => unreachable!(),
2993 };
2994 for (k, v) in new_attrs.iter_unsorted() {
2995 merge_nested_insert(&mut existing_attrs, k.clone(), v.clone());
2996 }
2997 target.insert(key, Value::Attrs(Rc::new(existing_attrs)));
2998}
2999
3000fn eval_entries<N: HasEntry + AstNode>(node: &N, env: &mut Env) -> Result<(), EvalError> {
3002 for entry in node.entries() {
3003 match entry {
3004 ast::Entry::AttrpathValue(apv) => {
3005 let attrpath = apv.attrpath().ok_or_else(|| {
3006 EvalError::ParseError("binding missing attrpath".to_string())
3007 })?;
3008 let value_expr = apv.value().ok_or_else(|| {
3009 EvalError::ParseError("binding missing value".to_string())
3010 })?;
3011 let mut path_keys: Vec<String> = attrpath
3012 .attrs()
3013 .map(|a| eval_attr(&a, env))
3014 .collect::<Result<_, _>>()?;
3015 if path_keys.len() == 1 {
3016 let key = path_keys.pop().unwrap();
3017 let value = eval_expr(&value_expr, env)?;
3018 env.bind(key, value);
3019 }
3020 }
3022 ast::Entry::Inherit(inherit) => {
3023 if let Some(from) = inherit.from() {
3024 let source_expr = from.expr().ok_or_else(|| {
3025 EvalError::ParseError("inherit from missing expr".to_string())
3026 })?;
3027 let source = force_value(&eval_expr(&source_expr, env)?)?;
3028 let source_attrs = source.as_attrs()?;
3029 for attr in inherit.attrs() {
3030 let name = eval_attr(&attr, env)?;
3031 let value = source_attrs
3032 .get(&name)
3033 .cloned()
3034 .ok_or_else(|| EvalError::AttrNotFound(
3035 format!("'{name}' in inherit{}", eval_file_ctx()),
3036 ))?;
3037 env.bind(name, value);
3038 }
3039 } else {
3040 for attr in inherit.attrs() {
3041 let name = eval_attr(&attr, env)?;
3042 let value = env
3043 .lookup(&name)
3044 .ok_or_else(|| EvalError::UndefinedVar(
3045 format!("'{name}'{}", eval_file_ctx()),
3046 ))?;
3047 env.bind(name, value);
3048 }
3049 }
3050 }
3051 }
3052 }
3053 Ok(())
3054}
3055
3056fn eval_binop(
3057 op: ast::BinOpKind,
3058 lhs: &ast::Expr,
3059 rhs: &ast::Expr,
3060 env: &Env,
3061) -> Result<Value, EvalError> {
3062 match op {
3064 ast::BinOpKind::And => {
3065 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3066 if !l {
3067 return Ok(Value::Bool(false));
3068 }
3069 return eval_expr(rhs, env);
3070 }
3071 ast::BinOpKind::Or => {
3072 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3073 if l {
3074 return Ok(Value::Bool(true));
3075 }
3076 return eval_expr(rhs, env);
3077 }
3078 ast::BinOpKind::Implication => {
3079 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3080 if !l {
3081 return Ok(Value::Bool(true));
3082 }
3083 return eval_expr(rhs, env);
3084 }
3085 _ => {}
3086 }
3087
3088 let lc = force_concrete(&eval_expr(lhs, env)?)?;
3089 let rc = force_concrete(&eval_expr(rhs, env)?)?;
3090 let l = lc.into_value();
3097 let r = rc.into_value();
3098
3099 match op {
3100 ast::BinOpKind::Add => match (&l, &r) {
3101 (Value::Int(a), Value::Int(b)) => a
3102 .checked_add(*b)
3103 .map(Value::Int)
3104 .ok_or_else(|| int_overflow("adding", *a, '+', *b)),
3105 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
3106 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
3107 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + *b as f64)),
3108 (Value::String(a), Value::String(b)) => {
3109 let mut ctx = a.context.clone();
3110 ctx.merge(&b.context);
3111 let mut s = String::with_capacity(a.chars.len() + b.chars.len());
3119 s.push_str(&a.chars);
3120 s.push_str(&b.chars);
3121 Ok(Value::String(Rc::new(NixString::with_context(s, ctx))))
3122 }
3123 (Value::Path(a), Value::String(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}{}", b.chars).as_str())))),
3124 (Value::Path(a), Value::Path(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}/{b}").as_str())))),
3125 (Value::Attrs(_), _) | (_, Value::Attrs(_)) => {
3127 let (ls, lctx) = l.coerce_to_string()?;
3128 let (rs, rctx) = r.coerce_to_string()?;
3129 let mut ctx = lctx;
3130 ctx.merge(&rctx);
3131 Ok(Value::String(Rc::new(NixString::with_context(
3132 format!("{ls}{rs}"),
3133 ctx,
3134 ))))
3135 }
3136 _ => Err(EvalError::op_type("add", l.type_name(), r.type_name())),
3137 },
3138 ast::BinOpKind::Sub => num_op(
3139 &l,
3140 &r,
3141 |a, b| a.checked_sub(b),
3142 |a, b| a - b,
3143 |a, b| int_overflow("subtracting", a, '-', b),
3144 ),
3145 ast::BinOpKind::Mul => num_op(
3146 &l,
3147 &r,
3148 |a, b| a.checked_mul(b),
3149 |a, b| a * b,
3150 |a, b| int_overflow("multiplying", a, '*', b),
3151 ),
3152 ast::BinOpKind::Div => {
3153 let rhs_is_zero = match &r {
3162 Value::Int(0) => true,
3163 Value::Float(f) => *f == 0.0,
3164 _ => false,
3165 };
3166 if rhs_is_zero {
3167 return Err(EvalError::DivisionByZero);
3168 }
3169 num_op(
3170 &l,
3171 &r,
3172 |a, b| a.checked_div(b),
3173 |a, b| a / b,
3174 |a, b| int_overflow("dividing", a, '/', b),
3175 )
3176 }
3177 ast::BinOpKind::Equal => Ok(Value::Bool(l == r)),
3178 ast::BinOpKind::NotEqual => Ok(Value::Bool(l != r)),
3179 ast::BinOpKind::Less => compare(&l, &r, |o| o == std::cmp::Ordering::Less),
3180 ast::BinOpKind::LessOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Greater),
3181 ast::BinOpKind::More => compare(&l, &r, |o| o == std::cmp::Ordering::Greater),
3182 ast::BinOpKind::MoreOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Less),
3183 ast::BinOpKind::Update => {
3184 let la = l.to_attrs()?;
3185 let ra = r.to_attrs()?;
3186 Ok(Value::Attrs(Rc::new(la.overlay(ra))))
3188 }
3189 ast::BinOpKind::Concat => {
3190 crate::value::concat_lists(l, r.as_list()?)
3200 }
3201 ast::BinOpKind::And | ast::BinOpKind::Or | ast::BinOpKind::Implication => {
3202 unreachable!("handled above")
3203 }
3204 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
3205 Err(EvalError::NotImplemented("pipe operators".to_string()))
3206 }
3207 }
3208}
3209
3210#[inline]
3215fn int_overflow(verb: &str, a: i64, sym: char, b: i64) -> EvalError {
3216 EvalError::Abort(format!("integer overflow in {verb} {a} {sym} {b}"))
3217}
3218
3219fn num_op(
3220 l: &Value,
3221 r: &Value,
3222 int_op: impl Fn(i64, i64) -> Option<i64>,
3223 float_op: impl Fn(f64, f64) -> f64,
3224 overflow: impl Fn(i64, i64) -> EvalError,
3225) -> Result<Value, EvalError> {
3226 match (l, r) {
3227 (Value::Int(a), Value::Int(b)) => {
3228 int_op(*a, *b).map(Value::Int).ok_or_else(|| overflow(*a, *b))
3229 }
3230 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(float_op(*a, *b))),
3231 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(float_op(*a as f64, *b))),
3232 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(float_op(*a, *b as f64))),
3233 _ => Err(EvalError::op_type("perform arithmetic on", l.type_name(), r.type_name())),
3234 }
3235}
3236
3237fn compare(
3238 l: &Value,
3239 r: &Value,
3240 pred: impl Fn(std::cmp::Ordering) -> bool,
3241) -> Result<Value, EvalError> {
3242 let ord = match (l, r) {
3243 (Value::Int(a), Value::Int(b)) => a.cmp(b),
3244 (Value::Float(a), Value::Float(b)) => {
3245 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
3246 }
3247 (Value::Int(a), Value::Float(b)) => (*a as f64)
3248 .partial_cmp(b)
3249 .unwrap_or(std::cmp::Ordering::Equal),
3250 (Value::Float(a), Value::Int(b)) => a
3251 .partial_cmp(&(*b as f64))
3252 .unwrap_or(std::cmp::Ordering::Equal),
3253 (Value::String(a), Value::String(b)) => a.chars.cmp(&b.chars),
3254 _ => {
3255 return Err(EvalError::op_type("compare", l.type_name(), r.type_name()));
3256 }
3257 };
3258 Ok(Value::Bool(pred(ord)))
3259}
3260
3261pub fn apply_and_force(func: Value, arg: Value) -> Result<Value, EvalError> {
3275 force_value(&apply(func, arg)?)
3276}
3277
3278pub fn apply(func: Value, arg: Value) -> Result<Value, EvalError> {
3279 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || apply_inner(func, arg))
3280}
3281
3282fn apply_inner(func: Value, arg: Value) -> Result<Value, EvalError> {
3283 crate::perf::inc(crate::perf::Counter::Apply);
3284 let func = force_concrete(&func)?.into_value();
3285 match func {
3286 Value::Lambda(closure) => {
3287 if crate::perf::enabled() {
3289 APPLY_SITES.with(|sites| {
3290 let file = closure.env.eval_file()
3291 .map(|p| p.display().to_string())
3292 .unwrap_or_else(|| "<eval>".into());
3293 let param_name = match &closure.param {
3295 rnix::ast::Param::IdentParam(ip) => ip.ident().map(|i| ident_text(&i)).unwrap_or_default(),
3296 rnix::ast::Param::Pattern(pat) => {
3297 let mut names: Vec<String> = pat.pat_entries()
3298 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3299 .take(3)
3300 .collect();
3301 if pat.pat_entries().count() > 3 { names.push("...".to_string()); }
3302 format!("{{{}}}", names.join(","))
3303 }
3304 };
3305 let key = format!("{}:{}", file.rsplit_once("-source/").map_or(file.as_str(), |(_,s)| s), param_name);
3306 *sites.borrow_mut().entry(key).or_insert(0u64) += 1;
3307 });
3308 }
3309 let mut call_env = closure.env.child();
3310 let _file_guard = push_eval_frame(closure.env.eval_file().cloned());
3316 let _trace = push_nix_trace_lambda(&closure.env);
3322 match &closure.param {
3323 rnix::ast::Param::IdentParam(_) => {
3324 bind_param(&closure.param, &arg, &mut call_env)?;
3327 }
3328 rnix::ast::Param::Pattern(_) => {
3329 let forced_arg = force_concrete(&arg)?.into_value();
3331 bind_param(&closure.param, &forced_arg, &mut call_env)?;
3332 }
3333 }
3334 eval_expr(&closure.body, &call_env)
3335 }
3336 Value::Builtin(b) => {
3337 let _trace = push_nix_trace(format!("while calling the '{}' builtin", b.name));
3338 if builtin_takes_lazy_arg(&b.name) {
3348 (b.func)(&[arg])
3349 } else {
3350 let forced_arg = force_value(&arg)?;
3351 (b.func)(&[forced_arg])
3352 }
3353 }
3354 Value::Attrs(ref attrs) => {
3355 if let Some(functor) = attrs.get("__functor") {
3356 let functor = force_value(functor)?;
3357 let partial = apply(functor, func.clone())?;
3359 apply(partial, arg)
3360 } else if crate::value::in_promise_eval() {
3361 Ok(Value::Null)
3366 } else {
3367 Err(EvalError::type_error(
3368 format!("cannot call {} (missing __functor){}", func.type_name(), eval_file_ctx()),
3369 ))
3370 }
3371 }
3372 _ if crate::value::in_promise_eval() => {
3373 Ok(Value::Null)
3378 }
3379 _ => Err(EvalError::type_error(
3380 format!("cannot call {}{}", func.type_name(), eval_file_ctx()),
3381 )),
3382 }
3383}
3384
3385static SUI_BATCH_BIND: std::sync::LazyLock<bool> =
3395 std::sync::LazyLock::new(|| std::env::var_os("SUI_BATCH_BIND").is_some());
3396
3397fn bind_param(param: &ast::Param, arg: &Value, env: &mut Env) -> Result<(), EvalError> {
3398 match param {
3399 ast::Param::IdentParam(ip) => {
3400 let ident = ip
3401 .ident()
3402 .ok_or_else(|| EvalError::ParseError("ident param missing ident".to_string()))?;
3403 let name = ident_text(&ident);
3404 env.bind(name, arg.clone());
3405 }
3406 ast::Param::Pattern(pat) => {
3407 let attrs = arg.as_attrs()?;
3408
3409 if let Some(pat_bind) = pat.pat_bind()
3411 && let Some(ident) = pat_bind.ident()
3412 {
3413 let name = ident_text(&ident);
3414 env.bind(name, arg.clone());
3415 }
3416
3417 let has_ellipsis = pat.ellipsis_token().is_some();
3418 let entries: Vec<ast::PatEntry> = pat.pat_entries().collect();
3419
3420 let mut default_thunks: Vec<Thunk> = Vec::new();
3427 let use_batch = *SUI_BATCH_BIND;
3437 let mut pairs: Vec<(String, Value)> =
3438 if use_batch { Vec::with_capacity(entries.len()) } else { Vec::new() };
3439
3440 for entry in &entries {
3441 let ident = entry.ident().ok_or_else(|| {
3442 EvalError::ParseError("pat entry missing ident".to_string())
3443 })?;
3444 let name = ident_text(&ident);
3445 let value = if let Some(v) = attrs.get(&name) {
3446 v.clone()
3447 } else if let Some(default_expr) = entry.default() {
3448 let thunk = Thunk::new_suspended(
3454 ast::Expr::cast(default_expr.syntax().clone()).unwrap(),
3455 env.clone(),
3456 );
3457 default_thunks.push(thunk.clone());
3458 Value::Thunk(thunk)
3459 } else {
3460 return Err(EvalError::type_error(
3461 format!("missing argument '{name}'{}", eval_file_ctx()),
3462 ));
3463 };
3464 if use_batch {
3465 pairs.push((name, value));
3466 } else {
3467 env.bind(name, value);
3468 }
3469 }
3470 if use_batch {
3471 env.bind_many(pairs);
3472 }
3473
3474 for thunk in &default_thunks {
3476 thunk.update_env(env);
3477 }
3478
3479 if !has_ellipsis {
3480 let entry_names: std::collections::HashSet<String> = entries
3481 .iter()
3482 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3483 .collect();
3484 for key in attrs.keys() {
3485 if !entry_names.contains(key.as_str()) {
3486 return Err(EvalError::type_error(
3487 format!("unexpected argument '{key}'{}", eval_file_ctx()),
3488 ));
3489 }
3490 }
3491 }
3492 }
3493 }
3494 Ok(())
3495}
3496
3497#[cfg(test)]
3498mod tests {
3499 use super::*;
3500
3501 fn ev(input: &str) -> Value {
3502 eval(input).unwrap()
3503 }
3504
3505 #[test]
3512 fn is_self_recursive_binding_ignores_attribute_names() {
3513 fn expr(s: &str) -> ast::Expr {
3514 rnix::Root::parse(s).tree().expr().expect("parse")
3515 }
3516 assert!(!is_self_recursive_binding(&expr("lhs.placeholder"), "placeholder"));
3518 assert!(!is_self_recursive_binding(&expr("{ placeholder = 1; }"), "placeholder"));
3519 assert!(!is_self_recursive_binding(
3520 &expr("if lhs.placeholder == rhs.placeholder then lhs.placeholder else null"),
3521 "placeholder",
3522 ));
3523 assert!(is_self_recursive_binding(&expr("placeholder + 1"), "placeholder"));
3525 assert!(is_self_recursive_binding(
3526 &expr("if placeholder then 1 else 2"),
3527 "placeholder"
3528 ));
3529 }
3530
3531 #[test]
3535 fn maybe_thunk_eager_constant_str_is_byte_identical() {
3536 fn expr(s: &str) -> ast::Expr {
3537 rnix::Root::parse(s).tree().expr().expect("parse")
3538 }
3539 let env = Env::new();
3540 let v = maybe_thunk(&expr(r#""abc""#), &env, false, None);
3542 assert!(matches!(v, Value::String(_)), "constant str should be eager, got {v:?}");
3543 assert_eq!(force_value(&v).unwrap(), Value::string("abc"));
3544 let vi = maybe_thunk(&expr(r#""a${b}c""#), &env, false, None);
3546 assert!(matches!(vi, Value::Thunk(_)), "interpolated str must stay thunked");
3547 }
3548
3549 #[test]
3553 fn eval_pure_constant_arg_classification() {
3554 fn expr(s: &str) -> ast::Expr {
3555 rnix::Root::parse(s).tree().expr().expect("parse")
3556 }
3557 assert!(eval_pure_constant_arg(&expr("42")).is_some());
3559 assert!(eval_pure_constant_arg(&expr("3.14")).is_some());
3560 assert!(eval_pure_constant_arg(&expr(r#""const""#)).is_some());
3561 assert!(eval_pure_constant_arg(&expr("/abs/path")).is_some());
3562 assert!(eval_pure_constant_arg(&expr(r#""a${b}c""#)).is_none(), "interpolated str");
3564 assert!(eval_pure_constant_arg(&expr("true")).is_none(), "bool is an ident");
3567 assert!(eval_pure_constant_arg(&expr("x")).is_none(), "ident (with-scope force)");
3568 assert!(eval_pure_constant_arg(&expr("a.b")).is_none(), "select (fixpoint)");
3569 assert!(eval_pure_constant_arg(&expr("f x")).is_none(), "apply (may throw)");
3570 assert!(eval_pure_constant_arg(&expr("1 + 1")).is_none(), "binop (may throw)");
3571 assert!(eval_pure_constant_arg(&expr("throw \"x\"")).is_none(), "throw stays lazy");
3572 }
3573
3574 #[test]
3578 fn ignored_throwing_arg_stays_lazy() {
3579 assert_eq!(ev(r#"(x: 7) (throw "boom")"#), Value::Int(7));
3580 assert_eq!(ev(r#"(x: 7) "const""#), Value::Int(7));
3582 assert_eq!(ev(r#"(x: x) "used""#), Value::string("used"));
3584 }
3585
3586 #[test]
3587 fn eval_int() { assert_eq!(ev("42"), Value::Int(42)); }
3588
3589 #[test]
3590 fn eval_float() { assert_eq!(ev("3.14"), Value::Float(3.14)); }
3591
3592 #[test]
3593 fn eval_string() { assert_eq!(ev(r#""hello""#), Value::string("hello")); }
3594
3595 #[test]
3596 fn eval_bool() { assert_eq!(ev("true"), Value::Bool(true)); }
3597
3598 #[test]
3599 fn eval_null() { assert_eq!(ev("null"), Value::Null); }
3600
3601 #[test]
3602 fn eval_arithmetic() {
3603 assert_eq!(ev("1 + 2"), Value::Int(3));
3604 assert_eq!(ev("10 - 3"), Value::Int(7));
3605 assert_eq!(ev("2 * 3"), Value::Int(6));
3606 assert_eq!(ev("10 / 3"), Value::Int(3));
3607 }
3608
3609 #[test]
3610 fn eval_precedence() {
3611 assert_eq!(ev("1 + 2 * 3"), Value::Int(7));
3612 assert_eq!(ev("(1 + 2) * 3"), Value::Int(9));
3613 }
3614
3615 #[test]
3616 fn eval_comparison() {
3617 assert_eq!(ev("1 == 1"), Value::Bool(true));
3618 assert_eq!(ev("1 == 2"), Value::Bool(false));
3619 assert_eq!(ev("1 < 2"), Value::Bool(true));
3620 assert_eq!(ev("2 <= 2"), Value::Bool(true));
3621 }
3622
3623 #[test]
3624 fn eval_logic() {
3625 assert_eq!(ev("true && false"), Value::Bool(false));
3626 assert_eq!(ev("true || false"), Value::Bool(true));
3627 assert_eq!(ev("!true"), Value::Bool(false));
3628 }
3629
3630 #[test]
3631 fn eval_string_concat() {
3632 assert_eq!(ev(r#""hello" + " " + "world""#), Value::string("hello world"));
3633 }
3634
3635 #[test]
3636 fn eval_if() {
3637 assert_eq!(ev("if true then 1 else 2"), Value::Int(1));
3638 assert_eq!(ev("if false then 1 else 2"), Value::Int(2));
3639 }
3640
3641 #[test]
3642 fn eval_let() {
3643 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
3644 assert_eq!(ev("let x = 1; y = 2; in x + y"), Value::Int(3));
3645 }
3646
3647 #[test]
3648 fn eval_let_dotted_simple() {
3649 assert_eq!(ev("let a.b = 1; a.c = 2; in a.b + a.c"), Value::Int(3));
3651 }
3652
3653 #[test]
3654 fn eval_let_dotted_deep() {
3655 assert_eq!(ev("let a.b.c = 1; in a.b.c"), Value::Int(1));
3657 }
3658
3659 #[test]
3660 fn eval_let_dotted_mixed() {
3661 assert_eq!(
3663 ev("let a.x = 1; b = 2; a.y = 3; in a.x + a.y + b"),
3664 Value::Int(6),
3665 );
3666 }
3667
3668 #[test]
3669 fn eval_let_dotted_produces_attrset() {
3670 let v = ev("let a.b = 1; a.c = 2; in a");
3672 if let Value::Attrs(attrs) = v {
3673 assert_eq!(attrs.get("b"), Some(&Value::Int(1)));
3674 assert_eq!(attrs.get("c"), Some(&Value::Int(2)));
3675 } else {
3676 panic!("expected Attrs, got {v:?}");
3677 }
3678 }
3679
3680 #[test]
3688 fn dynamic_inner_attr_key_is_lazy_on_sibling_read() {
3689 assert_eq!(
3691 ev(r#"let s = { a.${throw "KEYFORCED"} = 7; other = 9; }; in s.other"#),
3692 Value::Int(9),
3693 );
3694 }
3695
3696 #[test]
3697 fn dynamic_inner_attr_key_resolves_on_head_demand() {
3698 let v = ev(r#"let u = "bob"; s = { homes.${u} = 7; }; in s.homes"#);
3700 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
3701 assert_eq!(attrs.get("bob"), Some(&Value::Int(7)));
3702 } else {
3703 panic!("expected Attrs");
3704 }
3705 }
3706
3707 #[test]
3708 fn dynamic_inner_attr_key_merges_with_static_sibling() {
3709 let v = ev(r#"let u = "x"; s = { a.${u} = 1; a.b = 2; }; in s.a"#);
3711 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
3712 assert_eq!(attrs.get("x"), Some(&Value::Int(1)));
3713 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
3714 } else {
3715 panic!("expected Attrs");
3716 }
3717 }
3718
3719 #[test]
3720 fn dynamic_inner_attr_key_null_skips_binding() {
3721 let v = ev(
3724 r#"let c = true; s = { a.${if c then null else "n"} = 5; b = 1; }; in s.b"#,
3725 );
3726 assert_eq!(v, Value::Int(1));
3727 }
3728
3729 #[test]
3735 fn interpolated_string_attr_key_is_lazy_on_sibling_read() {
3736 assert_eq!(
3737 ev(r#"let s = { a."p/${throw "KEYFORCED"}" = 7; other = 9; }; in s.other"#),
3738 Value::Int(9),
3739 );
3740 }
3741
3742 #[test]
3743 fn interpolated_string_attr_key_resolves_on_head_demand() {
3744 let v = ev(r#"let u = "bob"; s = { homes."u/${u}" = 7; }; in s.homes"#);
3746 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
3747 assert_eq!(attrs.get("u/bob"), Some(&Value::Int(7)));
3748 } else {
3749 panic!("expected Attrs");
3750 }
3751 }
3752
3753 #[test]
3754 fn purely_literal_string_attr_key_stays_eager_static() {
3755 let v = ev(r#"let s = { a."foo bar" = 1; a.b = 2; }; in s.a"#);
3758 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
3759 assert_eq!(attrs.get("foo bar"), Some(&Value::Int(1)));
3760 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
3761 } else {
3762 panic!("expected Attrs");
3763 }
3764 }
3765
3766 #[test]
3769 fn dynamic_tail_key_under_colliding_head_is_lazy() {
3770 let v = ev(
3773 r#"let s = { sd.services.x = 1; sd.tmpfiles.${throw "KEYFORCED"}.d = 2; }; in s.sd.services.x"#,
3774 );
3775 assert_eq!(v, Value::Int(1));
3776 }
3777
3778 #[test]
3779 fn dynamic_tail_key_under_colliding_head_resolves_and_merges() {
3780 let v = ev(
3783 r#"let k = "z"; s = { sd.services.x = 1; sd.tmpfiles.${k}.d = 2; }; in s.sd"#,
3784 );
3785 let sd = force_value(&v).unwrap();
3786 if let Value::Attrs(sd_attrs) = &sd {
3787 let services = force_value(sd_attrs.get("services").unwrap()).unwrap();
3789 if let Value::Attrs(a) = &services {
3790 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
3791 } else { panic!("expected services attrs"); }
3792 let tmpfiles = force_value(sd_attrs.get("tmpfiles").unwrap()).unwrap();
3794 if let Value::Attrs(a) = &tmpfiles {
3795 let z = force_value(a.get("z").unwrap()).unwrap();
3796 if let Value::Attrs(zd) = &z {
3797 assert_eq!(force_value(zd.get("d").unwrap()).unwrap(), Value::Int(2));
3798 } else { panic!("expected z attrs"); }
3799 } else { panic!("expected tmpfiles attrs"); }
3800 } else {
3801 panic!("expected sd attrs");
3802 }
3803 }
3804
3805 #[test]
3814 fn with_namespace_is_lazy_on_body_whnf() {
3815 let v = ev(r#"builtins.attrNames (with (throw "WITH-FORCED"); { a = 1; b = 2; })"#);
3816 if let Value::List(items) = force_value(&v).unwrap() {
3817 let names: Vec<String> = items
3818 .iter()
3819 .map(|i| match force_value(i).unwrap() {
3820 Value::String(s) => s.as_str().to_string(),
3821 other => panic!("expected string, got {}", other.type_name()),
3822 })
3823 .collect();
3824 assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
3825 } else {
3826 panic!("expected list");
3827 }
3828 }
3829
3830 #[test]
3831 fn with_namespace_forces_only_on_fallthrough() {
3832 assert_eq!(ev(r#"with { x = 42; }; x"#), Value::Int(42));
3836 assert_eq!(ev(r#"let x = 7; in with (throw "NS"); x"#), Value::Int(7));
3839 }
3840
3841 #[test]
3852 fn dotted_fullset_leaf_deep_merges_with_deeper_sibling() {
3853 let v = ev(r#"{ o.a = { x = 1; }; o.a.y = 2; }.o.a"#);
3854 if let Value::Attrs(a) = force_value(&v).unwrap() {
3855 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
3856 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
3857 } else {
3858 panic!("expected attrs");
3859 }
3860 }
3861
3862 #[test]
3863 fn dotted_fullset_leaf_deep_merge_reverse_order() {
3864 let v = ev(r#"{ o.a.y = 2; o.a = { x = 1; }; }.o.a"#);
3867 if let Value::Attrs(a) = force_value(&v).unwrap() {
3868 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
3869 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
3870 } else {
3871 panic!("expected attrs");
3872 }
3873 }
3874
3875 #[test]
3876 fn dotted_fullset_leaf_merge_preserves_leaf_laziness() {
3877 assert_eq!(ev(r#"{ o.a = { x = throw "X-NEVER"; }; o.a.y = 2; }.o.a.y"#), Value::Int(2));
3881 }
3882
3883 #[test]
3884 fn eval_nested_let() {
3885 assert_eq!(ev("let a = 1; b = let c = 2; in c; in a + b"), Value::Int(3));
3886 }
3887
3888 #[test]
3889 fn eval_lambda() {
3890 assert_eq!(ev("(x: x + 1) 41"), Value::Int(42));
3891 }
3892
3893 #[test]
3894 fn eval_lambda_multi_arg() {
3895 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
3896 }
3897
3898 #[test]
3899 fn eval_list() {
3900 let v = ev("[1 2 3]");
3901 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
3902 }
3903
3904 #[test]
3905 fn eval_list_concat() {
3906 let v = ev("[1 2] ++ [3 4]");
3907 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]));
3908 }
3909
3910 #[test]
3911 fn eval_attrset() {
3912 let v = ev("{ a = 1; b = 2; }");
3913 if let Value::Attrs(attrs) = v {
3914 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
3915 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
3916 } else {
3917 panic!("expected attrset");
3918 }
3919 }
3920
3921 #[test]
3922 fn eval_select() {
3923 assert_eq!(ev("{ a = 42; }.a"), Value::Int(42));
3924 }
3925
3926 #[test]
3927 fn eval_select_or() {
3928 assert_eq!(ev("{ a = 42; }.b or 0"), Value::Int(0));
3929 }
3930
3931 #[test]
3932 fn eval_has_attr() {
3933 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
3934 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
3935 }
3936
3937 #[test]
3938 fn eval_update() {
3939 let v = ev("{ a = 1; b = 2; } // { b = 3; c = 4; }");
3940 if let Value::Attrs(attrs) = v {
3941 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
3942 assert_eq!(attrs.get("b"), Some(&Value::Int(3)));
3943 assert_eq!(attrs.get("c"), Some(&Value::Int(4)));
3944 } else {
3945 panic!("expected attrset");
3946 }
3947 }
3948
3949 #[test]
3950 fn eval_with() {
3951 assert_eq!(ev("with { x = 42; }; x"), Value::Int(42));
3952 }
3953
3954 #[test]
3955 fn eval_assert() {
3956 assert_eq!(ev("assert true; 42"), Value::Int(42));
3957 assert!(eval("assert false; 42").is_err());
3958 }
3959
3960 #[test]
3961 fn eval_formals() {
3962 assert_eq!(ev("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3));
3963 }
3964
3965 #[test]
3966 fn eval_formals_default() {
3967 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 1; }"), Value::Int(11));
3968 }
3969
3970 #[test]
3971 fn eval_formals_ellipsis() {
3972 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; }"), Value::Int(1));
3973 }
3974
3975 #[test]
3976 fn eval_named_formals() {
3977 assert_eq!(ev("(args @ { a }: args.a) { a = 42; }"), Value::Int(42));
3978 }
3979
3980 #[test]
3981 fn eval_rec_attrset() {
3982 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
3983 }
3984
3985 #[test]
3986 fn eval_negation() {
3987 assert_eq!(ev("-42"), Value::Int(-42));
3988 }
3989
3990 #[test]
3991 fn eval_float_arithmetic() {
3992 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
3993 assert_eq!(ev("1 + 1.5"), Value::Float(2.5));
3994 }
3995
3996 #[test]
3997 fn eval_division_by_zero() {
3998 assert!(eval("1 / 0").is_err());
3999 }
4000
4001 #[test]
4002 fn eval_builtins_available() {
4003 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
4004 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
4005 }
4006
4007 #[test]
4008 fn eval_builtins_length() {
4009 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
4010 }
4011
4012 #[test]
4013 fn eval_builtins_head_tail() {
4014 assert_eq!(ev("builtins.head [1 2 3]"), Value::Int(1));
4015 assert_eq!(ev("builtins.length (builtins.tail [1 2 3])"), Value::Int(2));
4016 }
4017
4018 #[test]
4019 fn eval_builtins_add() {
4020 assert_eq!(ev("builtins.add 1 2"), Value::Int(3));
4021 }
4022
4023 #[test]
4024 fn eval_builtins_to_string() {
4025 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
4026 }
4027
4028 #[test]
4029 fn eval_implication() {
4030 assert_eq!(ev("false -> true"), Value::Bool(true));
4031 assert_eq!(ev("true -> false"), Value::Bool(false));
4032 assert_eq!(ev("true -> true"), Value::Bool(true));
4033 }
4034
4035 #[test]
4038 fn eval_error_undefined_variable() {
4039 let result = eval("nonexistent");
4040 assert!(result.is_err());
4041 let msg = format!("{}", result.unwrap_err());
4042 assert!(msg.contains("undefined variable"));
4043 }
4044
4045 #[test]
4046 fn eval_error_type_mismatch_arithmetic() {
4047 let result = eval(r#"1 + "hello""#);
4048 assert!(result.is_err());
4049 let msg = format!("{}", result.unwrap_err());
4050 assert!(msg.contains("cannot add") || msg.contains("type"));
4051 }
4052
4053 #[test]
4054 fn eval_error_unexpected_argument() {
4055 let result = eval("({ a }: a) { a = 1; b = 2; }");
4056 assert!(result.is_err());
4057 let msg = format!("{}", result.unwrap_err());
4058 assert!(msg.contains("unexpected argument"));
4059 }
4060
4061 #[test]
4062 fn eval_error_missing_required_argument() {
4063 let result = eval("({ a, b }: a + b) { a = 1; }");
4064 assert!(result.is_err());
4065 let msg = format!("{}", result.unwrap_err());
4066 assert!(msg.contains("missing argument"));
4067 }
4068
4069 #[test]
4070 fn eval_builtins_attr_names_sorted() {
4071 let v = ev("builtins.attrNames { z = 1; a = 2; m = 3; }");
4072 assert_eq!(
4074 v,
4075 Value::list(vec![
4076 Value::string("a"),
4077 Value::string("m"),
4078 Value::string("z"),
4079 ]),
4080 );
4081 }
4082
4083 #[test]
4084 fn eval_builtins_attr_values() {
4085 let v = ev("builtins.attrValues { a = 1; b = 2; }");
4086 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
4088 }
4089
4090 #[test]
4091 fn eval_builtins_is_null() {
4092 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
4093 assert_eq!(ev("builtins.isNull 1"), Value::Bool(false));
4094 }
4095
4096 #[test]
4097 fn eval_builtins_is_int() {
4098 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
4099 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
4100 }
4101
4102 #[test]
4103 fn eval_builtins_is_bool() {
4104 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
4105 assert_eq!(ev("builtins.isBool 0"), Value::Bool(false));
4106 }
4107
4108 #[test]
4109 fn eval_builtins_is_string() {
4110 assert_eq!(ev(r#"builtins.isString "hi""#), Value::Bool(true));
4111 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
4112 }
4113
4114 #[test]
4115 fn eval_builtins_is_list() {
4116 assert_eq!(ev("builtins.isList [1 2]"), Value::Bool(true));
4117 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
4118 }
4119
4120 #[test]
4121 fn eval_builtins_is_attrs() {
4122 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
4123 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
4124 }
4125
4126 #[test]
4127 fn eval_builtins_string_length() {
4128 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
4129 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
4130 }
4131
4132 #[test]
4133 fn eval_builtins_to_json_roundtrip() {
4134 assert_eq!(
4136 ev(r#"builtins.fromJSON (builtins.toJSON 42)"#),
4137 Value::Int(42),
4138 );
4139 assert_eq!(
4140 ev(r#"builtins.fromJSON (builtins.toJSON [1 2 3])"#),
4141 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4142 );
4143 }
4144
4145 #[test]
4146 fn eval_builtins_from_json() {
4147 assert_eq!(
4148 ev(r#"builtins.fromJSON "{\"a\": 1}""#),
4149 {
4150 let mut attrs = NixAttrs::new();
4151 attrs.insert("a".to_string(), Value::Int(1));
4152 Value::Attrs(Rc::new(attrs))
4153 },
4154 );
4155 assert_eq!(ev(r#"builtins.fromJSON "null""#), Value::Null);
4156 assert_eq!(ev(r#"builtins.fromJSON "true""#), Value::Bool(true));
4157 }
4158
4159 #[test]
4160 fn eval_nested_function_application() {
4161 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
4163 assert_eq!(ev("((x: y: x + y) 1) 2"), Value::Int(3));
4165 }
4166
4167 #[test]
4168 fn eval_recursive_let() {
4169 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
4170 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
4171 }
4172
4173 #[test]
4174 fn eval_string_comparison() {
4175 assert_eq!(ev(r#""a" < "b""#), Value::Bool(true));
4176 assert_eq!(ev(r#""b" < "a""#), Value::Bool(false));
4177 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4178 assert_eq!(ev(r#""abc" != "def""#), Value::Bool(true));
4179 }
4180
4181 #[test]
4182 fn eval_list_in_attrset() {
4183 let v = ev("{ x = [1 2 3]; }.x");
4184 assert_eq!(
4185 v,
4186 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4187 );
4188 }
4189
4190 #[test]
4191 fn eval_nested_attrset_select() {
4192 assert_eq!(ev("{ a = { b = 42; }; }.a.b"), Value::Int(42));
4193 }
4194
4195 #[test]
4196 fn eval_let_shadows_outer() {
4197 assert_eq!(
4198 ev("let x = 1; in let x = 2; in x"),
4199 Value::Int(2),
4200 );
4201 }
4202
4203 #[test]
4204 fn eval_with_provides_scope() {
4205 assert_eq!(
4207 ev("with { x = 42; y = 10; }; x + y"),
4208 Value::Int(52),
4209 );
4210 }
4211
4212 #[test]
4213 fn eval_list_equality() {
4214 assert_eq!(ev("[1 2] == [1 2]"), Value::Bool(true));
4215 assert_eq!(ev("[1 2] == [1 3]"), Value::Bool(false));
4216 }
4217
4218 #[test]
4219 fn eval_attrset_equality() {
4220 assert_eq!(ev("{ a = 1; } == { a = 1; }"), Value::Bool(true));
4221 assert_eq!(ev("{ a = 1; } == { a = 2; }"), Value::Bool(false));
4222 }
4223
4224 #[test]
4229 fn literal_int_large_zero_negative() {
4230 assert_eq!(ev("9223372036854775807"), Value::Int(i64::MAX));
4232 assert_eq!(ev("0"), Value::Int(0));
4234 assert_eq!(ev("-1"), Value::Int(-1));
4236 assert_eq!(ev("-999999"), Value::Int(-999999));
4237 }
4238
4239 #[test]
4240 fn literal_float_small_large() {
4241 assert_eq!(ev("0.001"), Value::Float(0.001));
4242 assert_eq!(ev("999999.999"), Value::Float(999999.999));
4243 assert_eq!(ev("1.0e3"), Value::Float(1000.0));
4245 assert_eq!(ev("1.5e2"), Value::Float(150.0));
4246 }
4247
4248 #[test]
4249 fn literal_string_empty_and_escapes() {
4250 assert_eq!(ev(r#""""#), Value::string(""));
4251 assert_eq!(ev(r#""hello\nworld""#), Value::string("hello\nworld"));
4253 assert_eq!(ev(r#""tab\there""#), Value::string("tab\there"));
4254 }
4255
4256 #[test]
4257 fn literal_multiline_string() {
4258 assert_eq!(
4260 ev("''hello''"),
4261 Value::string("hello"),
4262 );
4263 assert_eq!(
4265 ev("''\n line1\n line2\n''"),
4266 Value::string("line1\nline2\n"),
4267 );
4268 }
4269
4270 #[test]
4271 fn literal_paths() {
4272 assert_eq!(ev("./foo"), Value::Path(Box::new(SmolStr::from("./foo"))));
4274 assert_eq!(ev("/nix/store/abc"), Value::Path(Box::new(SmolStr::from("/nix/store/abc"))));
4276 assert_eq!(ev("~/myfile"), Value::Path(Box::new(SmolStr::from("~/myfile"))));
4278 }
4279
4280 #[test]
4290 fn interp_path_abs_splices_and_types_path() {
4291 let v = ev(r#"let x = "foo"; in /a/${x}/b"#);
4293 assert_eq!(v, Value::Path(Box::new(SmolStr::from("/a/foo/b"))));
4294 }
4295
4296 #[test]
4297 fn interp_path_abs_multi_and_slash_in_value() {
4298 assert_eq!(
4300 ev(r#"let a = "x"; b = "y/z"; in /p/${a}/${b}.nix"#),
4301 Value::Path(Box::new(SmolStr::from("/p/x/y/z.nix"))),
4302 );
4303 }
4304
4305 #[test]
4306 fn interp_path_abs_normalizes_double_slash_seam() {
4307 assert_eq!(
4310 ev(r#"/bar/${/tmp/foo}"#),
4311 Value::Path(Box::new(SmolStr::from("/bar/tmp/foo"))),
4312 );
4313 }
4314
4315 #[test]
4316 fn interp_path_rel_resolves_against_eval_dir() {
4317 let _g = push_eval_file(std::path::PathBuf::from("/tmp/example/default.nix"));
4321 assert_eq!(
4322 ev(r#"let x = "foo"; in ./${x}.nix"#),
4323 Value::Path(Box::new(SmolStr::from("/tmp/example/foo.nix"))),
4324 );
4325 }
4326
4327 #[test]
4328 fn interp_path_rel_no_eval_dir_keeps_relative_text() {
4329 assert_eq!(
4332 ev(r#"let x = "foo"; in ./${x}.nix"#),
4333 Value::Path(Box::new(SmolStr::from("./foo.nix"))),
4334 );
4335 }
4336
4337 #[test]
4338 fn interp_path_home_splices_leading_tilde_preserved() {
4339 assert_eq!(
4343 ev(r#"let x = "foo"; in ~/${x}/bar"#),
4344 Value::Path(Box::new(SmolStr::from("~/foo/bar"))),
4345 );
4346 }
4347
4348 #[test]
4349 fn interp_path_non_interpolated_still_raw() {
4350 assert_eq!(ev("/a/b/c"), Value::Path(Box::new(SmolStr::from("/a/b/c"))));
4353 assert_eq!(ev("~/plain"), Value::Path(Box::new(SmolStr::from("~/plain"))));
4354 }
4355
4356 #[test]
4357 fn literal_null_true_false_standalone() {
4358 assert_eq!(ev("null"), Value::Null);
4359 assert_eq!(ev("true"), Value::Bool(true));
4360 assert_eq!(ev("false"), Value::Bool(false));
4361 }
4362
4363 #[test]
4368 fn op_arithmetic_int() {
4369 assert_eq!(ev("100 + 200"), Value::Int(300));
4370 assert_eq!(ev("50 - 30"), Value::Int(20));
4371 assert_eq!(ev("7 * 8"), Value::Int(56));
4372 assert_eq!(ev("17 / 3"), Value::Int(5)); }
4374
4375 #[test]
4376 fn op_arithmetic_float() {
4377 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
4378 assert_eq!(ev("5.0 - 1.5"), Value::Float(3.5));
4379 assert_eq!(ev("2.0 * 3.0"), Value::Float(6.0));
4380 assert_eq!(ev("7.0 / 2.0"), Value::Float(3.5));
4381 }
4382
4383 #[test]
4384 fn op_arithmetic_mixed_int_float() {
4385 assert_eq!(ev("1 + 2.5"), Value::Float(3.5));
4387 assert_eq!(ev("2.5 + 1"), Value::Float(3.5));
4388 assert_eq!(ev("2 * 1.5"), Value::Float(3.0));
4390 assert_eq!(ev("5.5 - 2"), Value::Float(3.5));
4392 }
4393
4394 #[test]
4395 fn op_string_concat() {
4396 assert_eq!(ev(r#""foo" + "bar""#), Value::string("foobar"));
4397 assert_eq!(ev(r#""" + "x""#), Value::string("x"));
4398 assert_eq!(ev(r#""a" + "" + "b""#), Value::string("ab"));
4399 }
4400
4401 #[test]
4402 fn op_path_concat() {
4403 assert_eq!(ev(r#"./foo + "/bar""#), Value::Path(Box::new(SmolStr::from("./foo/bar"))));
4405 assert_eq!(ev("./a + ./b"), Value::Path(Box::new(SmolStr::from("./a/./b"))));
4407 }
4408
4409 #[test]
4410 fn op_comparison_ints() {
4411 assert_eq!(ev("1 < 2"), Value::Bool(true));
4412 assert_eq!(ev("2 < 1"), Value::Bool(false));
4413 assert_eq!(ev("2 > 1"), Value::Bool(true));
4414 assert_eq!(ev("1 > 2"), Value::Bool(false));
4415 assert_eq!(ev("2 <= 2"), Value::Bool(true));
4416 assert_eq!(ev("3 <= 2"), Value::Bool(false));
4417 assert_eq!(ev("2 >= 2"), Value::Bool(true));
4418 assert_eq!(ev("1 >= 2"), Value::Bool(false));
4419 }
4420
4421 #[test]
4422 fn op_comparison_floats() {
4423 assert_eq!(ev("1.5 < 2.5"), Value::Bool(true));
4424 assert_eq!(ev("2.5 > 1.5"), Value::Bool(true));
4425 assert_eq!(ev("1.5 <= 1.5"), Value::Bool(true));
4426 assert_eq!(ev("1.5 >= 1.5"), Value::Bool(true));
4427 }
4428
4429 #[test]
4430 fn op_comparison_strings() {
4431 assert_eq!(ev(r#""apple" < "banana""#), Value::Bool(true));
4432 assert_eq!(ev(r#""banana" > "apple""#), Value::Bool(true));
4433 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4434 assert_eq!(ev(r#""abc" != "xyz""#), Value::Bool(true));
4435 assert_eq!(ev(r#""abc" <= "abd""#), Value::Bool(true));
4436 assert_eq!(ev(r#""abc" >= "abb""#), Value::Bool(true));
4437 }
4438
4439 #[test]
4440 fn op_equality_various_types() {
4441 assert_eq!(ev("null == null"), Value::Bool(true));
4442 assert_eq!(ev("true == true"), Value::Bool(true));
4443 assert_eq!(ev("false == false"), Value::Bool(true));
4444 assert_eq!(ev("true == false"), Value::Bool(false));
4445 assert_eq!(ev("1 == 1"), Value::Bool(true));
4446 assert_eq!(ev("1 != 2"), Value::Bool(true));
4447 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
4449 assert_eq!(ev("null == false"), Value::Bool(false));
4450 }
4451
4452 #[test]
4453 fn op_logic_short_circuit() {
4454 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
4456 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
4458 }
4459
4460 #[test]
4461 fn op_logic_full() {
4462 assert_eq!(ev("true && true"), Value::Bool(true));
4463 assert_eq!(ev("true && false"), Value::Bool(false));
4464 assert_eq!(ev("false && true"), Value::Bool(false));
4465 assert_eq!(ev("false && false"), Value::Bool(false));
4466 assert_eq!(ev("true || true"), Value::Bool(true));
4467 assert_eq!(ev("true || false"), Value::Bool(true));
4468 assert_eq!(ev("false || true"), Value::Bool(true));
4469 assert_eq!(ev("false || false"), Value::Bool(false));
4470 assert_eq!(ev("!true"), Value::Bool(false));
4471 assert_eq!(ev("!false"), Value::Bool(true));
4472 }
4473
4474 #[test]
4475 fn op_implication_truth_table() {
4476 assert_eq!(ev("false -> false"), Value::Bool(true));
4478 assert_eq!(ev("false -> true"), Value::Bool(true));
4479 assert_eq!(ev("true -> true"), Value::Bool(true));
4481 assert_eq!(ev("true -> false"), Value::Bool(false));
4482 }
4483
4484 #[test]
4485 fn op_implication_short_circuit() {
4486 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
4488 }
4489
4490 #[test]
4491 fn op_update_merge() {
4492 let v = ev("{ a = 1; } // { b = 2; }");
4493 if let Value::Attrs(attrs) = v {
4494 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4495 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4496 } else {
4497 panic!("expected attrs");
4498 }
4499 }
4500
4501 #[test]
4502 fn op_update_right_wins() {
4503 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
4504 }
4505
4506 #[test]
4507 fn op_list_concat() {
4508 assert_eq!(
4509 ev("[1 2] ++ [3 4]"),
4510 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]),
4511 );
4512 assert_eq!(ev("[] ++ [1]"), Value::list(vec![Value::Int(1)]));
4514 assert_eq!(ev("[1] ++ []"), Value::list(vec![Value::Int(1)]));
4515 }
4516
4517 #[test]
4518 fn op_has_attr_present_and_absent() {
4519 assert_eq!(ev("{ x = 1; y = 2; } ? x"), Value::Bool(true));
4520 assert_eq!(ev("{ x = 1; } ? z"), Value::Bool(false));
4521 assert_eq!(ev("{} ? anything"), Value::Bool(false));
4522 }
4523
4524 #[test]
4525 fn op_unary_negate() {
4526 assert_eq!(ev("-42"), Value::Int(-42));
4527 assert_eq!(ev("-3.14"), Value::Float(-3.14));
4528 assert_eq!(ev("- -5"), Value::Int(5));
4530 }
4531
4532 #[test]
4537 fn control_if_true_branch() {
4538 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
4539 }
4540
4541 #[test]
4542 fn control_if_false_branch() {
4543 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
4544 }
4545
4546 #[test]
4547 fn control_if_nested() {
4548 assert_eq!(
4549 ev("if true then (if false then 1 else 2) else 3"),
4550 Value::Int(2),
4551 );
4552 assert_eq!(
4553 ev("if false then 1 else (if true then 2 else 3)"),
4554 Value::Int(2),
4555 );
4556 }
4557
4558 #[test]
4559 fn control_assert_passing() {
4560 assert_eq!(ev("assert 1 == 1; 42"), Value::Int(42));
4561 assert_eq!(ev("assert true; true"), Value::Bool(true));
4562 }
4563
4564 #[test]
4565 fn control_assert_failing() {
4566 assert!(eval("assert false; 42").is_err());
4567 assert!(eval("assert 1 == 2; 42").is_err());
4568 }
4569
4570 #[test]
4571 fn control_with_basic_scope() {
4572 assert_eq!(ev("with { a = 1; b = 2; }; a + b"), Value::Int(3));
4573 }
4574
4575 #[test]
4576 fn control_with_lexical_precedence() {
4577 assert_eq!(
4579 ev("let x = 10; in with { x = 99; }; x"),
4580 Value::Int(10),
4581 );
4582 }
4583
4584 #[test]
4585 fn control_with_nested() {
4586 assert_eq!(
4587 ev("with { a = 1; }; with { b = 2; }; a + b"),
4588 Value::Int(3),
4589 );
4590 }
4591
4592 #[test]
4593 fn control_with_lazy_fix_self() {
4594 let result = eval(
4599 "let fix = f: let x = f x; in x; in fix (self: with self; { a = 1; b = a + 1; })"
4600 );
4601 assert!(result.is_ok(), "fix with self should work: {:?}", result);
4602 if let Ok(Value::Attrs(attrs)) = result {
4603 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4604 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4605 } else {
4606 panic!("expected Attrs, got {:?}", result);
4607 }
4608 }
4609
4610 #[test]
4611 fn control_with_lazy_fix_self_lib_pattern() {
4612 let result = eval(r#"
4615 let fix = f: let x = f x; in x;
4616 in (fix (self: with self; {
4617 lib = { version = "1.0"; };
4618 hello = "hello ${lib.version}";
4619 })).hello
4620 "#);
4621 assert!(result.is_ok(), "nixpkgs-style lib pattern: {:?}", result);
4622 assert_eq!(
4623 result.unwrap(),
4624 Value::String(Rc::new(NixString::plain("hello 1.0"))),
4625 );
4626 }
4627
4628 #[test]
4629 fn control_with_non_attrset_errors() {
4630 let result = eval("with 42; 1");
4632 assert_eq!(result.unwrap(), Value::Int(1));
4635 }
4636
4637 #[test]
4638 fn control_with_non_attrset_lookup_falls_through() {
4639 let result = eval("let x = 1; in with 42; x");
4642 assert_eq!(result.unwrap(), Value::Int(1));
4643 }
4644
4645 #[test]
4646 fn control_let_simple_and_multiple() {
4647 assert_eq!(ev("let x = 5; in x"), Value::Int(5));
4648 assert_eq!(ev("let x = 1; y = 2; z = 3; in x + y + z"), Value::Int(6));
4649 }
4650
4651 #[test]
4652 fn control_let_shadow_outer() {
4653 assert_eq!(
4654 ev("let x = 1; in let x = 2; in x"),
4655 Value::Int(2),
4656 );
4657 }
4658
4659 #[test]
4660 fn control_let_recursive_reference() {
4661 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
4662 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
4663 }
4664
4665 #[test]
4666 fn control_nested_let_expression() {
4667 assert_eq!(
4668 ev("let a = let b = 1; in b; in a"),
4669 Value::Int(1),
4670 );
4671 assert_eq!(
4672 ev("let a = let b = 10; in b + 5; in a * 2"),
4673 Value::Int(30),
4674 );
4675 }
4676
4677 #[test]
4682 fn func_identity_lambda() {
4683 assert_eq!(ev("(x: x) 42"), Value::Int(42));
4684 assert_eq!(ev(r#"(x: x) "hello""#), Value::string("hello"));
4685 }
4686
4687 #[test]
4688 fn func_curried_two_args() {
4689 assert_eq!(ev("(x: y: x + y) 3 4"), Value::Int(7));
4690 }
4691
4692 #[test]
4693 fn func_curried_three_args() {
4694 assert_eq!(ev("(a: b: c: a + b + c) 1 2 3"), Value::Int(6));
4695 }
4696
4697 #[test]
4698 fn func_formals_basic() {
4699 assert_eq!(ev("({ a, b }: a + b) { a = 3; b = 7; }"), Value::Int(10));
4700 }
4701
4702 #[test]
4703 fn func_formals_with_defaults() {
4704 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; }"), Value::Int(15));
4705 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; b = 20; }"), Value::Int(25));
4707 }
4708
4709 #[test]
4710 fn func_formals_with_ellipsis() {
4711 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; c = 3; }"), Value::Int(1));
4712 }
4713
4714 #[test]
4715 fn func_named_formals_at_before() {
4716 assert_eq!(
4718 ev("(args @ { a, b }: args.a + args.b) { a = 3; b = 4; }"),
4719 Value::Int(7),
4720 );
4721 }
4722
4723 #[test]
4724 fn func_named_formals_at_after() {
4725 assert_eq!(
4727 ev("({ a, b } @ args: args.a + args.b) { a = 10; b = 20; }"),
4728 Value::Int(30),
4729 );
4730 }
4731
4732 #[test]
4733 fn func_nested_application() {
4734 assert_eq!(ev("((x: y: x * y) 3) 4"), Value::Int(12));
4736 }
4737
4738 #[test]
4739 fn func_higher_order_map() {
4740 assert_eq!(
4741 ev("builtins.map (x: x * 2) [1 2 3]"),
4742 Value::list(vec![Value::Int(2), Value::Int(4), Value::Int(6)]),
4743 );
4744 }
4745
4746 #[test]
4747 fn func_higher_order_filter() {
4748 assert_eq!(
4749 ev("builtins.filter (x: x > 2) [1 2 3 4 5]"),
4750 Value::list(vec![Value::Int(3), Value::Int(4), Value::Int(5)]),
4751 );
4752 }
4753
4754 #[test]
4755 fn func_higher_order_foldl() {
4756 assert_eq!(
4758 ev("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4]"),
4759 Value::Int(10),
4760 );
4761 }
4762
4763 #[test]
4764 fn func_as_attrset_value() {
4765 assert_eq!(
4766 ev("let s = { f = x: x + 1; }; in s.f 5"),
4767 Value::Int(6),
4768 );
4769 }
4770
4771 #[test]
4772 fn func_immediate_application() {
4773 assert_eq!(ev("(x: x * x) 7"), Value::Int(49));
4774 }
4775
4776 #[test]
4777 fn func_in_let_binding() {
4778 assert_eq!(
4779 ev("let double = x: x * 2; in double 21"),
4780 Value::Int(42),
4781 );
4782 }
4783
4784 #[test]
4789 fn attrs_empty_set() {
4790 let v = ev("{}");
4791 if let Value::Attrs(attrs) = v {
4792 assert!(attrs.is_empty());
4793 } else {
4794 panic!("expected attrs");
4795 }
4796 }
4797
4798 #[test]
4799 fn attrs_simple() {
4800 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
4801 }
4802
4803 #[test]
4804 fn attrs_nested_access() {
4805 assert_eq!(ev("{ a = { b = { c = 42; }; }; }.a.b.c"), Value::Int(42));
4806 }
4807
4808 #[test]
4809 fn attrs_recursive_set() {
4810 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
4811 }
4812
4813 #[test]
4814 fn attrs_update_disjoint() {
4815 let v = ev("{ a = 1; } // { b = 2; }");
4816 if let Value::Attrs(attrs) = v {
4817 assert_eq!(attrs.len(), 2);
4818 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4819 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4820 } else {
4821 panic!("expected attrs");
4822 }
4823 }
4824
4825 #[test]
4826 fn attrs_update_override() {
4827 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
4828 }
4829
4830 #[test]
4831 fn attrs_has_attr_operator() {
4832 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
4833 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
4834 }
4835
4836 #[test]
4837 fn attrs_select_with_default() {
4838 assert_eq!(ev("{ a = 1; }.a or 99"), Value::Int(1));
4839 assert_eq!(ev("{}.missing or 99"), Value::Int(99));
4840 assert_eq!(ev("{ a = 1; }.b or 42"), Value::Int(42));
4841 }
4842
4843 #[test]
4844 fn attrs_nested_attr_path_in_binding() {
4845 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
4847 }
4848
4849 #[test]
4850 fn attrs_inherit_from_scope() {
4851 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.x"), Value::Int(1));
4852 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.y"), Value::Int(2));
4853 }
4854
4855 #[test]
4856 fn attrs_inherit_from_expr() {
4857 assert_eq!(
4858 ev("{ inherit ({ a = 42; b = 10; }) a; }.a"),
4859 Value::Int(42),
4860 );
4861 }
4862
4863 #[test]
4864 fn attrs_dynamic_attr_name() {
4865 assert_eq!(
4866 ev(r#"let name = "x"; in { ${name} = 42; }.x"#),
4867 Value::Int(42),
4868 );
4869 }
4870
4871 #[test]
4872 fn attrs_attr_names_sorted() {
4873 assert_eq!(
4874 ev("builtins.attrNames { z = 1; m = 2; a = 3; }"),
4875 Value::list(vec![
4876 Value::string("a"),
4877 Value::string("m"),
4878 Value::string("z"),
4879 ]),
4880 );
4881 }
4882
4883 #[test]
4884 fn attrs_attr_values_follow_key_order() {
4885 assert_eq!(
4887 ev("builtins.attrValues { c = 3; a = 1; b = 2; }"),
4888 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4889 );
4890 }
4891
4892 #[test]
4893 fn attrs_update_is_shallow() {
4894 assert_eq!(
4896 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a ? x"),
4897 Value::Bool(false),
4898 );
4899 assert_eq!(
4900 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a.y"),
4901 Value::Int(2),
4902 );
4903 }
4904
4905 #[test]
4910 fn list_empty() {
4911 assert_eq!(ev("[]"), Value::list(vec![]));
4912 }
4913
4914 #[test]
4915 fn list_single_element() {
4916 assert_eq!(ev("[1]"), Value::list(vec![Value::Int(1)]));
4917 }
4918
4919 #[test]
4920 fn list_mixed_types() {
4921 assert_eq!(
4922 ev(r#"[1 "two" true null]"#),
4923 Value::list(vec![
4924 Value::Int(1),
4925 Value::string("two"),
4926 Value::Bool(true),
4927 Value::Null,
4928 ]),
4929 );
4930 }
4931
4932 #[test]
4933 fn list_nested() {
4934 assert_eq!(
4935 ev("[[1 2] [3 4]]"),
4936 Value::list(vec![
4937 Value::list(vec![Value::Int(1), Value::Int(2)]),
4938 Value::list(vec![Value::Int(3), Value::Int(4)]),
4939 ]),
4940 );
4941 }
4942
4943 #[test]
4944 fn list_concat_operator() {
4945 assert_eq!(
4946 ev("[1] ++ [2] ++ [3]"),
4947 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4948 );
4949 }
4950
4951 #[test]
4952 fn list_builtins_length() {
4953 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
4954 assert_eq!(ev("builtins.length []"), Value::Int(0));
4955 }
4956
4957 #[test]
4958 fn list_builtins_elem_at() {
4959 assert_eq!(ev("builtins.elemAt [10 20 30] 0"), Value::Int(10));
4960 assert_eq!(ev("builtins.elemAt [10 20 30] 1"), Value::Int(20));
4961 assert_eq!(ev("builtins.elemAt [10 20 30] 2"), Value::Int(30));
4962 }
4963
4964 #[test]
4965 fn list_equality() {
4966 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
4967 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
4968 assert_eq!(ev("[] == []"), Value::Bool(true));
4969 }
4970
4971 #[test]
4976 fn interp_simple_variable() {
4977 assert_eq!(
4978 ev(r#"let name = "world"; in "hello ${name}""#),
4979 Value::string("hello world"),
4980 );
4981 }
4982
4983 #[test]
4984 fn interp_nested_expression() {
4985 assert_eq!(
4986 ev(r#""result: ${builtins.toString (1 + 2)}""#),
4987 Value::string("result: 3"),
4988 );
4989 }
4990
4991 #[test]
4992 fn interp_int_coercion() {
4993 assert_eq!(
4995 ev(r#"let x = 42; in "count: ${builtins.toString x}""#),
4996 Value::string("count: 42"),
4997 );
4998 }
4999
5000 #[test]
5001 fn interp_multiple() {
5002 assert_eq!(
5003 ev(r#"let a = "foo"; b = "bar"; in "${a} and ${b}""#),
5004 Value::string("foo and bar"),
5005 );
5006 }
5007
5008 #[test]
5009 fn interp_in_let() {
5010 assert_eq!(
5011 ev(r#"let x = "world"; in "hello ${x}""#),
5012 Value::string("hello world"),
5013 );
5014 }
5015
5016 #[test]
5017 fn interp_empty_result() {
5018 assert_eq!(
5019 ev(r#"let x = ""; in "a${x}b""#),
5020 Value::string("ab"),
5021 );
5022 }
5023
5024 #[test]
5025 fn interp_path_in_string_context() {
5026 assert!(eval(r#""path: ${./foo-nonexistent-xyz}""#).is_err());
5032 }
5033
5034 #[test]
5035 fn interp_adjacent_interpolations() {
5036 assert_eq!(
5037 ev(r#"let a = "x"; b = "y"; in "${a}${b}""#),
5038 Value::string("xy"),
5039 );
5040 }
5041
5042 #[test]
5047 fn builtins_map_filter_foldl() {
5048 assert_eq!(
5050 ev("builtins.map (x: x + 10) [1 2 3]"),
5051 Value::list(vec![Value::Int(11), Value::Int(12), Value::Int(13)]),
5052 );
5053 assert_eq!(
5055 ev("builtins.filter (x: x > 1) [1 2 3]"),
5056 Value::list(vec![Value::Int(2), Value::Int(3)]),
5057 );
5058 assert_eq!(
5060 ev("builtins.foldl' (a: b: a * b) 1 [2 3 4]"),
5061 Value::Int(24),
5062 );
5063 }
5064
5065 #[test]
5066 fn builtins_map_attrs() {
5067 assert_eq!(
5068 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).a"),
5069 Value::Int(2),
5070 );
5071 assert_eq!(
5072 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).b"),
5073 Value::Int(4),
5074 );
5075 }
5076
5077 #[test]
5078 fn builtins_list_to_attrs() {
5079 assert_eq!(
5080 ev(r#"(builtins.listToAttrs [{ name = "x"; value = 1; } { name = "y"; value = 2; }]).x"#),
5081 Value::Int(1),
5082 );
5083 }
5084
5085 #[test]
5086 fn builtins_list_to_attrs_duplicate_key_first_wins() {
5087 assert_eq!(
5096 ev(r#"(builtins.listToAttrs [{ name = "k"; value = 1; } { name = "k"; value = 2; }]).k"#),
5097 Value::Int(1),
5098 );
5099 }
5100
5101 #[test]
5102 fn builtins_concat_map() {
5103 assert_eq!(
5104 ev("builtins.concatMap (x: [x (x * 2)]) [1 2 3]"),
5105 Value::list(vec![
5106 Value::Int(1), Value::Int(2),
5107 Value::Int(2), Value::Int(4),
5108 Value::Int(3), Value::Int(6),
5109 ]),
5110 );
5111 }
5112
5113 #[test]
5114 fn builtins_concat_lists() {
5115 assert_eq!(
5116 ev("builtins.concatLists [[1 2] [3] [4 5]]"),
5117 Value::list(vec![
5118 Value::Int(1), Value::Int(2), Value::Int(3),
5119 Value::Int(4), Value::Int(5),
5120 ]),
5121 );
5122 }
5123
5124 #[test]
5125 fn builtins_concat_strings_sep() {
5126 assert_eq!(
5127 ev(r#"builtins.concatStringsSep ", " ["a" "b" "c"]"#),
5128 Value::string("a, b, c"),
5129 );
5130 assert_eq!(
5131 ev(r#"builtins.concatStringsSep "" ["x" "y"]"#),
5132 Value::string("xy"),
5133 );
5134 }
5135
5136 #[test]
5137 fn builtins_replace_strings() {
5138 assert_eq!(
5139 ev(r#"builtins.replaceStrings ["o"] ["0"] "foobar""#),
5140 Value::string("f00bar"),
5141 );
5142 assert_eq!(
5143 ev(r#"builtins.replaceStrings ["hello"] ["goodbye"] "hello world""#),
5144 Value::string("goodbye world"),
5145 );
5146 }
5147
5148 #[test]
5149 fn builtins_has_prefix_has_suffix() {
5150 assert_eq!(ev(r#"builtins.hasPrefix "he" "hello""#), Value::Bool(true));
5151 assert_eq!(ev(r#"builtins.hasPrefix "xx" "hello""#), Value::Bool(false));
5152 assert_eq!(ev(r#"builtins.hasSuffix "lo" "hello""#), Value::Bool(true));
5153 assert_eq!(ev(r#"builtins.hasSuffix "xx" "hello""#), Value::Bool(false));
5154 }
5155
5156 #[test]
5157 fn builtins_all_any() {
5158 assert_eq!(ev("builtins.all (x: x > 0) [1 2 3]"), Value::Bool(true));
5159 assert_eq!(ev("builtins.all (x: x > 1) [1 2 3]"), Value::Bool(false));
5160 assert_eq!(ev("builtins.any (x: x > 2) [1 2 3]"), Value::Bool(true));
5161 assert_eq!(ev("builtins.any (x: x > 5) [1 2 3]"), Value::Bool(false));
5162 }
5163
5164 #[test]
5165 fn builtins_sort() {
5166 assert_eq!(
5167 ev("builtins.sort (a: b: a < b) [3 1 2]"),
5168 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5169 );
5170 }
5171
5172 #[test]
5173 fn builtins_remove_attrs() {
5174 let v = ev(r#"builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b" "c"]"#);
5175 if let Value::Attrs(attrs) = v {
5176 assert_eq!(attrs.len(), 1);
5177 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5178 assert!(attrs.get("b").is_none());
5179 } else {
5180 panic!("expected attrs");
5181 }
5182 }
5183
5184 #[test]
5185 fn builtins_intersect_attrs() {
5186 let v = ev("builtins.intersectAttrs { a = 1; b = 2; } { b = 20; c = 30; }");
5187 if let Value::Attrs(attrs) = v {
5188 assert_eq!(attrs.len(), 1);
5189 assert_eq!(attrs.get("b"), Some(&Value::Int(20)));
5191 } else {
5192 panic!("expected attrs");
5193 }
5194 }
5195
5196 #[test]
5197 fn builtins_type_of_all_types() {
5198 assert_eq!(ev("builtins.typeOf null"), Value::string("null"));
5199 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
5200 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
5201 assert_eq!(ev("builtins.typeOf 3.14"), Value::string("float"));
5202 assert_eq!(ev(r#"builtins.typeOf "hi""#), Value::string("string"));
5203 assert_eq!(ev("builtins.typeOf [1]"), Value::string("list"));
5204 assert_eq!(ev("builtins.typeOf {}"), Value::string("set"));
5205 assert_eq!(ev("builtins.typeOf (x: x)"), Value::string("lambda"));
5206 }
5207
5208 #[test]
5209 fn builtins_is_type_checks() {
5210 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
5211 assert_eq!(ev("builtins.isNull 0"), Value::Bool(false));
5212 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
5213 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
5214 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
5215 assert_eq!(ev("builtins.isBool 1"), Value::Bool(false));
5216 assert_eq!(ev(r#"builtins.isString "x""#), Value::Bool(true));
5217 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
5218 assert_eq!(ev("builtins.isList []"), Value::Bool(true));
5219 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
5220 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
5221 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
5222 assert_eq!(ev("builtins.isFunction (x: x)"), Value::Bool(true));
5223 assert_eq!(ev("builtins.isFunction 1"), Value::Bool(false));
5224 assert_eq!(ev("builtins.isFloat 3.14"), Value::Bool(true));
5225 assert_eq!(ev("builtins.isFloat 1"), Value::Bool(false));
5226 }
5227
5228 #[test]
5229 fn builtins_to_json_from_json_roundtrip() {
5230 assert_eq!(ev("builtins.fromJSON (builtins.toJSON 42)"), Value::Int(42));
5232 assert_eq!(
5234 ev(r#"builtins.fromJSON (builtins.toJSON "hello")"#),
5235 Value::string("hello"),
5236 );
5237 assert_eq!(
5239 ev("builtins.fromJSON (builtins.toJSON [1 2 3])"),
5240 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5241 );
5242 assert_eq!(ev("builtins.fromJSON (builtins.toJSON null)"), Value::Null);
5244 assert_eq!(ev("builtins.fromJSON (builtins.toJSON true)"), Value::Bool(true));
5246 }
5247
5248 #[test]
5249 fn builtins_to_string_various() {
5250 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
5251 assert_eq!(ev("builtins.toString true"), Value::string("1"));
5252 assert_eq!(ev("builtins.toString false"), Value::string(""));
5253 assert_eq!(ev("builtins.toString null"), Value::string(""));
5254 assert_eq!(ev(r#"builtins.toString "hello""#), Value::string("hello"));
5255 }
5256
5257 #[test]
5258 fn builtins_function_args() {
5259 let v = ev("builtins.functionArgs ({ a, b ? 1 }: a)");
5260 if let Value::Attrs(attrs) = v {
5261 assert_eq!(attrs.get("a"), Some(&Value::Bool(false))); assert_eq!(attrs.get("b"), Some(&Value::Bool(true))); } else {
5264 panic!("expected attrs");
5265 }
5266 }
5267
5268 #[test]
5269 fn builtins_gen_list() {
5270 assert_eq!(
5271 ev("builtins.genList (x: x * x) 5"),
5272 Value::list(vec![
5273 Value::Int(0), Value::Int(1), Value::Int(4),
5274 Value::Int(9), Value::Int(16),
5275 ]),
5276 );
5277 assert_eq!(ev("builtins.genList (x: x) 0"), Value::list(vec![]));
5278 }
5279
5280 #[test]
5281 fn builtins_elem() {
5282 assert_eq!(ev("builtins.elem 2 [1 2 3]"), Value::Bool(true));
5283 assert_eq!(ev("builtins.elem 5 [1 2 3]"), Value::Bool(false));
5284 assert_eq!(ev("builtins.elem 1 []"), Value::Bool(false));
5285 }
5286
5287 #[test]
5288 fn builtins_head_tail() {
5289 assert_eq!(ev("builtins.head [10 20 30]"), Value::Int(10));
5290 assert_eq!(
5291 ev("builtins.tail [10 20 30]"),
5292 Value::list(vec![Value::Int(20), Value::Int(30)]),
5293 );
5294 }
5295
5296 #[test]
5297 fn builtins_string_length() {
5298 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
5299 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
5300 assert_eq!(ev(r#"builtins.stringLength "abc def""#), Value::Int(7));
5301 }
5302
5303 #[test]
5304 fn builtins_ceil_floor() {
5305 assert_eq!(ev("builtins.ceil 2.3"), Value::Int(3));
5306 assert_eq!(ev("builtins.ceil 2.0"), Value::Int(2));
5307 assert_eq!(ev("builtins.floor 2.9"), Value::Int(2));
5308 assert_eq!(ev("builtins.floor 2.0"), Value::Int(2));
5309 assert_eq!(ev("builtins.ceil 5"), Value::Int(5));
5311 assert_eq!(ev("builtins.floor 5"), Value::Int(5));
5312 }
5313
5314 #[test]
5315 fn builtins_try_eval() {
5316 let v = ev("builtins.tryEval 42");
5317 if let Value::Attrs(attrs) = v {
5318 assert_eq!(attrs.get("success"), Some(&Value::Bool(true)));
5319 assert_eq!(attrs.get("value"), Some(&Value::Int(42)));
5320 } else {
5321 panic!("expected attrs");
5322 }
5323 }
5324
5325 #[test]
5326 fn builtins_throw() {
5327 let result = eval(r#"builtins.throw "oops""#);
5328 assert!(result.is_err());
5329 let msg = format!("{}", result.unwrap_err());
5330 assert!(msg.contains("oops"));
5331 }
5332
5333 #[test]
5334 fn builtins_seq_deep_seq() {
5335 assert_eq!(ev("builtins.seq 1 42"), Value::Int(42));
5337 assert_eq!(ev("builtins.deepSeq [1 2 3] 99"), Value::Int(99));
5339 }
5340
5341 #[test]
5342 fn builtins_current_system() {
5343 let v = ev("builtins.currentSystem");
5344 if let Value::String(ns) = v {
5345 let s = &ns.chars;
5346 assert!(
5348 s == "aarch64-darwin"
5349 || s == "x86_64-darwin"
5350 || s == "aarch64-linux"
5351 || s == "x86_64-linux",
5352 "unexpected system: {s}",
5353 );
5354 } else {
5355 panic!("expected string");
5356 }
5357 }
5358
5359 #[test]
5364 fn pattern_mkif_like() {
5365 assert_eq!(
5367 ev("(if true then { x = 1; } else {}).x"),
5368 Value::Int(1),
5369 );
5370 let v = ev("if false then { x = 1; } else {}");
5371 if let Value::Attrs(attrs) = v {
5372 assert!(attrs.is_empty());
5373 } else {
5374 panic!("expected attrs");
5375 }
5376 }
5377
5378 #[test]
5379 fn pattern_optional_attrs() {
5380 assert_eq!(
5382 ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in (optionalAttrs true { a = 1; }).a"),
5383 Value::Int(1),
5384 );
5385 let v = ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in optionalAttrs false { a = 1; }");
5386 if let Value::Attrs(attrs) = v {
5387 assert!(attrs.is_empty());
5388 } else {
5389 panic!("expected attrs");
5390 }
5391 }
5392
5393 #[test]
5394 fn pattern_filter_attrs_via_remove() {
5395 assert_eq!(
5397 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]).a"#),
5398 Value::Int(1),
5399 );
5400 assert_eq!(
5401 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]) ? b"#),
5402 Value::Bool(false),
5403 );
5404 }
5405
5406 #[test]
5407 fn pattern_override() {
5408 let v = ev(r#"
5410 let
5411 defaults = { debug = false; port = 8080; host = "localhost"; };
5412 overrides = { debug = true; port = 9090; };
5413 in defaults // overrides
5414 "#);
5415 if let Value::Attrs(attrs) = v {
5416 assert_eq!(attrs.get("debug"), Some(&Value::Bool(true)));
5417 assert_eq!(attrs.get("port"), Some(&Value::Int(9090)));
5418 assert_eq!(attrs.get("host"), Some(&Value::string("localhost")));
5419 } else {
5420 panic!("expected attrs");
5421 }
5422 }
5423
5424 #[test]
5425 fn pattern_functor() {
5426 assert_eq!(
5428 ev("let s = { __functor = self: x: self.value + x; value = 10; }; in s 5"),
5429 Value::Int(15),
5430 );
5431 }
5432
5433 #[test]
5434 fn pattern_platform_check() {
5435 let v = ev(r#"if builtins.currentSystem == "aarch64-darwin" then "arm" else "other""#);
5437 if let Value::String(_) = v {
5439 } else {
5441 panic!("expected string");
5442 }
5443 }
5444
5445 #[test]
5446 fn pattern_recursive_overlay_lambda_structure() {
5447 let v = ev("let overlay = self: super: { pkg = 42; }; in overlay {} {}");
5449 if let Value::Attrs(attrs) = v {
5450 assert_eq!(attrs.get("pkg"), Some(&Value::Int(42)));
5451 } else {
5452 panic!("expected attrs");
5453 }
5454 }
5455
5456 #[test]
5457 fn pattern_call_package_simplified() {
5458 assert_eq!(
5460 ev("let callPkg = f: f { lib = { id = x: x; }; }; lib = { id = x: x; }; in callPkg ({ lib }: lib.id 42)"),
5461 Value::Int(42),
5462 );
5463 }
5464
5465 #[test]
5466 fn pattern_derivation_like_attrset() {
5467 let v = ev(r#"{ type = "derivation"; name = "hello"; system = builtins.currentSystem; builder = "/bin/sh"; }"#);
5468 if let Value::Attrs(attrs) = v {
5469 assert_eq!(attrs.get("type"), Some(&Value::string("derivation")));
5470 assert_eq!(attrs.get("name"), Some(&Value::string("hello")));
5471 assert_eq!(attrs.get("builder"), Some(&Value::string("/bin/sh")));
5472 let system = force_value(attrs.get("system").unwrap()).unwrap();
5474 assert!(matches!(system, Value::String(_)), "expected string, got {system:?}");
5475 } else {
5476 panic!("expected attrs");
5477 }
5478 }
5479
5480 #[test]
5481 fn pattern_module_system_simplified() {
5482 assert_eq!(
5484 ev(r#"
5485 let
5486 eval = m: m { config = {}; lib = { mkDefault = x: x; }; };
5487 in eval ({ config, lib }: { result = lib.mkDefault 42; })
5488 "#),
5489 {
5490 let mut attrs = NixAttrs::new();
5491 attrs.insert("result".to_string(), Value::Int(42));
5492 Value::Attrs(Rc::new(attrs))
5493 },
5494 );
5495 }
5496
5497 #[test]
5502 fn error_undefined_variable() {
5503 let result = eval("nonexistent_var");
5504 assert!(result.is_err());
5505 let msg = format!("{}", result.unwrap_err());
5506 assert!(msg.contains("undefined variable") || msg.contains("nonexistent_var"));
5507 }
5508
5509 #[test]
5510 fn error_type_mismatch_arithmetic() {
5511 let result = eval(r#"1 + "hello""#);
5512 assert!(result.is_err());
5513 }
5514
5515 #[test]
5516 fn error_missing_attribute() {
5517 let result = eval("{}.nonexistent");
5518 assert!(result.is_err());
5519 let msg = format!("{}", result.unwrap_err());
5520 assert!(msg.contains("nonexistent") || msg.contains("not found"));
5521 }
5522
5523 #[test]
5524 fn error_division_by_zero() {
5525 assert!(eval("1 / 0").is_err());
5526 assert!(eval("100 / 0").is_err());
5527 }
5528
5529 #[test]
5530 fn error_missing_required_function_arg() {
5531 let result = eval("({ a, b }: a + b) { a = 1; }");
5532 assert!(result.is_err());
5533 let msg = format!("{}", result.unwrap_err());
5534 assert!(msg.contains("missing argument"));
5535 }
5536
5537 #[test]
5538 fn error_unexpected_function_arg() {
5539 let result = eval("({ a }: a) { a = 1; b = 2; }");
5540 assert!(result.is_err());
5541 let msg = format!("{}", result.unwrap_err());
5542 assert!(msg.contains("unexpected argument"));
5543 }
5544
5545 #[test]
5546 fn error_assertion_failure() {
5547 assert!(eval("assert false; 1").is_err());
5548 assert!(eval("assert 1 == 2; 1").is_err());
5549 }
5550
5551 #[test]
5552 fn error_infinite_recursion() {
5553 let result = eval("let x = x; in x");
5556 assert!(result.is_err());
5557 }
5558
5559 #[test]
5560 fn error_infinite_recursion_via_lambda() {
5561 let result = eval("let f = x: f x; in f 1");
5563 assert!(result.is_err());
5564 let msg = format!("{}", result.unwrap_err());
5565 assert!(
5566 msg.contains("infinite recursion") || msg.contains("eval depth") || msg.contains("undefined"),
5567 );
5568 }
5569
5570 #[test]
5575 fn integration_let_with_function_returning_attrset() {
5576 assert_eq!(
5577 ev("let mkPkg = name: { inherit name; version = 1; }; in (mkPkg \"hello\").name"),
5578 Value::string("hello"),
5579 );
5580 }
5581
5582 #[test]
5583 fn integration_chained_updates() {
5584 assert_eq!(
5585 ev("({ a = 1; } // { b = 2; } // { c = 3; }).c"),
5586 Value::Int(3),
5587 );
5588 }
5589
5590 #[test]
5591 fn integration_map_over_attrnames() {
5592 assert_eq!(
5594 ev(r#"
5595 let
5596 set = { a = 1; b = 2; };
5597 names = builtins.attrNames set;
5598 in builtins.length names
5599 "#),
5600 Value::Int(2),
5601 );
5602 }
5603
5604 #[test]
5605 fn integration_compose_functions() {
5606 assert_eq!(
5608 ev("let compose = f: g: x: f (g x); double = x: x * 2; inc = x: x + 1; in compose double inc 5"),
5609 Value::Int(12), );
5611 }
5612
5613 #[test]
5614 fn integration_recursive_list_building() {
5615 assert_eq!(
5617 ev("builtins.map (x: x * x) (builtins.genList (x: x + 1) 4)"),
5618 Value::list(vec![Value::Int(1), Value::Int(4), Value::Int(9), Value::Int(16)]),
5619 );
5620 }
5621
5622 #[test]
5623 fn integration_attrset_from_list() {
5624 let v = ev(r#"
5626 builtins.listToAttrs (builtins.map (x: { name = x; value = true; }) ["a" "b" "c"])
5627 "#);
5628 if let Value::Attrs(attrs) = v {
5629 assert_eq!(attrs.get("a"), Some(&Value::Bool(true)));
5630 assert_eq!(attrs.get("b"), Some(&Value::Bool(true)));
5631 assert_eq!(attrs.get("c"), Some(&Value::Bool(true)));
5632 } else {
5633 panic!("expected attrs");
5634 }
5635 }
5636
5637 #[test]
5638 fn integration_nested_with_and_let() {
5639 assert_eq!(
5640 ev("let x = 10; in with { y = 20; }; x + y"),
5641 Value::Int(30),
5642 );
5643 }
5644
5645 #[test]
5646 fn integration_complex_pattern_match() {
5647 assert_eq!(
5649 ev("(args @ { a, b ? 5, ... }: a + b + (if args ? c then args.c else 0)) { a = 1; c = 10; }"),
5650 Value::Int(16), );
5652 }
5653
5654 #[test]
5655 fn integration_substring() {
5656 assert_eq!(
5657 ev(r#"builtins.substring 0 5 "hello world""#),
5658 Value::string("hello"),
5659 );
5660 assert_eq!(
5661 ev(r#"builtins.substring 6 5 "hello world""#),
5662 Value::string("world"),
5663 );
5664 }
5665
5666 #[test]
5667 fn integration_has_attr_on_nested() {
5668 assert_eq!(ev("{ a = { b = 1; }; } ? a"), Value::Bool(true));
5670 assert_eq!(
5671 ev("({ a = { b = 1; }; }.a) ? b"),
5672 Value::Bool(true),
5673 );
5674 }
5675
5676 #[test]
5677 fn integration_cat_attrs() {
5678 assert_eq!(
5679 ev(r#"builtins.catAttrs "x" [{ x = 1; } { y = 2; } { x = 3; }]"#),
5680 Value::list(vec![Value::Int(1), Value::Int(3)]),
5681 );
5682 }
5683
5684 #[test]
5685 fn integration_get_attr_builtin() {
5686 assert_eq!(
5687 ev(r#"builtins.getAttr "a" { a = 42; b = 10; }"#),
5688 Value::Int(42),
5689 );
5690 }
5691
5692 #[test]
5693 fn integration_has_attr_builtin() {
5694 assert_eq!(
5695 ev(r#"builtins.hasAttr "a" { a = 1; }"#),
5696 Value::Bool(true),
5697 );
5698 assert_eq!(
5699 ev(r#"builtins.hasAttr "z" { a = 1; }"#),
5700 Value::Bool(false),
5701 );
5702 }
5703
5704 #[test]
5705 fn integration_is_path() {
5706 assert_eq!(ev("builtins.isPath ./foo"), Value::Bool(true));
5707 assert_eq!(ev("builtins.isPath 42"), Value::Bool(false));
5708 }
5709
5710 #[test]
5711 fn integration_builtins_trace() {
5712 assert_eq!(ev(r#"builtins.trace "debug msg" 42"#), Value::Int(42));
5714 }
5715
5716 #[test]
5717 fn integration_builtins_split() {
5718 assert_eq!(
5722 ev(r#"builtins.split "/" "a/b/c""#),
5723 Value::list(vec![
5724 Value::string("a"),
5725 Value::list(vec![]),
5726 Value::string("b"),
5727 Value::list(vec![]),
5728 Value::string("c"),
5729 ]),
5730 );
5731 assert_eq!(
5734 ev(r#"builtins.split "(/)" "a/b/c""#),
5735 Value::list(vec![
5736 Value::string("a"),
5737 Value::list(vec![Value::string("/")]),
5738 Value::string("b"),
5739 Value::list(vec![Value::string("/")]),
5740 Value::string("c"),
5741 ]),
5742 );
5743 }
5744
5745 #[test]
5746 fn integration_builtins_split_no_capture_groups() {
5747 assert_eq!(
5752 ev(r#"builtins.split "-" "aarch64-darwin""#),
5753 Value::list(vec![
5754 Value::string("aarch64"),
5755 Value::list(vec![]),
5756 Value::string("darwin"),
5757 ]),
5758 );
5759 }
5760
5761 #[test]
5762 fn integration_builtins_split_system_string_filter() {
5763 assert_eq!(
5766 ev(r#"builtins.filter builtins.isString (builtins.split "-" "aarch64-darwin")"#),
5767 Value::list(vec![
5768 Value::string("aarch64"),
5769 Value::string("darwin"),
5770 ]),
5771 );
5772 }
5773
5774 #[test]
5775 fn integration_deeply_nested_let() {
5776 assert_eq!(
5778 ev("let a = let b = let c = 10; in c * 2; in b + 1; in a"),
5779 Value::Int(21),
5780 );
5781 }
5782
5783 #[test]
5784 fn integration_if_in_attrset_value() {
5785 assert_eq!(
5786 ev("{ x = if true then 1 else 2; }.x"),
5787 Value::Int(1),
5788 );
5789 }
5790
5791 #[test]
5792 fn integration_lambda_in_list() {
5793 assert_eq!(
5795 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 0) 5"),
5796 Value::Int(6),
5797 );
5798 assert_eq!(
5799 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 1) 5"),
5800 Value::Int(10),
5801 );
5802 }
5803
5804 #[test]
5805 fn integration_nixpkgs_lib_id() {
5806 assert_eq!(
5808 ev("let lib = { id = x: x; const = a: b: a; }; in lib.id 42"),
5809 Value::Int(42),
5810 );
5811 assert_eq!(
5812 ev("let lib = { id = x: x; const = a: b: a; }; in lib.const 1 2"),
5813 Value::Int(1),
5814 );
5815 }
5816
5817 #[test]
5818 fn integration_multiple_inherit() {
5819 assert_eq!(
5820 ev("let a = 1; b = 2; c = 3; in { inherit a b c; }.b"),
5821 Value::Int(2),
5822 );
5823 }
5824
5825 #[test]
5826 fn integration_rec_set_with_builtins() {
5827 assert_eq!(
5828 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
5829 Value::Int(5),
5830 );
5831 }
5832
5833 #[test]
5838 fn functor_simple_callable_attrset() {
5839 assert_eq!(
5840 ev("let s = { __functor = self: x: x + 1; }; in s 41"),
5841 Value::Int(42),
5842 );
5843 }
5844
5845 #[test]
5846 fn functor_with_self_reference() {
5847 assert_eq!(
5848 ev("let s = { __functor = self: x: self.base + x; base = 100; }; in s 23"),
5849 Value::Int(123),
5850 );
5851 }
5852
5853 #[test]
5854 fn functor_updated_attrset() {
5855 assert_eq!(
5857 ev(r#"
5858 let
5859 mk = { __functor = self: x: self.n + x; n = 0; };
5860 s = mk // { n = 50; };
5861 in s 7
5862 "#),
5863 Value::Int(57),
5864 );
5865 }
5866
5867 #[test]
5868 fn functor_error_on_non_callable_attrset() {
5869 let result = eval("let s = { a = 1; }; in s 5");
5871 assert!(result.is_err());
5872 }
5873
5874 #[test]
5879 fn to_string_protocol_in_interpolation() {
5880 assert_eq!(
5881 ev(r#"let s = { __toString = self: "world"; }; in "hello ${s}""#),
5882 Value::string("hello world"),
5883 );
5884 }
5885
5886 #[test]
5887 fn to_string_protocol_accesses_self() {
5888 assert_eq!(
5889 ev(r#"let s = { __toString = self: self.val; val = "abc"; }; in "${s}""#),
5890 Value::string("abc"),
5891 );
5892 }
5893
5894 #[test]
5895 fn to_string_protocol_via_builtin_to_string() {
5896 assert_eq!(
5897 ev(r#"builtins.toString { __toString = self: "via-builtin"; }"#),
5898 Value::string("via-builtin"),
5899 );
5900 }
5901
5902 #[test]
5903 fn to_string_protocol_attrset_without_toString_fails() {
5904 let result = eval(r#""${{}}"#);
5906 assert!(result.is_err());
5907 }
5908
5909 #[test]
5914 fn eval_builtins_concat_strings() {
5915 assert_eq!(
5916 ev(r#"builtins.concatStrings ["a" "b" "c"]"#),
5917 Value::string("abc"),
5918 );
5919 assert_eq!(
5920 ev(r#"builtins.concatStrings []"#),
5921 Value::string(""),
5922 );
5923 }
5924
5925 #[test]
5926 fn eval_builtins_partition() {
5927 let v = ev("builtins.partition (x: x > 3) [1 2 3 4 5]");
5928 if let Value::Attrs(a) = v {
5929 assert_eq!(a.get("right"), Some(&Value::list(vec![Value::Int(4), Value::Int(5)])));
5930 assert_eq!(a.get("wrong"), Some(&Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)])));
5931 } else {
5932 panic!("expected attrs");
5933 }
5934 }
5935
5936 #[test]
5937 fn eval_builtins_group_by() {
5938 let v = ev(r#"builtins.groupBy (x: if x > 0 then "pos" else "neg") [1 (0 - 2) 3 (0 - 4)]"#);
5939 if let Value::Attrs(a) = v {
5940 assert_eq!(a.get("pos"), Some(&Value::list(vec![Value::Int(1), Value::Int(3)])));
5941 assert_eq!(a.get("neg"), Some(&Value::list(vec![Value::Int(-2), Value::Int(-4)])));
5942 } else {
5943 panic!("expected attrs");
5944 }
5945 }
5946
5947 #[test]
5948 fn eval_builtins_zip_attrs_with() {
5949 let v = ev("builtins.zipAttrsWith (n: vs: builtins.head vs) [{ a = 1; } { a = 2; b = 3; }]");
5950 if let Value::Attrs(a) = v {
5951 assert_eq!(a.get("a"), Some(&Value::Int(1)));
5952 assert_eq!(a.get("b"), Some(&Value::Int(3)));
5953 } else {
5954 panic!("expected attrs");
5955 }
5956 }
5957
5958 #[test]
5959 fn eval_builtins_compare_versions() {
5960 assert_eq!(ev(r#"builtins.compareVersions "2.0" "1.0""#), Value::Int(1));
5961 assert_eq!(ev(r#"builtins.compareVersions "1.0" "2.0""#), Value::Int(-1));
5962 assert_eq!(ev(r#"builtins.compareVersions "1.0" "1.0""#), Value::Int(0));
5963 }
5964
5965 #[test]
5966 fn eval_builtins_parse_drv_name() {
5967 let v = ev(r#"builtins.parseDrvName "nix-2.3.4""#);
5968 if let Value::Attrs(a) = v {
5969 assert_eq!(a.get("name"), Some(&Value::string("nix")));
5970 assert_eq!(a.get("version"), Some(&Value::string("2.3.4")));
5971 } else {
5972 panic!("expected attrs");
5973 }
5974 }
5975
5976 #[test]
5977 fn eval_builtins_base_name_of() {
5978 assert_eq!(
5979 ev(r#"builtins.baseNameOf "/foo/bar/baz""#),
5980 Value::string("baz"),
5981 );
5982 }
5983
5984 #[test]
5985 fn eval_builtins_dir_of() {
5986 assert_eq!(
5987 ev(r#"builtins.dirOf "/foo/bar/baz""#),
5988 Value::string("/foo/bar"),
5989 );
5990 }
5991
5992 #[test]
5993 fn eval_builtins_add_error_context() {
5994 assert_eq!(
5995 ev(r#"builtins.addErrorContext "some context" 42"#),
5996 Value::Int(42),
5997 );
5998 }
5999
6000 #[test]
6001 fn eval_builtins_abort() {
6002 let result = eval(r#"builtins.abort "fatal error""#);
6003 assert!(result.is_err());
6004 let msg = format!("{}", result.unwrap_err());
6005 assert!(msg.contains("fatal error"));
6006 }
6007
6008 #[test]
6013 fn indented_string_simple() {
6014 assert_eq!(ev("''hello''"), Value::string("hello"));
6015 }
6016
6017 #[test]
6018 fn indented_string_multiline_strips_indent() {
6019 assert_eq!(
6020 ev("''\n line1\n line2\n''"),
6021 Value::string("line1\nline2\n"),
6022 );
6023 }
6024
6025 #[test]
6026 fn indented_string_with_interpolation() {
6027 let code = "let x = \"world\"; in ''hello ${x}''";
6028 assert_eq!(
6029 ev(code),
6030 Value::string("hello world"),
6031 );
6032 }
6033
6034 #[test]
6035 fn indented_string_deeper_indent_preserved() {
6036 assert_eq!(
6038 ev("''\n a\n b\n''"),
6039 Value::string("a\n b\n"),
6040 );
6041 }
6042
6043 #[test]
6048 fn dynamic_attr_name_in_set() {
6049 assert_eq!(
6050 ev(r#"let key = "mykey"; in { ${key} = 42; }.mykey"#),
6051 Value::Int(42),
6052 );
6053 }
6054
6055 #[test]
6056 fn dynamic_attr_name_with_expression() {
6057 assert_eq!(
6058 ev(r#"let prefix = "foo"; in { ${"${prefix}bar"} = 1; }.foobar"#),
6059 Value::Int(1),
6060 );
6061 }
6062
6063 #[test]
6068 fn eval_builtins_match() {
6069 assert_eq!(
6070 ev(r#"builtins.match "([0-9]+)" "42""#),
6071 Value::list(vec![Value::string("42")]),
6072 );
6073 }
6074
6075 #[test]
6076 fn eval_builtins_hash_string() {
6077 let v = ev(r#"builtins.hashString "sha256" "hello""#);
6078 if let Value::String(ns) = v {
6079 assert_eq!(ns.chars.len(), 64);
6080 } else {
6081 panic!("expected string");
6082 }
6083 }
6084
6085 #[test]
6086 fn eval_builtins_import() {
6087 let dir = std::env::temp_dir();
6088 let path = dir.join("sui_eval_test_import_eval.nix");
6089 std::fs::write(&path, "42").unwrap();
6090 let expr = format!(r#"import "{}""#, path.display());
6091 let v = eval(&expr).unwrap();
6092 assert_eq!(v, Value::Int(42));
6093 std::fs::remove_file(&path).ok();
6094 }
6095
6096 #[test]
6097 fn eval_builtins_derivation() {
6098 let v = eval(r#"builtins.derivation { name = "test"; system = "x86_64-linux"; builder = "/bin/sh"; }"#).unwrap();
6099 if let Value::Attrs(a) = v {
6100 assert_eq!(a.get("type"), Some(&Value::string("derivation")));
6101 } else {
6102 panic!("expected attrs");
6103 }
6104 }
6105
6106 #[test]
6107 fn eval_mutual_recursive_let() {
6108 let v = eval("let a = { x = b; }; b = { y = a; }; in a.x.y");
6115 assert!(v.is_ok(), "mutual recursive let should not error: {v:?}");
6116 let val = v.unwrap();
6118 assert!(
6119 matches!(val, Value::Attrs(_)),
6120 "a.x.y should be an attrset, got: {val:?}",
6121 );
6122 }
6123
6124 #[test]
6125 fn eval_mutual_recursive_let_simple() {
6126 let v = eval("let a = b; b = 42; in a");
6128 assert!(v.is_ok());
6129 assert_eq!(v.unwrap(), Value::Int(42));
6132 }
6133
6134 #[test]
6135 fn eval_builtins_read_dir() {
6136 let dir = std::env::temp_dir().join("sui_eval_test_readdir_eval");
6137 let _ = std::fs::remove_dir_all(&dir);
6138 std::fs::create_dir_all(&dir).unwrap();
6139 std::fs::write(dir.join("a.txt"), "").unwrap();
6140 let expr = format!(r#"builtins.readDir "{}""#, dir.display());
6141 let v = eval(&expr).unwrap();
6142 if let Value::Attrs(a) = v {
6143 assert_eq!(a.get("a.txt"), Some(&Value::string("regular")));
6144 } else {
6145 panic!("expected attrs");
6146 }
6147 let _ = std::fs::remove_dir_all(&dir);
6148 }
6149
6150 #[test]
6155 fn thunk_basic_let() {
6156 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
6158 }
6159
6160 #[test]
6161 fn thunk_forward_ref() {
6162 assert_eq!(ev("let a = b; b = 1; in a"), Value::Int(1));
6164 }
6165
6166 #[test]
6167 fn thunk_mutual_rec_attrset_in_let() {
6168 assert_eq!(ev("let a = { x = b; }; b = { y = 1; }; in a.x.y"), Value::Int(1));
6170 }
6171
6172 #[test]
6173 fn thunk_rec_attrset() {
6174 assert_eq!(ev("(rec { a = b; b = 1; }).a"), Value::Int(1));
6176 }
6177
6178 #[test]
6179 fn thunk_rec_attrset_chain() {
6180 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
6182 }
6183
6184 #[test]
6185 fn thunk_fixpoint() {
6186 assert_eq!(
6188 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; })).b"),
6189 Value::Int(2),
6190 );
6191 }
6192
6193 #[test]
6194 fn thunk_blackhole_self_reference() {
6195 let result = eval("let x = x; in x");
6197 assert!(result.is_err());
6198 let msg = format!("{}", result.unwrap_err());
6199 assert!(
6200 msg.contains("infinite recursion") || msg.contains("blackhole"),
6201 "expected blackhole error, got: {msg}",
6202 );
6203 }
6204
6205 #[test]
6206 fn thunk_mutual_blackhole() {
6207 let result = eval("let a = b; b = a; in a");
6209 assert!(result.is_err());
6210 }
6211
6212 #[test]
6213 fn thunk_let_body_forces_correctly() {
6214 assert_eq!(ev("let a = 10; b = 20; in a + b"), Value::Int(30));
6216 }
6217
6218 #[test]
6219 fn thunk_only_forced_when_needed() {
6220 assert_eq!(ev("let bad = 1 / 0; good = 42; in good"), Value::Int(42));
6222 }
6223
6224 #[test]
6225 fn thunk_forward_ref_in_function_body() {
6226 assert_eq!(
6228 ev("let f = x: x + b; b = 10; in f 5"),
6229 Value::Int(15),
6230 );
6231 }
6232
6233 #[test]
6234 fn thunk_rec_set_self_ref_through_self() {
6235 assert_eq!(
6237 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
6238 Value::Int(5),
6239 );
6240 }
6241
6242 #[test]
6243 fn thunk_nested_let_forward_ref() {
6244 assert_eq!(
6246 ev("let a = b + 1; b = 2; in a"),
6247 Value::Int(3),
6248 );
6249 }
6250
6251 #[test]
6252 fn thunk_deep_chain() {
6253 assert_eq!(
6255 ev("let a = 1; b = a; c = b; d = c; e = d; in e"),
6256 Value::Int(1),
6257 );
6258 }
6259
6260 #[test]
6261 fn thunk_rec_set_fixpoint() {
6262 assert_eq!(
6264 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; c = self.b + 1; })).c"),
6265 Value::Int(3),
6266 );
6267 }
6268
6269 #[test]
6270 fn thunk_let_with_inherit() {
6271 assert_eq!(
6273 ev("let a = 1; in let inherit a; b = a + 1; in b"),
6274 Value::Int(2),
6275 );
6276 }
6277
6278 #[test]
6279 fn thunk_attrset_value_lazy() {
6280 assert_eq!(
6283 ev("let x = 42; in { a = x; }.a"),
6284 Value::Int(42),
6285 );
6286 }
6287
6288 #[test]
6289 fn thunk_unused_error_not_forced() {
6290 assert_eq!(
6292 ev(r#"let bad = builtins.throw "boom"; ok = 1; in ok"#),
6293 Value::Int(1),
6294 );
6295 }
6296
6297 #[test]
6298 fn thunk_rec_set_mutual_reference() {
6299 let v = ev("rec { a = { val = b.val + 1; }; b = { val = 10; }; }");
6301 if let Value::Attrs(attrs) = v {
6302 let a = attrs.get("a").unwrap();
6303 let a_forced = force_value(a).unwrap();
6304 if let Value::Attrs(a_attrs) = a_forced {
6305 assert_eq!(a_attrs.get("val"), Some(&Value::Int(11)));
6306 } else {
6307 panic!("expected attrs for a");
6308 }
6309 } else {
6310 panic!("expected attrs");
6311 }
6312 }
6313
6314 #[test]
6317 fn let_rec_self_reference_simple() {
6318 assert_eq!(
6319 ev("let x = 1; y = x + 1; in y"),
6320 Value::Int(2),
6321 );
6322 }
6323
6324 #[test]
6325 fn let_rec_self_reference_chain() {
6326 assert_eq!(
6327 ev("let a = 1; b = a + 1; c = b + 1; in c"),
6328 Value::Int(3),
6329 );
6330 }
6331
6332 #[test]
6333 fn let_rec_self_reference_with_function() {
6334 assert_eq!(
6335 ev("let f = x: x + 1; y = f 10; in y"),
6336 Value::Int(11),
6337 );
6338 }
6339
6340 #[test]
6341 fn let_rec_mutual_recursion_via_if() {
6342 assert_eq!(
6343 ev("let isEven = n: if n == 0 then true else isOdd (n - 1); isOdd = n: if n == 0 then false else isEven (n - 1); in isEven 4"),
6344 Value::Bool(true),
6345 );
6346 }
6347
6348 #[test]
6349 fn let_rec_forward_ref_in_list() {
6350 assert_eq!(
6351 ev("let xs = [a b]; a = 1; b = 2; in builtins.length xs"),
6352 Value::Int(2),
6353 );
6354 }
6355
6356 #[test]
6359 fn with_shadowing_let_wins_over_with() {
6360 assert_eq!(
6361 ev("let x = 1; in with { x = 2; }; x"),
6362 Value::Int(1),
6363 );
6364 }
6365
6366 #[test]
6367 fn with_shadowing_inner_with_wins() {
6368 assert_eq!(
6369 ev("with { x = 1; }; with { x = 2; }; x"),
6370 Value::Int(2),
6371 );
6372 }
6373
6374 #[test]
6375 fn with_shadowing_outer_provides_missing() {
6376 assert_eq!(
6377 ev("with { x = 1; y = 10; }; with { x = 2; }; x + y"),
6378 Value::Int(12),
6379 );
6380 }
6381
6382 #[test]
6383 fn with_shadowing_lambda_arg_wins() {
6384 assert_eq!(
6385 ev("(x: with { x = 99; }; x) 42"),
6386 Value::Int(42),
6387 );
6388 }
6389
6390 #[test]
6391 fn with_shadowing_nested_let_wins_over_with() {
6392 assert_eq!(
6393 ev("with { x = 1; }; let x = 2; in x"),
6394 Value::Int(2),
6395 );
6396 }
6397
6398 #[test]
6399 fn with_scope_dynamic_attrs() {
6400 assert_eq!(
6401 ev(r#"with { x = 1; y = 2; z = 3; }; x + y + z"#),
6402 Value::Int(6),
6403 );
6404 }
6405
6406 #[test]
6407 fn with_scope_over_lazy_thunk_chain_resolves() {
6408 assert_eq!(
6417 ev(r#"let outer = if true then (if true then { unix = 42; } else {}) else {};
6418 # force a two-deep lazy wrap of the with-head
6419 head = (x: x) ((y: y) outer);
6420 in with head; unix"#),
6421 Value::Int(42),
6422 );
6423 }
6424
6425 #[test]
6426 fn with_scope_head_from_deep_select_resolves() {
6427 assert_eq!(
6430 ev(r#"let a = { b = { c = { key = 7; }; }; }; in with a.b.c; key"#),
6431 Value::Int(7),
6432 );
6433 }
6434
6435 #[test]
6438 fn attrset_deep_merge_simple() {
6439 let v = ev("{ a.b = 1; a.c = 2; }");
6440 if let Value::Attrs(attrs) = v {
6441 let a = force_value(attrs.get("a").unwrap()).unwrap();
6442 if let Value::Attrs(inner) = a {
6443 assert_eq!(force_value(inner.get("b").unwrap()).unwrap(), Value::Int(1));
6444 assert_eq!(force_value(inner.get("c").unwrap()).unwrap(), Value::Int(2));
6445 } else {
6446 panic!("expected nested attrs");
6447 }
6448 } else {
6449 panic!("expected attrs");
6450 }
6451 }
6452
6453 #[test]
6454 fn attrset_deep_merge_three_levels() {
6455 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
6456 if let Value::Attrs(attrs) = v {
6457 let a = force_value(attrs.get("a").unwrap()).unwrap();
6458 if let Value::Attrs(a_inner) = a {
6459 let e = force_value(a_inner.get("e").unwrap()).unwrap();
6460 assert_eq!(e, Value::Int(3));
6461 let b = force_value(a_inner.get("b").unwrap()).unwrap();
6462 if let Value::Attrs(b_inner) = b {
6463 assert_eq!(force_value(b_inner.get("c").unwrap()).unwrap(), Value::Int(1));
6464 assert_eq!(force_value(b_inner.get("d").unwrap()).unwrap(), Value::Int(2));
6465 } else {
6466 panic!("expected nested attrs for b");
6467 }
6468 } else {
6469 panic!("expected nested attrs for a");
6470 }
6471 } else {
6472 panic!("expected attrs");
6473 }
6474 }
6475
6476 #[test]
6477 fn attrset_deep_merge_preserves_siblings() {
6478 assert_eq!(
6479 ev("{ a.x = 1; b = 2; a.y = 3; }.b"),
6480 Value::Int(2),
6481 );
6482 }
6483
6484 #[test]
6485 fn attrset_deep_merge_in_let() {
6486 let v = ev("let s = { a.b = 1; a.c = 2; }; in s.a.b + s.a.c");
6487 assert_eq!(v, Value::Int(3));
6488 }
6489
6490 #[test]
6491 fn attrset_deep_merge_fullset_then_dotted() {
6492 let v = ev("let s = { a = { x = 1; }; a.y = 2; }; in s.a.x + s.a.y");
6499 assert_eq!(v, Value::Int(3));
6500 let both = ev("let s = { a = { x = 1; }; a.y = 2; }; in [ s.a.x s.a.y ]");
6502 if let Value::List(items) = both {
6503 assert_eq!(force_value(&items[0]).unwrap(), Value::Int(1));
6504 assert_eq!(force_value(&items[1]).unwrap(), Value::Int(2));
6505 } else {
6506 panic!("expected list");
6507 }
6508 }
6509
6510 #[test]
6513 fn inherit_from_basic() {
6514 assert_eq!(
6515 ev("let s = { x = 1; y = 2; }; in let inherit (s) x y; in x + y"),
6516 Value::Int(3),
6517 );
6518 }
6519
6520 #[test]
6521 fn inherit_from_with_shadowing() {
6522 assert_eq!(
6523 ev("let x = 10; in let inherit ({ x = 20; }) x; in x"),
6524 Value::Int(20),
6525 );
6526 }
6527
6528 #[test]
6529 fn inherit_from_in_attrset() {
6530 let v = ev(r#"let s = { a = 1; b = 2; }; in { inherit (s) a b; c = 3; }"#);
6531 if let Value::Attrs(attrs) = v {
6532 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
6533 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
6534 assert_eq!(force_value(attrs.get("c").unwrap()).unwrap(), Value::Int(3));
6535 } else {
6536 panic!("expected attrs");
6537 }
6538 }
6539
6540 #[test]
6541 fn inherit_from_rec_set() {
6542 assert_eq!(
6543 ev("rec { inherit ({ x = 42; }) x; y = x; }.y"),
6544 Value::Int(42),
6545 );
6546 }
6547
6548 #[test]
6549 fn inherit_plain_from_scope() {
6550 assert_eq!(
6551 ev("let x = 1; in { inherit x; }.x"),
6552 Value::Int(1),
6553 );
6554 }
6555
6556 #[test]
6565 fn inherit_plain_from_with_scope_lazy() {
6566 assert_eq!(
6570 ev("let fix = f: let x = f x; in x;
6571 self = fix (self: with self; {
6572 a = use { inherit cp; };
6573 use = { cp }: cp 5;
6574 cp = x: x + 100;
6575 });
6576 in self.a"),
6577 Value::Int(105),
6578 );
6579 assert_eq!(
6581 ev("with { y = 7; }; { inherit y; }.y"),
6582 Value::Int(7),
6583 );
6584 }
6585
6586 #[test]
6587 fn inherit_multiple_from_expr() {
6588 assert_eq!(
6589 ev("let s = { a = 10; b = 20; c = 30; }; in let inherit (s) a b c; in a + b + c"),
6590 Value::Int(60),
6591 );
6592 }
6593
6594 #[test]
6597 fn interp_nested_attrset_access() {
6598 assert_eq!(
6599 ev(r#"let x = { a = "hello"; }; in "${x.a} world""#),
6600 Value::string("hello world"),
6601 );
6602 }
6603
6604 #[test]
6605 fn interp_with_let_expression() {
6606 assert_eq!(
6607 ev(r#""${let x = "inner"; in x}""#),
6608 Value::string("inner"),
6609 );
6610 }
6611
6612 #[test]
6613 fn interp_float_coercion() {
6614 assert_eq!(
6616 ev(r#""${toString 3.14}""#),
6617 Value::string("3.140000"),
6618 );
6619 }
6620
6621 #[test]
6624 fn compare_mixed_int_float() {
6625 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
6626 assert_eq!(ev("1.5 > 1"), Value::Bool(true));
6627 assert_eq!(ev("2.0 == 2"), Value::Bool(true));
6628 }
6629
6630 #[test]
6631 fn compare_string_lexicographic() {
6632 assert_eq!(ev(r#""abc" < "abd""#), Value::Bool(true));
6633 assert_eq!(ev(r#""abc" < "abc""#), Value::Bool(false));
6634 assert_eq!(ev(r#""abc" <= "abc""#), Value::Bool(true));
6635 }
6636
6637 #[test]
6640 fn update_empty_sets() {
6641 let v = ev("{} // {}");
6642 if let Value::Attrs(a) = v { assert!(a.is_empty()); } else { panic!(); }
6643 }
6644
6645 #[test]
6646 fn update_right_overrides_completely() {
6647 assert_eq!(
6648 ev("{ a = 1; b = 2; } // { a = 10; c = 30; }"),
6649 ev("{ a = 10; b = 2; c = 30; }"),
6650 );
6651 }
6652
6653 #[test]
6654 fn update_chained() {
6655 assert_eq!(
6656 ev("{ a = 1; } // { b = 2; } // { c = 3; }"),
6657 ev("{ a = 1; b = 2; c = 3; }"),
6658 );
6659 }
6660
6661 #[test]
6664 fn force_value_concrete_unchanged() {
6665 let v = Value::Int(42);
6666 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
6667 }
6668
6669 #[test]
6670 fn force_value_null() {
6671 assert_eq!(force_value(&Value::Null).unwrap(), Value::Null);
6672 }
6673
6674 #[test]
6677 fn eval_with_file_none() {
6678 let result = eval_with_file("1 + 2", None).unwrap();
6679 assert_eq!(result, Value::Int(3));
6680 }
6681
6682 #[test]
6685 fn error_type_mismatch_in_comparison() {
6686 let result = eval(r#"1 < "a""#);
6687 assert!(result.is_err());
6688 }
6689
6690 #[test]
6691 fn error_select_from_non_set() {
6692 let result = eval("42.x");
6693 assert!(result.is_err());
6694 }
6695
6696 #[test]
6697 fn error_call_non_function() {
6698 let result = eval("42 1");
6699 assert!(result.is_err());
6700 }
6701
6702 #[test]
6703 fn error_negate_string() {
6704 let result = eval(r#"-"hello""#);
6705 assert!(result.is_err());
6706 }
6707
6708 #[test]
6711 fn multiline_string_empty() {
6712 assert_eq!(ev("''''"), Value::string(""));
6713 }
6714
6715 #[test]
6716 fn multiline_string_with_trailing_newline() {
6717 let v = ev("''\n hello\n''");
6718 assert_eq!(v, Value::string("hello\n"));
6719 }
6720
6721 #[test]
6724 fn list_concat_empty_left() {
6725 assert_eq!(ev("[] ++ [1 2]"), Value::list(vec![Value::Int(1), Value::Int(2)]));
6726 }
6727
6728 #[test]
6729 fn list_concat_empty_right() {
6730 assert_eq!(ev("[1 2] ++ []"), Value::list(vec![Value::Int(1), Value::Int(2)]));
6731 }
6732
6733 #[test]
6734 fn list_concat_both_empty() {
6735 assert_eq!(ev("[] ++ []"), Value::list(vec![]));
6736 }
6737
6738 #[test]
6741 fn formals_at_pattern_accessible() {
6742 assert_eq!(
6743 ev("({ x, ... } @ args: builtins.length (builtins.attrNames args)) { x = 1; y = 2; z = 3; }"),
6744 Value::Int(3),
6745 );
6746 }
6747
6748 #[test]
6749 fn formals_default_uses_other_arg() {
6750 assert_eq!(
6751 ev("({ x, y ? x + 1 }: y) { x = 10; }"),
6752 Value::Int(11),
6753 );
6754 }
6755
6756 #[test]
6757 fn formals_default_lazy_assert_false() {
6758 assert_eq!(
6762 ev("({ cpu, vendor ? assert false; null, kernel } @ args: if args ? vendor then vendor else \"inferred\") { cpu = \"x86_64\"; kernel = \"linux\"; }"),
6763 Value::String(Rc::new(NixString::plain("inferred"))),
6764 );
6765 }
6766
6767 #[test]
6768 fn formals_default_lazy_only_forced_when_accessed() {
6769 assert_eq!(
6771 ev("({ a, b ? 42 }: b) { a = 1; }"),
6772 Value::Int(42),
6773 );
6774 }
6775
6776 #[test]
6777 fn formals_ellipsis_ignores_extra() {
6778 assert_eq!(
6779 ev("({ x, ... }: x) { x = 1; y = 2; z = 3; }"),
6780 Value::Int(1),
6781 );
6782 }
6783
6784 #[test]
6787 fn pure_mode_roundtrip() {
6788 let was_pure = is_pure_mode();
6789 set_pure_mode(true);
6790 assert!(is_pure_mode());
6791 set_pure_mode(false);
6792 assert!(!is_pure_mode());
6793 set_pure_mode(was_pure);
6794 }
6795
6796 #[test]
6799 fn path_concat_with_string() {
6800 assert_eq!(
6801 ev(r#"/foo + "bar""#),
6802 Value::Path(Box::new(SmolStr::from("/foobar"))),
6803 );
6804 }
6805
6806 #[test]
6807 fn path_concat_with_path() {
6808 assert_eq!(
6809 ev("/foo + /bar"),
6810 Value::Path(Box::new(SmolStr::from("/foo//bar"))),
6811 );
6812 }
6813
6814 #[test]
6817 fn current_eval_dir_empty_when_no_file_pushed() {
6818 let snapshot = current_eval_dir();
6822 let _ = snapshot;
6824 }
6825
6826 #[test]
6827 fn push_eval_file_sets_current_dir() {
6828 let p = std::path::PathBuf::from("/tmp/example/file.nix");
6829 {
6830 let _g = push_eval_file(p.clone());
6831 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/tmp/example")));
6832 }
6833 }
6837
6838 #[test]
6839 fn push_eval_file_nested_stack() {
6840 let outer = std::path::PathBuf::from("/a/x.nix");
6841 let inner = std::path::PathBuf::from("/b/y.nix");
6842 {
6843 let _g_outer = push_eval_file(outer.clone());
6844 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
6845 {
6846 let _g_inner = push_eval_file(inner.clone());
6847 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/b")));
6848 }
6849 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
6851 }
6852 }
6853
6854 #[test]
6862 fn fileless_frame_masks_parent_file() {
6863 let outer = std::path::PathBuf::from("/a/x.nix");
6864 let _g_outer = push_eval_file(outer.clone());
6865 assert_eq!(current_eval_file(), Some(outer.clone()));
6866 {
6867 let _g_none = push_eval_frame(None);
6868 assert_eq!(current_eval_file(), None);
6870 assert_eq!(current_eval_dir(), None);
6871 assert_eq!(eval_file_stack_snapshot().last().map(String::as_str), Some("<no-file>"));
6872 }
6873 assert_eq!(current_eval_file(), Some(outer));
6875 }
6876
6877 #[test]
6880 fn error_undefined_var_includes_file_context() {
6881 let p = std::path::PathBuf::from("/nix/store/abc-default.nix");
6882 let _g = push_eval_file(p);
6883 let result = eval("nonexistent_xyz");
6884 let msg = format!("{}", result.unwrap_err());
6885 assert!(msg.contains("undefined variable"), "msg: {msg}");
6886 assert!(msg.contains("nonexistent_xyz"), "msg: {msg}");
6887 assert!(msg.contains("abc-default.nix"), "msg: {msg}");
6888 }
6889
6890 #[test]
6891 fn error_attr_not_found_includes_file_context() {
6892 let p = std::path::PathBuf::from("/nix/store/xyz-module.nix");
6893 let _g = push_eval_file(p);
6894 let result = eval("{}.missing_key");
6895 let msg = format!("{}", result.unwrap_err());
6896 assert!(msg.contains("not found") || msg.contains("missing_key"), "msg: {msg}");
6897 assert!(msg.contains("xyz-module.nix"), "msg: {msg}");
6898 }
6899
6900 #[test]
6901 fn error_assertion_failed_includes_file_context() {
6902 let p = std::path::PathBuf::from("/nix/store/test-assert.nix");
6903 let _g = push_eval_file(p);
6904 let result = eval("assert false; 1");
6905 let msg = format!("{}", result.unwrap_err());
6906 assert!(msg.contains("assertion failed"), "msg: {msg}");
6907 assert!(msg.contains("test-assert.nix"), "msg: {msg}");
6908 }
6909
6910 #[test]
6925 fn inherit_bindings_carry_positions() {
6926 let dir = tempfile::tempdir().unwrap();
6927 let body = "{ inherit ({ x = 1; }) x; }\n";
6932 let f = dir.path().join("inh.nix");
6933 std::fs::write(&f, body).unwrap();
6934 let v = eval(&format!("builtins.unsafeGetAttrPos \"x\" (import {})", f.display())).unwrap();
6935 let attrs = match v {
6936 Value::Attrs(a) => a,
6937 Value::Null => panic!("null — the inherit binding carried no position"),
6938 o => panic!("expected attrs, got {o:?}"),
6939 };
6940 let off = body.rfind("x; }").unwrap();
6944 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
6945 assert_eq!(*attrs.get("line").unwrap(), Value::Int(1));
6946 assert_eq!(*attrs.get("column").unwrap(), Value::Int((off - bol) as i64 + 1));
6947 }
6948
6949 #[test]
6966 fn every_binding_form_carries_a_position() {
6967 let dir = tempfile::tempdir().unwrap();
6968 let body = concat!(
6970 "let src = { i = 1; j = 2; }; in {\n",
6971 " plain = 1;\n",
6972 " \"quoted\" = 2;\n",
6973 " inherit (src) i;\n",
6974 " inherit src;\n",
6975 " nested.deep = 3;\n",
6976 "}\n",
6977 );
6978 let f = dir.path().join("forms.nix");
6979 std::fs::write(&f, body).unwrap();
6980
6981 let keys = ["plain", "quoted", "i", "src", "nested"];
6983 let probe = keys
6984 .iter()
6985 .map(|k| format!(
6986 "(let q = builtins.unsafeGetAttrPos \"{k}\" t; \
6987 in if q == null then \"{k}=NULL\" \
6988 else \"{k}=${{toString q.line}}:${{toString q.column}}\")"
6989 ))
6990 .collect::<Vec<_>>()
6991 .join(" + \" \" + ");
6992 let got = eval(&format!("let t = import {}; in {probe}", f.display()))
6993 .unwrap()
6994 .as_string()
6995 .unwrap()
6996 .to_string();
6997
6998 assert!(!got.contains("NULL"), "a binding form lost its position: {got}");
6999 let rows: Vec<&str> = got.split(' ').collect();
7000 assert_eq!(rows.len(), keys.len(), "corpus shrank — gate would be vacuous: {got}");
7001
7002 for (k, row) in keys.iter().zip(&rows) {
7004 let needle = match *k {
7005 "quoted" => "\"quoted\"".to_string(),
7006 "i" => "i;".to_string(),
7007 "src" => "src;".to_string(),
7008 "nested" => "nested.".to_string(),
7011 other => format!("{other} ="),
7012 };
7013 let off = body.find(&needle).unwrap();
7014 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
7015 let line = 1 + body[..off].matches('\n').count();
7016 let col = off - bol + 1;
7017 assert_eq!(*row, format!("{k}={line}:{col}"), "wrong position for `{k}` in:\n{body}");
7018 }
7019 }
7020
7021 #[test]
7034 fn error_missing_argument_includes_file_context() {
7035 let p = std::path::PathBuf::from("/nix/store/func.nix");
7036 let result = eval_with_file("({ a, b }: a) { a = 1; }", Some(p));
7037 let msg = format!("{}", result.unwrap_err());
7038 assert!(msg.contains("missing argument"), "msg: {msg}");
7039 assert!(msg.contains("func.nix"), "msg: {msg}");
7040 }
7041
7042 #[test]
7043 fn error_cannot_call_includes_file_context() {
7044 let p = std::path::PathBuf::from("/nix/store/call.nix");
7045 let _g = push_eval_file(p);
7046 let result = eval("42 99");
7047 let msg = format!("{}", result.unwrap_err());
7048 assert!(msg.contains("cannot call"), "msg: {msg}");
7049 assert!(msg.contains("call.nix"), "msg: {msg}");
7050 }
7051
7052 #[test]
7053 fn error_without_file_has_no_in_prefix() {
7054 let result = eval("nonexistent_xyz");
7057 let msg = format!("{}", result.unwrap_err());
7058 assert!(msg.contains("undefined variable"), "msg: {msg}");
7059 assert!(!msg.contains(", in"), "msg should not contain file context: {msg}");
7060 }
7061
7062 #[test]
7065 fn pure_mode_set_get_independence() {
7066 let was = is_pure_mode();
7067 set_pure_mode(true);
7068 assert!(is_pure_mode());
7069 set_pure_mode(false);
7070 assert!(!is_pure_mode());
7071 set_pure_mode(was);
7072 }
7073
7074 #[test]
7077 fn eval_with_file_some_path_arithmetic() {
7078 let p = std::path::PathBuf::from("/tmp/imaginary.nix");
7079 let result = eval_with_file("1 + 2", Some(p)).unwrap();
7080 assert_eq!(result, Value::Int(3));
7081 }
7082
7083 #[test]
7091 fn unsafe_get_attr_pos_reports_file_and_offset_column() {
7092 let dir = tempfile::tempdir().unwrap();
7104 let file_body = "{ a = 1;\n b = 2; }\n";
7106 let f = dir.path().join("lit.nix");
7107 std::fs::write(&f, file_body).unwrap();
7108 let src = format!("builtins.unsafeGetAttrPos \"b\" (import {})", f.display());
7109 let v = eval(&src).unwrap();
7110 let attrs = match v { Value::Attrs(a) => a, other => panic!("expected attrs, got {other:?}") };
7111 assert_eq!(
7112 attrs.get("file").unwrap().as_string().unwrap(),
7113 f.to_string_lossy(),
7114 );
7115 let off = file_body.find("b = 2").unwrap();
7117 let bol = file_body[..off].rfind('\n').map_or(0, |i| i + 1);
7118 let expected_line = 1 + file_body[..off].matches('\n').count() as i64;
7119 let expected_col = (off - bol) as i64 + 1;
7120 assert_eq!(expected_line, 2, "fixture must put `b` on line 2");
7121 assert_eq!(*attrs.get("line").unwrap(), Value::Int(expected_line));
7122 let col = match attrs.get("column").unwrap() { Value::Int(n) => *n, o => panic!("{o:?}") };
7123 assert_eq!(col, expected_col, "column must be the 1-based BYTE column");
7124 }
7125
7126 #[test]
7127 fn unsafe_get_attr_pos_null_for_string_origin() {
7128 let v = eval("builtins.unsafeGetAttrPos \"a\" { a = 1; }").unwrap();
7130 assert_eq!(v, Value::Null);
7131 }
7132
7133 #[test]
7134 fn unsafe_get_attr_pos_null_for_missing_key() {
7135 let dir = tempfile::tempdir().unwrap();
7137 let f = dir.path().join("lit.nix");
7138 std::fs::write(&f, "{ a = 1; }\n").unwrap();
7139 let src = format!("builtins.unsafeGetAttrPos \"zzz\" (import {})", f.display());
7140 let v = eval(&src).unwrap();
7141 assert_eq!(v, Value::Null);
7142 }
7143
7144 #[test]
7147 fn interp_int_into_string() {
7148 assert_eq!(ev(r#""val=${toString 42}""#), Value::string("val=42"));
7150 }
7151
7152 #[test]
7153 fn interp_bool_true_becomes_one() {
7154 let v = ev(r#"let x = true; in "${builtins.toString x}""#);
7156 assert_eq!(v, Value::string("1"));
7157 }
7158
7159 #[test]
7160 fn interp_null_becomes_empty() {
7161 let v = ev(r#"let x = null; in "${builtins.toString x}""#);
7163 assert_eq!(v, Value::string(""));
7164 }
7165
7166 #[test]
7167 fn interp_attrset_without_to_string_errors() {
7168 let result = eval(r#"let s = { x = 1; }; in "${s}""#);
7170 assert!(result.is_err());
7171 }
7172
7173 #[test]
7174 fn interp_attrset_with_to_string_protocol() {
7175 let v = ev(r#""${{ __toString = self: "ok"; }}""#);
7177 assert_eq!(v, Value::string("ok"));
7178 }
7179
7180 #[test]
7183 fn eval_path_absolute_literal() {
7184 let v = ev("/tmp/foo");
7185 match v {
7186 Value::Path(p) => assert!(p.contains("/tmp/foo")),
7187 _ => panic!("expected Path"),
7188 }
7189 }
7190
7191 #[test]
7192 fn eval_path_home_literal() {
7193 let v = ev("~/foo.nix");
7194 match v {
7195 Value::Path(p) => assert!(p.contains("~/foo.nix") || p.ends_with("foo.nix")),
7196 _ => panic!("expected Path"),
7197 }
7198 }
7199
7200 #[test]
7203 fn path_search_unmatched_errors() {
7204 let saved = std::env::var("NIX_PATH").ok();
7207 unsafe {
7211 std::env::remove_var("NIX_PATH");
7212 }
7213 let result = eval("<this_should_not_resolve>");
7214 if let Some(v) = saved {
7215 unsafe {
7216 std::env::set_var("NIX_PATH", v);
7217 }
7218 }
7219 assert!(result.is_err());
7220 }
7221
7222 #[test]
7225 fn unary_negate_int() {
7226 assert_eq!(ev("-7"), Value::Int(-7));
7227 }
7228
7229 #[test]
7230 fn unary_negate_float() {
7231 assert_eq!(ev("-2.5"), Value::Float(-2.5));
7232 }
7233
7234 #[test]
7235 fn unary_invert_true() {
7236 assert_eq!(ev("!true"), Value::Bool(false));
7237 }
7238
7239 #[test]
7240 fn unary_invert_false() {
7241 assert_eq!(ev("!false"), Value::Bool(true));
7242 }
7243
7244 #[test]
7245 fn unary_negate_bool_errors() {
7246 let result = eval("-true");
7247 assert!(result.is_err());
7248 }
7249
7250 #[test]
7251 fn unary_invert_int_errors() {
7252 let result = eval("!42");
7253 assert!(result.is_err());
7254 }
7255
7256 #[test]
7259 fn binop_add_attrs_errors() {
7260 let result = eval("{a=1;} + {b=2;}");
7261 assert!(result.is_err());
7262 }
7263
7264 #[test]
7265 fn binop_sub_string_errors() {
7266 let result = eval(r#""a" - "b""#);
7267 assert!(result.is_err());
7268 }
7269
7270 #[test]
7271 fn binop_mul_string_errors() {
7272 let result = eval(r#""a" * "b""#);
7273 assert!(result.is_err());
7274 }
7275
7276 #[test]
7277 fn binop_div_string_errors() {
7278 let result = eval(r#""a" / "b""#);
7279 assert!(result.is_err());
7280 }
7281
7282 #[test]
7283 fn binop_compare_attrs_errors() {
7284 let result = eval("{a=1;} < {b=2;}");
7285 assert!(result.is_err());
7286 }
7287
7288 #[test]
7289 fn binop_div_float_by_zero_int() {
7290 let result = eval("1.0 / 0");
7294 let _ = result;
7297 }
7298
7299 #[test]
7300 fn binop_int_div_zero_is_division_by_zero() {
7301 let result = eval("5 / 0");
7302 match result {
7303 Err(EvalError::DivisionByZero) => {}
7304 other => panic!("expected DivisionByZero, got {other:?}"),
7305 }
7306 }
7307
7308 #[test]
7311 fn if_else_only_chosen_branch_evaluated_then() {
7312 assert_eq!(ev("if true then 42 else 1 / 0"), Value::Int(42));
7315 }
7316
7317 #[test]
7318 fn if_else_only_chosen_branch_evaluated_else() {
7319 assert_eq!(ev("if false then 1 / 0 else 99"), Value::Int(99));
7320 }
7321
7322 #[test]
7323 fn if_condition_must_be_bool() {
7324 let result = eval("if 1 then 1 else 2");
7325 assert!(result.is_err());
7326 }
7327
7328 #[test]
7329 fn if_condition_lazy_does_not_force_unused() {
7330 assert_eq!(
7333 ev("let bad = 1 / 0; in if true then 42 else bad"),
7334 Value::Int(42),
7335 );
7336 }
7337
7338 #[test]
7341 fn and_short_circuits_on_false() {
7342 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
7344 }
7345
7346 #[test]
7347 fn or_short_circuits_on_true() {
7348 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
7349 }
7350
7351 #[test]
7352 fn implication_short_circuits_on_false_lhs() {
7353 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
7355 }
7356
7357 #[test]
7360 fn lambda_fix_combinator_returns_attrset() {
7361 let v = ev(
7363 "let fix = f: let x = f x; in x; in
7364 (fix (self: { val = 1; double = self.val * 2; })).double",
7365 );
7366 assert_eq!(v, Value::Int(2));
7367 }
7368
7369 #[test]
7372 fn rec_attrset_self_reference() {
7373 let v = ev("(rec { a = b; b = 1; }).a");
7375 assert_eq!(v, Value::Int(1));
7376 }
7377
7378 #[test]
7379 fn rec_attrset_inherit_from_uses_outer_scope() {
7380 let v = ev(
7384 "let src = { a = 10; }; in
7385 rec {
7386 inherit (src) a;
7387 b = a + 1;
7388 }",
7389 );
7390 if let Value::Attrs(attrs) = v {
7391 let b = attrs.get("b").unwrap();
7392 let b_forced = force_value(b).unwrap();
7393 assert_eq!(b_forced, Value::Int(11));
7394 } else {
7395 panic!("expected attrs");
7396 }
7397 }
7398
7399 #[test]
7400 fn nonrec_attrset_no_self_reference() {
7401 let result = eval("({ a = 1; b = a + 1; }).b");
7404 assert!(result.is_err());
7405 }
7406
7407 #[test]
7410 fn dotted_binding_three_segments_then_sibling() {
7411 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
7412 if let Value::Attrs(attrs) = v {
7413 let a = attrs.get("a").unwrap();
7414 let a_forced = force_value(a).unwrap();
7415 if let Value::Attrs(a_attrs) = a_forced {
7416 let b = a_attrs.get("b").unwrap();
7417 let b_forced = force_value(b).unwrap();
7418 if let Value::Attrs(b_attrs) = b_forced {
7419 assert_eq!(force_value(b_attrs.get("c").unwrap()).unwrap(), Value::Int(1));
7420 assert_eq!(force_value(b_attrs.get("d").unwrap()).unwrap(), Value::Int(2));
7421 } else {
7422 panic!("expected b to be attrs");
7423 }
7424 assert_eq!(force_value(a_attrs.get("e").unwrap()).unwrap(), Value::Int(3));
7425 } else {
7426 panic!("expected a to be attrs");
7427 }
7428 } else {
7429 panic!("expected outer attrs");
7430 }
7431 }
7432
7433 #[test]
7436 fn rec_dotted_bindings_visible_to_siblings() {
7437 let v = ev("rec { types.openSB = 1; types.openCpu = 2; foo = types.openSB; }.foo");
7440 assert_eq!(v, Value::Int(1));
7441 }
7442
7443 #[test]
7444 fn rec_dotted_leaf_uses_rec_scope() {
7445 let v = ev("rec { types.a = f 1; f = x: x + 1; }.types.a");
7448 assert_eq!(v, Value::Int(2));
7449 }
7450
7451 #[test]
7452 fn rec_dotted_multiple_keys_merge() {
7453 let v = ev("rec { types.a = 1; types.b = 2; x = types; }.x");
7455 if let Value::Attrs(attrs) = v {
7456 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
7457 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
7458 } else {
7459 panic!("expected attrs");
7460 }
7461 }
7462
7463 #[test]
7464 fn rec_nixpkgs_parse_pattern() {
7465 let v = ev(r#"
7469 let
7470 mkOptionType = x: x;
7471 mergeOneOption = "merge";
7472 attrValues = builtins.attrValues;
7473 setType = name: value: { __type = name; } // value;
7474 mapAttrs = builtins.mapAttrs;
7475 enum = xs: mkOptionType { name = "enum"; check = x: builtins.elem x xs; };
7476 setTypes = type: mapAttrs (name: value: setType type.name ({ inherit name; } // value));
7477 in
7478 rec {
7479 types.openSB = mkOptionType { name = "sb"; merge = mergeOneOption; };
7480 types.significantByte = enum (attrValues significantBytes);
7481 significantBytes = setTypes types.openSB { bigEndian = {}; littleEndian = {}; };
7482 types.openCpuType = mkOptionType { name = "cpu-type"; };
7483 types.cpuType = enum (attrValues cpuTypes);
7484 cpuTypes = setTypes types.openCpuType { arm = { bits = 32; }; };
7485 }.types.openCpuType
7486 "#);
7487 if let Value::Attrs(attrs) = v {
7488 assert_eq!(
7489 force_value(attrs.get("name").unwrap()).unwrap(),
7490 Value::string("cpu-type")
7491 );
7492 } else {
7493 panic!("expected attrs");
7494 }
7495 }
7496
7497 #[test]
7498 fn let_dotted_leaf_uses_let_scope() {
7499 let v = ev("let a.x = f 1; f = x: x + 1; in a.x");
7501 assert_eq!(v, Value::Int(2));
7502 }
7503
7504 #[test]
7505 fn let_inherit_from_plus_dotted_overrides() {
7506 let v = ev(r#"
7512 let
7513 src = { types = { existing = true; }; };
7514 inherit (src) types;
7515 types.added = true;
7516 in types
7517 "#);
7518 if let Value::Attrs(attrs) = v {
7519 assert_eq!(
7521 force_value(attrs.get("added").unwrap()).unwrap(),
7522 Value::Bool(true)
7523 );
7524 assert!(attrs.get("existing").is_none());
7526 } else {
7527 panic!("expected attrs");
7528 }
7529 }
7530
7531 #[test]
7534 fn pattern_empty_no_args_no_ellipsis() {
7535 assert_eq!(ev("({}: 1) {}"), Value::Int(1));
7537 }
7538
7539 #[test]
7540 fn pattern_empty_with_ellipsis_accepts_extra() {
7541 assert_eq!(ev("({...}: 1) { a = 1; b = 2; }"), Value::Int(1));
7542 }
7543
7544 #[test]
7545 fn pattern_all_defaults() {
7546 assert_eq!(
7547 ev("({a ? 1, b ? 2}: a + b) {}"),
7548 Value::Int(3),
7549 );
7550 }
7551
7552 #[test]
7553 fn pattern_at_bind_before() {
7554 assert_eq!(ev("(args @ { x }: args.x) { x = 7; }"), Value::Int(7));
7556 }
7557
7558 #[test]
7559 fn pattern_at_bind_after() {
7560 assert_eq!(ev("({ x } @ args: args.x) { x = 7; }"), Value::Int(7));
7562 }
7563
7564 #[test]
7565 fn pattern_default_references_other_arg() {
7566 assert_eq!(ev("({a, b ? a + 1}: b) {a = 10;}"), Value::Int(11));
7568 }
7569
7570 #[test]
7571 fn pattern_required_missing_errors() {
7572 let result = eval("({ a, b }: a) { a = 1; }");
7573 assert!(result.is_err());
7574 }
7575
7576 #[test]
7577 fn pattern_unexpected_errors_without_ellipsis() {
7578 let result = eval("({ a }: a) { a = 1; b = 2; }");
7579 assert!(result.is_err());
7580 }
7581
7582 #[test]
7585 fn apply_int_errors() {
7586 let result = eval("42 5");
7587 assert!(result.is_err());
7588 }
7589
7590 #[test]
7591 fn apply_string_errors() {
7592 let result = eval(r#""hi" 5"#);
7593 assert!(result.is_err());
7594 }
7595
7596 #[test]
7597 fn apply_attrset_without_functor_errors() {
7598 let result = eval("{ x = 1; } 5");
7599 assert!(result.is_err());
7600 let msg = format!("{}", result.unwrap_err());
7601 assert!(msg.contains("__functor") || msg.contains("cannot call"));
7602 }
7603
7604 #[test]
7607 fn select_multi_segment_with_default() {
7608 assert_eq!(ev("{ a = { b = 1; }; }.a.c or 99"), Value::Int(99));
7610 }
7611
7612 #[test]
7613 fn select_from_int_errors() {
7614 let result = eval("(1).x");
7615 assert!(result.is_err());
7616 }
7617
7618 #[test]
7621 fn has_attr_on_non_set_returns_false() {
7622 assert_eq!(ev("1 ? x"), Value::Bool(false));
7624 }
7625
7626 #[test]
7627 fn has_attr_nested_path_present() {
7628 assert_eq!(ev("{ a = { b = 1; }; } ? a.b"), Value::Bool(true));
7629 }
7630
7631 #[test]
7632 fn has_attr_nested_path_missing() {
7633 assert_eq!(ev("{ a = { b = 1; }; } ? a.c"), Value::Bool(false));
7634 }
7635
7636 #[test]
7637 fn has_attr_intermediate_missing_returns_false() {
7638 assert_eq!(ev("{} ? a.b.c"), Value::Bool(false));
7639 }
7640
7641 #[test]
7644 fn list_with_function_value() {
7645 let v = ev("[(x: x + 1)]");
7646 if let Value::List(items) = v {
7647 assert_eq!(items.len(), 1);
7648 let forced = force_value(&items[0]).unwrap();
7650 assert!(matches!(forced, Value::Lambda(_)));
7651 } else {
7652 panic!("expected list");
7653 }
7654 }
7655
7656 #[test]
7659 fn inherit_unknown_name_errors() {
7660 let result = eval("let x = 1; in let inherit nonexistent; in nonexistent");
7661 assert!(result.is_err());
7662 }
7663
7664 #[test]
7667 fn string_concat_no_context_when_both_plain() {
7668 let v = ev(r#""abc" + "def""#);
7669 if let Value::String(ns) = v {
7670 assert_eq!(ns.chars, "abcdef");
7671 assert!(!ns.has_context());
7672 } else {
7673 panic!("expected string");
7674 }
7675 }
7676
7677 #[test]
7680 fn parens_around_expression() {
7681 assert_eq!(ev("(1 + 2)"), Value::Int(3));
7682 }
7683
7684 #[test]
7685 fn nested_parens() {
7686 assert_eq!(ev("(((42)))"), Value::Int(42));
7687 }
7688
7689 #[test]
7692 fn throw_propagates_as_error() {
7693 let result = eval(r#"builtins.throw "kaboom""#);
7694 match result {
7695 Err(EvalError::Throw(s)) => assert!(s.contains("kaboom")),
7696 other => panic!("expected Throw, got {other:?}"),
7697 }
7698 }
7699
7700 #[test]
7701 fn assert_failed_propagates_as_error() {
7702 let result = eval("assert false; 1");
7703 match result {
7704 Err(EvalError::AssertionFailed(_)) => {}
7705 other => panic!("expected AssertionFailed, got {other:?}"),
7706 }
7707 }
7708
7709 #[test]
7712 fn string_no_interp_yields_no_context() {
7713 let v = ev(r#""just literal""#);
7714 if let Value::String(ns) = v {
7715 assert!(!ns.has_context());
7716 } else {
7717 panic!("expected string");
7718 }
7719 }
7720
7721 #[test]
7730 fn interp_path_copies_to_store_byte_matches_cppnix() {
7731 let dir = std::env::temp_dir().join(format!("sui-r5-interp-{}", std::process::id()));
7732 let _ = std::fs::remove_dir_all(&dir);
7733 std::fs::create_dir_all(&dir).unwrap();
7734 let f = dir.join("data.txt");
7735 std::fs::write(&f, b"hello\n").unwrap();
7736 let expr = format!(r#""${{{}}}""#, f.display());
7737 let v = eval(&expr).unwrap();
7738 if let Value::String(ns) = v {
7739 assert_eq!(
7740 ns.chars.to_string(),
7741 "/nix/store/y9dmvfhip31hg8ia4njwjz9vfa3ndphr-data.txt",
7742 );
7743 assert!(ns.has_context());
7744 } else {
7745 panic!("expected string");
7746 }
7747 let _ = std::fs::remove_dir_all(&dir);
7748 }
7749
7750 #[test]
7759 fn parse_error_unbalanced_braces() {
7760 let result = eval("{ a = 1");
7761 assert!(result.is_err());
7762 let err = result.unwrap_err();
7763 assert!(matches!(err, EvalError::ParseError(_)));
7764 }
7765
7766 #[test]
7767 fn parse_error_dangling_let() {
7768 let result = eval("let in");
7769 assert!(result.is_err());
7770 }
7771
7772 #[test]
7773 fn parse_error_empty_input() {
7774 let result = eval("");
7775 assert!(result.is_err());
7776 }
7777
7778 #[test]
7781 fn float_int_subtraction() {
7782 assert_eq!(ev("3.5 - 1"), Value::Float(2.5));
7783 }
7784
7785 #[test]
7786 fn int_float_subtraction() {
7787 assert_eq!(ev("3 - 0.5"), Value::Float(2.5));
7788 }
7789
7790 #[test]
7791 fn float_float_division() {
7792 assert_eq!(ev("6.0 / 2.0"), Value::Float(3.0));
7793 }
7794
7795 #[test]
7796 fn int_float_multiplication() {
7797 assert_eq!(ev("3 * 2.5"), Value::Float(7.5));
7798 }
7799
7800 #[test]
7803 fn compare_int_float_less() {
7804 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
7805 }
7806
7807 #[test]
7808 fn compare_float_int_more() {
7809 assert_eq!(ev("3.5 > 3"), Value::Bool(true));
7810 }
7811
7812 #[test]
7813 fn compare_equal_int_float() {
7814 assert_eq!(ev("3 <= 3.0"), Value::Bool(true));
7815 }
7816
7817 #[test]
7820 fn equal_lists_same() {
7821 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
7822 }
7823
7824 #[test]
7825 fn equal_lists_diff_length() {
7826 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
7827 }
7828
7829 #[test]
7830 fn not_equal_lists() {
7831 assert_eq!(ev("[1] != [2]"), Value::Bool(true));
7832 }
7833
7834 #[test]
7835 fn equal_attrsets_same() {
7836 assert_eq!(ev("{a = 1; b = 2;} == {b = 2; a = 1;}"), Value::Bool(true));
7837 }
7838
7839 #[test]
7846 fn lambda_self_equality_in_attrset() {
7847 assert_eq!(
7849 ev("let f = x: x; in { a = 1; inherit f; } == { a = 1; inherit f; }"),
7850 Value::Bool(true),
7851 );
7852 }
7853
7854 #[test]
7855 fn lambda_self_reference_attrset_equality() {
7856 assert_eq!(
7858 ev("let x = { a = 1; f = y: y; }; in x == x"),
7859 Value::Bool(true),
7860 );
7861 }
7862
7863 #[test]
7864 fn lambda_different_closures_not_equal() {
7865 assert_eq!(
7867 ev("{ f = x: x; } == { f = x: x; }"),
7868 Value::Bool(false),
7869 );
7870 }
7871
7872 #[test]
7873 fn lambda_ne_does_not_force_unused_branch() {
7874 assert_eq!(
7877 ev("let ls = { a = 1; f = x: x; }; in if ls != ls then builtins.throw \"bug\" else 42"),
7878 Value::Int(42),
7879 );
7880 }
7881
7882 #[test]
7885 fn force_value_through_thunk() {
7886 let root = rnix::Root::parse("1 + 2");
7887 let expr = root.tree().expr().unwrap();
7888 let thunk = Thunk::new_suspended(expr, Env::new());
7889 let val = Value::Thunk(thunk);
7890 assert_eq!(force_value(&val).unwrap(), Value::Int(3));
7891 }
7892
7893 #[test]
7896 fn try_eval_catches_thrown_error() {
7897 let v = ev(r#"(builtins.tryEval (builtins.throw "oops")).success"#);
7899 assert_eq!(v, Value::Bool(false));
7900 }
7901
7902 #[test]
7903 fn try_eval_returns_value_on_success() {
7904 let v = ev("(builtins.tryEval 42).value");
7905 assert_eq!(v, Value::Int(42));
7906 }
7907
7908 #[test]
7911 fn legacy_let_returns_body_attr() {
7912 assert_eq!(ev("let { x = 1; body = x + 41; }"), Value::Int(42));
7916 }
7917
7918 #[test]
7919 fn legacy_let_missing_body_errors() {
7920 let result = eval("let { x = 1; }");
7921 assert!(result.is_err());
7922 }
7923
7924 #[test]
7925 fn legacy_let_with_inherit_from_scope() {
7926 assert_eq!(
7927 ev("let outer = 5; in let { inherit outer; body = outer * 2; }"),
7928 Value::Int(10),
7929 );
7930 }
7931
7932 #[test]
7935 fn interp_with_string_concat_preserves_order() {
7936 assert_eq!(
7937 ev(r#"let a = "x"; b = "y"; in "${a}-${b}""#),
7938 Value::string("x-y"),
7939 );
7940 }
7941
7942 #[test]
7943 fn interp_only_literal_part() {
7944 assert_eq!(ev(r#""no interp here""#), Value::string("no interp here"));
7945 }
7946
7947 #[test]
7950 fn dynamic_attr_via_string_key_in_set() {
7951 assert_eq!(ev(r#"{ "a" = 1; }.a"#), Value::Int(1));
7953 }
7954
7955 #[test]
7956 fn dynamic_attr_via_interpolated_key() {
7957 let v = ev(r#"let k = "foo"; in { ${k} = 99; }.foo"#);
7958 assert_eq!(v, Value::Int(99));
7959 }
7960
7961 #[test]
7964 fn select_with_string_key() {
7965 let v = ev(r#"{ a = 42; }."a""#);
7966 assert_eq!(v, Value::Int(42));
7967 }
7968
7969 #[test]
7972 fn apply_attrset_with_functor_works() {
7973 let v = ev("let s = { __functor = self: x: x + 1; }; in s 5");
7974 assert_eq!(v, Value::Int(6));
7975 }
7976
7977 #[test]
7980 fn double_negate_int() {
7981 assert_eq!(ev("- (-5)"), Value::Int(5));
7982 }
7983
7984 #[test]
7987 fn inherit_in_let_makes_name_available() {
7988 assert_eq!(
7989 ev("let src = { a = 7; }; in let inherit (src) a; in a"),
7990 Value::Int(7),
7991 );
7992 }
7993
7994 #[test]
7997 fn path_plus_string_yields_path() {
7998 let v = ev(r#"/foo + "/bar""#);
7999 match v {
8000 Value::Path(p) => assert_eq!(&*p, "/foo/bar"),
8001 _ => panic!("expected path"),
8002 }
8003 }
8004
8005 #[test]
8008 fn attrset_value_not_forced_unless_selected() {
8009 assert_eq!(
8012 ev(r#"{ bad = builtins.throw "boom"; good = 42; }.good"#),
8013 Value::Int(42),
8014 );
8015 }
8016
8017 #[test]
8020 fn lambda_recursive_via_let() {
8021 assert_eq!(
8023 ev("let fact = n: if n == 0 then 1 else n * fact (n - 1); in fact 5"),
8024 Value::Int(120),
8025 );
8026 }
8027
8028 #[test]
8031 fn select_with_dynamic_key_via_var() {
8032 assert_eq!(ev(r#"let k = { x = 1; }; in k.x"#), Value::Int(1));
8035 }
8036
8037 #[test]
8040 fn compare_string_lex_greater_or_equal() {
8041 assert_eq!(ev(r#""b" >= "a""#), Value::Bool(true));
8042 assert_eq!(ev(r#""a" >= "a""#), Value::Bool(true));
8043 assert_eq!(ev(r#""a" >= "b""#), Value::Bool(false));
8044 }
8045
8046 #[test]
8049 fn equal_int_string_false() {
8050 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
8051 }
8052
8053 #[test]
8054 fn equal_null_int_false() {
8055 assert_eq!(ev("null == 0"), Value::Bool(false));
8056 }
8057
8058 #[test]
8061 fn update_with_let_bound_operands() {
8062 assert_eq!(
8063 ev("let a = { x = 1; }; b = { y = 2; }; in (a // b).y"),
8064 Value::Int(2),
8065 );
8066 }
8067
8068 #[test]
8071 fn concat_lists_from_let() {
8072 assert_eq!(
8073 ev("let a = [1 2]; b = [3 4]; in builtins.length (a ++ b)"),
8074 Value::Int(4),
8075 );
8076 }
8077
8078 #[test]
8081 fn interp_list_coerces_with_spaces() {
8082 assert_eq!(
8085 ev(r#""${toString [1 2 3]}""#),
8086 Value::string("1 2 3"),
8087 );
8088 }
8089
8090 #[test]
8091 fn interp_list_directly_coerces() {
8092 assert_eq!(
8094 ev(r#""${[1 2]}""#),
8095 Value::string("1 2"),
8096 );
8097 }
8098
8099 #[test]
8102 fn interp_outpath_attrset() {
8103 assert_eq!(
8104 ev(r#"let x = { outPath = "/nix/store/abc"; }; in "${x}""#),
8105 Value::string("/nix/store/abc"),
8106 );
8107 }
8108
8109 #[test]
8110 fn interp_tostring_takes_priority_over_outpath() {
8111 assert_eq!(
8112 ev(r#"let x = { __toString = self: "custom"; outPath = "/ignored"; }; in "${x}""#),
8113 Value::string("custom"),
8114 );
8115 }
8116
8117 #[test]
8118 fn interp_derivation_coerces_to_outpath() {
8119 let result = eval(r#"
8121 let drv = builtins.derivation {
8122 name = "test";
8123 system = "x86_64-linux";
8124 builder = "/bin/sh";
8125 };
8126 in "${drv}"
8127 "#).unwrap();
8128 if let Value::String(s) = result {
8129 assert!(s.chars.starts_with("/nix/store/"), "got: {}", s.chars);
8130 } else {
8131 panic!("expected string");
8132 }
8133 }
8134
8135 #[test]
8138 fn interp_lambda_errors() {
8139 let result = eval(r#""${x: x}""#);
8140 assert!(result.is_err());
8141 }
8142
8143 #[test]
8146 fn force_value_int_returns_same() {
8147 let v = Value::Int(42);
8148 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
8149 }
8150
8151 #[test]
8152 fn force_value_bool_returns_same() {
8153 let v = Value::Bool(true);
8154 assert_eq!(force_value(&v).unwrap(), Value::Bool(true));
8155 }
8156
8157 #[test]
8158 fn force_value_string_returns_same() {
8159 let v = Value::string("hello");
8160 assert_eq!(force_value(&v).unwrap(), Value::string("hello"));
8161 }
8162
8163 #[test]
8164 fn force_value_attrs_returns_same() {
8165 let mut a = NixAttrs::new();
8166 a.insert("x".to_string(), Value::Int(1));
8167 let v = Value::Attrs(Rc::new(a.clone()));
8168 assert_eq!(force_value(&v).unwrap(), Value::Attrs(Rc::new(a)));
8169 }
8170
8171 #[test]
8172 fn force_value_list_returns_same() {
8173 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
8174 assert_eq!(
8175 force_value(&v).unwrap(),
8176 Value::list(vec![Value::Int(1), Value::Int(2)]),
8177 );
8178 }
8179
8180 #[test]
8181 fn force_value_null_returns_null() {
8182 let v = Value::Null;
8183 assert_eq!(force_value(&v).unwrap(), Value::Null);
8184 }
8185
8186 #[test]
8187 fn force_value_evaluated_thunk_returns_cached() {
8188 let v = ev("let x = 1 + 2; in x");
8190 assert_eq!(v, Value::Int(3));
8191 assert_eq!(force_value(&v).unwrap(), Value::Int(3));
8193 }
8194
8195 #[test]
8198 fn tco_if_true_condition() {
8199 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
8200 }
8201
8202 #[test]
8203 fn tco_if_false_condition() {
8204 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
8205 }
8206
8207 #[test]
8208 fn tco_deeply_nested_if_else_chain() {
8209 let mut expr = String::from("150");
8212 for i in (1..150).rev() {
8213 expr = format!("if false then {} else {}", i, expr);
8214 }
8215 let v = ev(&expr);
8216 assert_eq!(v, Value::Int(150));
8217 }
8218
8219 #[test]
8220 fn tco_assert_true_passes_through() {
8221 assert_eq!(ev("assert true; 42"), Value::Int(42));
8222 }
8223
8224 #[test]
8225 fn tco_assert_false_throws_assertion_failed() {
8226 let result = eval("assert false; 42");
8227 assert!(result.is_err());
8228 let err = result.unwrap_err();
8229 assert!(
8230 matches!(err, EvalError::AssertionFailed(_)),
8231 "expected AssertionFailed, got: {err}",
8232 );
8233 }
8234
8235 #[test]
8236 fn tco_with_makes_scope_available() {
8237 assert_eq!(ev("with { x = 10; y = 20; }; x + y"), Value::Int(30));
8238 }
8239
8240 #[test]
8241 fn tco_let_in_creates_bindings() {
8242 assert_eq!(ev("let a = 5; in a"), Value::Int(5));
8243 }
8244
8245 #[test]
8246 fn tco_let_in_multiple_bindings() {
8247 assert_eq!(ev("let a = 1; b = 2; c = 3; in a + b + c"), Value::Int(6));
8248 }
8249
8250 #[test]
8253 fn eval_attrset_empty() {
8254 let v = ev("{}");
8255 if let Value::Attrs(attrs) = v {
8256 assert!(attrs.is_empty(), "expected empty attrset");
8257 } else {
8258 panic!("expected attrset, got {v:?}");
8259 }
8260 }
8261
8262 #[test]
8263 fn eval_attrset_simple_kv() {
8264 let v = ev("{ a = 1; b = 2; }");
8265 if let Value::Attrs(attrs) = v {
8266 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8267 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8268 } else {
8269 panic!("expected attrset, got {v:?}");
8270 }
8271 }
8272
8273 #[test]
8274 fn eval_attrset_recursive() {
8275 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
8276 assert_eq!(ev("(rec { a = 1; b = a + 1; }).a"), Value::Int(1));
8277 }
8278
8279 #[test]
8280 fn eval_attrset_inherit_from_scope() {
8281 assert_eq!(ev("let x = 1; in { inherit x; }.x"), Value::Int(1));
8282 }
8283
8284 #[test]
8285 fn eval_attrset_inherit_from_expr() {
8286 assert_eq!(
8287 ev("{ inherit (builtins) true; }.true"),
8288 Value::Bool(true),
8289 );
8290 }
8291
8292 #[test]
8293 fn eval_attrset_dotted_path() {
8294 assert_eq!(ev("{ a.b.c = 1; }.a.b.c"), Value::Int(1));
8295 }
8296
8297 #[test]
8298 fn eval_attrset_update_merge() {
8299 let v = ev("{ a = 1; } // { b = 2; }");
8300 if let Value::Attrs(attrs) = v {
8301 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8302 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8303 } else {
8304 panic!("expected attrset, got {v:?}");
8305 }
8306 }
8307
8308 #[test]
8311 fn eval_apply_simple_function() {
8312 assert_eq!(ev("(x: x + 1) 2"), Value::Int(3));
8313 }
8314
8315 #[test]
8316 fn eval_apply_pattern_destructuring() {
8317 assert_eq!(ev("({a, b}: a + b) { a = 1; b = 2; }"), Value::Int(3));
8318 }
8319
8320 #[test]
8321 fn eval_apply_default_arguments() {
8322 assert_eq!(ev("({a, b ? 0}: a + b) { a = 1; }"), Value::Int(1));
8323 }
8324
8325 #[test]
8326 fn eval_apply_ellipsis() {
8327 assert_eq!(ev("({a, ...}: a) { a = 1; b = 2; }"), Value::Int(1));
8328 }
8329
8330 #[test]
8333 fn eval_select_single_key() {
8334 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
8335 }
8336
8337 #[test]
8338 fn eval_select_multi_level() {
8339 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
8340 }
8341
8342 #[test]
8343 fn eval_select_with_or_default() {
8344 assert_eq!(ev("{}.a or 42"), Value::Int(42));
8345 }
8346
8347 #[test]
8348 fn eval_select_missing_key_without_default_throws() {
8349 let result = eval("{}.a");
8350 assert!(result.is_err());
8351 }
8352
8353 #[test]
8356 fn binop_add_ints() {
8357 assert_eq!(ev("1 + 2"), Value::Int(3));
8358 }
8359
8360 #[test]
8361 fn binop_sub_ints() {
8362 assert_eq!(ev("3 - 1"), Value::Int(2));
8363 }
8364
8365 #[test]
8366 fn binop_mul_ints() {
8367 assert_eq!(ev("2 * 3"), Value::Int(6));
8368 }
8369
8370 #[test]
8371 fn binop_div_ints() {
8372 assert_eq!(ev("6 / 2"), Value::Int(3));
8373 }
8374
8375 #[test]
8376 fn binop_float_arithmetic() {
8377 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
8378 }
8379
8380 #[test]
8381 fn binop_string_concat() {
8382 assert_eq!(
8383 ev(r#""hello" + " " + "world""#),
8384 Value::string("hello world"),
8385 );
8386 }
8387
8388 #[test]
8389 fn binop_list_concat() {
8390 assert_eq!(
8391 ev("[1 2] ++ [3 4]"),
8392 Value::list(vec![
8393 Value::Int(1),
8394 Value::Int(2),
8395 Value::Int(3),
8396 Value::Int(4),
8397 ]),
8398 );
8399 }
8400
8401 #[test]
8402 fn binop_attrset_update() {
8403 let v = ev("{ a = 1; } // { b = 2; }");
8404 if let Value::Attrs(attrs) = v {
8405 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8406 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8407 } else {
8408 panic!("expected attrset, got {v:?}");
8409 }
8410 }
8411
8412 #[test]
8413 fn binop_less_than() {
8414 assert_eq!(ev("1 < 2"), Value::Bool(true));
8415 assert_eq!(ev("2 < 1"), Value::Bool(false));
8416 }
8417
8418 #[test]
8419 fn binop_greater_than() {
8420 assert_eq!(ev("2 > 1"), Value::Bool(true));
8421 assert_eq!(ev("1 > 2"), Value::Bool(false));
8422 }
8423
8424 #[test]
8425 fn binop_equal() {
8426 assert_eq!(ev("1 == 1"), Value::Bool(true));
8427 assert_eq!(ev("1 == 2"), Value::Bool(false));
8428 }
8429
8430 #[test]
8431 fn binop_not_equal() {
8432 assert_eq!(ev("1 != 2"), Value::Bool(true));
8433 assert_eq!(ev("1 != 1"), Value::Bool(false));
8434 }
8435
8436 #[test]
8437 fn binop_logical_and() {
8438 assert_eq!(ev("true && false"), Value::Bool(false));
8439 assert_eq!(ev("true && true"), Value::Bool(true));
8440 }
8441
8442 #[test]
8443 fn binop_logical_or() {
8444 assert_eq!(ev("true || false"), Value::Bool(true));
8445 assert_eq!(ev("false || false"), Value::Bool(false));
8446 }
8447
8448 #[test]
8449 fn binop_logical_not() {
8450 assert_eq!(ev("!true"), Value::Bool(false));
8451 assert_eq!(ev("!false"), Value::Bool(true));
8452 }
8453
8454 #[test]
8455 fn binop_implication() {
8456 assert_eq!(ev("false -> true"), Value::Bool(true));
8457 assert_eq!(ev("false -> false"), Value::Bool(true));
8458 assert_eq!(ev("true -> true"), Value::Bool(true));
8459 assert_eq!(ev("true -> false"), Value::Bool(false));
8460 }
8461}