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 scope_narrow_level() -> u8 {
794 static LEVEL: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
795 *LEVEL.get_or_init(
796 || match std::env::var("SUI_SCOPE_NARROW").ok().as_deref() {
797 Some("0") => 0,
798 Some("1") => 1,
799 _ => 2,
800 },
801 )
802}
803
804#[inline]
806fn scope_narrow_enabled() -> bool {
807 scope_narrow_level() >= 1
808}
809
810#[inline]
812fn scope_cluster_enabled() -> bool {
813 scope_narrow_level() >= 2
814}
815
816fn referenced_idents(value_expr: &ast::Expr) -> HashSet<SmolStr> {
841 use rnix::SyntaxKind;
842 let perf_on = crate::perf::enabled();
848 let t0 = if perf_on {
849 Some(std::time::Instant::now())
850 } else {
851 None
852 };
853 crate::perf::inc(crate::perf::Counter::SelfRecWalkCalls);
854 let mut nodes_walked: u64 = 0;
855 let mut set: HashSet<SmolStr> = HashSet::new();
856 for node in value_expr.syntax().descendants() {
857 nodes_walked += 1;
858 if node.kind() == SyntaxKind::NODE_IDENT
859 && node
860 .parent()
861 .is_none_or(|p| p.kind() != SyntaxKind::NODE_ATTRPATH)
862 && let Some(i) = ast::Ident::cast(node)
863 {
864 set.insert(SmolStr::from(ident_text(&i).as_str()));
865 }
866 }
867 crate::perf::add(crate::perf::Counter::SelfRecWalkNodes, nodes_walked);
868 if let Some(t0) = t0 {
869 crate::trace::add_self_rec_walk_nanos(t0.elapsed().as_nanos());
870 }
871 set
872}
873
874fn is_self_recursive_binding(value_expr: &ast::Expr, name: &str) -> bool {
878 referenced_idents(value_expr).contains(name)
879}
880
881fn maybe_thunk(
882 expr: &ast::Expr,
883 env: &Env,
884 is_rec: bool,
885 defined_so_far: Option<&HashSet<String>>,
886) -> Value {
887 match expr {
888 ast::Expr::Literal(lit) => eval_literal(lit).unwrap_or_else(|_| {
890 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
891 }),
892 ast::Expr::Ident(ident) if !is_rec => {
899 let sym = {
909 let src_id = env.source_id();
910 let offset = u32::from(ident.syntax().text_range().start());
911 crate::value::intern_cached_with(src_id, offset, || {
912 crate::value::intern(&ident_text(ident))
913 })
914 };
915 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
917 "true" => Some(Value::Bool(true)),
918 "false" => Some(Value::Bool(false)),
919 "null" => Some(Value::Null),
920 _ => None,
921 }) {
922 return kw;
923 }
924 {
925 {
926 if let Some(v) = env.lookup_fast(sym, "") {
930 return v;
931 }
932 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
935 return Value::Thunk(Thunk::new_with_ident(
936 SmolStr::from(ident_text(ident).as_str()),
937 scope_cache,
938 scope_value,
939 env.clone(),
940 ));
941 }
942 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
943 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
944 }
945 }
946 }
947 ast::Expr::Ident(ident) if is_rec => {
951 let name = ident_text(ident);
952 match name.as_str() {
953 "true" => Value::Bool(true),
954 "false" => Value::Bool(false),
955 "null" => Value::Null,
956 _ => {
957 if defined_so_far.map_or(false, |d| d.contains(&name)) {
960 env.lookup(&name).unwrap_or_else(|| {
961 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
962 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
963 })
964 } else {
965 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
967 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
968 }
969 }
970 }
971 }
972 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
977 let text = crate::path::canon_abs(&p.syntax().text().to_string());
983 Value::Path(Box::new(SmolStr::from(text.as_str())))
984 }
985 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
986 let text = p.syntax().text().to_string();
987 Value::Path(Box::new(SmolStr::from(text.as_str())))
988 }
989 ast::Expr::Str(st) if !str_has_interpolation(st) => {
1002 eval_str(st, env).unwrap_or_else(|_| {
1003 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
1004 })
1005 }
1006 ast::Expr::Lambda(lam) if !is_rec => {
1010 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
1011 Value::Lambda(Rc::new(Closure {
1012 param,
1013 body,
1014 env: env.clone(),
1015 }))
1016 } else {
1017 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
1018 }
1019 }
1020 _ => {
1031 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeOther);
1032 if crate::perf::enabled() {
1033 let kind = match expr {
1034 ast::Expr::Select(_) => "Select",
1035 ast::Expr::Apply(_) => "Apply",
1036 ast::Expr::BinOp(_) => "BinOp",
1037 ast::Expr::IfElse(_) => "IfElse",
1038 ast::Expr::Str(_) => "Str",
1039 ast::Expr::List(_) => "List",
1040 ast::Expr::With(_) => "With",
1041 ast::Expr::Assert(_) => "Assert",
1042 ast::Expr::HasAttr(_) => "HasAttr",
1043 ast::Expr::UnaryOp(_) => "UnaryOp",
1044 ast::Expr::Paren(_) => "Paren",
1045 ast::Expr::LetIn(_) => "LetIn",
1046 ast::Expr::AttrSet(_) => "AttrSet",
1047 ast::Expr::Ident(_) => "Ident(rec)",
1048 ast::Expr::Lambda(_) => "Lambda(rec)",
1049 ast::Expr::LegacyLet(_) => "LegacyLet",
1050 ast::Expr::PathAbs(_)
1051 | ast::Expr::PathHome(_)
1052 | ast::Expr::PathRel(_)
1053 | ast::Expr::PathSearch(_) => "Path(interp)",
1054 _ => "Other",
1055 };
1056 crate::trace::inc_maybe_other_kind(kind);
1057 }
1058 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
1059 }
1060 }
1061}
1062
1063#[inline(always)]
1074pub fn eval_expr(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1075 match expr {
1078 ast::Expr::Ident(ident) => {
1079 crate::perf::inc(crate::perf::Counter::EvalExpr);
1080 if crate::perf::enabled() {
1081 crate::perf::inc(crate::perf::Counter::ExprIdent);
1082 }
1083 if crate::resolve_env::enabled() {
1096 let src_id = CURRENT_SOURCE_ID.with(std::cell::Cell::get);
1097 let offset = u32::from(ident.syntax().text_range().start());
1098 if let sui_resolve::Resolution::Lexical { sym } =
1099 crate::resolve_env::resolution_for(src_id, offset)
1100 {
1101 if let Some(v) = env.lookup_lexical_sym(sym) {
1102 return Ok(v);
1103 }
1104 }
1105 }
1107 let sym = {
1141 let src_id = env.source_id();
1142 let offset = u32::from(ident.syntax().text_range().start());
1143 crate::value::intern_cached_with(src_id, offset, || {
1144 crate::value::intern(&ident_text(ident))
1145 })
1146 };
1147 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
1151 "true" => Some(Value::Bool(true)),
1152 "false" => Some(Value::Bool(false)),
1153 "null" => Some(Value::Null),
1154 _ => None,
1155 }) {
1156 return Ok(kw);
1157 }
1158 return {
1159 {
1160 if let Some(v) = env.lookup_fast(sym, "") {
1164 Ok(v)
1165 } else {
1166 let name = ident_text(ident);
1167 let fresh = crate::value::intern(name.as_str());
1186 if fresh != sym {
1187 if let Some(v) = env.lookup_fast(fresh, name.as_str()) {
1188 return Ok(v);
1189 }
1190 }
1191 if env.with_scope_count() > 0 {
1192 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
1196 Ok(Value::Thunk(Thunk::new_with_ident(
1197 SmolStr::from(name.as_str()),
1198 scope_cache,
1199 scope_value,
1200 env.clone(),
1201 )))
1202 } else if crate::value::in_promise_eval() {
1203 Ok(Value::Null)
1213 } else {
1214 Err(EvalError::UndefinedVar(
1215 format!("'{name}'{}", eval_file_ctx()),
1216 ))
1217 }
1218 } else {
1219 if let Ok(dbg_var) = std::env::var("SUI_DEBUG_VAR") {
1220 if dbg_var == name || dbg_var == "*" {
1221 eprintln!(
1222 "[sui-debug] UndefinedVar '{name}' in {}\n\
1223 [sui-debug] env bindings ({} total): {:?}\n\
1224 [sui-debug] with_scopes: {}",
1225 eval_file_ctx(),
1226 env.binding_count(),
1227 env.binding_names_preview(20),
1228 env.with_scope_count(),
1229 );
1230 }
1231 }
1232 if crate::value::in_promise_eval() {
1233 return Ok(Value::Null);
1236 }
1237 Err(EvalError::UndefinedVar(
1238 format!("'{name}'{}", eval_file_ctx()),
1239 ))
1240 }
1241 }
1242 }
1243 };
1244 }
1245 ast::Expr::Literal(lit) => {
1246 crate::perf::inc(crate::perf::Counter::EvalExpr);
1247 if crate::perf::enabled() {
1248 crate::perf::inc(crate::perf::Counter::ExprLiteral);
1249 }
1250 return eval_literal(lit);
1251 }
1252 ast::Expr::Paren(p) => {
1253 if let Some(inner) = p.expr() {
1254 return eval_expr(&inner, env);
1255 }
1256 }
1257 ast::Expr::Root(r) => {
1258 if let Some(inner) = r.expr() {
1259 return eval_expr(&inner, env);
1260 }
1261 }
1262 ast::Expr::Lambda(lam) => {
1264 crate::perf::inc(crate::perf::Counter::EvalExpr);
1265 if crate::perf::enabled() {
1266 crate::perf::inc(crate::perf::Counter::ExprLambda);
1267 }
1268 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
1269 return Ok(Value::Lambda(Rc::new(Closure {
1270 param,
1271 body,
1272 env: env.clone(),
1273 })));
1274 }
1275 }
1276 _ => {}
1277 }
1278 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1280 eval_expr_inner(expr, env)
1281 })
1282}
1283
1284fn eval_expr_inner(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1292 let mut cur_expr = expr.clone();
1295 let mut cur_env = env.clone();
1296
1297 loop {
1298 crate::perf::inc(crate::perf::Counter::EvalExpr);
1299 if crate::perf::enabled() {
1301 use crate::perf::Counter;
1302 let c = match &cur_expr {
1303 ast::Expr::Ident(_) => Counter::ExprIdent,
1304 ast::Expr::Literal(_) => Counter::ExprLiteral,
1305 ast::Expr::Str(_) => Counter::ExprStr,
1306 ast::Expr::List(_) => Counter::ExprList,
1307 ast::Expr::AttrSet(_) => Counter::ExprAttrs,
1308 ast::Expr::Select(_) => Counter::ExprSelect,
1309 ast::Expr::Apply(_) => Counter::ExprApply,
1310 ast::Expr::LetIn(_) => Counter::ExprLetIn,
1311 ast::Expr::IfElse(_) => Counter::ExprIfElse,
1312 ast::Expr::With(_) => Counter::ExprWith,
1313 ast::Expr::Lambda(_) => Counter::ExprLambda,
1314 ast::Expr::BinOp(_) => Counter::ExprBinOp,
1315 ast::Expr::HasAttr(_) => Counter::ExprHasAttr,
1316 ast::Expr::UnaryOp(_) => Counter::ExprUnaryOp,
1317 ast::Expr::Assert(_) => Counter::ExprAssert,
1318 ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
1319 | ast::Expr::PathHome(_) | ast::Expr::PathSearch(_) => Counter::ExprPath,
1320 _ => Counter::ExprOther,
1321 };
1322 crate::perf::inc(c);
1323 }
1324 let _guard = DepthGuard::enter()?;
1325 let env = &cur_env;
1326 match &cur_expr {
1327 ast::Expr::Literal(lit) => return eval_literal(lit),
1328
1329 ast::Expr::Str(s) => return eval_str(s, env),
1330
1331 ast::Expr::PathAbs(p) => {
1332 let parts = p.parts();
1335 if parts_have_interpolation(&parts) {
1336 return eval_interpol_path_parts(&parts, PathKind::Abs, env);
1337 }
1338 let text = crate::path::canon_abs(&p.syntax().text().to_string());
1341 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1342 }
1343 ast::Expr::PathRel(p) => {
1344 let parts = p.parts();
1355 if parts_have_interpolation(&parts) {
1356 return eval_interpol_path_parts(&parts, PathKind::Rel, env);
1357 }
1358 let text = p.syntax().text().to_string();
1359 let resolved = if let Some(dir) = current_eval_dir() {
1360 let joined = dir.join(&text);
1361 let norm = normalize_path(&joined);
1365 crate::path::dematerialize(&norm)
1375 .to_string_lossy()
1376 .into_owned()
1377 } else {
1378 text.clone()
1379 };
1380 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1381 }
1382 ast::Expr::PathHome(p) => {
1383 let parts = p.parts();
1384 if parts_have_interpolation(&parts) {
1385 return eval_interpol_path_parts(&parts, PathKind::Home, env);
1386 }
1387 let text = p.syntax().text().to_string();
1388 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1389 }
1390 ast::Expr::PathSearch(p) => {
1391 let text = p.syntax().text().to_string();
1396 let inner = text
1397 .strip_prefix('<')
1398 .and_then(|s| s.strip_suffix('>'))
1399 .unwrap_or(&text);
1400 if let Some(resolved) = crate::builtins::resolve_search_path(inner) {
1401 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1402 }
1403 return Err(EvalError::Throw(
1407 format!("search path '{text}' not in NIX_PATH"),
1408 ));
1409 }
1410
1411 ast::Expr::Ident(ident) => {
1412 let name = ident_text(ident);
1413 return match name.as_str() {
1414 "true" => Ok(Value::Bool(true)),
1415 "false" => Ok(Value::Bool(false)),
1416 "null" => Ok(Value::Null),
1417 _ => {
1418 env.lookup(&name)
1419 .ok_or_else(|| EvalError::UndefinedVar(
1420 format!("'{name}'{}", eval_file_ctx()),
1421 ))
1422 }
1423 };
1424 }
1425
1426 ast::Expr::List(list) => {
1427 let values: Vec<Value> = list.items()
1432 .map(|e| maybe_thunk(&e, env, false, None))
1433 .collect();
1434 return Ok(Value::list(values));
1435 }
1436
1437 ast::Expr::AttrSet(set) => return eval_attrset(set, env),
1438
1439 ast::Expr::Select(sel) => return eval_select(sel, env),
1440
1441 ast::Expr::HasAttr(ha) => return eval_has_attr(ha, env),
1442
1443 ast::Expr::UnaryOp(op) => return eval_unary_op(op, env),
1444
1445 ast::Expr::BinOp(binop) => {
1446 let lhs_expr = binop
1447 .lhs()
1448 .ok_or_else(|| EvalError::ParseError("binop missing lhs".to_string()))?;
1449 let rhs_expr = binop
1450 .rhs()
1451 .ok_or_else(|| EvalError::ParseError("binop missing rhs".to_string()))?;
1452 let kind = binop
1453 .operator()
1454 .ok_or_else(|| EvalError::ParseError("binop missing operator".to_string()))?;
1455 return eval_binop(kind, &lhs_expr, &rhs_expr, env);
1456 }
1457
1458 ast::Expr::Apply(app) => return eval_apply(app, env),
1459
1460 ast::Expr::IfElse(ie) => {
1461 let cond = ie
1462 .condition()
1463 .ok_or_else(|| EvalError::ParseError("if missing condition".to_string()))?;
1464 let body = ie
1465 .body()
1466 .ok_or_else(|| EvalError::ParseError("if missing then body".to_string()))?;
1467 let else_body = ie
1468 .else_body()
1469 .ok_or_else(|| EvalError::ParseError("if missing else body".to_string()))?;
1470 if force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1471 cur_expr = body;
1472 } else {
1473 cur_expr = else_body;
1474 }
1475 continue;
1477 }
1478
1479 ast::Expr::Assert(assert) => {
1480 let cond = assert
1481 .condition()
1482 .ok_or_else(|| EvalError::ParseError("assert missing condition".to_string()))?;
1483 let body = assert
1484 .body()
1485 .ok_or_else(|| EvalError::ParseError("assert missing body".to_string()))?;
1486 if !force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1487 return Err(EvalError::AssertionFailed(eval_file_ctx()));
1488 }
1489 cur_expr = body;
1490 continue;
1491 }
1492
1493 ast::Expr::With(with) => {
1494 let ns = with
1495 .namespace()
1496 .ok_or_else(|| EvalError::ParseError("with missing namespace".to_string()))?;
1497 let body = with
1498 .body()
1499 .ok_or_else(|| EvalError::ParseError("with missing body".to_string()))?;
1500 let scope_val = maybe_thunk(&ns, env, false, None);
1526 let new_env = env.child().with_scope(scope_val);
1527 cur_expr = body;
1528 cur_env = new_env;
1529 continue;
1530 }
1531
1532 ast::Expr::LetIn(letin) => {
1533 let mut new_env = env.child();
1534
1535 let mut thunks: Vec<(String, Thunk)> = Vec::new();
1538
1539 let mut defined_so_far: HashSet<String> = HashSet::new();
1543
1544 let mut dotted_attrs: NixAttrs = NixAttrs::new();
1548
1549 let mut names_complete = true;
1568 let let_scope_names: HashSet<String> = {
1569 let mut s = HashSet::new();
1570 for entry in letin.entries() {
1571 match entry {
1572 ast::Entry::AttrpathValue(apv) => {
1573 if let Some(attrpath) = apv.attrpath() {
1574 if let Some(first) = attrpath.attrs().next() {
1575 if let ast::Attr::Dynamic(_) = &first {
1576 names_complete = false;
1577 }
1578 if let Ok(name) = eval_attr(&first, env) {
1579 s.insert(name);
1580 } else {
1581 names_complete = false;
1582 }
1583 } else {
1584 names_complete = false;
1585 }
1586 } else {
1587 names_complete = false;
1588 }
1589 }
1590 ast::Entry::Inherit(inherit) => {
1591 for attr in inherit.attrs() {
1592 if let ast::Attr::Dynamic(_) = &attr {
1593 names_complete = false;
1594 }
1595 if let Ok(name) = eval_attr(&attr, env) {
1596 s.insert(name);
1597 } else {
1598 names_complete = false;
1599 }
1600 }
1601 }
1602 }
1603 }
1604 s
1605 };
1606 let narrow = scope_narrow_enabled() && names_complete;
1607
1608 let cluster = narrow && scope_cluster_enabled();
1626 let mut all_bound: Vec<(String, Value)> = Vec::new();
1629 let mut pinned_names: HashSet<String> = HashSet::new();
1636 let mut pinned_refs: Vec<HashSet<SmolStr>> = Vec::new();
1637 let mut has_dotted = false;
1642
1643 for entry in letin.entries() {
1644 match entry {
1645 ast::Entry::AttrpathValue(ref apv) => {
1646 let attrpath = apv.attrpath().ok_or_else(|| {
1647 EvalError::ParseError("binding missing attrpath".to_string())
1648 })?;
1649 let value_expr = apv.value().ok_or_else(|| {
1650 EvalError::ParseError("binding missing value".to_string())
1651 })?;
1652 let mut path_keys: Vec<String> = attrpath
1653 .attrs()
1654 .map(|a| eval_attr(&a, env))
1655 .collect::<Result<_, _>>()?;
1656 if path_keys.len() == 1 {
1657 let key = path_keys.pop().unwrap();
1658 let referenced = referenced_idents(&value_expr);
1681 let in_mutual_cycle = std::iter::once(&key)
1682 .chain(let_scope_names.iter())
1683 .any(|n| referenced.contains(n.as_str()));
1684 let value = if in_mutual_cycle {
1685 Value::Thunk(Thunk::new_suspended_recursive(
1686 value_expr.clone(),
1687 env.clone(),
1688 ))
1689 } else {
1690 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
1691 };
1692 new_env.bind(key.clone(), value.clone());
1693 if cluster {
1694 all_bound.push((key.clone(), value.clone()));
1695 }
1696 if let Value::Thunk(t) = &value {
1697 if in_mutual_cycle || !narrow {
1717 thunks.push((key.clone(), t.clone()));
1718 if cluster {
1719 pinned_names.insert(key.clone());
1720 pinned_refs.push(referenced);
1721 }
1722 crate::value::census::scope_pinned();
1723 } else {
1724 crate::value::census::scope_narrowed();
1725 }
1726 }
1727 defined_so_far.insert(key);
1728 } else if path_keys.len() > 1 {
1729 has_dotted = true;
1734 let key = path_keys[0].clone();
1735 let value = build_nested_attr_thunk(
1736 &path_keys[1..],
1737 &value_expr,
1738 env,
1739 &mut thunks,
1740 );
1741 merge_nested_insert(&mut dotted_attrs, key, value);
1742 }
1743 }
1744 ast::Entry::Inherit(ref inherit) => {
1745 if let Some(from) = inherit.from() {
1746 let source_expr = from.expr().ok_or_else(|| {
1747 EvalError::ParseError(
1748 "inherit from missing expr".to_string(),
1749 )
1750 })?;
1751 let source_refs: Option<HashSet<SmolStr>> = if narrow {
1761 Some(referenced_idents(&source_expr))
1762 } else {
1763 None
1764 };
1765 let source_needs_scope = match &source_refs {
1766 Some(refs) => let_scope_names
1767 .iter()
1768 .any(|n| refs.contains(n.as_str())),
1769 None => true,
1770 };
1771 let source_thunk = Thunk::new_suspended(
1776 source_expr, env.clone(),
1777 );
1778 for attr in inherit.attrs() {
1779 let name = eval_attr(&attr, env)?;
1780 let thunk = Thunk::new_inherit_select(
1781 source_thunk.clone(),
1782 name.clone(),
1783 );
1784 new_env.bind(name.clone(), Value::Thunk(thunk.clone()));
1785 if cluster {
1786 all_bound.push((
1787 name.clone(),
1788 Value::Thunk(thunk.clone()),
1789 ));
1790 }
1791 if source_needs_scope {
1792 if cluster {
1793 pinned_names.insert(name.clone());
1794 }
1795 thunks.push((name, thunk));
1796 crate::value::census::scope_pinned();
1797 } else {
1798 crate::value::census::scope_narrowed();
1799 }
1800 }
1801 if cluster
1804 && source_needs_scope
1805 && let Some(refs) = source_refs
1806 {
1807 pinned_refs.push(refs);
1808 }
1809 } else {
1810 for attr in inherit.attrs() {
1815 let name = eval_attr(&attr, env)?;
1816 let value = env.lookup(&name).ok_or_else(|| {
1817 EvalError::UndefinedVar(
1818 format!("'{name}'{}", eval_file_ctx()),
1819 )
1820 })?;
1821 if cluster {
1822 all_bound.push((name.clone(), value.clone()));
1823 }
1824 new_env.bind(name, value);
1825 }
1826 }
1827 }
1828 }
1829 }
1830
1831 for (key, value) in dotted_attrs.iter() {
1836 new_env.bind(key.clone(), value.clone());
1837 if cluster {
1838 all_bound.push((key.clone(), value.clone()));
1839 }
1840 }
1841
1842 let fix_env: Option<Env> = if cluster && !has_dotted && !thunks.is_empty() {
1847 let mut pin = pinned_names;
1854 for refs in &pinned_refs {
1855 for n in &let_scope_names {
1856 if refs.contains(n.as_str()) {
1857 pin.insert(n.clone());
1858 }
1859 }
1860 }
1861 if pin.len() < all_bound.len() {
1862 let mut fe = env.child();
1863 for (name, value) in &all_bound {
1864 if pin.contains(name) {
1865 fe.bind(name.clone(), value.clone());
1866 }
1867 }
1868 Some(fe)
1869 } else {
1870 None
1871 }
1872 } else {
1873 None
1874 };
1875
1876 let phase2_env: &Env = fix_env.as_ref().unwrap_or(&new_env);
1879 for (_key, thunk) in &thunks {
1880 thunk.update_env(phase2_env);
1881 }
1882
1883 let body = letin
1884 .body()
1885 .ok_or_else(|| EvalError::ParseError("let missing body".to_string()))?;
1886 cur_expr = body;
1887 cur_env = new_env;
1888 continue;
1889 }
1890
1891 ast::Expr::Lambda(lam) => {
1892 let param = lam
1893 .param()
1894 .ok_or_else(|| EvalError::ParseError("lambda missing param".to_string()))?;
1895 let body = lam
1896 .body()
1897 .ok_or_else(|| EvalError::ParseError("lambda missing body".to_string()))?;
1898 return Ok(Value::Lambda(Rc::new(Closure {
1899 param,
1900 body,
1901 env: env.clone(),
1902 })));
1903 }
1904
1905 ast::Expr::Paren(p) => {
1906 let inner = p
1907 .expr()
1908 .ok_or_else(|| EvalError::ParseError("paren missing expr".to_string()))?;
1909 cur_expr = inner;
1910 continue;
1911 }
1912
1913 ast::Expr::Root(r) => {
1914 let inner = r
1915 .expr()
1916 .ok_or_else(|| EvalError::ParseError("root missing expr".to_string()))?;
1917 cur_expr = inner;
1918 continue;
1919 }
1920
1921 ast::Expr::LegacyLet(ll) => {
1922 let mut new_env = env.child();
1923 eval_entries(ll, &mut new_env)?;
1924 return new_env
1926 .lookup("body")
1927 .ok_or_else(|| EvalError::AttrNotFound(
1928 format!("'body' in legacy let{}", eval_file_ctx()),
1929 ));
1930 }
1931
1932 ast::Expr::CurPos(_) => return Err(EvalError::NotImplemented("__curPos".to_string())),
1933 ast::Expr::Error(_) => return Err(EvalError::ParseError("parse error node".to_string())),
1934 } } }
1937
1938fn eval_literal(lit: &ast::Literal) -> Result<Value, EvalError> {
1939 use ast::LiteralKind;
1940 match lit.kind() {
1941 LiteralKind::Integer(tok) => {
1942 let n = tok
1943 .value()
1944 .map_err(|e| EvalError::ParseError(format!("invalid integer: {e}")))?;
1945 Ok(Value::Int(n))
1946 }
1947 LiteralKind::Float(tok) => {
1948 let f = tok
1949 .value()
1950 .map_err(|e| EvalError::ParseError(format!("invalid float: {e}")))?;
1951 Ok(Value::Float(f))
1952 }
1953 LiteralKind::Uri(tok) => Ok(Value::string(tok.syntax().text().to_string())),
1954 }
1955}
1956
1957enum TraverseResult {
1959 Found(Value),
1961 Missing(String),
1963 NotAttrs(Value),
1965}
1966
1967fn traverse_attrpath(
1972 base: Value,
1973 attrpath: &rnix::ast::Attrpath,
1974 env: &Env,
1975) -> Result<TraverseResult, EvalError> {
1976 let attrs: Vec<_> = attrpath.attrs().collect();
1977 let mut value = base;
1978 for (i, attr) in attrs.iter().enumerate() {
1979 let key = eval_attr(attr, env)?;
1980 let forced = force_value(&value)?;
1982 match forced {
1983 Value::Attrs(ref a) => match a.get(&key) {
1984 Some(v) => {
1985 if i < attrs.len() - 1 {
1986 value = force_value(v)?;
1988 } else {
1989 value = v.clone();
1992 }
1993 }
1994 None => return Ok(TraverseResult::Missing(key)),
1995 },
1996 _ => return Ok(TraverseResult::NotAttrs(forced)),
1997 }
1998 }
1999 Ok(TraverseResult::Found(value))
2000}
2001
2002fn eval_select(sel: &ast::Select, env: &Env) -> Result<Value, EvalError> {
2003 crate::perf::inc(crate::perf::Counter::Select);
2004 let base_expr = sel.expr().ok_or_else(|| {
2005 EvalError::ParseError("select missing expression".to_string())
2006 })?;
2007 let base_result = eval_expr(&base_expr, env)
2016 .and_then(|v| force_concrete(&v).map(Concrete::into_value));
2017 let base = match base_result {
2018 Ok(v) => v,
2019 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
2020 return eval_expr(&sel.default_expr().expect("checked"), env);
2021 }
2022 Err(e) => return Err(e),
2023 };
2024 let base_type = base.type_name();
2025 let attrpath = sel.attrpath().ok_or_else(|| {
2026 EvalError::ParseError("select missing attrpath".to_string())
2027 })?;
2028 let bridge_active = std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some()
2050 || std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some();
2051 let traversal = traverse_attrpath(base, &attrpath, env);
2052 match traversal {
2053 Ok(TraverseResult::Found(v)) => Ok(v),
2054 Ok(TraverseResult::Missing(key)) => {
2055 if let Some(def) = sel.default_expr() {
2056 eval_expr(&def, env)
2057 } else if bridge_active {
2058 if std::env::var_os("SUI_M26_SELTRACE").is_some() {
2059 let path: Vec<String> = sel.attrpath().map(|ap|
2060 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2061 ).unwrap_or_default();
2062 eprintln!("[M26 SEL-MISS→null] base_type={base_type} path={path:?} missing-key={key}{}", eval_file_ctx());
2063 }
2064 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
2065 let path: Vec<String> = sel.attrpath().map(|ap|
2066 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2067 ).unwrap_or_default();
2068 if path.iter().any(|p| p.contains(&filt)) {
2069 return Err(EvalError::type_error(format!(
2070 "M26-HARDSOFTEN path={path:?} key={key}"
2071 )));
2072 }
2073 }
2074 Ok(Value::Null)
2075 } else {
2076 Err(EvalError::AttrNotFound(
2077 format!("'{key}'{}", eval_file_ctx()),
2078 ))
2079 }
2080 }
2081 Ok(TraverseResult::NotAttrs(forced)) => {
2082 if let Some(def) = sel.default_expr() {
2088 eval_expr(&def, env)
2089 } else if bridge_active {
2090 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
2091 let path: Vec<String> = sel.attrpath().map(|ap|
2092 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2093 ).unwrap_or_default();
2094 if path.iter().any(|p| p.contains(&filt)) {
2095 return Err(EvalError::type_error(format!(
2096 "M26-HARDSOFTEN-NOTATTRS path={path:?} base_type={base_type}"
2097 )));
2098 }
2099 }
2100 return Ok(Value::Null);
2101 } else {
2102 if std::env::var("SUI_DEBUG_SELECT").is_ok() {
2103 let path: Vec<String> = sel.attrpath().map(|ap|
2104 ap.attrs().filter_map(|a| match a {
2105 ast::Attr::Ident(i) => Some(i.to_string()),
2106 ast::Attr::Str(s) => Some(format!("\"{}\"", s.syntax().text())),
2107 ast::Attr::Dynamic(_) => Some("<dyn>".into()),
2108 }).collect()
2109 ).unwrap_or_default();
2110 let dbg = format!("{:?}", forced);
2111 let truncated = if dbg.len() > 200 { format!("{}…", &dbg[..200]) } else { dbg };
2112 eprintln!("[SUI_DEBUG_SELECT] base_type={base_type} path={path:?} base={truncated}{}", eval_file_ctx());
2113 }
2114 Err(attach_trace(EvalError::type_error(
2115 format!("cannot select from {base_type}"),
2116 )))
2117 }
2118 }
2119 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
2124 eval_expr(&sel.default_expr().expect("checked"), env)
2125 }
2126 Err(e) => Err(e),
2127 }
2128}
2129
2130fn eval_has_attr(ha: &ast::HasAttr, env: &Env) -> Result<Value, EvalError> {
2132 let base_expr = ha.expr().ok_or_else(|| {
2133 EvalError::ParseError("hasattr missing expression".to_string())
2134 })?;
2135 let base = force_concrete(&eval_expr(&base_expr, env)?)?.into_value();
2136 let attrpath = ha.attrpath().ok_or_else(|| {
2137 EvalError::ParseError("hasattr missing attrpath".to_string())
2138 })?;
2139 match traverse_attrpath(base, &attrpath, env)? {
2140 TraverseResult::Found(_) => Ok(Value::Bool(true)),
2141 TraverseResult::Missing(_) | TraverseResult::NotAttrs(_) => Ok(Value::Bool(false)),
2142 }
2143}
2144
2145fn eval_unary_op(op: &ast::UnaryOp, env: &Env) -> Result<Value, EvalError> {
2146 let inner = op
2147 .expr()
2148 .ok_or_else(|| EvalError::ParseError("unary op missing expr".to_string()))?;
2149 let val = force_value(&eval_expr(&inner, env)?)?;
2150 let kind = op
2151 .operator()
2152 .ok_or_else(|| EvalError::ParseError("unary op missing operator".to_string()))?;
2153 match kind {
2154 ast::UnaryOpKind::Negate => match val {
2155 Value::Int(n) => Ok(Value::Int(-n)),
2156 Value::Float(f) => Ok(Value::Float(-f)),
2157 _ => Err(EvalError::type_error(
2158 format!("cannot negate {}", val.type_name()),
2159 )),
2160 },
2161 ast::UnaryOpKind::Invert => Ok(Value::Bool(!val.as_bool()?)),
2162 }
2163}
2164
2165#[inline]
2176pub(crate) fn builtin_takes_lazy_arg(name: &str) -> bool {
2177 matches!(
2178 name,
2179 "tryEval" | "addErrorContext<partial>" | "seq<partial>" | "deepSeq<partial>" | "foldl'<p1>"
2180 )
2181}
2182
2183fn eval_apply(app: &ast::Apply, env: &Env) -> Result<Value, EvalError> {
2184 let func_expr = app
2185 .lambda()
2186 .ok_or_else(|| EvalError::ParseError("apply missing function".to_string()))?;
2187 let arg_expr = app
2188 .argument()
2189 .ok_or_else(|| EvalError::ParseError("apply missing argument".to_string()))?;
2190 let func = force_value(&eval_expr(&func_expr, env)?)?;
2191 let arg = match &func {
2199 Value::Lambda(_) => {
2200 if let Some(v) = eval_pure_constant_arg(&arg_expr) {
2209 v
2210 } else {
2211 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
2212 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
2213 }
2214 }
2215 Value::Builtin(b) if builtin_takes_lazy_arg(&b.name) => {
2216 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
2221 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
2222 }
2223 _ => eval_expr(&arg_expr, env)?,
2224 };
2225 apply(func, arg)
2226}
2227
2228fn eval_pure_constant_arg(arg_expr: &ast::Expr) -> Option<Value> {
2243 match arg_expr {
2244 ast::Expr::Literal(lit) => eval_literal(lit).ok(),
2245 ast::Expr::Str(st) if !str_has_interpolation(st) => {
2246 eval_str(st, &Env::new()).ok()
2248 }
2249 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
2250 let text = crate::path::canon_abs(&p.syntax().text().to_string());
2251 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2252 }
2253 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
2254 let text = p.syntax().text().to_string();
2255 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2256 }
2257 _ => None,
2258 }
2259}
2260
2261fn eval_str(s: &ast::Str, env: &Env) -> Result<Value, EvalError> {
2262 let mut result = String::new();
2263 let mut ctx = StringContext::new();
2264 for part in s.normalized_parts() {
2265 match part {
2266 InterpolPart::Literal(text) => result.push_str(&text),
2267 InterpolPart::Interpolation(interpol) => {
2268 let expr = interpol.expr().ok_or_else(|| {
2269 EvalError::ParseError("interpolation missing expr".to_string())
2270 })?;
2271 let val = force_value(&eval_expr(&expr, env)?)?;
2272 let (s, c) = val.coerce_to_string_copy_to_store()?;
2277 result.push_str(&s);
2278 ctx.merge(&c);
2279 }
2280 }
2281 }
2282 Ok(Value::String(Rc::new(NixString::with_context(result, ctx))))
2283}
2284
2285fn parts_have_interpolation(parts: &[InterpolPart<rnix::ast::PathContent>]) -> bool {
2289 parts
2290 .iter()
2291 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2292}
2293
2294fn str_has_interpolation(s: &ast::Str) -> bool {
2298 s.normalized_parts()
2299 .iter()
2300 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2301}
2302
2303fn eval_interpol_path_parts(
2318 parts: &[InterpolPart<rnix::ast::PathContent>],
2319 kind: PathKind,
2320 env: &Env,
2321) -> Result<Value, EvalError> {
2322 let mut text = String::new();
2323 for part in parts {
2324 match part {
2325 InterpolPart::Literal(content) => text.push_str(content.text()),
2326 InterpolPart::Interpolation(interpol) => {
2327 let expr = interpol.expr().ok_or_else(|| {
2328 EvalError::ParseError("path interpolation missing expr".to_string())
2329 })?;
2330 let val = force_value(&eval_expr(&expr, env)?)?;
2331 let (s, _ctx) = val.coerce_to_string()?;
2335 text.push_str(&s);
2336 }
2337 }
2338 }
2339 let resolved = match kind {
2340 PathKind::Rel => {
2343 if let Some(dir) = current_eval_dir() {
2344 let norm = normalize_path(&dir.join(&text));
2345 crate::path::dematerialize(&norm).to_string_lossy().into_owned()
2354 } else {
2355 text
2359 }
2360 }
2361 PathKind::Abs => crate::path::canon_abs(&text),
2369 PathKind::Home => normalize_path(std::path::Path::new(&text))
2372 .to_string_lossy()
2373 .into_owned(),
2374 };
2375 Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))))
2376}
2377
2378#[derive(Clone, Copy)]
2381enum PathKind {
2382 Abs,
2383 Rel,
2384 Home,
2385}
2386
2387fn eval_attr(attr: &ast::Attr, env: &Env) -> Result<String, EvalError> {
2390 eval_attr_maybe_null(attr, env)?
2391 .ok_or_else(|| EvalError::TypeError("null dynamic attribute name".into()))
2392}
2393
2394fn eval_attr_maybe_null(attr: &ast::Attr, env: &Env) -> Result<Option<String>, EvalError> {
2397 match attr {
2398 ast::Attr::Ident(ident) => Ok(Some(ident_text(ident))),
2399 ast::Attr::Dynamic(dyn_) => {
2400 let expr = dyn_
2401 .expr()
2402 .ok_or_else(|| EvalError::ParseError("dynamic attr missing expr".to_string()))?;
2403 let val = force_value(&eval_expr(&expr, env)?)?;
2404 if val == Value::Null {
2407 return Ok(None);
2408 }
2409 Ok(Some(val.as_string()?.to_string()))
2410 }
2411 ast::Attr::Str(s) => {
2412 let val = eval_str(s, env)?;
2413 Ok(Some(val.as_string()?.to_string()))
2414 }
2415 }
2416}
2417
2418fn ident_text(ident: &ast::Ident) -> String {
2420 match ident.ident_token() {
2428 Some(tok) => tok.text().to_string(),
2429 None => ident.syntax().text().to_string(),
2430 }
2431}
2432
2433fn static_attr_offset(attr: &ast::Attr) -> Option<u32> {
2440 let node = match attr {
2441 ast::Attr::Ident(i) => i.syntax(),
2442 ast::Attr::Str(s) => s.syntax(),
2443 ast::Attr::Dynamic(_) => return None,
2444 };
2445 Some(u32::from(node.text_range().start()))
2446}
2447
2448fn attach_attrset_positions(set: &ast::AttrSet, attrs: &mut NixAttrs, env: &Env) {
2455 let mut table = crate::pos::AttrPositions::new(current_eval_file());
2462 for entry in set.entries() {
2463 if let ast::Entry::AttrpathValue(apv) = entry {
2464 let Some(attrpath) = apv.attrpath() else { continue };
2465 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2466 let Some(head) = path_attrs.first() else { continue };
2474 let Some(offset) = static_attr_offset(head) else { continue };
2475 if let Ok(Some(name)) = eval_attr_maybe_null(&path_attrs[0], env) {
2478 table.insert(intern(&name), offset);
2479 }
2480 } else if let ast::Entry::Inherit(inh) = entry {
2481 for attr in inh.attrs() {
2495 let Some(offset) = static_attr_offset(&attr) else { continue };
2496 if let Ok(Some(name)) = eval_attr_maybe_null(&attr, env) {
2497 table.insert(intern(&name), offset);
2498 }
2499 }
2500 }
2501 }
2502 if !table.is_empty() {
2503 attrs.set_positions(std::rc::Rc::new(table));
2504 }
2505}
2506
2507fn eval_attrset(set: &ast::AttrSet, env: &Env) -> Result<Value, EvalError> {
2508 crate::perf::inc(crate::perf::Counter::Attrset);
2509 let mut attrs = NixAttrs::new();
2510 let is_rec = set.rec_token().is_some();
2511
2512 if is_rec {
2513 let mut rec_env = env.child();
2514 let mut thunks: Vec<(String, Thunk)> = Vec::new();
2515
2516 let mut defined_so_far: HashSet<String> = HashSet::new();
2520
2521 let mut dotted_attrs: NixAttrs = NixAttrs::new();
2527
2528 let mut names_complete = scope_narrow_enabled();
2550 let rec_scope_names: HashSet<String> = if names_complete {
2551 let mut s = HashSet::new();
2552 for entry in set.entries() {
2553 match entry {
2554 ast::Entry::AttrpathValue(apv) => {
2555 match apv.attrpath().and_then(|p| p.attrs().next()) {
2556 Some(ast::Attr::Ident(i)) => {
2557 s.insert(ident_text(&i));
2558 }
2559 _ => names_complete = false,
2560 }
2561 }
2562 ast::Entry::Inherit(inh) => {
2563 for attr in inh.attrs() {
2564 match attr {
2565 ast::Attr::Ident(i) => {
2566 s.insert(ident_text(&i));
2567 }
2568 _ => names_complete = false,
2569 }
2570 }
2571 }
2572 }
2573 }
2574 s
2575 } else {
2576 HashSet::new()
2577 };
2578 let narrow = names_complete;
2579
2580 for entry in set.entries() {
2582 match entry {
2583 ast::Entry::AttrpathValue(apv) => {
2584 let attrpath = apv.attrpath().ok_or_else(|| {
2585 EvalError::ParseError("binding missing attrpath".to_string())
2586 })?;
2587 let value_expr = apv.value().ok_or_else(|| {
2588 EvalError::ParseError("binding missing value".to_string())
2589 })?;
2590 let mut path_keys: Vec<String> = attrpath
2591 .attrs()
2592 .filter_map(|a| eval_attr_maybe_null(&a, env).transpose())
2593 .collect::<Result<_, _>>()?;
2594 if path_keys.is_empty() { continue; }
2596 if path_keys.len() == 1 {
2597 let key = path_keys.pop().unwrap();
2598 let referenced = referenced_idents(&value_expr);
2615 let is_recursive_binding = referenced.contains(key.as_str())
2616 || defined_so_far
2617 .iter()
2618 .any(|n| referenced.contains(n.as_str()));
2619 let value = if is_recursive_binding {
2620 Value::Thunk(Thunk::new_suspended_recursive(
2621 value_expr.clone(),
2622 env.clone(),
2623 ))
2624 } else {
2625 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
2631 };
2632 let needs_scope = !narrow
2638 || is_recursive_binding
2639 || rec_scope_names
2640 .iter()
2641 .any(|n| referenced.contains(n.as_str()));
2642 rec_env.bind(key.clone(), value.clone());
2643 attrs.insert(key.clone(), value.clone());
2644 if let Value::Thunk(t) = &value {
2645 if needs_scope {
2646 thunks.push((key.clone(), t.clone()));
2647 crate::value::census::scope_pinned();
2648 } else {
2649 crate::value::census::scope_narrowed();
2650 }
2651 }
2652 defined_so_far.insert(key);
2653 } else {
2654 let key = path_keys[0].clone();
2658 let value =
2659 build_nested_attr_thunk(&path_keys[1..], &value_expr, env, &mut thunks);
2660 merge_nested_insert(&mut dotted_attrs, key, value);
2661 }
2662 }
2663 ast::Entry::Inherit(inherit) => {
2664 eval_inherit(&inherit, env, &mut attrs, Some(&mut rec_env), Some(&mut thunks))?;
2665 }
2666 }
2667 }
2668
2669 for (key, value) in dotted_attrs.iter() {
2674 attrs.insert(key.clone(), value.clone());
2675 rec_env.bind(key.clone(), value.clone());
2676 }
2677
2678 for (_key, thunk) in &thunks {
2681 thunk.update_env(&rec_env);
2682 }
2683 } else {
2684 for entry in set.entries() {
2685 match entry {
2686 ast::Entry::AttrpathValue(apv) => {
2687 let attrpath = apv.attrpath().ok_or_else(|| {
2688 EvalError::ParseError("binding missing attrpath".to_string())
2689 })?;
2690 let value_expr = apv.value().ok_or_else(|| {
2691 EvalError::ParseError("binding missing value".to_string())
2692 })?;
2693 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2694 let tail_is_dynamic =
2704 path_attrs.len() > 1 && attrs_have_dynamic(&path_attrs[1..]);
2705 let head_key = match eval_attr_maybe_null(&path_attrs[0], env)? {
2706 Some(k) => k,
2707 None => continue,
2709 };
2710 if tail_is_dynamic && attrs.get(&head_key).is_none() {
2711 let value =
2712 build_deferred_tail_attr(&path_attrs[1..], &value_expr, env);
2713 attrs.insert(head_key, value);
2714 continue;
2715 }
2716 if tail_is_dynamic {
2730 if let Some(existing) = attrs.get(&head_key).cloned() {
2731 let merged = merge_deferred_dynamic_tail(
2732 existing,
2733 &path_attrs[1..],
2734 &value_expr,
2735 env,
2736 )?;
2737 attrs.insert(head_key, merged);
2738 continue;
2739 }
2740 }
2741 let mut path_keys: Vec<String> = {
2744 let mut v = Vec::with_capacity(path_attrs.len());
2745 v.push(head_key);
2746 let mut skip = false;
2747 for a in &path_attrs[1..] {
2748 match eval_attr_maybe_null(a, env)? {
2749 Some(k) => v.push(k),
2750 None => { skip = true; break; }
2751 }
2752 }
2753 if skip { v.clear(); }
2754 v
2755 };
2756 if path_keys.is_empty() { continue; }
2758 if path_keys.len() == 1 {
2759 let key = path_keys.pop().unwrap();
2760 let value = maybe_thunk(&value_expr, env, false, None);
2763 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2788 let existing = attrs.get(&key).cloned().unwrap();
2789 let forced_existing = force_value(&existing)?;
2790 attrs.insert(key.clone(), forced_existing);
2791 }
2792 if matches!(attrs.get(&key), Some(Value::Attrs(_))) {
2793 let forced = force_value(&value)?;
2794 merge_nested_insert(&mut attrs, key, forced);
2795 } else {
2796 attrs.insert(key, value);
2797 }
2798 } else {
2799 let key = path_keys[0].clone();
2800 let value = build_nested_attr(&path_keys[1..], &value_expr, env)?;
2801 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2815 let existing = attrs.get(&key).cloned().unwrap();
2816 let forced = force_value(&existing)?;
2817 attrs.insert(key.clone(), forced);
2818 }
2819 merge_nested_insert(&mut attrs, key, value);
2820 }
2821 }
2822 ast::Entry::Inherit(inherit) => {
2823 eval_inherit(&inherit, env, &mut attrs, None, None)?;
2824 }
2825 }
2826 }
2827 }
2828
2829 attach_attrset_positions(set, &mut attrs, env);
2835
2836 Ok(Value::Attrs(Rc::new(attrs)))
2837}
2838
2839fn eval_inherit(
2840 inherit: &ast::Inherit,
2841 env: &Env,
2842 attrs: &mut NixAttrs,
2843 bind_env: Option<&mut Env>,
2844 mut thunks: Option<&mut Vec<(String, Thunk)>>,
2845) -> Result<(), EvalError> {
2846 if let Some(from) = inherit.from() {
2847 let source_expr = from
2867 .expr()
2868 .ok_or_else(|| EvalError::ParseError("inherit from missing expr".to_string()))?;
2869 let source_thunk = Thunk::new_suspended(source_expr, env.clone());
2873 let mut be = bind_env;
2874 for attr in inherit.attrs() {
2875 let name = eval_attr(&attr, env)?;
2876 let thunk = Thunk::new_inherit_select(source_thunk.clone(), name.clone());
2877 let value = Value::Thunk(thunk.clone());
2878 attrs.insert(name.clone(), value.clone());
2879 if let Some(ref mut e) = be {
2880 e.bind(name.clone(), value);
2881 }
2882 if let Some(ref mut t) = thunks {
2883 t.push((name, thunk));
2884 }
2885 }
2886 } else {
2887 let mut be = bind_env;
2903 for attr in inherit.attrs() {
2904 let name = eval_attr(&attr, env)?;
2905 let sym = crate::value::intern(&name);
2906 let value = if let Some(v) = env.lookup_fast(sym, &name) {
2907 v
2908 } else if let Some((scope_cache, scope_value)) =
2909 env.innermost_with_scope()
2910 {
2911 Value::Thunk(Thunk::new_with_ident(
2912 SmolStr::from(name.as_str()),
2913 scope_cache,
2914 scope_value,
2915 env.clone(),
2916 ))
2917 } else {
2918 return Err(EvalError::UndefinedVar(format!(
2919 "'{name}'{}",
2920 eval_file_ctx()
2921 )));
2922 };
2923 attrs.insert(name.clone(), value.clone());
2924 if let Some(ref mut e) = be {
2925 e.bind(name, value);
2926 }
2927 }
2928 }
2929 Ok(())
2930}
2931
2932fn build_nested_attr(
2933 path: &[String],
2934 expr: &ast::Expr,
2935 env: &Env,
2936) -> Result<Value, EvalError> {
2937 if path.is_empty() {
2938 return Ok(maybe_thunk(expr, env, false, None));
2943 }
2944 let key = path[0].clone();
2945 let inner = build_nested_attr(&path[1..], expr, env)?;
2946 let mut attrs = NixAttrs::new();
2947 attrs.insert(key, inner);
2948 Ok(Value::Attrs(Rc::new(attrs)))
2949}
2950
2951fn attr_is_dynamic(attr: &ast::Attr) -> bool {
2972 match attr {
2973 ast::Attr::Dynamic(_) => true,
2974 ast::Attr::Str(s) => s
2977 .normalized_parts()
2978 .iter()
2979 .any(|p| matches!(p, InterpolPart::Interpolation(_))),
2980 ast::Attr::Ident(_) => false,
2981 }
2982}
2983
2984fn attrs_have_dynamic(attrs: &[ast::Attr]) -> bool {
2992 attrs.iter().any(attr_is_dynamic)
2993}
2994
2995fn build_deferred_tail_attr(
3008 tail: &[ast::Attr],
3009 value_expr: &ast::Expr,
3010 env: &Env,
3011) -> Value {
3012 let tail: Vec<ast::Attr> = tail.to_vec();
3013 let value_expr = value_expr.clone();
3014 let env = env.clone();
3015 Value::Thunk(Thunk::new_native(move || {
3016 build_tail_attrs_now(&tail, &value_expr, &env)
3017 }))
3018}
3019
3020fn build_tail_attrs_now(
3041 tail: &[ast::Attr],
3042 value_expr: &ast::Expr,
3043 env: &Env,
3044) -> Result<Value, EvalError> {
3045 if tail.is_empty() {
3046 return Ok(maybe_thunk(value_expr, env, false, None));
3047 }
3048 if std::env::var_os("SUI_M26_TAILTRACE").is_some() {
3049 let t: String = tail[0].syntax().text().to_string().chars().take(40).collect();
3050 eprintln!("[M26 TAIL-RESOLVE] forcing dynamic tail key `{t}`");
3051 if attrs_have_dynamic(&tail[..1]) {
3052 crate::trace::dump_force_stack_ids();
3053 }
3054 }
3055 let key = match eval_attr_maybe_null(&tail[0], env)? {
3056 Some(k) => k,
3057 None => return Ok(Value::Attrs(Rc::new(NixAttrs::new()))),
3060 };
3061 let inner = if tail.len() == 1 {
3067 maybe_thunk(value_expr, env, false, None)
3068 } else {
3069 build_deferred_tail_attr(&tail[1..], value_expr, env)
3070 };
3071 let mut attrs = NixAttrs::new();
3072 attrs.insert(key, inner);
3073 Ok(Value::Attrs(Rc::new(attrs)))
3074}
3075
3076fn merge_deferred_dynamic_tail(
3094 existing: Value,
3095 tail: &[ast::Attr],
3096 value_expr: &ast::Expr,
3097 env: &Env,
3098) -> Result<Value, EvalError> {
3099 debug_assert!(!tail.is_empty());
3102
3103 if attr_is_dynamic(&tail[0]) {
3108 let deferred = build_deferred_tail_attr(tail, value_expr, env);
3109 return Ok(lazy_overlay_merge(existing, deferred));
3110 }
3111
3112 let key = match eval_attr_maybe_null(&tail[0], env)? {
3115 Some(k) => k,
3116 None => return Ok(existing),
3117 };
3118
3119 let existing_forced = force_value(&existing)?;
3123 let mut base = match existing_forced {
3124 Value::Attrs(a) => (*a).clone(),
3125 _ => {
3130 let deferred = build_deferred_tail_attr(tail, value_expr, env);
3131 return Ok(deferred);
3132 }
3133 };
3134
3135 let child_existing = base.get(&key).cloned();
3137 let new_child = match child_existing {
3138 Some(child) if tail.len() > 1 => {
3139 merge_deferred_dynamic_tail(child, &tail[1..], value_expr, env)?
3141 }
3142 Some(child) => {
3143 let leaf = maybe_thunk(value_expr, env, false, None);
3146 lazy_overlay_merge(child, leaf)
3147 }
3148 None if tail.len() > 1 => {
3149 build_deferred_tail_attr(&tail[1..], value_expr, env)
3153 }
3154 None => maybe_thunk(value_expr, env, false, None),
3155 };
3156 base.insert(key, new_child);
3157 Ok(Value::Attrs(Rc::new(base)))
3158}
3159
3160fn lazy_overlay_merge(left: Value, right: Value) -> Value {
3167 match (&left, &right) {
3168 (Value::Attrs(la), Value::Attrs(_)) => {
3169 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
3170 let mut merged = (**la).clone();
3171 if let Value::Attrs(ra) = &right {
3172 for (k, v) in ra.iter_unsorted() {
3176 merge_nested_insert(&mut merged, k.clone(), v.clone());
3177 }
3178 }
3179 Value::Attrs(Rc::new(merged))
3180 }
3181 _ => {
3182 Value::Thunk(Thunk::new_native(move || {
3186 let lf = force_value(&left)?;
3187 let rf = force_value(&right)?;
3188 let la = lf.as_attrs()?;
3189 let ra = rf.as_attrs()?;
3190 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
3191 let mut merged = (*la).clone();
3192 for (k, v) in ra.iter_unsorted() {
3193 merge_nested_insert(&mut merged, k.clone(), v.clone());
3194 }
3195 Ok(Value::Attrs(Rc::new(merged)))
3196 }))
3197 }
3198 }
3199}
3200
3201fn build_nested_attr_thunk(
3209 path: &[String],
3210 expr: &ast::Expr,
3211 env: &Env,
3212 thunks: &mut Vec<(String, Thunk)>,
3213) -> Value {
3214 if path.is_empty() {
3215 let thunk = Thunk::new_suspended(expr.clone(), env.clone());
3216 let val = Value::Thunk(thunk.clone());
3217 thunks.push((String::new(), thunk));
3218 return val;
3219 }
3220 let key = path[0].clone();
3221 let inner = build_nested_attr_thunk(&path[1..], expr, env, thunks);
3222 let mut attrs = NixAttrs::new();
3223 attrs.insert(key, inner);
3224 Value::Attrs(Rc::new(attrs))
3225}
3226
3227fn merge_nested_insert(target: &mut NixAttrs, key: String, value: Value) {
3234 let existing = match target.get(&key) {
3238 Some(e) => e.clone(),
3239 None => {
3240 target.insert(key, value);
3241 return;
3242 }
3243 };
3244 let value = match value {
3268 Value::Thunk(_) => match force_value(&value) {
3269 Ok(v @ Value::Attrs(_)) => v,
3270 _ => value,
3271 },
3272 other => other,
3273 };
3274 if !matches!(value, Value::Attrs(_)) {
3275 target.insert(key, value);
3276 return;
3277 }
3278 let existing_concrete = match &existing {
3281 Value::Attrs(_) => existing.clone(),
3282 Value::Thunk(_) => match force_value(&existing) {
3283 Ok(v @ Value::Attrs(_)) => v,
3284 _ => {
3285 target.insert(key, value);
3286 return;
3287 }
3288 },
3289 _ => {
3290 target.insert(key, value);
3291 return;
3292 }
3293 };
3294 let mut existing_attrs = match existing_concrete {
3298 Value::Attrs(a) => (*a).clone(),
3299 _ => unreachable!(),
3300 };
3301 let new_attrs = match value {
3302 Value::Attrs(ref a) => a,
3303 _ => unreachable!(),
3304 };
3305 for (k, v) in new_attrs.iter_unsorted() {
3306 merge_nested_insert(&mut existing_attrs, k.clone(), v.clone());
3307 }
3308 target.insert(key, Value::Attrs(Rc::new(existing_attrs)));
3309}
3310
3311fn eval_entries<N: HasEntry + AstNode>(node: &N, env: &mut Env) -> Result<(), EvalError> {
3313 for entry in node.entries() {
3314 match entry {
3315 ast::Entry::AttrpathValue(apv) => {
3316 let attrpath = apv.attrpath().ok_or_else(|| {
3317 EvalError::ParseError("binding missing attrpath".to_string())
3318 })?;
3319 let value_expr = apv.value().ok_or_else(|| {
3320 EvalError::ParseError("binding missing value".to_string())
3321 })?;
3322 let mut path_keys: Vec<String> = attrpath
3323 .attrs()
3324 .map(|a| eval_attr(&a, env))
3325 .collect::<Result<_, _>>()?;
3326 if path_keys.len() == 1 {
3327 let key = path_keys.pop().unwrap();
3328 let value = eval_expr(&value_expr, env)?;
3329 env.bind(key, value);
3330 }
3331 }
3333 ast::Entry::Inherit(inherit) => {
3334 if let Some(from) = inherit.from() {
3335 let source_expr = from.expr().ok_or_else(|| {
3336 EvalError::ParseError("inherit from missing expr".to_string())
3337 })?;
3338 let source = force_value(&eval_expr(&source_expr, env)?)?;
3339 let source_attrs = source.as_attrs()?;
3340 for attr in inherit.attrs() {
3341 let name = eval_attr(&attr, env)?;
3342 let value = source_attrs
3343 .get(&name)
3344 .cloned()
3345 .ok_or_else(|| EvalError::AttrNotFound(
3346 format!("'{name}' in inherit{}", eval_file_ctx()),
3347 ))?;
3348 env.bind(name, value);
3349 }
3350 } else {
3351 for attr in inherit.attrs() {
3352 let name = eval_attr(&attr, env)?;
3353 let value = env
3354 .lookup(&name)
3355 .ok_or_else(|| EvalError::UndefinedVar(
3356 format!("'{name}'{}", eval_file_ctx()),
3357 ))?;
3358 env.bind(name, value);
3359 }
3360 }
3361 }
3362 }
3363 }
3364 Ok(())
3365}
3366
3367fn eval_binop(
3368 op: ast::BinOpKind,
3369 lhs: &ast::Expr,
3370 rhs: &ast::Expr,
3371 env: &Env,
3372) -> Result<Value, EvalError> {
3373 match op {
3375 ast::BinOpKind::And => {
3376 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3377 if !l {
3378 return Ok(Value::Bool(false));
3379 }
3380 return eval_expr(rhs, env);
3381 }
3382 ast::BinOpKind::Or => {
3383 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3384 if l {
3385 return Ok(Value::Bool(true));
3386 }
3387 return eval_expr(rhs, env);
3388 }
3389 ast::BinOpKind::Implication => {
3390 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3391 if !l {
3392 return Ok(Value::Bool(true));
3393 }
3394 return eval_expr(rhs, env);
3395 }
3396 _ => {}
3397 }
3398
3399 let lc = force_concrete(&eval_expr(lhs, env)?)?;
3400 let rc = force_concrete(&eval_expr(rhs, env)?)?;
3401 let l = lc.into_value();
3408 let r = rc.into_value();
3409
3410 match op {
3411 ast::BinOpKind::Add => match (&l, &r) {
3412 (Value::Int(a), Value::Int(b)) => a
3413 .checked_add(*b)
3414 .map(Value::Int)
3415 .ok_or_else(|| int_overflow("adding", *a, '+', *b)),
3416 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
3417 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
3418 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + *b as f64)),
3419 (Value::String(a), Value::String(b)) => {
3420 let mut ctx = a.context.clone();
3421 ctx.merge(&b.context);
3422 let mut s = String::with_capacity(a.chars.len() + b.chars.len());
3430 s.push_str(&a.chars);
3431 s.push_str(&b.chars);
3432 Ok(Value::String(Rc::new(NixString::with_context(s, ctx))))
3433 }
3434 (Value::Path(a), Value::String(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}{}", b.chars).as_str())))),
3435 (Value::Path(a), Value::Path(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}/{b}").as_str())))),
3436 (Value::Attrs(_), _) | (_, Value::Attrs(_)) => {
3438 let (ls, lctx) = l.coerce_to_string()?;
3439 let (rs, rctx) = r.coerce_to_string()?;
3440 let mut ctx = lctx;
3441 ctx.merge(&rctx);
3442 Ok(Value::String(Rc::new(NixString::with_context(
3443 format!("{ls}{rs}"),
3444 ctx,
3445 ))))
3446 }
3447 _ => Err(EvalError::op_type("add", l.type_name(), r.type_name())),
3448 },
3449 ast::BinOpKind::Sub => num_op(
3450 &l,
3451 &r,
3452 |a, b| a.checked_sub(b),
3453 |a, b| a - b,
3454 |a, b| int_overflow("subtracting", a, '-', b),
3455 ),
3456 ast::BinOpKind::Mul => num_op(
3457 &l,
3458 &r,
3459 |a, b| a.checked_mul(b),
3460 |a, b| a * b,
3461 |a, b| int_overflow("multiplying", a, '*', b),
3462 ),
3463 ast::BinOpKind::Div => {
3464 let rhs_is_zero = match &r {
3473 Value::Int(0) => true,
3474 Value::Float(f) => *f == 0.0,
3475 _ => false,
3476 };
3477 if rhs_is_zero {
3478 return Err(EvalError::DivisionByZero);
3479 }
3480 num_op(
3481 &l,
3482 &r,
3483 |a, b| a.checked_div(b),
3484 |a, b| a / b,
3485 |a, b| int_overflow("dividing", a, '/', b),
3486 )
3487 }
3488 ast::BinOpKind::Equal => Ok(Value::Bool(l == r)),
3489 ast::BinOpKind::NotEqual => Ok(Value::Bool(l != r)),
3490 ast::BinOpKind::Less => compare(&l, &r, |o| o == std::cmp::Ordering::Less),
3491 ast::BinOpKind::LessOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Greater),
3492 ast::BinOpKind::More => compare(&l, &r, |o| o == std::cmp::Ordering::Greater),
3493 ast::BinOpKind::MoreOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Less),
3494 ast::BinOpKind::Update => {
3495 let la = l.to_attrs()?;
3496 let ra = r.to_attrs()?;
3497 Ok(Value::Attrs(Rc::new(la.overlay(ra))))
3499 }
3500 ast::BinOpKind::Concat => {
3501 crate::value::concat_lists(l, r.as_list()?)
3511 }
3512 ast::BinOpKind::And | ast::BinOpKind::Or | ast::BinOpKind::Implication => {
3513 unreachable!("handled above")
3514 }
3515 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
3516 Err(EvalError::NotImplemented("pipe operators".to_string()))
3517 }
3518 }
3519}
3520
3521#[inline]
3526fn int_overflow(verb: &str, a: i64, sym: char, b: i64) -> EvalError {
3527 EvalError::Abort(format!("integer overflow in {verb} {a} {sym} {b}"))
3528}
3529
3530fn num_op(
3531 l: &Value,
3532 r: &Value,
3533 int_op: impl Fn(i64, i64) -> Option<i64>,
3534 float_op: impl Fn(f64, f64) -> f64,
3535 overflow: impl Fn(i64, i64) -> EvalError,
3536) -> Result<Value, EvalError> {
3537 match (l, r) {
3538 (Value::Int(a), Value::Int(b)) => {
3539 int_op(*a, *b).map(Value::Int).ok_or_else(|| overflow(*a, *b))
3540 }
3541 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(float_op(*a, *b))),
3542 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(float_op(*a as f64, *b))),
3543 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(float_op(*a, *b as f64))),
3544 _ => Err(EvalError::op_type("perform arithmetic on", l.type_name(), r.type_name())),
3545 }
3546}
3547
3548fn compare(
3549 l: &Value,
3550 r: &Value,
3551 pred: impl Fn(std::cmp::Ordering) -> bool,
3552) -> Result<Value, EvalError> {
3553 let ord = match (l, r) {
3554 (Value::Int(a), Value::Int(b)) => a.cmp(b),
3555 (Value::Float(a), Value::Float(b)) => {
3556 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
3557 }
3558 (Value::Int(a), Value::Float(b)) => (*a as f64)
3559 .partial_cmp(b)
3560 .unwrap_or(std::cmp::Ordering::Equal),
3561 (Value::Float(a), Value::Int(b)) => a
3562 .partial_cmp(&(*b as f64))
3563 .unwrap_or(std::cmp::Ordering::Equal),
3564 (Value::String(a), Value::String(b)) => a.chars.cmp(&b.chars),
3565 _ => {
3566 return Err(EvalError::op_type("compare", l.type_name(), r.type_name()));
3567 }
3568 };
3569 Ok(Value::Bool(pred(ord)))
3570}
3571
3572pub fn apply_and_force(func: Value, arg: Value) -> Result<Value, EvalError> {
3586 force_value(&apply(func, arg)?)
3587}
3588
3589pub fn apply(func: Value, arg: Value) -> Result<Value, EvalError> {
3590 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || apply_inner(func, arg))
3591}
3592
3593fn apply_inner(func: Value, arg: Value) -> Result<Value, EvalError> {
3594 crate::perf::inc(crate::perf::Counter::Apply);
3595 let func = force_concrete(&func)?.into_value();
3596 match func {
3597 Value::Lambda(closure) => {
3598 if crate::perf::enabled() {
3600 APPLY_SITES.with(|sites| {
3601 let file = closure.env.eval_file()
3602 .map(|p| p.display().to_string())
3603 .unwrap_or_else(|| "<eval>".into());
3604 let param_name = match &closure.param {
3606 rnix::ast::Param::IdentParam(ip) => ip.ident().map(|i| ident_text(&i)).unwrap_or_default(),
3607 rnix::ast::Param::Pattern(pat) => {
3608 let mut names: Vec<String> = pat.pat_entries()
3609 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3610 .take(3)
3611 .collect();
3612 if pat.pat_entries().count() > 3 { names.push("...".to_string()); }
3613 format!("{{{}}}", names.join(","))
3614 }
3615 };
3616 let key = format!("{}:{}", file.rsplit_once("-source/").map_or(file.as_str(), |(_,s)| s), param_name);
3617 *sites.borrow_mut().entry(key).or_insert(0u64) += 1;
3618 });
3619 }
3620 let mut call_env = closure.env.child();
3621 let _file_guard = push_eval_frame(closure.env.eval_file().cloned());
3627 let _trace = push_nix_trace_lambda(&closure.env);
3633 match &closure.param {
3634 rnix::ast::Param::IdentParam(_) => {
3635 bind_param(&closure.param, &arg, &mut call_env)?;
3638 }
3639 rnix::ast::Param::Pattern(_) => {
3640 let forced_arg = force_concrete(&arg)?.into_value();
3642 bind_param(&closure.param, &forced_arg, &mut call_env)?;
3643 }
3644 }
3645 eval_expr(&closure.body, &call_env)
3646 }
3647 Value::Builtin(b) => {
3648 let _trace = push_nix_trace(format!("while calling the '{}' builtin", b.name));
3649 if builtin_takes_lazy_arg(&b.name) {
3659 (b.func)(&[arg])
3660 } else {
3661 let forced_arg = force_value(&arg)?;
3662 (b.func)(&[forced_arg])
3663 }
3664 }
3665 Value::Attrs(ref attrs) => {
3666 if let Some(functor) = attrs.get("__functor") {
3667 let functor = force_value(functor)?;
3668 let partial = apply(functor, func.clone())?;
3670 apply(partial, arg)
3671 } else if crate::value::in_promise_eval() {
3672 Ok(Value::Null)
3677 } else {
3678 Err(EvalError::type_error(
3679 format!("cannot call {} (missing __functor){}", func.type_name(), eval_file_ctx()),
3680 ))
3681 }
3682 }
3683 _ if crate::value::in_promise_eval() => {
3684 Ok(Value::Null)
3689 }
3690 _ => Err(EvalError::type_error(
3691 format!("cannot call {}{}", func.type_name(), eval_file_ctx()),
3692 )),
3693 }
3694}
3695
3696static SUI_BATCH_BIND: std::sync::LazyLock<bool> =
3706 std::sync::LazyLock::new(|| std::env::var_os("SUI_BATCH_BIND").is_some());
3707
3708fn bind_param(param: &ast::Param, arg: &Value, env: &mut Env) -> Result<(), EvalError> {
3709 match param {
3710 ast::Param::IdentParam(ip) => {
3711 let ident = ip
3712 .ident()
3713 .ok_or_else(|| EvalError::ParseError("ident param missing ident".to_string()))?;
3714 let name = ident_text(&ident);
3715 env.bind(name, arg.clone());
3716 }
3717 ast::Param::Pattern(pat) => {
3718 let attrs = arg.as_attrs()?;
3719
3720 if let Some(pat_bind) = pat.pat_bind()
3722 && let Some(ident) = pat_bind.ident()
3723 {
3724 let name = ident_text(&ident);
3725 env.bind(name, arg.clone());
3726 }
3727
3728 let has_ellipsis = pat.ellipsis_token().is_some();
3729 let entries: Vec<ast::PatEntry> = pat.pat_entries().collect();
3730
3731 let mut default_thunks: Vec<Thunk> = Vec::new();
3738 let use_batch = *SUI_BATCH_BIND;
3748 let mut pairs: Vec<(String, Value)> =
3749 if use_batch { Vec::with_capacity(entries.len()) } else { Vec::new() };
3750
3751 let narrow = scope_narrow_enabled();
3776 let default_names: HashSet<String> = if narrow {
3779 entries
3780 .iter()
3781 .filter(|e| e.default().is_some())
3782 .filter_map(ast::PatEntry::ident)
3783 .map(|i| ident_text(&i))
3784 .filter(|n| attrs.get(n).is_none())
3785 .collect()
3786 } else {
3787 HashSet::new()
3788 };
3789
3790 if narrow {
3791 let mut deferred: Vec<(String, ast::Expr)> =
3795 Vec::with_capacity(default_names.len());
3796 for entry in &entries {
3797 let ident = entry.ident().ok_or_else(|| {
3798 EvalError::ParseError("pat entry missing ident".to_string())
3799 })?;
3800 let name = ident_text(&ident);
3801 if let Some(v) = attrs.get(&name) {
3802 env.bind(name, v.clone());
3803 } else if let Some(default_expr) = entry.default() {
3804 deferred.push((
3805 name,
3806 ast::Expr::cast(default_expr.syntax().clone()).unwrap(),
3807 ));
3808 } else {
3809 return Err(EvalError::type_error(
3810 format!("missing argument '{name}'{}", eval_file_ctx()),
3811 ));
3812 }
3813 }
3814 for (name, default_expr) in deferred {
3817 let thunk =
3818 Thunk::new_suspended(default_expr.clone(), env.clone());
3819 let referenced = referenced_idents(&default_expr);
3820 if default_names.iter().any(|n| referenced.contains(n.as_str())) {
3821 default_thunks.push(thunk.clone());
3825 crate::value::census::scope_pinned();
3826 } else {
3827 crate::value::census::scope_narrowed();
3828 }
3829 env.bind(name, Value::Thunk(thunk));
3830 }
3831 } else {
3832 for entry in &entries {
3833 let ident = entry.ident().ok_or_else(|| {
3834 EvalError::ParseError("pat entry missing ident".to_string())
3835 })?;
3836 let name = ident_text(&ident);
3837 let value = if let Some(v) = attrs.get(&name) {
3838 v.clone()
3839 } else if let Some(default_expr) = entry.default() {
3840 let thunk = Thunk::new_suspended(
3846 ast::Expr::cast(default_expr.syntax().clone()).unwrap(),
3847 env.clone(),
3848 );
3849 default_thunks.push(thunk.clone());
3850 Value::Thunk(thunk)
3851 } else {
3852 return Err(EvalError::type_error(
3853 format!("missing argument '{name}'{}", eval_file_ctx()),
3854 ));
3855 };
3856 if use_batch {
3857 pairs.push((name, value));
3858 } else {
3859 env.bind(name, value);
3860 }
3861 }
3862 if use_batch {
3863 env.bind_many(pairs);
3864 }
3865 }
3866
3867 for thunk in &default_thunks {
3869 thunk.update_env(env);
3870 }
3871
3872 if !has_ellipsis {
3873 let entry_names: std::collections::HashSet<String> = entries
3874 .iter()
3875 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3876 .collect();
3877 for key in attrs.keys() {
3878 if !entry_names.contains(key.as_str()) {
3879 return Err(EvalError::type_error(
3880 format!("unexpected argument '{key}'{}", eval_file_ctx()),
3881 ));
3882 }
3883 }
3884 }
3885 }
3886 }
3887 Ok(())
3888}
3889
3890#[cfg(test)]
3891mod tests {
3892 use super::*;
3893
3894 fn ev(input: &str) -> Value {
3895 eval(input).unwrap()
3896 }
3897
3898 #[test]
3905 fn is_self_recursive_binding_ignores_attribute_names() {
3906 fn expr(s: &str) -> ast::Expr {
3907 rnix::Root::parse(s).tree().expr().expect("parse")
3908 }
3909 assert!(!is_self_recursive_binding(&expr("lhs.placeholder"), "placeholder"));
3911 assert!(!is_self_recursive_binding(&expr("{ placeholder = 1; }"), "placeholder"));
3912 assert!(!is_self_recursive_binding(
3913 &expr("if lhs.placeholder == rhs.placeholder then lhs.placeholder else null"),
3914 "placeholder",
3915 ));
3916 assert!(is_self_recursive_binding(&expr("placeholder + 1"), "placeholder"));
3918 assert!(is_self_recursive_binding(
3919 &expr("if placeholder then 1 else 2"),
3920 "placeholder"
3921 ));
3922 }
3923
3924 #[test]
3928 fn maybe_thunk_eager_constant_str_is_byte_identical() {
3929 fn expr(s: &str) -> ast::Expr {
3930 rnix::Root::parse(s).tree().expr().expect("parse")
3931 }
3932 let env = Env::new();
3933 let v = maybe_thunk(&expr(r#""abc""#), &env, false, None);
3935 assert!(matches!(v, Value::String(_)), "constant str should be eager, got {v:?}");
3936 assert_eq!(force_value(&v).unwrap(), Value::string("abc"));
3937 let vi = maybe_thunk(&expr(r#""a${b}c""#), &env, false, None);
3939 assert!(matches!(vi, Value::Thunk(_)), "interpolated str must stay thunked");
3940 }
3941
3942 #[test]
3946 fn eval_pure_constant_arg_classification() {
3947 fn expr(s: &str) -> ast::Expr {
3948 rnix::Root::parse(s).tree().expr().expect("parse")
3949 }
3950 assert!(eval_pure_constant_arg(&expr("42")).is_some());
3952 assert!(eval_pure_constant_arg(&expr("3.14")).is_some());
3953 assert!(eval_pure_constant_arg(&expr(r#""const""#)).is_some());
3954 assert!(eval_pure_constant_arg(&expr("/abs/path")).is_some());
3955 assert!(eval_pure_constant_arg(&expr(r#""a${b}c""#)).is_none(), "interpolated str");
3957 assert!(eval_pure_constant_arg(&expr("true")).is_none(), "bool is an ident");
3960 assert!(eval_pure_constant_arg(&expr("x")).is_none(), "ident (with-scope force)");
3961 assert!(eval_pure_constant_arg(&expr("a.b")).is_none(), "select (fixpoint)");
3962 assert!(eval_pure_constant_arg(&expr("f x")).is_none(), "apply (may throw)");
3963 assert!(eval_pure_constant_arg(&expr("1 + 1")).is_none(), "binop (may throw)");
3964 assert!(eval_pure_constant_arg(&expr("throw \"x\"")).is_none(), "throw stays lazy");
3965 }
3966
3967 #[test]
3971 fn ignored_throwing_arg_stays_lazy() {
3972 assert_eq!(ev(r#"(x: 7) (throw "boom")"#), Value::Int(7));
3973 assert_eq!(ev(r#"(x: 7) "const""#), Value::Int(7));
3975 assert_eq!(ev(r#"(x: x) "used""#), Value::string("used"));
3977 }
3978
3979 #[test]
3980 fn eval_int() { assert_eq!(ev("42"), Value::Int(42)); }
3981
3982 #[test]
3983 fn eval_float() { assert_eq!(ev("3.14"), Value::Float(3.14)); }
3984
3985 #[test]
3986 fn eval_string() { assert_eq!(ev(r#""hello""#), Value::string("hello")); }
3987
3988 #[test]
3989 fn eval_bool() { assert_eq!(ev("true"), Value::Bool(true)); }
3990
3991 #[test]
3992 fn eval_null() { assert_eq!(ev("null"), Value::Null); }
3993
3994 #[test]
3995 fn eval_arithmetic() {
3996 assert_eq!(ev("1 + 2"), Value::Int(3));
3997 assert_eq!(ev("10 - 3"), Value::Int(7));
3998 assert_eq!(ev("2 * 3"), Value::Int(6));
3999 assert_eq!(ev("10 / 3"), Value::Int(3));
4000 }
4001
4002 #[test]
4003 fn eval_precedence() {
4004 assert_eq!(ev("1 + 2 * 3"), Value::Int(7));
4005 assert_eq!(ev("(1 + 2) * 3"), Value::Int(9));
4006 }
4007
4008 #[test]
4009 fn eval_comparison() {
4010 assert_eq!(ev("1 == 1"), Value::Bool(true));
4011 assert_eq!(ev("1 == 2"), Value::Bool(false));
4012 assert_eq!(ev("1 < 2"), Value::Bool(true));
4013 assert_eq!(ev("2 <= 2"), Value::Bool(true));
4014 }
4015
4016 #[test]
4017 fn eval_logic() {
4018 assert_eq!(ev("true && false"), Value::Bool(false));
4019 assert_eq!(ev("true || false"), Value::Bool(true));
4020 assert_eq!(ev("!true"), Value::Bool(false));
4021 }
4022
4023 #[test]
4024 fn eval_string_concat() {
4025 assert_eq!(ev(r#""hello" + " " + "world""#), Value::string("hello world"));
4026 }
4027
4028 #[test]
4029 fn eval_if() {
4030 assert_eq!(ev("if true then 1 else 2"), Value::Int(1));
4031 assert_eq!(ev("if false then 1 else 2"), Value::Int(2));
4032 }
4033
4034 #[test]
4035 fn eval_let() {
4036 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
4037 assert_eq!(ev("let x = 1; y = 2; in x + y"), Value::Int(3));
4038 }
4039
4040 #[test]
4041 fn eval_let_dotted_simple() {
4042 assert_eq!(ev("let a.b = 1; a.c = 2; in a.b + a.c"), Value::Int(3));
4044 }
4045
4046 #[test]
4047 fn eval_let_dotted_deep() {
4048 assert_eq!(ev("let a.b.c = 1; in a.b.c"), Value::Int(1));
4050 }
4051
4052 #[test]
4053 fn eval_let_dotted_mixed() {
4054 assert_eq!(
4056 ev("let a.x = 1; b = 2; a.y = 3; in a.x + a.y + b"),
4057 Value::Int(6),
4058 );
4059 }
4060
4061 #[test]
4062 fn eval_let_dotted_produces_attrset() {
4063 let v = ev("let a.b = 1; a.c = 2; in a");
4065 if let Value::Attrs(attrs) = v {
4066 assert_eq!(attrs.get("b"), Some(&Value::Int(1)));
4067 assert_eq!(attrs.get("c"), Some(&Value::Int(2)));
4068 } else {
4069 panic!("expected Attrs, got {v:?}");
4070 }
4071 }
4072
4073 #[test]
4081 fn dynamic_inner_attr_key_is_lazy_on_sibling_read() {
4082 assert_eq!(
4084 ev(r#"let s = { a.${throw "KEYFORCED"} = 7; other = 9; }; in s.other"#),
4085 Value::Int(9),
4086 );
4087 }
4088
4089 #[test]
4090 fn dynamic_inner_attr_key_resolves_on_head_demand() {
4091 let v = ev(r#"let u = "bob"; s = { homes.${u} = 7; }; in s.homes"#);
4093 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4094 assert_eq!(attrs.get("bob"), Some(&Value::Int(7)));
4095 } else {
4096 panic!("expected Attrs");
4097 }
4098 }
4099
4100 #[test]
4101 fn dynamic_inner_attr_key_merges_with_static_sibling() {
4102 let v = ev(r#"let u = "x"; s = { a.${u} = 1; a.b = 2; }; in s.a"#);
4104 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4105 assert_eq!(attrs.get("x"), Some(&Value::Int(1)));
4106 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4107 } else {
4108 panic!("expected Attrs");
4109 }
4110 }
4111
4112 #[test]
4113 fn dynamic_inner_attr_key_null_skips_binding() {
4114 let v = ev(
4117 r#"let c = true; s = { a.${if c then null else "n"} = 5; b = 1; }; in s.b"#,
4118 );
4119 assert_eq!(v, Value::Int(1));
4120 }
4121
4122 #[test]
4128 fn interpolated_string_attr_key_is_lazy_on_sibling_read() {
4129 assert_eq!(
4130 ev(r#"let s = { a."p/${throw "KEYFORCED"}" = 7; other = 9; }; in s.other"#),
4131 Value::Int(9),
4132 );
4133 }
4134
4135 #[test]
4136 fn interpolated_string_attr_key_resolves_on_head_demand() {
4137 let v = ev(r#"let u = "bob"; s = { homes."u/${u}" = 7; }; in s.homes"#);
4139 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4140 assert_eq!(attrs.get("u/bob"), Some(&Value::Int(7)));
4141 } else {
4142 panic!("expected Attrs");
4143 }
4144 }
4145
4146 #[test]
4147 fn purely_literal_string_attr_key_stays_eager_static() {
4148 let v = ev(r#"let s = { a."foo bar" = 1; a.b = 2; }; in s.a"#);
4151 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4152 assert_eq!(attrs.get("foo bar"), Some(&Value::Int(1)));
4153 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4154 } else {
4155 panic!("expected Attrs");
4156 }
4157 }
4158
4159 #[test]
4162 fn dynamic_tail_key_under_colliding_head_is_lazy() {
4163 let v = ev(
4166 r#"let s = { sd.services.x = 1; sd.tmpfiles.${throw "KEYFORCED"}.d = 2; }; in s.sd.services.x"#,
4167 );
4168 assert_eq!(v, Value::Int(1));
4169 }
4170
4171 #[test]
4172 fn dynamic_tail_key_under_colliding_head_resolves_and_merges() {
4173 let v = ev(
4176 r#"let k = "z"; s = { sd.services.x = 1; sd.tmpfiles.${k}.d = 2; }; in s.sd"#,
4177 );
4178 let sd = force_value(&v).unwrap();
4179 if let Value::Attrs(sd_attrs) = &sd {
4180 let services = force_value(sd_attrs.get("services").unwrap()).unwrap();
4182 if let Value::Attrs(a) = &services {
4183 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4184 } else { panic!("expected services attrs"); }
4185 let tmpfiles = force_value(sd_attrs.get("tmpfiles").unwrap()).unwrap();
4187 if let Value::Attrs(a) = &tmpfiles {
4188 let z = force_value(a.get("z").unwrap()).unwrap();
4189 if let Value::Attrs(zd) = &z {
4190 assert_eq!(force_value(zd.get("d").unwrap()).unwrap(), Value::Int(2));
4191 } else { panic!("expected z attrs"); }
4192 } else { panic!("expected tmpfiles attrs"); }
4193 } else {
4194 panic!("expected sd attrs");
4195 }
4196 }
4197
4198 #[test]
4207 fn with_namespace_is_lazy_on_body_whnf() {
4208 let v = ev(r#"builtins.attrNames (with (throw "WITH-FORCED"); { a = 1; b = 2; })"#);
4209 if let Value::List(items) = force_value(&v).unwrap() {
4210 let names: Vec<String> = items
4211 .iter()
4212 .map(|i| match force_value(i).unwrap() {
4213 Value::String(s) => s.as_str().to_string(),
4214 other => panic!("expected string, got {}", other.type_name()),
4215 })
4216 .collect();
4217 assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
4218 } else {
4219 panic!("expected list");
4220 }
4221 }
4222
4223 #[test]
4224 fn with_namespace_forces_only_on_fallthrough() {
4225 assert_eq!(ev(r#"with { x = 42; }; x"#), Value::Int(42));
4229 assert_eq!(ev(r#"let x = 7; in with (throw "NS"); x"#), Value::Int(7));
4232 }
4233
4234 #[test]
4245 fn dotted_fullset_leaf_deep_merges_with_deeper_sibling() {
4246 let v = ev(r#"{ o.a = { x = 1; }; o.a.y = 2; }.o.a"#);
4247 if let Value::Attrs(a) = force_value(&v).unwrap() {
4248 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4249 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
4250 } else {
4251 panic!("expected attrs");
4252 }
4253 }
4254
4255 #[test]
4256 fn dotted_fullset_leaf_deep_merge_reverse_order() {
4257 let v = ev(r#"{ o.a.y = 2; o.a = { x = 1; }; }.o.a"#);
4260 if let Value::Attrs(a) = force_value(&v).unwrap() {
4261 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4262 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
4263 } else {
4264 panic!("expected attrs");
4265 }
4266 }
4267
4268 #[test]
4269 fn dotted_fullset_leaf_merge_preserves_leaf_laziness() {
4270 assert_eq!(ev(r#"{ o.a = { x = throw "X-NEVER"; }; o.a.y = 2; }.o.a.y"#), Value::Int(2));
4274 }
4275
4276 #[test]
4277 fn eval_nested_let() {
4278 assert_eq!(ev("let a = 1; b = let c = 2; in c; in a + b"), Value::Int(3));
4279 }
4280
4281 #[test]
4282 fn eval_lambda() {
4283 assert_eq!(ev("(x: x + 1) 41"), Value::Int(42));
4284 }
4285
4286 #[test]
4287 fn eval_lambda_multi_arg() {
4288 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
4289 }
4290
4291 #[test]
4292 fn eval_list() {
4293 let v = ev("[1 2 3]");
4294 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
4295 }
4296
4297 #[test]
4298 fn eval_list_concat() {
4299 let v = ev("[1 2] ++ [3 4]");
4300 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]));
4301 }
4302
4303 #[test]
4304 fn eval_attrset() {
4305 let v = ev("{ a = 1; b = 2; }");
4306 if let Value::Attrs(attrs) = v {
4307 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4308 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4309 } else {
4310 panic!("expected attrset");
4311 }
4312 }
4313
4314 #[test]
4315 fn eval_select() {
4316 assert_eq!(ev("{ a = 42; }.a"), Value::Int(42));
4317 }
4318
4319 #[test]
4320 fn eval_select_or() {
4321 assert_eq!(ev("{ a = 42; }.b or 0"), Value::Int(0));
4322 }
4323
4324 #[test]
4325 fn eval_has_attr() {
4326 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
4327 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
4328 }
4329
4330 #[test]
4331 fn eval_update() {
4332 let v = ev("{ a = 1; b = 2; } // { b = 3; c = 4; }");
4333 if let Value::Attrs(attrs) = v {
4334 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4335 assert_eq!(attrs.get("b"), Some(&Value::Int(3)));
4336 assert_eq!(attrs.get("c"), Some(&Value::Int(4)));
4337 } else {
4338 panic!("expected attrset");
4339 }
4340 }
4341
4342 #[test]
4343 fn eval_with() {
4344 assert_eq!(ev("with { x = 42; }; x"), Value::Int(42));
4345 }
4346
4347 #[test]
4348 fn eval_assert() {
4349 assert_eq!(ev("assert true; 42"), Value::Int(42));
4350 assert!(eval("assert false; 42").is_err());
4351 }
4352
4353 #[test]
4354 fn eval_formals() {
4355 assert_eq!(ev("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3));
4356 }
4357
4358 #[test]
4359 fn eval_formals_default() {
4360 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 1; }"), Value::Int(11));
4361 }
4362
4363 #[test]
4364 fn eval_formals_ellipsis() {
4365 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; }"), Value::Int(1));
4366 }
4367
4368 #[test]
4369 fn eval_named_formals() {
4370 assert_eq!(ev("(args @ { a }: args.a) { a = 42; }"), Value::Int(42));
4371 }
4372
4373 #[test]
4374 fn eval_rec_attrset() {
4375 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
4376 }
4377
4378 #[test]
4379 fn eval_negation() {
4380 assert_eq!(ev("-42"), Value::Int(-42));
4381 }
4382
4383 #[test]
4384 fn eval_float_arithmetic() {
4385 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
4386 assert_eq!(ev("1 + 1.5"), Value::Float(2.5));
4387 }
4388
4389 #[test]
4390 fn eval_division_by_zero() {
4391 assert!(eval("1 / 0").is_err());
4392 }
4393
4394 #[test]
4395 fn eval_builtins_available() {
4396 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
4397 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
4398 }
4399
4400 #[test]
4401 fn eval_builtins_length() {
4402 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
4403 }
4404
4405 #[test]
4406 fn eval_builtins_head_tail() {
4407 assert_eq!(ev("builtins.head [1 2 3]"), Value::Int(1));
4408 assert_eq!(ev("builtins.length (builtins.tail [1 2 3])"), Value::Int(2));
4409 }
4410
4411 #[test]
4412 fn eval_builtins_add() {
4413 assert_eq!(ev("builtins.add 1 2"), Value::Int(3));
4414 }
4415
4416 #[test]
4417 fn eval_builtins_to_string() {
4418 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
4419 }
4420
4421 #[test]
4422 fn eval_implication() {
4423 assert_eq!(ev("false -> true"), Value::Bool(true));
4424 assert_eq!(ev("true -> false"), Value::Bool(false));
4425 assert_eq!(ev("true -> true"), Value::Bool(true));
4426 }
4427
4428 #[test]
4431 fn eval_error_undefined_variable() {
4432 let result = eval("nonexistent");
4433 assert!(result.is_err());
4434 let msg = format!("{}", result.unwrap_err());
4435 assert!(msg.contains("undefined variable"));
4436 }
4437
4438 #[test]
4439 fn eval_error_type_mismatch_arithmetic() {
4440 let result = eval(r#"1 + "hello""#);
4441 assert!(result.is_err());
4442 let msg = format!("{}", result.unwrap_err());
4443 assert!(msg.contains("cannot add") || msg.contains("type"));
4444 }
4445
4446 #[test]
4447 fn eval_error_unexpected_argument() {
4448 let result = eval("({ a }: a) { a = 1; b = 2; }");
4449 assert!(result.is_err());
4450 let msg = format!("{}", result.unwrap_err());
4451 assert!(msg.contains("unexpected argument"));
4452 }
4453
4454 #[test]
4455 fn eval_error_missing_required_argument() {
4456 let result = eval("({ a, b }: a + b) { a = 1; }");
4457 assert!(result.is_err());
4458 let msg = format!("{}", result.unwrap_err());
4459 assert!(msg.contains("missing argument"));
4460 }
4461
4462 #[test]
4463 fn eval_builtins_attr_names_sorted() {
4464 let v = ev("builtins.attrNames { z = 1; a = 2; m = 3; }");
4465 assert_eq!(
4467 v,
4468 Value::list(vec![
4469 Value::string("a"),
4470 Value::string("m"),
4471 Value::string("z"),
4472 ]),
4473 );
4474 }
4475
4476 #[test]
4477 fn eval_builtins_attr_values() {
4478 let v = ev("builtins.attrValues { a = 1; b = 2; }");
4479 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
4481 }
4482
4483 #[test]
4484 fn eval_builtins_is_null() {
4485 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
4486 assert_eq!(ev("builtins.isNull 1"), Value::Bool(false));
4487 }
4488
4489 #[test]
4490 fn eval_builtins_is_int() {
4491 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
4492 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
4493 }
4494
4495 #[test]
4496 fn eval_builtins_is_bool() {
4497 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
4498 assert_eq!(ev("builtins.isBool 0"), Value::Bool(false));
4499 }
4500
4501 #[test]
4502 fn eval_builtins_is_string() {
4503 assert_eq!(ev(r#"builtins.isString "hi""#), Value::Bool(true));
4504 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
4505 }
4506
4507 #[test]
4508 fn eval_builtins_is_list() {
4509 assert_eq!(ev("builtins.isList [1 2]"), Value::Bool(true));
4510 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
4511 }
4512
4513 #[test]
4514 fn eval_builtins_is_attrs() {
4515 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
4516 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
4517 }
4518
4519 #[test]
4520 fn eval_builtins_string_length() {
4521 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
4522 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
4523 }
4524
4525 #[test]
4526 fn eval_builtins_to_json_roundtrip() {
4527 assert_eq!(
4529 ev(r#"builtins.fromJSON (builtins.toJSON 42)"#),
4530 Value::Int(42),
4531 );
4532 assert_eq!(
4533 ev(r#"builtins.fromJSON (builtins.toJSON [1 2 3])"#),
4534 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4535 );
4536 }
4537
4538 #[test]
4539 fn eval_builtins_from_json() {
4540 assert_eq!(
4541 ev(r#"builtins.fromJSON "{\"a\": 1}""#),
4542 {
4543 let mut attrs = NixAttrs::new();
4544 attrs.insert("a".to_string(), Value::Int(1));
4545 Value::Attrs(Rc::new(attrs))
4546 },
4547 );
4548 assert_eq!(ev(r#"builtins.fromJSON "null""#), Value::Null);
4549 assert_eq!(ev(r#"builtins.fromJSON "true""#), Value::Bool(true));
4550 }
4551
4552 #[test]
4553 fn eval_nested_function_application() {
4554 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
4556 assert_eq!(ev("((x: y: x + y) 1) 2"), Value::Int(3));
4558 }
4559
4560 #[test]
4561 fn eval_recursive_let() {
4562 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
4563 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
4564 }
4565
4566 #[test]
4567 fn eval_string_comparison() {
4568 assert_eq!(ev(r#""a" < "b""#), Value::Bool(true));
4569 assert_eq!(ev(r#""b" < "a""#), Value::Bool(false));
4570 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4571 assert_eq!(ev(r#""abc" != "def""#), Value::Bool(true));
4572 }
4573
4574 #[test]
4575 fn eval_list_in_attrset() {
4576 let v = ev("{ x = [1 2 3]; }.x");
4577 assert_eq!(
4578 v,
4579 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4580 );
4581 }
4582
4583 #[test]
4584 fn eval_nested_attrset_select() {
4585 assert_eq!(ev("{ a = { b = 42; }; }.a.b"), Value::Int(42));
4586 }
4587
4588 #[test]
4589 fn eval_let_shadows_outer() {
4590 assert_eq!(
4591 ev("let x = 1; in let x = 2; in x"),
4592 Value::Int(2),
4593 );
4594 }
4595
4596 #[test]
4597 fn eval_with_provides_scope() {
4598 assert_eq!(
4600 ev("with { x = 42; y = 10; }; x + y"),
4601 Value::Int(52),
4602 );
4603 }
4604
4605 #[test]
4606 fn eval_list_equality() {
4607 assert_eq!(ev("[1 2] == [1 2]"), Value::Bool(true));
4608 assert_eq!(ev("[1 2] == [1 3]"), Value::Bool(false));
4609 }
4610
4611 #[test]
4612 fn eval_attrset_equality() {
4613 assert_eq!(ev("{ a = 1; } == { a = 1; }"), Value::Bool(true));
4614 assert_eq!(ev("{ a = 1; } == { a = 2; }"), Value::Bool(false));
4615 }
4616
4617 #[test]
4622 fn literal_int_large_zero_negative() {
4623 assert_eq!(ev("9223372036854775807"), Value::Int(i64::MAX));
4625 assert_eq!(ev("0"), Value::Int(0));
4627 assert_eq!(ev("-1"), Value::Int(-1));
4629 assert_eq!(ev("-999999"), Value::Int(-999999));
4630 }
4631
4632 #[test]
4633 fn literal_float_small_large() {
4634 assert_eq!(ev("0.001"), Value::Float(0.001));
4635 assert_eq!(ev("999999.999"), Value::Float(999999.999));
4636 assert_eq!(ev("1.0e3"), Value::Float(1000.0));
4638 assert_eq!(ev("1.5e2"), Value::Float(150.0));
4639 }
4640
4641 #[test]
4642 fn literal_string_empty_and_escapes() {
4643 assert_eq!(ev(r#""""#), Value::string(""));
4644 assert_eq!(ev(r#""hello\nworld""#), Value::string("hello\nworld"));
4646 assert_eq!(ev(r#""tab\there""#), Value::string("tab\there"));
4647 }
4648
4649 #[test]
4650 fn literal_multiline_string() {
4651 assert_eq!(
4653 ev("''hello''"),
4654 Value::string("hello"),
4655 );
4656 assert_eq!(
4658 ev("''\n line1\n line2\n''"),
4659 Value::string("line1\nline2\n"),
4660 );
4661 }
4662
4663 #[test]
4664 fn literal_paths() {
4665 assert_eq!(ev("./foo"), Value::Path(Box::new(SmolStr::from("./foo"))));
4667 assert_eq!(ev("/nix/store/abc"), Value::Path(Box::new(SmolStr::from("/nix/store/abc"))));
4669 assert_eq!(ev("~/myfile"), Value::Path(Box::new(SmolStr::from("~/myfile"))));
4671 }
4672
4673 #[test]
4683 fn interp_path_abs_splices_and_types_path() {
4684 let v = ev(r#"let x = "foo"; in /a/${x}/b"#);
4686 assert_eq!(v, Value::Path(Box::new(SmolStr::from("/a/foo/b"))));
4687 }
4688
4689 #[test]
4690 fn interp_path_abs_multi_and_slash_in_value() {
4691 assert_eq!(
4693 ev(r#"let a = "x"; b = "y/z"; in /p/${a}/${b}.nix"#),
4694 Value::Path(Box::new(SmolStr::from("/p/x/y/z.nix"))),
4695 );
4696 }
4697
4698 #[test]
4699 fn interp_path_abs_normalizes_double_slash_seam() {
4700 assert_eq!(
4703 ev(r#"/bar/${/tmp/foo}"#),
4704 Value::Path(Box::new(SmolStr::from("/bar/tmp/foo"))),
4705 );
4706 }
4707
4708 #[test]
4709 fn interp_path_rel_resolves_against_eval_dir() {
4710 let _g = push_eval_file(std::path::PathBuf::from("/tmp/example/default.nix"));
4714 assert_eq!(
4715 ev(r#"let x = "foo"; in ./${x}.nix"#),
4716 Value::Path(Box::new(SmolStr::from("/tmp/example/foo.nix"))),
4717 );
4718 }
4719
4720 #[test]
4721 fn interp_path_rel_no_eval_dir_keeps_relative_text() {
4722 assert_eq!(
4725 ev(r#"let x = "foo"; in ./${x}.nix"#),
4726 Value::Path(Box::new(SmolStr::from("./foo.nix"))),
4727 );
4728 }
4729
4730 #[test]
4731 fn interp_path_home_splices_leading_tilde_preserved() {
4732 assert_eq!(
4736 ev(r#"let x = "foo"; in ~/${x}/bar"#),
4737 Value::Path(Box::new(SmolStr::from("~/foo/bar"))),
4738 );
4739 }
4740
4741 #[test]
4742 fn interp_path_non_interpolated_still_raw() {
4743 assert_eq!(ev("/a/b/c"), Value::Path(Box::new(SmolStr::from("/a/b/c"))));
4746 assert_eq!(ev("~/plain"), Value::Path(Box::new(SmolStr::from("~/plain"))));
4747 }
4748
4749 #[test]
4750 fn literal_null_true_false_standalone() {
4751 assert_eq!(ev("null"), Value::Null);
4752 assert_eq!(ev("true"), Value::Bool(true));
4753 assert_eq!(ev("false"), Value::Bool(false));
4754 }
4755
4756 #[test]
4761 fn op_arithmetic_int() {
4762 assert_eq!(ev("100 + 200"), Value::Int(300));
4763 assert_eq!(ev("50 - 30"), Value::Int(20));
4764 assert_eq!(ev("7 * 8"), Value::Int(56));
4765 assert_eq!(ev("17 / 3"), Value::Int(5)); }
4767
4768 #[test]
4769 fn op_arithmetic_float() {
4770 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
4771 assert_eq!(ev("5.0 - 1.5"), Value::Float(3.5));
4772 assert_eq!(ev("2.0 * 3.0"), Value::Float(6.0));
4773 assert_eq!(ev("7.0 / 2.0"), Value::Float(3.5));
4774 }
4775
4776 #[test]
4777 fn op_arithmetic_mixed_int_float() {
4778 assert_eq!(ev("1 + 2.5"), Value::Float(3.5));
4780 assert_eq!(ev("2.5 + 1"), Value::Float(3.5));
4781 assert_eq!(ev("2 * 1.5"), Value::Float(3.0));
4783 assert_eq!(ev("5.5 - 2"), Value::Float(3.5));
4785 }
4786
4787 #[test]
4788 fn op_string_concat() {
4789 assert_eq!(ev(r#""foo" + "bar""#), Value::string("foobar"));
4790 assert_eq!(ev(r#""" + "x""#), Value::string("x"));
4791 assert_eq!(ev(r#""a" + "" + "b""#), Value::string("ab"));
4792 }
4793
4794 #[test]
4795 fn op_path_concat() {
4796 assert_eq!(ev(r#"./foo + "/bar""#), Value::Path(Box::new(SmolStr::from("./foo/bar"))));
4798 assert_eq!(ev("./a + ./b"), Value::Path(Box::new(SmolStr::from("./a/./b"))));
4800 }
4801
4802 #[test]
4803 fn op_comparison_ints() {
4804 assert_eq!(ev("1 < 2"), Value::Bool(true));
4805 assert_eq!(ev("2 < 1"), Value::Bool(false));
4806 assert_eq!(ev("2 > 1"), Value::Bool(true));
4807 assert_eq!(ev("1 > 2"), Value::Bool(false));
4808 assert_eq!(ev("2 <= 2"), Value::Bool(true));
4809 assert_eq!(ev("3 <= 2"), Value::Bool(false));
4810 assert_eq!(ev("2 >= 2"), Value::Bool(true));
4811 assert_eq!(ev("1 >= 2"), Value::Bool(false));
4812 }
4813
4814 #[test]
4815 fn op_comparison_floats() {
4816 assert_eq!(ev("1.5 < 2.5"), Value::Bool(true));
4817 assert_eq!(ev("2.5 > 1.5"), Value::Bool(true));
4818 assert_eq!(ev("1.5 <= 1.5"), Value::Bool(true));
4819 assert_eq!(ev("1.5 >= 1.5"), Value::Bool(true));
4820 }
4821
4822 #[test]
4823 fn op_comparison_strings() {
4824 assert_eq!(ev(r#""apple" < "banana""#), Value::Bool(true));
4825 assert_eq!(ev(r#""banana" > "apple""#), Value::Bool(true));
4826 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4827 assert_eq!(ev(r#""abc" != "xyz""#), Value::Bool(true));
4828 assert_eq!(ev(r#""abc" <= "abd""#), Value::Bool(true));
4829 assert_eq!(ev(r#""abc" >= "abb""#), Value::Bool(true));
4830 }
4831
4832 #[test]
4833 fn op_equality_various_types() {
4834 assert_eq!(ev("null == null"), Value::Bool(true));
4835 assert_eq!(ev("true == true"), Value::Bool(true));
4836 assert_eq!(ev("false == false"), Value::Bool(true));
4837 assert_eq!(ev("true == false"), Value::Bool(false));
4838 assert_eq!(ev("1 == 1"), Value::Bool(true));
4839 assert_eq!(ev("1 != 2"), Value::Bool(true));
4840 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
4842 assert_eq!(ev("null == false"), Value::Bool(false));
4843 }
4844
4845 #[test]
4846 fn op_logic_short_circuit() {
4847 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
4849 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
4851 }
4852
4853 #[test]
4854 fn op_logic_full() {
4855 assert_eq!(ev("true && true"), Value::Bool(true));
4856 assert_eq!(ev("true && false"), Value::Bool(false));
4857 assert_eq!(ev("false && true"), Value::Bool(false));
4858 assert_eq!(ev("false && false"), Value::Bool(false));
4859 assert_eq!(ev("true || true"), Value::Bool(true));
4860 assert_eq!(ev("true || false"), Value::Bool(true));
4861 assert_eq!(ev("false || true"), Value::Bool(true));
4862 assert_eq!(ev("false || false"), Value::Bool(false));
4863 assert_eq!(ev("!true"), Value::Bool(false));
4864 assert_eq!(ev("!false"), Value::Bool(true));
4865 }
4866
4867 #[test]
4868 fn op_implication_truth_table() {
4869 assert_eq!(ev("false -> false"), Value::Bool(true));
4871 assert_eq!(ev("false -> true"), Value::Bool(true));
4872 assert_eq!(ev("true -> true"), Value::Bool(true));
4874 assert_eq!(ev("true -> false"), Value::Bool(false));
4875 }
4876
4877 #[test]
4878 fn op_implication_short_circuit() {
4879 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
4881 }
4882
4883 #[test]
4884 fn op_update_merge() {
4885 let v = ev("{ a = 1; } // { b = 2; }");
4886 if let Value::Attrs(attrs) = v {
4887 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4888 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4889 } else {
4890 panic!("expected attrs");
4891 }
4892 }
4893
4894 #[test]
4895 fn op_update_right_wins() {
4896 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
4897 }
4898
4899 #[test]
4900 fn op_list_concat() {
4901 assert_eq!(
4902 ev("[1 2] ++ [3 4]"),
4903 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]),
4904 );
4905 assert_eq!(ev("[] ++ [1]"), Value::list(vec![Value::Int(1)]));
4907 assert_eq!(ev("[1] ++ []"), Value::list(vec![Value::Int(1)]));
4908 }
4909
4910 #[test]
4911 fn op_has_attr_present_and_absent() {
4912 assert_eq!(ev("{ x = 1; y = 2; } ? x"), Value::Bool(true));
4913 assert_eq!(ev("{ x = 1; } ? z"), Value::Bool(false));
4914 assert_eq!(ev("{} ? anything"), Value::Bool(false));
4915 }
4916
4917 #[test]
4918 fn op_unary_negate() {
4919 assert_eq!(ev("-42"), Value::Int(-42));
4920 assert_eq!(ev("-3.14"), Value::Float(-3.14));
4921 assert_eq!(ev("- -5"), Value::Int(5));
4923 }
4924
4925 #[test]
4930 fn control_if_true_branch() {
4931 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
4932 }
4933
4934 #[test]
4935 fn control_if_false_branch() {
4936 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
4937 }
4938
4939 #[test]
4940 fn control_if_nested() {
4941 assert_eq!(
4942 ev("if true then (if false then 1 else 2) else 3"),
4943 Value::Int(2),
4944 );
4945 assert_eq!(
4946 ev("if false then 1 else (if true then 2 else 3)"),
4947 Value::Int(2),
4948 );
4949 }
4950
4951 #[test]
4952 fn control_assert_passing() {
4953 assert_eq!(ev("assert 1 == 1; 42"), Value::Int(42));
4954 assert_eq!(ev("assert true; true"), Value::Bool(true));
4955 }
4956
4957 #[test]
4958 fn control_assert_failing() {
4959 assert!(eval("assert false; 42").is_err());
4960 assert!(eval("assert 1 == 2; 42").is_err());
4961 }
4962
4963 #[test]
4964 fn control_with_basic_scope() {
4965 assert_eq!(ev("with { a = 1; b = 2; }; a + b"), Value::Int(3));
4966 }
4967
4968 #[test]
4969 fn control_with_lexical_precedence() {
4970 assert_eq!(
4972 ev("let x = 10; in with { x = 99; }; x"),
4973 Value::Int(10),
4974 );
4975 }
4976
4977 #[test]
4978 fn control_with_nested() {
4979 assert_eq!(
4980 ev("with { a = 1; }; with { b = 2; }; a + b"),
4981 Value::Int(3),
4982 );
4983 }
4984
4985 #[test]
4986 fn control_with_lazy_fix_self() {
4987 let result = eval(
4992 "let fix = f: let x = f x; in x; in fix (self: with self; { a = 1; b = a + 1; })"
4993 );
4994 assert!(result.is_ok(), "fix with self should work: {:?}", result);
4995 if let Ok(Value::Attrs(attrs)) = result {
4996 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4997 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4998 } else {
4999 panic!("expected Attrs, got {:?}", result);
5000 }
5001 }
5002
5003 #[test]
5004 fn control_with_lazy_fix_self_lib_pattern() {
5005 let result = eval(r#"
5008 let fix = f: let x = f x; in x;
5009 in (fix (self: with self; {
5010 lib = { version = "1.0"; };
5011 hello = "hello ${lib.version}";
5012 })).hello
5013 "#);
5014 assert!(result.is_ok(), "nixpkgs-style lib pattern: {:?}", result);
5015 assert_eq!(
5016 result.unwrap(),
5017 Value::String(Rc::new(NixString::plain("hello 1.0"))),
5018 );
5019 }
5020
5021 #[test]
5022 fn control_with_non_attrset_errors() {
5023 let result = eval("with 42; 1");
5025 assert_eq!(result.unwrap(), Value::Int(1));
5028 }
5029
5030 #[test]
5031 fn control_with_non_attrset_lookup_falls_through() {
5032 let result = eval("let x = 1; in with 42; x");
5035 assert_eq!(result.unwrap(), Value::Int(1));
5036 }
5037
5038 #[test]
5039 fn control_let_simple_and_multiple() {
5040 assert_eq!(ev("let x = 5; in x"), Value::Int(5));
5041 assert_eq!(ev("let x = 1; y = 2; z = 3; in x + y + z"), Value::Int(6));
5042 }
5043
5044 #[test]
5045 fn control_let_shadow_outer() {
5046 assert_eq!(
5047 ev("let x = 1; in let x = 2; in x"),
5048 Value::Int(2),
5049 );
5050 }
5051
5052 #[test]
5053 fn control_let_recursive_reference() {
5054 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
5055 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
5056 }
5057
5058 #[test]
5059 fn control_nested_let_expression() {
5060 assert_eq!(
5061 ev("let a = let b = 1; in b; in a"),
5062 Value::Int(1),
5063 );
5064 assert_eq!(
5065 ev("let a = let b = 10; in b + 5; in a * 2"),
5066 Value::Int(30),
5067 );
5068 }
5069
5070 #[test]
5075 fn func_identity_lambda() {
5076 assert_eq!(ev("(x: x) 42"), Value::Int(42));
5077 assert_eq!(ev(r#"(x: x) "hello""#), Value::string("hello"));
5078 }
5079
5080 #[test]
5081 fn func_curried_two_args() {
5082 assert_eq!(ev("(x: y: x + y) 3 4"), Value::Int(7));
5083 }
5084
5085 #[test]
5086 fn func_curried_three_args() {
5087 assert_eq!(ev("(a: b: c: a + b + c) 1 2 3"), Value::Int(6));
5088 }
5089
5090 #[test]
5091 fn func_formals_basic() {
5092 assert_eq!(ev("({ a, b }: a + b) { a = 3; b = 7; }"), Value::Int(10));
5093 }
5094
5095 #[test]
5096 fn func_formals_with_defaults() {
5097 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; }"), Value::Int(15));
5098 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; b = 20; }"), Value::Int(25));
5100 }
5101
5102 #[test]
5103 fn func_formals_with_ellipsis() {
5104 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; c = 3; }"), Value::Int(1));
5105 }
5106
5107 #[test]
5108 fn func_named_formals_at_before() {
5109 assert_eq!(
5111 ev("(args @ { a, b }: args.a + args.b) { a = 3; b = 4; }"),
5112 Value::Int(7),
5113 );
5114 }
5115
5116 #[test]
5117 fn func_named_formals_at_after() {
5118 assert_eq!(
5120 ev("({ a, b } @ args: args.a + args.b) { a = 10; b = 20; }"),
5121 Value::Int(30),
5122 );
5123 }
5124
5125 #[test]
5126 fn func_nested_application() {
5127 assert_eq!(ev("((x: y: x * y) 3) 4"), Value::Int(12));
5129 }
5130
5131 #[test]
5132 fn func_higher_order_map() {
5133 assert_eq!(
5134 ev("builtins.map (x: x * 2) [1 2 3]"),
5135 Value::list(vec![Value::Int(2), Value::Int(4), Value::Int(6)]),
5136 );
5137 }
5138
5139 #[test]
5140 fn func_higher_order_filter() {
5141 assert_eq!(
5142 ev("builtins.filter (x: x > 2) [1 2 3 4 5]"),
5143 Value::list(vec![Value::Int(3), Value::Int(4), Value::Int(5)]),
5144 );
5145 }
5146
5147 #[test]
5148 fn func_higher_order_foldl() {
5149 assert_eq!(
5151 ev("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4]"),
5152 Value::Int(10),
5153 );
5154 }
5155
5156 #[test]
5157 fn func_as_attrset_value() {
5158 assert_eq!(
5159 ev("let s = { f = x: x + 1; }; in s.f 5"),
5160 Value::Int(6),
5161 );
5162 }
5163
5164 #[test]
5165 fn func_immediate_application() {
5166 assert_eq!(ev("(x: x * x) 7"), Value::Int(49));
5167 }
5168
5169 #[test]
5170 fn func_in_let_binding() {
5171 assert_eq!(
5172 ev("let double = x: x * 2; in double 21"),
5173 Value::Int(42),
5174 );
5175 }
5176
5177 #[test]
5182 fn attrs_empty_set() {
5183 let v = ev("{}");
5184 if let Value::Attrs(attrs) = v {
5185 assert!(attrs.is_empty());
5186 } else {
5187 panic!("expected attrs");
5188 }
5189 }
5190
5191 #[test]
5192 fn attrs_simple() {
5193 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
5194 }
5195
5196 #[test]
5197 fn attrs_nested_access() {
5198 assert_eq!(ev("{ a = { b = { c = 42; }; }; }.a.b.c"), Value::Int(42));
5199 }
5200
5201 #[test]
5202 fn attrs_recursive_set() {
5203 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
5204 }
5205
5206 #[test]
5207 fn attrs_update_disjoint() {
5208 let v = ev("{ a = 1; } // { b = 2; }");
5209 if let Value::Attrs(attrs) = v {
5210 assert_eq!(attrs.len(), 2);
5211 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5212 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5213 } else {
5214 panic!("expected attrs");
5215 }
5216 }
5217
5218 #[test]
5219 fn attrs_update_override() {
5220 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
5221 }
5222
5223 #[test]
5224 fn attrs_has_attr_operator() {
5225 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
5226 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
5227 }
5228
5229 #[test]
5230 fn attrs_select_with_default() {
5231 assert_eq!(ev("{ a = 1; }.a or 99"), Value::Int(1));
5232 assert_eq!(ev("{}.missing or 99"), Value::Int(99));
5233 assert_eq!(ev("{ a = 1; }.b or 42"), Value::Int(42));
5234 }
5235
5236 #[test]
5237 fn attrs_nested_attr_path_in_binding() {
5238 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
5240 }
5241
5242 #[test]
5243 fn attrs_inherit_from_scope() {
5244 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.x"), Value::Int(1));
5245 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.y"), Value::Int(2));
5246 }
5247
5248 #[test]
5249 fn attrs_inherit_from_expr() {
5250 assert_eq!(
5251 ev("{ inherit ({ a = 42; b = 10; }) a; }.a"),
5252 Value::Int(42),
5253 );
5254 }
5255
5256 #[test]
5257 fn attrs_dynamic_attr_name() {
5258 assert_eq!(
5259 ev(r#"let name = "x"; in { ${name} = 42; }.x"#),
5260 Value::Int(42),
5261 );
5262 }
5263
5264 #[test]
5265 fn attrs_attr_names_sorted() {
5266 assert_eq!(
5267 ev("builtins.attrNames { z = 1; m = 2; a = 3; }"),
5268 Value::list(vec![
5269 Value::string("a"),
5270 Value::string("m"),
5271 Value::string("z"),
5272 ]),
5273 );
5274 }
5275
5276 #[test]
5277 fn attrs_attr_values_follow_key_order() {
5278 assert_eq!(
5280 ev("builtins.attrValues { c = 3; a = 1; b = 2; }"),
5281 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5282 );
5283 }
5284
5285 #[test]
5286 fn attrs_update_is_shallow() {
5287 assert_eq!(
5289 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a ? x"),
5290 Value::Bool(false),
5291 );
5292 assert_eq!(
5293 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a.y"),
5294 Value::Int(2),
5295 );
5296 }
5297
5298 #[test]
5303 fn list_empty() {
5304 assert_eq!(ev("[]"), Value::list(vec![]));
5305 }
5306
5307 #[test]
5308 fn list_single_element() {
5309 assert_eq!(ev("[1]"), Value::list(vec![Value::Int(1)]));
5310 }
5311
5312 #[test]
5313 fn list_mixed_types() {
5314 assert_eq!(
5315 ev(r#"[1 "two" true null]"#),
5316 Value::list(vec![
5317 Value::Int(1),
5318 Value::string("two"),
5319 Value::Bool(true),
5320 Value::Null,
5321 ]),
5322 );
5323 }
5324
5325 #[test]
5326 fn list_nested() {
5327 assert_eq!(
5328 ev("[[1 2] [3 4]]"),
5329 Value::list(vec![
5330 Value::list(vec![Value::Int(1), Value::Int(2)]),
5331 Value::list(vec![Value::Int(3), Value::Int(4)]),
5332 ]),
5333 );
5334 }
5335
5336 #[test]
5337 fn list_concat_operator() {
5338 assert_eq!(
5339 ev("[1] ++ [2] ++ [3]"),
5340 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5341 );
5342 }
5343
5344 #[test]
5345 fn list_builtins_length() {
5346 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
5347 assert_eq!(ev("builtins.length []"), Value::Int(0));
5348 }
5349
5350 #[test]
5351 fn list_builtins_elem_at() {
5352 assert_eq!(ev("builtins.elemAt [10 20 30] 0"), Value::Int(10));
5353 assert_eq!(ev("builtins.elemAt [10 20 30] 1"), Value::Int(20));
5354 assert_eq!(ev("builtins.elemAt [10 20 30] 2"), Value::Int(30));
5355 }
5356
5357 #[test]
5358 fn list_equality() {
5359 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
5360 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
5361 assert_eq!(ev("[] == []"), Value::Bool(true));
5362 }
5363
5364 #[test]
5369 fn interp_simple_variable() {
5370 assert_eq!(
5371 ev(r#"let name = "world"; in "hello ${name}""#),
5372 Value::string("hello world"),
5373 );
5374 }
5375
5376 #[test]
5377 fn interp_nested_expression() {
5378 assert_eq!(
5379 ev(r#""result: ${builtins.toString (1 + 2)}""#),
5380 Value::string("result: 3"),
5381 );
5382 }
5383
5384 #[test]
5385 fn interp_int_coercion() {
5386 assert_eq!(
5388 ev(r#"let x = 42; in "count: ${builtins.toString x}""#),
5389 Value::string("count: 42"),
5390 );
5391 }
5392
5393 #[test]
5394 fn interp_multiple() {
5395 assert_eq!(
5396 ev(r#"let a = "foo"; b = "bar"; in "${a} and ${b}""#),
5397 Value::string("foo and bar"),
5398 );
5399 }
5400
5401 #[test]
5402 fn interp_in_let() {
5403 assert_eq!(
5404 ev(r#"let x = "world"; in "hello ${x}""#),
5405 Value::string("hello world"),
5406 );
5407 }
5408
5409 #[test]
5410 fn interp_empty_result() {
5411 assert_eq!(
5412 ev(r#"let x = ""; in "a${x}b""#),
5413 Value::string("ab"),
5414 );
5415 }
5416
5417 #[test]
5418 fn interp_path_in_string_context() {
5419 assert!(eval(r#""path: ${./foo-nonexistent-xyz}""#).is_err());
5425 }
5426
5427 #[test]
5428 fn interp_adjacent_interpolations() {
5429 assert_eq!(
5430 ev(r#"let a = "x"; b = "y"; in "${a}${b}""#),
5431 Value::string("xy"),
5432 );
5433 }
5434
5435 #[test]
5440 fn builtins_map_filter_foldl() {
5441 assert_eq!(
5443 ev("builtins.map (x: x + 10) [1 2 3]"),
5444 Value::list(vec![Value::Int(11), Value::Int(12), Value::Int(13)]),
5445 );
5446 assert_eq!(
5448 ev("builtins.filter (x: x > 1) [1 2 3]"),
5449 Value::list(vec![Value::Int(2), Value::Int(3)]),
5450 );
5451 assert_eq!(
5453 ev("builtins.foldl' (a: b: a * b) 1 [2 3 4]"),
5454 Value::Int(24),
5455 );
5456 }
5457
5458 #[test]
5459 fn builtins_map_attrs() {
5460 assert_eq!(
5461 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).a"),
5462 Value::Int(2),
5463 );
5464 assert_eq!(
5465 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).b"),
5466 Value::Int(4),
5467 );
5468 }
5469
5470 #[test]
5471 fn builtins_list_to_attrs() {
5472 assert_eq!(
5473 ev(r#"(builtins.listToAttrs [{ name = "x"; value = 1; } { name = "y"; value = 2; }]).x"#),
5474 Value::Int(1),
5475 );
5476 }
5477
5478 #[test]
5479 fn builtins_list_to_attrs_duplicate_key_first_wins() {
5480 assert_eq!(
5489 ev(r#"(builtins.listToAttrs [{ name = "k"; value = 1; } { name = "k"; value = 2; }]).k"#),
5490 Value::Int(1),
5491 );
5492 }
5493
5494 #[test]
5495 fn builtins_concat_map() {
5496 assert_eq!(
5497 ev("builtins.concatMap (x: [x (x * 2)]) [1 2 3]"),
5498 Value::list(vec![
5499 Value::Int(1), Value::Int(2),
5500 Value::Int(2), Value::Int(4),
5501 Value::Int(3), Value::Int(6),
5502 ]),
5503 );
5504 }
5505
5506 #[test]
5507 fn builtins_concat_lists() {
5508 assert_eq!(
5509 ev("builtins.concatLists [[1 2] [3] [4 5]]"),
5510 Value::list(vec![
5511 Value::Int(1), Value::Int(2), Value::Int(3),
5512 Value::Int(4), Value::Int(5),
5513 ]),
5514 );
5515 }
5516
5517 #[test]
5518 fn builtins_concat_strings_sep() {
5519 assert_eq!(
5520 ev(r#"builtins.concatStringsSep ", " ["a" "b" "c"]"#),
5521 Value::string("a, b, c"),
5522 );
5523 assert_eq!(
5524 ev(r#"builtins.concatStringsSep "" ["x" "y"]"#),
5525 Value::string("xy"),
5526 );
5527 }
5528
5529 #[test]
5530 fn builtins_replace_strings() {
5531 assert_eq!(
5532 ev(r#"builtins.replaceStrings ["o"] ["0"] "foobar""#),
5533 Value::string("f00bar"),
5534 );
5535 assert_eq!(
5536 ev(r#"builtins.replaceStrings ["hello"] ["goodbye"] "hello world""#),
5537 Value::string("goodbye world"),
5538 );
5539 }
5540
5541 #[test]
5542 fn builtins_has_prefix_has_suffix() {
5543 assert_eq!(ev(r#"builtins.hasPrefix "he" "hello""#), Value::Bool(true));
5544 assert_eq!(ev(r#"builtins.hasPrefix "xx" "hello""#), Value::Bool(false));
5545 assert_eq!(ev(r#"builtins.hasSuffix "lo" "hello""#), Value::Bool(true));
5546 assert_eq!(ev(r#"builtins.hasSuffix "xx" "hello""#), Value::Bool(false));
5547 }
5548
5549 #[test]
5550 fn builtins_all_any() {
5551 assert_eq!(ev("builtins.all (x: x > 0) [1 2 3]"), Value::Bool(true));
5552 assert_eq!(ev("builtins.all (x: x > 1) [1 2 3]"), Value::Bool(false));
5553 assert_eq!(ev("builtins.any (x: x > 2) [1 2 3]"), Value::Bool(true));
5554 assert_eq!(ev("builtins.any (x: x > 5) [1 2 3]"), Value::Bool(false));
5555 }
5556
5557 #[test]
5558 fn builtins_sort() {
5559 assert_eq!(
5560 ev("builtins.sort (a: b: a < b) [3 1 2]"),
5561 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5562 );
5563 }
5564
5565 #[test]
5566 fn builtins_remove_attrs() {
5567 let v = ev(r#"builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b" "c"]"#);
5568 if let Value::Attrs(attrs) = v {
5569 assert_eq!(attrs.len(), 1);
5570 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5571 assert!(attrs.get("b").is_none());
5572 } else {
5573 panic!("expected attrs");
5574 }
5575 }
5576
5577 #[test]
5578 fn builtins_intersect_attrs() {
5579 let v = ev("builtins.intersectAttrs { a = 1; b = 2; } { b = 20; c = 30; }");
5580 if let Value::Attrs(attrs) = v {
5581 assert_eq!(attrs.len(), 1);
5582 assert_eq!(attrs.get("b"), Some(&Value::Int(20)));
5584 } else {
5585 panic!("expected attrs");
5586 }
5587 }
5588
5589 #[test]
5590 fn builtins_type_of_all_types() {
5591 assert_eq!(ev("builtins.typeOf null"), Value::string("null"));
5592 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
5593 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
5594 assert_eq!(ev("builtins.typeOf 3.14"), Value::string("float"));
5595 assert_eq!(ev(r#"builtins.typeOf "hi""#), Value::string("string"));
5596 assert_eq!(ev("builtins.typeOf [1]"), Value::string("list"));
5597 assert_eq!(ev("builtins.typeOf {}"), Value::string("set"));
5598 assert_eq!(ev("builtins.typeOf (x: x)"), Value::string("lambda"));
5599 }
5600
5601 #[test]
5602 fn builtins_is_type_checks() {
5603 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
5604 assert_eq!(ev("builtins.isNull 0"), Value::Bool(false));
5605 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
5606 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
5607 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
5608 assert_eq!(ev("builtins.isBool 1"), Value::Bool(false));
5609 assert_eq!(ev(r#"builtins.isString "x""#), Value::Bool(true));
5610 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
5611 assert_eq!(ev("builtins.isList []"), Value::Bool(true));
5612 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
5613 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
5614 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
5615 assert_eq!(ev("builtins.isFunction (x: x)"), Value::Bool(true));
5616 assert_eq!(ev("builtins.isFunction 1"), Value::Bool(false));
5617 assert_eq!(ev("builtins.isFloat 3.14"), Value::Bool(true));
5618 assert_eq!(ev("builtins.isFloat 1"), Value::Bool(false));
5619 }
5620
5621 #[test]
5622 fn builtins_to_json_from_json_roundtrip() {
5623 assert_eq!(ev("builtins.fromJSON (builtins.toJSON 42)"), Value::Int(42));
5625 assert_eq!(
5627 ev(r#"builtins.fromJSON (builtins.toJSON "hello")"#),
5628 Value::string("hello"),
5629 );
5630 assert_eq!(
5632 ev("builtins.fromJSON (builtins.toJSON [1 2 3])"),
5633 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5634 );
5635 assert_eq!(ev("builtins.fromJSON (builtins.toJSON null)"), Value::Null);
5637 assert_eq!(ev("builtins.fromJSON (builtins.toJSON true)"), Value::Bool(true));
5639 }
5640
5641 #[test]
5642 fn builtins_to_string_various() {
5643 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
5644 assert_eq!(ev("builtins.toString true"), Value::string("1"));
5645 assert_eq!(ev("builtins.toString false"), Value::string(""));
5646 assert_eq!(ev("builtins.toString null"), Value::string(""));
5647 assert_eq!(ev(r#"builtins.toString "hello""#), Value::string("hello"));
5648 }
5649
5650 #[test]
5651 fn builtins_function_args() {
5652 let v = ev("builtins.functionArgs ({ a, b ? 1 }: a)");
5653 if let Value::Attrs(attrs) = v {
5654 assert_eq!(attrs.get("a"), Some(&Value::Bool(false))); assert_eq!(attrs.get("b"), Some(&Value::Bool(true))); } else {
5657 panic!("expected attrs");
5658 }
5659 }
5660
5661 #[test]
5662 fn builtins_gen_list() {
5663 assert_eq!(
5664 ev("builtins.genList (x: x * x) 5"),
5665 Value::list(vec![
5666 Value::Int(0), Value::Int(1), Value::Int(4),
5667 Value::Int(9), Value::Int(16),
5668 ]),
5669 );
5670 assert_eq!(ev("builtins.genList (x: x) 0"), Value::list(vec![]));
5671 }
5672
5673 #[test]
5674 fn builtins_elem() {
5675 assert_eq!(ev("builtins.elem 2 [1 2 3]"), Value::Bool(true));
5676 assert_eq!(ev("builtins.elem 5 [1 2 3]"), Value::Bool(false));
5677 assert_eq!(ev("builtins.elem 1 []"), Value::Bool(false));
5678 }
5679
5680 #[test]
5681 fn builtins_head_tail() {
5682 assert_eq!(ev("builtins.head [10 20 30]"), Value::Int(10));
5683 assert_eq!(
5684 ev("builtins.tail [10 20 30]"),
5685 Value::list(vec![Value::Int(20), Value::Int(30)]),
5686 );
5687 }
5688
5689 #[test]
5690 fn builtins_string_length() {
5691 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
5692 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
5693 assert_eq!(ev(r#"builtins.stringLength "abc def""#), Value::Int(7));
5694 }
5695
5696 #[test]
5697 fn builtins_ceil_floor() {
5698 assert_eq!(ev("builtins.ceil 2.3"), Value::Int(3));
5699 assert_eq!(ev("builtins.ceil 2.0"), Value::Int(2));
5700 assert_eq!(ev("builtins.floor 2.9"), Value::Int(2));
5701 assert_eq!(ev("builtins.floor 2.0"), Value::Int(2));
5702 assert_eq!(ev("builtins.ceil 5"), Value::Int(5));
5704 assert_eq!(ev("builtins.floor 5"), Value::Int(5));
5705 }
5706
5707 #[test]
5708 fn builtins_try_eval() {
5709 let v = ev("builtins.tryEval 42");
5710 if let Value::Attrs(attrs) = v {
5711 assert_eq!(attrs.get("success"), Some(&Value::Bool(true)));
5712 assert_eq!(attrs.get("value"), Some(&Value::Int(42)));
5713 } else {
5714 panic!("expected attrs");
5715 }
5716 }
5717
5718 #[test]
5719 fn builtins_throw() {
5720 let result = eval(r#"builtins.throw "oops""#);
5721 assert!(result.is_err());
5722 let msg = format!("{}", result.unwrap_err());
5723 assert!(msg.contains("oops"));
5724 }
5725
5726 #[test]
5727 fn builtins_seq_deep_seq() {
5728 assert_eq!(ev("builtins.seq 1 42"), Value::Int(42));
5730 assert_eq!(ev("builtins.deepSeq [1 2 3] 99"), Value::Int(99));
5732 }
5733
5734 #[test]
5735 fn builtins_current_system() {
5736 let v = ev("builtins.currentSystem");
5737 if let Value::String(ns) = v {
5738 let s = &ns.chars;
5739 assert!(
5741 s == "aarch64-darwin"
5742 || s == "x86_64-darwin"
5743 || s == "aarch64-linux"
5744 || s == "x86_64-linux",
5745 "unexpected system: {s}",
5746 );
5747 } else {
5748 panic!("expected string");
5749 }
5750 }
5751
5752 #[test]
5757 fn pattern_mkif_like() {
5758 assert_eq!(
5760 ev("(if true then { x = 1; } else {}).x"),
5761 Value::Int(1),
5762 );
5763 let v = ev("if false then { x = 1; } else {}");
5764 if let Value::Attrs(attrs) = v {
5765 assert!(attrs.is_empty());
5766 } else {
5767 panic!("expected attrs");
5768 }
5769 }
5770
5771 #[test]
5772 fn pattern_optional_attrs() {
5773 assert_eq!(
5775 ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in (optionalAttrs true { a = 1; }).a"),
5776 Value::Int(1),
5777 );
5778 let v = ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in optionalAttrs false { a = 1; }");
5779 if let Value::Attrs(attrs) = v {
5780 assert!(attrs.is_empty());
5781 } else {
5782 panic!("expected attrs");
5783 }
5784 }
5785
5786 #[test]
5787 fn pattern_filter_attrs_via_remove() {
5788 assert_eq!(
5790 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]).a"#),
5791 Value::Int(1),
5792 );
5793 assert_eq!(
5794 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]) ? b"#),
5795 Value::Bool(false),
5796 );
5797 }
5798
5799 #[test]
5800 fn pattern_override() {
5801 let v = ev(r#"
5803 let
5804 defaults = { debug = false; port = 8080; host = "localhost"; };
5805 overrides = { debug = true; port = 9090; };
5806 in defaults // overrides
5807 "#);
5808 if let Value::Attrs(attrs) = v {
5809 assert_eq!(attrs.get("debug"), Some(&Value::Bool(true)));
5810 assert_eq!(attrs.get("port"), Some(&Value::Int(9090)));
5811 assert_eq!(attrs.get("host"), Some(&Value::string("localhost")));
5812 } else {
5813 panic!("expected attrs");
5814 }
5815 }
5816
5817 #[test]
5818 fn pattern_functor() {
5819 assert_eq!(
5821 ev("let s = { __functor = self: x: self.value + x; value = 10; }; in s 5"),
5822 Value::Int(15),
5823 );
5824 }
5825
5826 #[test]
5827 fn pattern_platform_check() {
5828 let v = ev(r#"if builtins.currentSystem == "aarch64-darwin" then "arm" else "other""#);
5830 if let Value::String(_) = v {
5832 } else {
5834 panic!("expected string");
5835 }
5836 }
5837
5838 #[test]
5839 fn pattern_recursive_overlay_lambda_structure() {
5840 let v = ev("let overlay = self: super: { pkg = 42; }; in overlay {} {}");
5842 if let Value::Attrs(attrs) = v {
5843 assert_eq!(attrs.get("pkg"), Some(&Value::Int(42)));
5844 } else {
5845 panic!("expected attrs");
5846 }
5847 }
5848
5849 #[test]
5850 fn pattern_call_package_simplified() {
5851 assert_eq!(
5853 ev("let callPkg = f: f { lib = { id = x: x; }; }; lib = { id = x: x; }; in callPkg ({ lib }: lib.id 42)"),
5854 Value::Int(42),
5855 );
5856 }
5857
5858 #[test]
5859 fn pattern_derivation_like_attrset() {
5860 let v = ev(r#"{ type = "derivation"; name = "hello"; system = builtins.currentSystem; builder = "/bin/sh"; }"#);
5861 if let Value::Attrs(attrs) = v {
5862 assert_eq!(attrs.get("type"), Some(&Value::string("derivation")));
5863 assert_eq!(attrs.get("name"), Some(&Value::string("hello")));
5864 assert_eq!(attrs.get("builder"), Some(&Value::string("/bin/sh")));
5865 let system = force_value(attrs.get("system").unwrap()).unwrap();
5867 assert!(matches!(system, Value::String(_)), "expected string, got {system:?}");
5868 } else {
5869 panic!("expected attrs");
5870 }
5871 }
5872
5873 #[test]
5874 fn pattern_module_system_simplified() {
5875 assert_eq!(
5877 ev(r#"
5878 let
5879 eval = m: m { config = {}; lib = { mkDefault = x: x; }; };
5880 in eval ({ config, lib }: { result = lib.mkDefault 42; })
5881 "#),
5882 {
5883 let mut attrs = NixAttrs::new();
5884 attrs.insert("result".to_string(), Value::Int(42));
5885 Value::Attrs(Rc::new(attrs))
5886 },
5887 );
5888 }
5889
5890 #[test]
5895 fn error_undefined_variable() {
5896 let result = eval("nonexistent_var");
5897 assert!(result.is_err());
5898 let msg = format!("{}", result.unwrap_err());
5899 assert!(msg.contains("undefined variable") || msg.contains("nonexistent_var"));
5900 }
5901
5902 #[test]
5903 fn error_type_mismatch_arithmetic() {
5904 let result = eval(r#"1 + "hello""#);
5905 assert!(result.is_err());
5906 }
5907
5908 #[test]
5909 fn error_missing_attribute() {
5910 let result = eval("{}.nonexistent");
5911 assert!(result.is_err());
5912 let msg = format!("{}", result.unwrap_err());
5913 assert!(msg.contains("nonexistent") || msg.contains("not found"));
5914 }
5915
5916 #[test]
5917 fn error_division_by_zero() {
5918 assert!(eval("1 / 0").is_err());
5919 assert!(eval("100 / 0").is_err());
5920 }
5921
5922 #[test]
5923 fn error_missing_required_function_arg() {
5924 let result = eval("({ a, b }: a + b) { a = 1; }");
5925 assert!(result.is_err());
5926 let msg = format!("{}", result.unwrap_err());
5927 assert!(msg.contains("missing argument"));
5928 }
5929
5930 #[test]
5931 fn error_unexpected_function_arg() {
5932 let result = eval("({ a }: a) { a = 1; b = 2; }");
5933 assert!(result.is_err());
5934 let msg = format!("{}", result.unwrap_err());
5935 assert!(msg.contains("unexpected argument"));
5936 }
5937
5938 #[test]
5939 fn error_assertion_failure() {
5940 assert!(eval("assert false; 1").is_err());
5941 assert!(eval("assert 1 == 2; 1").is_err());
5942 }
5943
5944 #[test]
5945 fn error_infinite_recursion() {
5946 let result = eval("let x = x; in x");
5949 assert!(result.is_err());
5950 }
5951
5952 #[test]
5953 fn error_infinite_recursion_via_lambda() {
5954 let result = eval("let f = x: f x; in f 1");
5956 assert!(result.is_err());
5957 let msg = format!("{}", result.unwrap_err());
5958 assert!(
5959 msg.contains("infinite recursion") || msg.contains("eval depth") || msg.contains("undefined"),
5960 );
5961 }
5962
5963 #[test]
5968 fn integration_let_with_function_returning_attrset() {
5969 assert_eq!(
5970 ev("let mkPkg = name: { inherit name; version = 1; }; in (mkPkg \"hello\").name"),
5971 Value::string("hello"),
5972 );
5973 }
5974
5975 #[test]
5976 fn integration_chained_updates() {
5977 assert_eq!(
5978 ev("({ a = 1; } // { b = 2; } // { c = 3; }).c"),
5979 Value::Int(3),
5980 );
5981 }
5982
5983 #[test]
5984 fn integration_map_over_attrnames() {
5985 assert_eq!(
5987 ev(r#"
5988 let
5989 set = { a = 1; b = 2; };
5990 names = builtins.attrNames set;
5991 in builtins.length names
5992 "#),
5993 Value::Int(2),
5994 );
5995 }
5996
5997 #[test]
5998 fn integration_compose_functions() {
5999 assert_eq!(
6001 ev("let compose = f: g: x: f (g x); double = x: x * 2; inc = x: x + 1; in compose double inc 5"),
6002 Value::Int(12), );
6004 }
6005
6006 #[test]
6007 fn integration_recursive_list_building() {
6008 assert_eq!(
6010 ev("builtins.map (x: x * x) (builtins.genList (x: x + 1) 4)"),
6011 Value::list(vec![Value::Int(1), Value::Int(4), Value::Int(9), Value::Int(16)]),
6012 );
6013 }
6014
6015 #[test]
6016 fn integration_attrset_from_list() {
6017 let v = ev(r#"
6019 builtins.listToAttrs (builtins.map (x: { name = x; value = true; }) ["a" "b" "c"])
6020 "#);
6021 if let Value::Attrs(attrs) = v {
6022 assert_eq!(attrs.get("a"), Some(&Value::Bool(true)));
6023 assert_eq!(attrs.get("b"), Some(&Value::Bool(true)));
6024 assert_eq!(attrs.get("c"), Some(&Value::Bool(true)));
6025 } else {
6026 panic!("expected attrs");
6027 }
6028 }
6029
6030 #[test]
6031 fn integration_nested_with_and_let() {
6032 assert_eq!(
6033 ev("let x = 10; in with { y = 20; }; x + y"),
6034 Value::Int(30),
6035 );
6036 }
6037
6038 #[test]
6039 fn integration_complex_pattern_match() {
6040 assert_eq!(
6042 ev("(args @ { a, b ? 5, ... }: a + b + (if args ? c then args.c else 0)) { a = 1; c = 10; }"),
6043 Value::Int(16), );
6045 }
6046
6047 #[test]
6048 fn integration_substring() {
6049 assert_eq!(
6050 ev(r#"builtins.substring 0 5 "hello world""#),
6051 Value::string("hello"),
6052 );
6053 assert_eq!(
6054 ev(r#"builtins.substring 6 5 "hello world""#),
6055 Value::string("world"),
6056 );
6057 }
6058
6059 #[test]
6060 fn integration_has_attr_on_nested() {
6061 assert_eq!(ev("{ a = { b = 1; }; } ? a"), Value::Bool(true));
6063 assert_eq!(
6064 ev("({ a = { b = 1; }; }.a) ? b"),
6065 Value::Bool(true),
6066 );
6067 }
6068
6069 #[test]
6070 fn integration_cat_attrs() {
6071 assert_eq!(
6072 ev(r#"builtins.catAttrs "x" [{ x = 1; } { y = 2; } { x = 3; }]"#),
6073 Value::list(vec![Value::Int(1), Value::Int(3)]),
6074 );
6075 }
6076
6077 #[test]
6078 fn integration_get_attr_builtin() {
6079 assert_eq!(
6080 ev(r#"builtins.getAttr "a" { a = 42; b = 10; }"#),
6081 Value::Int(42),
6082 );
6083 }
6084
6085 #[test]
6086 fn integration_has_attr_builtin() {
6087 assert_eq!(
6088 ev(r#"builtins.hasAttr "a" { a = 1; }"#),
6089 Value::Bool(true),
6090 );
6091 assert_eq!(
6092 ev(r#"builtins.hasAttr "z" { a = 1; }"#),
6093 Value::Bool(false),
6094 );
6095 }
6096
6097 #[test]
6098 fn integration_is_path() {
6099 assert_eq!(ev("builtins.isPath ./foo"), Value::Bool(true));
6100 assert_eq!(ev("builtins.isPath 42"), Value::Bool(false));
6101 }
6102
6103 #[test]
6104 fn integration_builtins_trace() {
6105 assert_eq!(ev(r#"builtins.trace "debug msg" 42"#), Value::Int(42));
6107 }
6108
6109 #[test]
6110 fn integration_builtins_split() {
6111 assert_eq!(
6115 ev(r#"builtins.split "/" "a/b/c""#),
6116 Value::list(vec![
6117 Value::string("a"),
6118 Value::list(vec![]),
6119 Value::string("b"),
6120 Value::list(vec![]),
6121 Value::string("c"),
6122 ]),
6123 );
6124 assert_eq!(
6127 ev(r#"builtins.split "(/)" "a/b/c""#),
6128 Value::list(vec![
6129 Value::string("a"),
6130 Value::list(vec![Value::string("/")]),
6131 Value::string("b"),
6132 Value::list(vec![Value::string("/")]),
6133 Value::string("c"),
6134 ]),
6135 );
6136 }
6137
6138 #[test]
6139 fn integration_builtins_split_no_capture_groups() {
6140 assert_eq!(
6145 ev(r#"builtins.split "-" "aarch64-darwin""#),
6146 Value::list(vec![
6147 Value::string("aarch64"),
6148 Value::list(vec![]),
6149 Value::string("darwin"),
6150 ]),
6151 );
6152 }
6153
6154 #[test]
6155 fn integration_builtins_split_system_string_filter() {
6156 assert_eq!(
6159 ev(r#"builtins.filter builtins.isString (builtins.split "-" "aarch64-darwin")"#),
6160 Value::list(vec![
6161 Value::string("aarch64"),
6162 Value::string("darwin"),
6163 ]),
6164 );
6165 }
6166
6167 #[test]
6168 fn integration_deeply_nested_let() {
6169 assert_eq!(
6171 ev("let a = let b = let c = 10; in c * 2; in b + 1; in a"),
6172 Value::Int(21),
6173 );
6174 }
6175
6176 #[test]
6177 fn integration_if_in_attrset_value() {
6178 assert_eq!(
6179 ev("{ x = if true then 1 else 2; }.x"),
6180 Value::Int(1),
6181 );
6182 }
6183
6184 #[test]
6185 fn integration_lambda_in_list() {
6186 assert_eq!(
6188 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 0) 5"),
6189 Value::Int(6),
6190 );
6191 assert_eq!(
6192 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 1) 5"),
6193 Value::Int(10),
6194 );
6195 }
6196
6197 #[test]
6198 fn integration_nixpkgs_lib_id() {
6199 assert_eq!(
6201 ev("let lib = { id = x: x; const = a: b: a; }; in lib.id 42"),
6202 Value::Int(42),
6203 );
6204 assert_eq!(
6205 ev("let lib = { id = x: x; const = a: b: a; }; in lib.const 1 2"),
6206 Value::Int(1),
6207 );
6208 }
6209
6210 #[test]
6211 fn integration_multiple_inherit() {
6212 assert_eq!(
6213 ev("let a = 1; b = 2; c = 3; in { inherit a b c; }.b"),
6214 Value::Int(2),
6215 );
6216 }
6217
6218 #[test]
6219 fn integration_rec_set_with_builtins() {
6220 assert_eq!(
6221 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
6222 Value::Int(5),
6223 );
6224 }
6225
6226 #[test]
6231 fn functor_simple_callable_attrset() {
6232 assert_eq!(
6233 ev("let s = { __functor = self: x: x + 1; }; in s 41"),
6234 Value::Int(42),
6235 );
6236 }
6237
6238 #[test]
6239 fn functor_with_self_reference() {
6240 assert_eq!(
6241 ev("let s = { __functor = self: x: self.base + x; base = 100; }; in s 23"),
6242 Value::Int(123),
6243 );
6244 }
6245
6246 #[test]
6247 fn functor_updated_attrset() {
6248 assert_eq!(
6250 ev(r#"
6251 let
6252 mk = { __functor = self: x: self.n + x; n = 0; };
6253 s = mk // { n = 50; };
6254 in s 7
6255 "#),
6256 Value::Int(57),
6257 );
6258 }
6259
6260 #[test]
6261 fn functor_error_on_non_callable_attrset() {
6262 let result = eval("let s = { a = 1; }; in s 5");
6264 assert!(result.is_err());
6265 }
6266
6267 #[test]
6272 fn to_string_protocol_in_interpolation() {
6273 assert_eq!(
6274 ev(r#"let s = { __toString = self: "world"; }; in "hello ${s}""#),
6275 Value::string("hello world"),
6276 );
6277 }
6278
6279 #[test]
6280 fn to_string_protocol_accesses_self() {
6281 assert_eq!(
6282 ev(r#"let s = { __toString = self: self.val; val = "abc"; }; in "${s}""#),
6283 Value::string("abc"),
6284 );
6285 }
6286
6287 #[test]
6288 fn to_string_protocol_via_builtin_to_string() {
6289 assert_eq!(
6290 ev(r#"builtins.toString { __toString = self: "via-builtin"; }"#),
6291 Value::string("via-builtin"),
6292 );
6293 }
6294
6295 #[test]
6296 fn to_string_protocol_attrset_without_toString_fails() {
6297 let result = eval(r#""${{}}"#);
6299 assert!(result.is_err());
6300 }
6301
6302 #[test]
6307 fn eval_builtins_concat_strings() {
6308 assert_eq!(
6309 ev(r#"builtins.concatStrings ["a" "b" "c"]"#),
6310 Value::string("abc"),
6311 );
6312 assert_eq!(
6313 ev(r#"builtins.concatStrings []"#),
6314 Value::string(""),
6315 );
6316 }
6317
6318 #[test]
6319 fn eval_builtins_partition() {
6320 let v = ev("builtins.partition (x: x > 3) [1 2 3 4 5]");
6321 if let Value::Attrs(a) = v {
6322 assert_eq!(a.get("right"), Some(&Value::list(vec![Value::Int(4), Value::Int(5)])));
6323 assert_eq!(a.get("wrong"), Some(&Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)])));
6324 } else {
6325 panic!("expected attrs");
6326 }
6327 }
6328
6329 #[test]
6330 fn eval_builtins_group_by() {
6331 let v = ev(r#"builtins.groupBy (x: if x > 0 then "pos" else "neg") [1 (0 - 2) 3 (0 - 4)]"#);
6332 if let Value::Attrs(a) = v {
6333 assert_eq!(a.get("pos"), Some(&Value::list(vec![Value::Int(1), Value::Int(3)])));
6334 assert_eq!(a.get("neg"), Some(&Value::list(vec![Value::Int(-2), Value::Int(-4)])));
6335 } else {
6336 panic!("expected attrs");
6337 }
6338 }
6339
6340 #[test]
6341 fn eval_builtins_zip_attrs_with() {
6342 let v = ev("builtins.zipAttrsWith (n: vs: builtins.head vs) [{ a = 1; } { a = 2; b = 3; }]");
6343 if let Value::Attrs(a) = v {
6344 assert_eq!(a.get("a"), Some(&Value::Int(1)));
6345 assert_eq!(a.get("b"), Some(&Value::Int(3)));
6346 } else {
6347 panic!("expected attrs");
6348 }
6349 }
6350
6351 #[test]
6352 fn eval_builtins_compare_versions() {
6353 assert_eq!(ev(r#"builtins.compareVersions "2.0" "1.0""#), Value::Int(1));
6354 assert_eq!(ev(r#"builtins.compareVersions "1.0" "2.0""#), Value::Int(-1));
6355 assert_eq!(ev(r#"builtins.compareVersions "1.0" "1.0""#), Value::Int(0));
6356 }
6357
6358 #[test]
6359 fn eval_builtins_parse_drv_name() {
6360 let v = ev(r#"builtins.parseDrvName "nix-2.3.4""#);
6361 if let Value::Attrs(a) = v {
6362 assert_eq!(a.get("name"), Some(&Value::string("nix")));
6363 assert_eq!(a.get("version"), Some(&Value::string("2.3.4")));
6364 } else {
6365 panic!("expected attrs");
6366 }
6367 }
6368
6369 #[test]
6370 fn eval_builtins_base_name_of() {
6371 assert_eq!(
6372 ev(r#"builtins.baseNameOf "/foo/bar/baz""#),
6373 Value::string("baz"),
6374 );
6375 }
6376
6377 #[test]
6378 fn eval_builtins_dir_of() {
6379 assert_eq!(
6380 ev(r#"builtins.dirOf "/foo/bar/baz""#),
6381 Value::string("/foo/bar"),
6382 );
6383 }
6384
6385 #[test]
6386 fn eval_builtins_add_error_context() {
6387 assert_eq!(
6388 ev(r#"builtins.addErrorContext "some context" 42"#),
6389 Value::Int(42),
6390 );
6391 }
6392
6393 #[test]
6394 fn eval_builtins_abort() {
6395 let result = eval(r#"builtins.abort "fatal error""#);
6396 assert!(result.is_err());
6397 let msg = format!("{}", result.unwrap_err());
6398 assert!(msg.contains("fatal error"));
6399 }
6400
6401 #[test]
6406 fn indented_string_simple() {
6407 assert_eq!(ev("''hello''"), Value::string("hello"));
6408 }
6409
6410 #[test]
6411 fn indented_string_multiline_strips_indent() {
6412 assert_eq!(
6413 ev("''\n line1\n line2\n''"),
6414 Value::string("line1\nline2\n"),
6415 );
6416 }
6417
6418 #[test]
6419 fn indented_string_with_interpolation() {
6420 let code = "let x = \"world\"; in ''hello ${x}''";
6421 assert_eq!(
6422 ev(code),
6423 Value::string("hello world"),
6424 );
6425 }
6426
6427 #[test]
6428 fn indented_string_deeper_indent_preserved() {
6429 assert_eq!(
6431 ev("''\n a\n b\n''"),
6432 Value::string("a\n b\n"),
6433 );
6434 }
6435
6436 #[test]
6441 fn dynamic_attr_name_in_set() {
6442 assert_eq!(
6443 ev(r#"let key = "mykey"; in { ${key} = 42; }.mykey"#),
6444 Value::Int(42),
6445 );
6446 }
6447
6448 #[test]
6449 fn dynamic_attr_name_with_expression() {
6450 assert_eq!(
6451 ev(r#"let prefix = "foo"; in { ${"${prefix}bar"} = 1; }.foobar"#),
6452 Value::Int(1),
6453 );
6454 }
6455
6456 #[test]
6461 fn eval_builtins_match() {
6462 assert_eq!(
6463 ev(r#"builtins.match "([0-9]+)" "42""#),
6464 Value::list(vec![Value::string("42")]),
6465 );
6466 }
6467
6468 #[test]
6469 fn eval_builtins_hash_string() {
6470 let v = ev(r#"builtins.hashString "sha256" "hello""#);
6471 if let Value::String(ns) = v {
6472 assert_eq!(ns.chars.len(), 64);
6473 } else {
6474 panic!("expected string");
6475 }
6476 }
6477
6478 #[test]
6479 fn eval_builtins_import() {
6480 let dir = std::env::temp_dir();
6481 let path = dir.join("sui_eval_test_import_eval.nix");
6482 std::fs::write(&path, "42").unwrap();
6483 let expr = format!(r#"import "{}""#, path.display());
6484 let v = eval(&expr).unwrap();
6485 assert_eq!(v, Value::Int(42));
6486 std::fs::remove_file(&path).ok();
6487 }
6488
6489 #[test]
6490 fn eval_builtins_derivation() {
6491 let v = eval(r#"builtins.derivation { name = "test"; system = "x86_64-linux"; builder = "/bin/sh"; }"#).unwrap();
6492 if let Value::Attrs(a) = v {
6493 assert_eq!(a.get("type"), Some(&Value::string("derivation")));
6494 } else {
6495 panic!("expected attrs");
6496 }
6497 }
6498
6499 #[test]
6500 fn eval_mutual_recursive_let() {
6501 let v = eval("let a = { x = b; }; b = { y = a; }; in a.x.y");
6508 assert!(v.is_ok(), "mutual recursive let should not error: {v:?}");
6509 let val = v.unwrap();
6511 assert!(
6512 matches!(val, Value::Attrs(_)),
6513 "a.x.y should be an attrset, got: {val:?}",
6514 );
6515 }
6516
6517 #[test]
6518 fn eval_mutual_recursive_let_simple() {
6519 let v = eval("let a = b; b = 42; in a");
6521 assert!(v.is_ok());
6522 assert_eq!(v.unwrap(), Value::Int(42));
6525 }
6526
6527 #[test]
6528 fn eval_builtins_read_dir() {
6529 let dir = std::env::temp_dir().join("sui_eval_test_readdir_eval");
6530 let _ = std::fs::remove_dir_all(&dir);
6531 std::fs::create_dir_all(&dir).unwrap();
6532 std::fs::write(dir.join("a.txt"), "").unwrap();
6533 let expr = format!(r#"builtins.readDir "{}""#, dir.display());
6534 let v = eval(&expr).unwrap();
6535 if let Value::Attrs(a) = v {
6536 assert_eq!(a.get("a.txt"), Some(&Value::string("regular")));
6537 } else {
6538 panic!("expected attrs");
6539 }
6540 let _ = std::fs::remove_dir_all(&dir);
6541 }
6542
6543 #[test]
6548 fn thunk_basic_let() {
6549 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
6551 }
6552
6553 #[test]
6554 fn thunk_forward_ref() {
6555 assert_eq!(ev("let a = b; b = 1; in a"), Value::Int(1));
6557 }
6558
6559 #[test]
6560 fn thunk_mutual_rec_attrset_in_let() {
6561 assert_eq!(ev("let a = { x = b; }; b = { y = 1; }; in a.x.y"), Value::Int(1));
6563 }
6564
6565 #[test]
6566 fn thunk_rec_attrset() {
6567 assert_eq!(ev("(rec { a = b; b = 1; }).a"), Value::Int(1));
6569 }
6570
6571 #[test]
6572 fn thunk_rec_attrset_chain() {
6573 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
6575 }
6576
6577 #[test]
6578 fn thunk_fixpoint() {
6579 assert_eq!(
6581 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; })).b"),
6582 Value::Int(2),
6583 );
6584 }
6585
6586 #[test]
6587 fn thunk_blackhole_self_reference() {
6588 let result = eval("let x = x; in x");
6590 assert!(result.is_err());
6591 let msg = format!("{}", result.unwrap_err());
6592 assert!(
6593 msg.contains("infinite recursion") || msg.contains("blackhole"),
6594 "expected blackhole error, got: {msg}",
6595 );
6596 }
6597
6598 #[test]
6599 fn thunk_mutual_blackhole() {
6600 let result = eval("let a = b; b = a; in a");
6602 assert!(result.is_err());
6603 }
6604
6605 #[test]
6606 fn thunk_let_body_forces_correctly() {
6607 assert_eq!(ev("let a = 10; b = 20; in a + b"), Value::Int(30));
6609 }
6610
6611 #[test]
6612 fn thunk_only_forced_when_needed() {
6613 assert_eq!(ev("let bad = 1 / 0; good = 42; in good"), Value::Int(42));
6615 }
6616
6617 #[test]
6618 fn thunk_forward_ref_in_function_body() {
6619 assert_eq!(
6621 ev("let f = x: x + b; b = 10; in f 5"),
6622 Value::Int(15),
6623 );
6624 }
6625
6626 #[test]
6627 fn thunk_rec_set_self_ref_through_self() {
6628 assert_eq!(
6630 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
6631 Value::Int(5),
6632 );
6633 }
6634
6635 #[test]
6636 fn thunk_nested_let_forward_ref() {
6637 assert_eq!(
6639 ev("let a = b + 1; b = 2; in a"),
6640 Value::Int(3),
6641 );
6642 }
6643
6644 #[test]
6645 fn thunk_deep_chain() {
6646 assert_eq!(
6648 ev("let a = 1; b = a; c = b; d = c; e = d; in e"),
6649 Value::Int(1),
6650 );
6651 }
6652
6653 #[test]
6654 fn thunk_rec_set_fixpoint() {
6655 assert_eq!(
6657 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; c = self.b + 1; })).c"),
6658 Value::Int(3),
6659 );
6660 }
6661
6662 #[test]
6663 fn thunk_let_with_inherit() {
6664 assert_eq!(
6666 ev("let a = 1; in let inherit a; b = a + 1; in b"),
6667 Value::Int(2),
6668 );
6669 }
6670
6671 #[test]
6672 fn thunk_attrset_value_lazy() {
6673 assert_eq!(
6676 ev("let x = 42; in { a = x; }.a"),
6677 Value::Int(42),
6678 );
6679 }
6680
6681 #[test]
6682 fn thunk_unused_error_not_forced() {
6683 assert_eq!(
6685 ev(r#"let bad = builtins.throw "boom"; ok = 1; in ok"#),
6686 Value::Int(1),
6687 );
6688 }
6689
6690 #[test]
6691 fn thunk_rec_set_mutual_reference() {
6692 let v = ev("rec { a = { val = b.val + 1; }; b = { val = 10; }; }");
6694 if let Value::Attrs(attrs) = v {
6695 let a = attrs.get("a").unwrap();
6696 let a_forced = force_value(a).unwrap();
6697 if let Value::Attrs(a_attrs) = a_forced {
6698 assert_eq!(a_attrs.get("val"), Some(&Value::Int(11)));
6699 } else {
6700 panic!("expected attrs for a");
6701 }
6702 } else {
6703 panic!("expected attrs");
6704 }
6705 }
6706
6707 #[test]
6710 fn let_rec_self_reference_simple() {
6711 assert_eq!(
6712 ev("let x = 1; y = x + 1; in y"),
6713 Value::Int(2),
6714 );
6715 }
6716
6717 #[test]
6718 fn let_rec_self_reference_chain() {
6719 assert_eq!(
6720 ev("let a = 1; b = a + 1; c = b + 1; in c"),
6721 Value::Int(3),
6722 );
6723 }
6724
6725 #[test]
6726 fn let_rec_self_reference_with_function() {
6727 assert_eq!(
6728 ev("let f = x: x + 1; y = f 10; in y"),
6729 Value::Int(11),
6730 );
6731 }
6732
6733 #[test]
6734 fn let_rec_mutual_recursion_via_if() {
6735 assert_eq!(
6736 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"),
6737 Value::Bool(true),
6738 );
6739 }
6740
6741 #[test]
6742 fn let_rec_forward_ref_in_list() {
6743 assert_eq!(
6744 ev("let xs = [a b]; a = 1; b = 2; in builtins.length xs"),
6745 Value::Int(2),
6746 );
6747 }
6748
6749 #[test]
6752 fn with_shadowing_let_wins_over_with() {
6753 assert_eq!(
6754 ev("let x = 1; in with { x = 2; }; x"),
6755 Value::Int(1),
6756 );
6757 }
6758
6759 #[test]
6760 fn with_shadowing_inner_with_wins() {
6761 assert_eq!(
6762 ev("with { x = 1; }; with { x = 2; }; x"),
6763 Value::Int(2),
6764 );
6765 }
6766
6767 #[test]
6768 fn with_shadowing_outer_provides_missing() {
6769 assert_eq!(
6770 ev("with { x = 1; y = 10; }; with { x = 2; }; x + y"),
6771 Value::Int(12),
6772 );
6773 }
6774
6775 #[test]
6776 fn with_shadowing_lambda_arg_wins() {
6777 assert_eq!(
6778 ev("(x: with { x = 99; }; x) 42"),
6779 Value::Int(42),
6780 );
6781 }
6782
6783 #[test]
6784 fn with_shadowing_nested_let_wins_over_with() {
6785 assert_eq!(
6786 ev("with { x = 1; }; let x = 2; in x"),
6787 Value::Int(2),
6788 );
6789 }
6790
6791 #[test]
6792 fn with_scope_dynamic_attrs() {
6793 assert_eq!(
6794 ev(r#"with { x = 1; y = 2; z = 3; }; x + y + z"#),
6795 Value::Int(6),
6796 );
6797 }
6798
6799 #[test]
6800 fn with_scope_over_lazy_thunk_chain_resolves() {
6801 assert_eq!(
6810 ev(r#"let outer = if true then (if true then { unix = 42; } else {}) else {};
6811 # force a two-deep lazy wrap of the with-head
6812 head = (x: x) ((y: y) outer);
6813 in with head; unix"#),
6814 Value::Int(42),
6815 );
6816 }
6817
6818 #[test]
6819 fn with_scope_head_from_deep_select_resolves() {
6820 assert_eq!(
6823 ev(r#"let a = { b = { c = { key = 7; }; }; }; in with a.b.c; key"#),
6824 Value::Int(7),
6825 );
6826 }
6827
6828 #[test]
6831 fn attrset_deep_merge_simple() {
6832 let v = ev("{ a.b = 1; a.c = 2; }");
6833 if let Value::Attrs(attrs) = v {
6834 let a = force_value(attrs.get("a").unwrap()).unwrap();
6835 if let Value::Attrs(inner) = a {
6836 assert_eq!(force_value(inner.get("b").unwrap()).unwrap(), Value::Int(1));
6837 assert_eq!(force_value(inner.get("c").unwrap()).unwrap(), Value::Int(2));
6838 } else {
6839 panic!("expected nested attrs");
6840 }
6841 } else {
6842 panic!("expected attrs");
6843 }
6844 }
6845
6846 #[test]
6847 fn attrset_deep_merge_three_levels() {
6848 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
6849 if let Value::Attrs(attrs) = v {
6850 let a = force_value(attrs.get("a").unwrap()).unwrap();
6851 if let Value::Attrs(a_inner) = a {
6852 let e = force_value(a_inner.get("e").unwrap()).unwrap();
6853 assert_eq!(e, Value::Int(3));
6854 let b = force_value(a_inner.get("b").unwrap()).unwrap();
6855 if let Value::Attrs(b_inner) = b {
6856 assert_eq!(force_value(b_inner.get("c").unwrap()).unwrap(), Value::Int(1));
6857 assert_eq!(force_value(b_inner.get("d").unwrap()).unwrap(), Value::Int(2));
6858 } else {
6859 panic!("expected nested attrs for b");
6860 }
6861 } else {
6862 panic!("expected nested attrs for a");
6863 }
6864 } else {
6865 panic!("expected attrs");
6866 }
6867 }
6868
6869 #[test]
6870 fn attrset_deep_merge_preserves_siblings() {
6871 assert_eq!(
6872 ev("{ a.x = 1; b = 2; a.y = 3; }.b"),
6873 Value::Int(2),
6874 );
6875 }
6876
6877 #[test]
6878 fn attrset_deep_merge_in_let() {
6879 let v = ev("let s = { a.b = 1; a.c = 2; }; in s.a.b + s.a.c");
6880 assert_eq!(v, Value::Int(3));
6881 }
6882
6883 #[test]
6884 fn attrset_deep_merge_fullset_then_dotted() {
6885 let v = ev("let s = { a = { x = 1; }; a.y = 2; }; in s.a.x + s.a.y");
6892 assert_eq!(v, Value::Int(3));
6893 let both = ev("let s = { a = { x = 1; }; a.y = 2; }; in [ s.a.x s.a.y ]");
6895 if let Value::List(items) = both {
6896 assert_eq!(force_value(&items[0]).unwrap(), Value::Int(1));
6897 assert_eq!(force_value(&items[1]).unwrap(), Value::Int(2));
6898 } else {
6899 panic!("expected list");
6900 }
6901 }
6902
6903 #[test]
6906 fn inherit_from_basic() {
6907 assert_eq!(
6908 ev("let s = { x = 1; y = 2; }; in let inherit (s) x y; in x + y"),
6909 Value::Int(3),
6910 );
6911 }
6912
6913 #[test]
6914 fn inherit_from_with_shadowing() {
6915 assert_eq!(
6916 ev("let x = 10; in let inherit ({ x = 20; }) x; in x"),
6917 Value::Int(20),
6918 );
6919 }
6920
6921 #[test]
6922 fn inherit_from_in_attrset() {
6923 let v = ev(r#"let s = { a = 1; b = 2; }; in { inherit (s) a b; c = 3; }"#);
6924 if let Value::Attrs(attrs) = v {
6925 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
6926 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
6927 assert_eq!(force_value(attrs.get("c").unwrap()).unwrap(), Value::Int(3));
6928 } else {
6929 panic!("expected attrs");
6930 }
6931 }
6932
6933 #[test]
6934 fn inherit_from_rec_set() {
6935 assert_eq!(
6936 ev("rec { inherit ({ x = 42; }) x; y = x; }.y"),
6937 Value::Int(42),
6938 );
6939 }
6940
6941 #[test]
6942 fn inherit_plain_from_scope() {
6943 assert_eq!(
6944 ev("let x = 1; in { inherit x; }.x"),
6945 Value::Int(1),
6946 );
6947 }
6948
6949 #[test]
6958 fn inherit_plain_from_with_scope_lazy() {
6959 assert_eq!(
6963 ev("let fix = f: let x = f x; in x;
6964 self = fix (self: with self; {
6965 a = use { inherit cp; };
6966 use = { cp }: cp 5;
6967 cp = x: x + 100;
6968 });
6969 in self.a"),
6970 Value::Int(105),
6971 );
6972 assert_eq!(
6974 ev("with { y = 7; }; { inherit y; }.y"),
6975 Value::Int(7),
6976 );
6977 }
6978
6979 #[test]
6980 fn inherit_multiple_from_expr() {
6981 assert_eq!(
6982 ev("let s = { a = 10; b = 20; c = 30; }; in let inherit (s) a b c; in a + b + c"),
6983 Value::Int(60),
6984 );
6985 }
6986
6987 #[test]
6990 fn interp_nested_attrset_access() {
6991 assert_eq!(
6992 ev(r#"let x = { a = "hello"; }; in "${x.a} world""#),
6993 Value::string("hello world"),
6994 );
6995 }
6996
6997 #[test]
6998 fn interp_with_let_expression() {
6999 assert_eq!(
7000 ev(r#""${let x = "inner"; in x}""#),
7001 Value::string("inner"),
7002 );
7003 }
7004
7005 #[test]
7006 fn interp_float_coercion() {
7007 assert_eq!(
7009 ev(r#""${toString 3.14}""#),
7010 Value::string("3.140000"),
7011 );
7012 }
7013
7014 #[test]
7017 fn compare_mixed_int_float() {
7018 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
7019 assert_eq!(ev("1.5 > 1"), Value::Bool(true));
7020 assert_eq!(ev("2.0 == 2"), Value::Bool(true));
7021 }
7022
7023 #[test]
7024 fn compare_string_lexicographic() {
7025 assert_eq!(ev(r#""abc" < "abd""#), Value::Bool(true));
7026 assert_eq!(ev(r#""abc" < "abc""#), Value::Bool(false));
7027 assert_eq!(ev(r#""abc" <= "abc""#), Value::Bool(true));
7028 }
7029
7030 #[test]
7033 fn update_empty_sets() {
7034 let v = ev("{} // {}");
7035 if let Value::Attrs(a) = v { assert!(a.is_empty()); } else { panic!(); }
7036 }
7037
7038 #[test]
7039 fn update_right_overrides_completely() {
7040 assert_eq!(
7041 ev("{ a = 1; b = 2; } // { a = 10; c = 30; }"),
7042 ev("{ a = 10; b = 2; c = 30; }"),
7043 );
7044 }
7045
7046 #[test]
7047 fn update_chained() {
7048 assert_eq!(
7049 ev("{ a = 1; } // { b = 2; } // { c = 3; }"),
7050 ev("{ a = 1; b = 2; c = 3; }"),
7051 );
7052 }
7053
7054 #[test]
7057 fn force_value_concrete_unchanged() {
7058 let v = Value::Int(42);
7059 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
7060 }
7061
7062 #[test]
7063 fn force_value_null() {
7064 assert_eq!(force_value(&Value::Null).unwrap(), Value::Null);
7065 }
7066
7067 #[test]
7070 fn eval_with_file_none() {
7071 let result = eval_with_file("1 + 2", None).unwrap();
7072 assert_eq!(result, Value::Int(3));
7073 }
7074
7075 #[test]
7078 fn error_type_mismatch_in_comparison() {
7079 let result = eval(r#"1 < "a""#);
7080 assert!(result.is_err());
7081 }
7082
7083 #[test]
7084 fn error_select_from_non_set() {
7085 let result = eval("42.x");
7086 assert!(result.is_err());
7087 }
7088
7089 #[test]
7090 fn error_call_non_function() {
7091 let result = eval("42 1");
7092 assert!(result.is_err());
7093 }
7094
7095 #[test]
7096 fn error_negate_string() {
7097 let result = eval(r#"-"hello""#);
7098 assert!(result.is_err());
7099 }
7100
7101 #[test]
7104 fn multiline_string_empty() {
7105 assert_eq!(ev("''''"), Value::string(""));
7106 }
7107
7108 #[test]
7109 fn multiline_string_with_trailing_newline() {
7110 let v = ev("''\n hello\n''");
7111 assert_eq!(v, Value::string("hello\n"));
7112 }
7113
7114 #[test]
7117 fn list_concat_empty_left() {
7118 assert_eq!(ev("[] ++ [1 2]"), Value::list(vec![Value::Int(1), Value::Int(2)]));
7119 }
7120
7121 #[test]
7122 fn list_concat_empty_right() {
7123 assert_eq!(ev("[1 2] ++ []"), Value::list(vec![Value::Int(1), Value::Int(2)]));
7124 }
7125
7126 #[test]
7127 fn list_concat_both_empty() {
7128 assert_eq!(ev("[] ++ []"), Value::list(vec![]));
7129 }
7130
7131 #[test]
7134 fn formals_at_pattern_accessible() {
7135 assert_eq!(
7136 ev("({ x, ... } @ args: builtins.length (builtins.attrNames args)) { x = 1; y = 2; z = 3; }"),
7137 Value::Int(3),
7138 );
7139 }
7140
7141 #[test]
7142 fn formals_default_uses_other_arg() {
7143 assert_eq!(
7144 ev("({ x, y ? x + 1 }: y) { x = 10; }"),
7145 Value::Int(11),
7146 );
7147 }
7148
7149 #[test]
7150 fn formals_default_lazy_assert_false() {
7151 assert_eq!(
7155 ev("({ cpu, vendor ? assert false; null, kernel } @ args: if args ? vendor then vendor else \"inferred\") { cpu = \"x86_64\"; kernel = \"linux\"; }"),
7156 Value::String(Rc::new(NixString::plain("inferred"))),
7157 );
7158 }
7159
7160 #[test]
7161 fn formals_default_lazy_only_forced_when_accessed() {
7162 assert_eq!(
7164 ev("({ a, b ? 42 }: b) { a = 1; }"),
7165 Value::Int(42),
7166 );
7167 }
7168
7169 #[test]
7170 fn formals_ellipsis_ignores_extra() {
7171 assert_eq!(
7172 ev("({ x, ... }: x) { x = 1; y = 2; z = 3; }"),
7173 Value::Int(1),
7174 );
7175 }
7176
7177 #[test]
7180 fn pure_mode_roundtrip() {
7181 let was_pure = is_pure_mode();
7182 set_pure_mode(true);
7183 assert!(is_pure_mode());
7184 set_pure_mode(false);
7185 assert!(!is_pure_mode());
7186 set_pure_mode(was_pure);
7187 }
7188
7189 #[test]
7192 fn path_concat_with_string() {
7193 assert_eq!(
7194 ev(r#"/foo + "bar""#),
7195 Value::Path(Box::new(SmolStr::from("/foobar"))),
7196 );
7197 }
7198
7199 #[test]
7200 fn path_concat_with_path() {
7201 assert_eq!(
7202 ev("/foo + /bar"),
7203 Value::Path(Box::new(SmolStr::from("/foo//bar"))),
7204 );
7205 }
7206
7207 #[test]
7210 fn current_eval_dir_empty_when_no_file_pushed() {
7211 let snapshot = current_eval_dir();
7215 let _ = snapshot;
7217 }
7218
7219 #[test]
7220 fn push_eval_file_sets_current_dir() {
7221 let p = std::path::PathBuf::from("/tmp/example/file.nix");
7222 {
7223 let _g = push_eval_file(p.clone());
7224 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/tmp/example")));
7225 }
7226 }
7230
7231 #[test]
7232 fn push_eval_file_nested_stack() {
7233 let outer = std::path::PathBuf::from("/a/x.nix");
7234 let inner = std::path::PathBuf::from("/b/y.nix");
7235 {
7236 let _g_outer = push_eval_file(outer.clone());
7237 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
7238 {
7239 let _g_inner = push_eval_file(inner.clone());
7240 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/b")));
7241 }
7242 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
7244 }
7245 }
7246
7247 #[test]
7255 fn fileless_frame_masks_parent_file() {
7256 let outer = std::path::PathBuf::from("/a/x.nix");
7257 let _g_outer = push_eval_file(outer.clone());
7258 assert_eq!(current_eval_file(), Some(outer.clone()));
7259 {
7260 let _g_none = push_eval_frame(None);
7261 assert_eq!(current_eval_file(), None);
7263 assert_eq!(current_eval_dir(), None);
7264 assert_eq!(eval_file_stack_snapshot().last().map(String::as_str), Some("<no-file>"));
7265 }
7266 assert_eq!(current_eval_file(), Some(outer));
7268 }
7269
7270 #[test]
7273 fn error_undefined_var_includes_file_context() {
7274 let p = std::path::PathBuf::from("/nix/store/abc-default.nix");
7275 let _g = push_eval_file(p);
7276 let result = eval("nonexistent_xyz");
7277 let msg = format!("{}", result.unwrap_err());
7278 assert!(msg.contains("undefined variable"), "msg: {msg}");
7279 assert!(msg.contains("nonexistent_xyz"), "msg: {msg}");
7280 assert!(msg.contains("abc-default.nix"), "msg: {msg}");
7281 }
7282
7283 #[test]
7284 fn error_attr_not_found_includes_file_context() {
7285 let p = std::path::PathBuf::from("/nix/store/xyz-module.nix");
7286 let _g = push_eval_file(p);
7287 let result = eval("{}.missing_key");
7288 let msg = format!("{}", result.unwrap_err());
7289 assert!(msg.contains("not found") || msg.contains("missing_key"), "msg: {msg}");
7290 assert!(msg.contains("xyz-module.nix"), "msg: {msg}");
7291 }
7292
7293 #[test]
7294 fn error_assertion_failed_includes_file_context() {
7295 let p = std::path::PathBuf::from("/nix/store/test-assert.nix");
7296 let _g = push_eval_file(p);
7297 let result = eval("assert false; 1");
7298 let msg = format!("{}", result.unwrap_err());
7299 assert!(msg.contains("assertion failed"), "msg: {msg}");
7300 assert!(msg.contains("test-assert.nix"), "msg: {msg}");
7301 }
7302
7303 #[test]
7318 fn inherit_bindings_carry_positions() {
7319 let dir = tempfile::tempdir().unwrap();
7320 let body = "{ inherit ({ x = 1; }) x; }\n";
7325 let f = dir.path().join("inh.nix");
7326 std::fs::write(&f, body).unwrap();
7327 let v = eval(&format!("builtins.unsafeGetAttrPos \"x\" (import {})", f.display())).unwrap();
7328 let attrs = match v {
7329 Value::Attrs(a) => a,
7330 Value::Null => panic!("null — the inherit binding carried no position"),
7331 o => panic!("expected attrs, got {o:?}"),
7332 };
7333 let off = body.rfind("x; }").unwrap();
7337 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
7338 assert_eq!(*attrs.get("line").unwrap(), Value::Int(1));
7339 assert_eq!(*attrs.get("column").unwrap(), Value::Int((off - bol) as i64 + 1));
7340 }
7341
7342 #[test]
7359 fn every_binding_form_carries_a_position() {
7360 let dir = tempfile::tempdir().unwrap();
7361 let body = concat!(
7363 "let src = { i = 1; j = 2; }; in {\n",
7364 " plain = 1;\n",
7365 " \"quoted\" = 2;\n",
7366 " inherit (src) i;\n",
7367 " inherit src;\n",
7368 " nested.deep = 3;\n",
7369 "}\n",
7370 );
7371 let f = dir.path().join("forms.nix");
7372 std::fs::write(&f, body).unwrap();
7373
7374 let keys = ["plain", "quoted", "i", "src", "nested"];
7376 let probe = keys
7377 .iter()
7378 .map(|k| format!(
7379 "(let q = builtins.unsafeGetAttrPos \"{k}\" t; \
7380 in if q == null then \"{k}=NULL\" \
7381 else \"{k}=${{toString q.line}}:${{toString q.column}}\")"
7382 ))
7383 .collect::<Vec<_>>()
7384 .join(" + \" \" + ");
7385 let got = eval(&format!("let t = import {}; in {probe}", f.display()))
7386 .unwrap()
7387 .as_string()
7388 .unwrap()
7389 .to_string();
7390
7391 assert!(!got.contains("NULL"), "a binding form lost its position: {got}");
7392 let rows: Vec<&str> = got.split(' ').collect();
7393 assert_eq!(rows.len(), keys.len(), "corpus shrank — gate would be vacuous: {got}");
7394
7395 for (k, row) in keys.iter().zip(&rows) {
7397 let needle = match *k {
7398 "quoted" => "\"quoted\"".to_string(),
7399 "i" => "i;".to_string(),
7400 "src" => "src;".to_string(),
7401 "nested" => "nested.".to_string(),
7404 other => format!("{other} ="),
7405 };
7406 let off = body.find(&needle).unwrap();
7407 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
7408 let line = 1 + body[..off].matches('\n').count();
7409 let col = off - bol + 1;
7410 assert_eq!(*row, format!("{k}={line}:{col}"), "wrong position for `{k}` in:\n{body}");
7411 }
7412 }
7413
7414 #[test]
7427 fn error_missing_argument_includes_file_context() {
7428 let p = std::path::PathBuf::from("/nix/store/func.nix");
7429 let result = eval_with_file("({ a, b }: a) { a = 1; }", Some(p));
7430 let msg = format!("{}", result.unwrap_err());
7431 assert!(msg.contains("missing argument"), "msg: {msg}");
7432 assert!(msg.contains("func.nix"), "msg: {msg}");
7433 }
7434
7435 #[test]
7436 fn error_cannot_call_includes_file_context() {
7437 let p = std::path::PathBuf::from("/nix/store/call.nix");
7438 let _g = push_eval_file(p);
7439 let result = eval("42 99");
7440 let msg = format!("{}", result.unwrap_err());
7441 assert!(msg.contains("cannot call"), "msg: {msg}");
7442 assert!(msg.contains("call.nix"), "msg: {msg}");
7443 }
7444
7445 #[test]
7446 fn error_without_file_has_no_in_prefix() {
7447 let result = eval("nonexistent_xyz");
7450 let msg = format!("{}", result.unwrap_err());
7451 assert!(msg.contains("undefined variable"), "msg: {msg}");
7452 assert!(!msg.contains(", in"), "msg should not contain file context: {msg}");
7453 }
7454
7455 #[test]
7458 fn pure_mode_set_get_independence() {
7459 let was = is_pure_mode();
7460 set_pure_mode(true);
7461 assert!(is_pure_mode());
7462 set_pure_mode(false);
7463 assert!(!is_pure_mode());
7464 set_pure_mode(was);
7465 }
7466
7467 #[test]
7470 fn eval_with_file_some_path_arithmetic() {
7471 let p = std::path::PathBuf::from("/tmp/imaginary.nix");
7472 let result = eval_with_file("1 + 2", Some(p)).unwrap();
7473 assert_eq!(result, Value::Int(3));
7474 }
7475
7476 #[test]
7484 fn unsafe_get_attr_pos_reports_file_and_offset_column() {
7485 let dir = tempfile::tempdir().unwrap();
7497 let file_body = "{ a = 1;\n b = 2; }\n";
7499 let f = dir.path().join("lit.nix");
7500 std::fs::write(&f, file_body).unwrap();
7501 let src = format!("builtins.unsafeGetAttrPos \"b\" (import {})", f.display());
7502 let v = eval(&src).unwrap();
7503 let attrs = match v { Value::Attrs(a) => a, other => panic!("expected attrs, got {other:?}") };
7504 assert_eq!(
7505 attrs.get("file").unwrap().as_string().unwrap(),
7506 f.to_string_lossy(),
7507 );
7508 let off = file_body.find("b = 2").unwrap();
7510 let bol = file_body[..off].rfind('\n').map_or(0, |i| i + 1);
7511 let expected_line = 1 + file_body[..off].matches('\n').count() as i64;
7512 let expected_col = (off - bol) as i64 + 1;
7513 assert_eq!(expected_line, 2, "fixture must put `b` on line 2");
7514 assert_eq!(*attrs.get("line").unwrap(), Value::Int(expected_line));
7515 let col = match attrs.get("column").unwrap() { Value::Int(n) => *n, o => panic!("{o:?}") };
7516 assert_eq!(col, expected_col, "column must be the 1-based BYTE column");
7517 }
7518
7519 #[test]
7520 fn unsafe_get_attr_pos_null_for_string_origin() {
7521 let v = eval("builtins.unsafeGetAttrPos \"a\" { a = 1; }").unwrap();
7523 assert_eq!(v, Value::Null);
7524 }
7525
7526 #[test]
7527 fn unsafe_get_attr_pos_null_for_missing_key() {
7528 let dir = tempfile::tempdir().unwrap();
7530 let f = dir.path().join("lit.nix");
7531 std::fs::write(&f, "{ a = 1; }\n").unwrap();
7532 let src = format!("builtins.unsafeGetAttrPos \"zzz\" (import {})", f.display());
7533 let v = eval(&src).unwrap();
7534 assert_eq!(v, Value::Null);
7535 }
7536
7537 #[test]
7540 fn interp_int_into_string() {
7541 assert_eq!(ev(r#""val=${toString 42}""#), Value::string("val=42"));
7543 }
7544
7545 #[test]
7546 fn interp_bool_true_becomes_one() {
7547 let v = ev(r#"let x = true; in "${builtins.toString x}""#);
7549 assert_eq!(v, Value::string("1"));
7550 }
7551
7552 #[test]
7553 fn interp_null_becomes_empty() {
7554 let v = ev(r#"let x = null; in "${builtins.toString x}""#);
7556 assert_eq!(v, Value::string(""));
7557 }
7558
7559 #[test]
7560 fn interp_attrset_without_to_string_errors() {
7561 let result = eval(r#"let s = { x = 1; }; in "${s}""#);
7563 assert!(result.is_err());
7564 }
7565
7566 #[test]
7567 fn interp_attrset_with_to_string_protocol() {
7568 let v = ev(r#""${{ __toString = self: "ok"; }}""#);
7570 assert_eq!(v, Value::string("ok"));
7571 }
7572
7573 #[test]
7576 fn eval_path_absolute_literal() {
7577 let v = ev("/tmp/foo");
7578 match v {
7579 Value::Path(p) => assert!(p.contains("/tmp/foo")),
7580 _ => panic!("expected Path"),
7581 }
7582 }
7583
7584 #[test]
7585 fn eval_path_home_literal() {
7586 let v = ev("~/foo.nix");
7587 match v {
7588 Value::Path(p) => assert!(p.contains("~/foo.nix") || p.ends_with("foo.nix")),
7589 _ => panic!("expected Path"),
7590 }
7591 }
7592
7593 #[test]
7596 fn path_search_unmatched_errors() {
7597 let saved = std::env::var("NIX_PATH").ok();
7600 unsafe {
7604 std::env::remove_var("NIX_PATH");
7605 }
7606 let result = eval("<this_should_not_resolve>");
7607 if let Some(v) = saved {
7608 unsafe {
7609 std::env::set_var("NIX_PATH", v);
7610 }
7611 }
7612 assert!(result.is_err());
7613 }
7614
7615 #[test]
7618 fn unary_negate_int() {
7619 assert_eq!(ev("-7"), Value::Int(-7));
7620 }
7621
7622 #[test]
7623 fn unary_negate_float() {
7624 assert_eq!(ev("-2.5"), Value::Float(-2.5));
7625 }
7626
7627 #[test]
7628 fn unary_invert_true() {
7629 assert_eq!(ev("!true"), Value::Bool(false));
7630 }
7631
7632 #[test]
7633 fn unary_invert_false() {
7634 assert_eq!(ev("!false"), Value::Bool(true));
7635 }
7636
7637 #[test]
7638 fn unary_negate_bool_errors() {
7639 let result = eval("-true");
7640 assert!(result.is_err());
7641 }
7642
7643 #[test]
7644 fn unary_invert_int_errors() {
7645 let result = eval("!42");
7646 assert!(result.is_err());
7647 }
7648
7649 #[test]
7652 fn binop_add_attrs_errors() {
7653 let result = eval("{a=1;} + {b=2;}");
7654 assert!(result.is_err());
7655 }
7656
7657 #[test]
7658 fn binop_sub_string_errors() {
7659 let result = eval(r#""a" - "b""#);
7660 assert!(result.is_err());
7661 }
7662
7663 #[test]
7664 fn binop_mul_string_errors() {
7665 let result = eval(r#""a" * "b""#);
7666 assert!(result.is_err());
7667 }
7668
7669 #[test]
7670 fn binop_div_string_errors() {
7671 let result = eval(r#""a" / "b""#);
7672 assert!(result.is_err());
7673 }
7674
7675 #[test]
7676 fn binop_compare_attrs_errors() {
7677 let result = eval("{a=1;} < {b=2;}");
7678 assert!(result.is_err());
7679 }
7680
7681 #[test]
7682 fn binop_div_float_by_zero_int() {
7683 let result = eval("1.0 / 0");
7687 let _ = result;
7690 }
7691
7692 #[test]
7693 fn binop_int_div_zero_is_division_by_zero() {
7694 let result = eval("5 / 0");
7695 match result {
7696 Err(EvalError::DivisionByZero) => {}
7697 other => panic!("expected DivisionByZero, got {other:?}"),
7698 }
7699 }
7700
7701 #[test]
7704 fn if_else_only_chosen_branch_evaluated_then() {
7705 assert_eq!(ev("if true then 42 else 1 / 0"), Value::Int(42));
7708 }
7709
7710 #[test]
7711 fn if_else_only_chosen_branch_evaluated_else() {
7712 assert_eq!(ev("if false then 1 / 0 else 99"), Value::Int(99));
7713 }
7714
7715 #[test]
7716 fn if_condition_must_be_bool() {
7717 let result = eval("if 1 then 1 else 2");
7718 assert!(result.is_err());
7719 }
7720
7721 #[test]
7722 fn if_condition_lazy_does_not_force_unused() {
7723 assert_eq!(
7726 ev("let bad = 1 / 0; in if true then 42 else bad"),
7727 Value::Int(42),
7728 );
7729 }
7730
7731 #[test]
7734 fn and_short_circuits_on_false() {
7735 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
7737 }
7738
7739 #[test]
7740 fn or_short_circuits_on_true() {
7741 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
7742 }
7743
7744 #[test]
7745 fn implication_short_circuits_on_false_lhs() {
7746 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
7748 }
7749
7750 #[test]
7753 fn lambda_fix_combinator_returns_attrset() {
7754 let v = ev(
7756 "let fix = f: let x = f x; in x; in
7757 (fix (self: { val = 1; double = self.val * 2; })).double",
7758 );
7759 assert_eq!(v, Value::Int(2));
7760 }
7761
7762 #[test]
7765 fn rec_attrset_self_reference() {
7766 let v = ev("(rec { a = b; b = 1; }).a");
7768 assert_eq!(v, Value::Int(1));
7769 }
7770
7771 #[test]
7772 fn rec_attrset_inherit_from_uses_outer_scope() {
7773 let v = ev(
7777 "let src = { a = 10; }; in
7778 rec {
7779 inherit (src) a;
7780 b = a + 1;
7781 }",
7782 );
7783 if let Value::Attrs(attrs) = v {
7784 let b = attrs.get("b").unwrap();
7785 let b_forced = force_value(b).unwrap();
7786 assert_eq!(b_forced, Value::Int(11));
7787 } else {
7788 panic!("expected attrs");
7789 }
7790 }
7791
7792 #[test]
7793 fn nonrec_attrset_no_self_reference() {
7794 let result = eval("({ a = 1; b = a + 1; }).b");
7797 assert!(result.is_err());
7798 }
7799
7800 #[test]
7803 fn dotted_binding_three_segments_then_sibling() {
7804 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
7805 if let Value::Attrs(attrs) = v {
7806 let a = attrs.get("a").unwrap();
7807 let a_forced = force_value(a).unwrap();
7808 if let Value::Attrs(a_attrs) = a_forced {
7809 let b = a_attrs.get("b").unwrap();
7810 let b_forced = force_value(b).unwrap();
7811 if let Value::Attrs(b_attrs) = b_forced {
7812 assert_eq!(force_value(b_attrs.get("c").unwrap()).unwrap(), Value::Int(1));
7813 assert_eq!(force_value(b_attrs.get("d").unwrap()).unwrap(), Value::Int(2));
7814 } else {
7815 panic!("expected b to be attrs");
7816 }
7817 assert_eq!(force_value(a_attrs.get("e").unwrap()).unwrap(), Value::Int(3));
7818 } else {
7819 panic!("expected a to be attrs");
7820 }
7821 } else {
7822 panic!("expected outer attrs");
7823 }
7824 }
7825
7826 #[test]
7829 fn rec_dotted_bindings_visible_to_siblings() {
7830 let v = ev("rec { types.openSB = 1; types.openCpu = 2; foo = types.openSB; }.foo");
7833 assert_eq!(v, Value::Int(1));
7834 }
7835
7836 #[test]
7837 fn rec_dotted_leaf_uses_rec_scope() {
7838 let v = ev("rec { types.a = f 1; f = x: x + 1; }.types.a");
7841 assert_eq!(v, Value::Int(2));
7842 }
7843
7844 #[test]
7845 fn rec_dotted_multiple_keys_merge() {
7846 let v = ev("rec { types.a = 1; types.b = 2; x = types; }.x");
7848 if let Value::Attrs(attrs) = v {
7849 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
7850 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
7851 } else {
7852 panic!("expected attrs");
7853 }
7854 }
7855
7856 #[test]
7857 fn rec_nixpkgs_parse_pattern() {
7858 let v = ev(r#"
7862 let
7863 mkOptionType = x: x;
7864 mergeOneOption = "merge";
7865 attrValues = builtins.attrValues;
7866 setType = name: value: { __type = name; } // value;
7867 mapAttrs = builtins.mapAttrs;
7868 enum = xs: mkOptionType { name = "enum"; check = x: builtins.elem x xs; };
7869 setTypes = type: mapAttrs (name: value: setType type.name ({ inherit name; } // value));
7870 in
7871 rec {
7872 types.openSB = mkOptionType { name = "sb"; merge = mergeOneOption; };
7873 types.significantByte = enum (attrValues significantBytes);
7874 significantBytes = setTypes types.openSB { bigEndian = {}; littleEndian = {}; };
7875 types.openCpuType = mkOptionType { name = "cpu-type"; };
7876 types.cpuType = enum (attrValues cpuTypes);
7877 cpuTypes = setTypes types.openCpuType { arm = { bits = 32; }; };
7878 }.types.openCpuType
7879 "#);
7880 if let Value::Attrs(attrs) = v {
7881 assert_eq!(
7882 force_value(attrs.get("name").unwrap()).unwrap(),
7883 Value::string("cpu-type")
7884 );
7885 } else {
7886 panic!("expected attrs");
7887 }
7888 }
7889
7890 #[test]
7891 fn let_dotted_leaf_uses_let_scope() {
7892 let v = ev("let a.x = f 1; f = x: x + 1; in a.x");
7894 assert_eq!(v, Value::Int(2));
7895 }
7896
7897 #[test]
7898 fn let_inherit_from_plus_dotted_overrides() {
7899 let v = ev(r#"
7905 let
7906 src = { types = { existing = true; }; };
7907 inherit (src) types;
7908 types.added = true;
7909 in types
7910 "#);
7911 if let Value::Attrs(attrs) = v {
7912 assert_eq!(
7914 force_value(attrs.get("added").unwrap()).unwrap(),
7915 Value::Bool(true)
7916 );
7917 assert!(attrs.get("existing").is_none());
7919 } else {
7920 panic!("expected attrs");
7921 }
7922 }
7923
7924 #[test]
7927 fn pattern_empty_no_args_no_ellipsis() {
7928 assert_eq!(ev("({}: 1) {}"), Value::Int(1));
7930 }
7931
7932 #[test]
7933 fn pattern_empty_with_ellipsis_accepts_extra() {
7934 assert_eq!(ev("({...}: 1) { a = 1; b = 2; }"), Value::Int(1));
7935 }
7936
7937 #[test]
7938 fn pattern_all_defaults() {
7939 assert_eq!(
7940 ev("({a ? 1, b ? 2}: a + b) {}"),
7941 Value::Int(3),
7942 );
7943 }
7944
7945 #[test]
7946 fn pattern_at_bind_before() {
7947 assert_eq!(ev("(args @ { x }: args.x) { x = 7; }"), Value::Int(7));
7949 }
7950
7951 #[test]
7952 fn pattern_at_bind_after() {
7953 assert_eq!(ev("({ x } @ args: args.x) { x = 7; }"), Value::Int(7));
7955 }
7956
7957 #[test]
7958 fn pattern_default_references_other_arg() {
7959 assert_eq!(ev("({a, b ? a + 1}: b) {a = 10;}"), Value::Int(11));
7961 }
7962
7963 #[test]
7964 fn pattern_required_missing_errors() {
7965 let result = eval("({ a, b }: a) { a = 1; }");
7966 assert!(result.is_err());
7967 }
7968
7969 #[test]
7970 fn pattern_unexpected_errors_without_ellipsis() {
7971 let result = eval("({ a }: a) { a = 1; b = 2; }");
7972 assert!(result.is_err());
7973 }
7974
7975 #[test]
7978 fn apply_int_errors() {
7979 let result = eval("42 5");
7980 assert!(result.is_err());
7981 }
7982
7983 #[test]
7984 fn apply_string_errors() {
7985 let result = eval(r#""hi" 5"#);
7986 assert!(result.is_err());
7987 }
7988
7989 #[test]
7990 fn apply_attrset_without_functor_errors() {
7991 let result = eval("{ x = 1; } 5");
7992 assert!(result.is_err());
7993 let msg = format!("{}", result.unwrap_err());
7994 assert!(msg.contains("__functor") || msg.contains("cannot call"));
7995 }
7996
7997 #[test]
8000 fn select_multi_segment_with_default() {
8001 assert_eq!(ev("{ a = { b = 1; }; }.a.c or 99"), Value::Int(99));
8003 }
8004
8005 #[test]
8006 fn select_from_int_errors() {
8007 let result = eval("(1).x");
8008 assert!(result.is_err());
8009 }
8010
8011 #[test]
8014 fn has_attr_on_non_set_returns_false() {
8015 assert_eq!(ev("1 ? x"), Value::Bool(false));
8017 }
8018
8019 #[test]
8020 fn has_attr_nested_path_present() {
8021 assert_eq!(ev("{ a = { b = 1; }; } ? a.b"), Value::Bool(true));
8022 }
8023
8024 #[test]
8025 fn has_attr_nested_path_missing() {
8026 assert_eq!(ev("{ a = { b = 1; }; } ? a.c"), Value::Bool(false));
8027 }
8028
8029 #[test]
8030 fn has_attr_intermediate_missing_returns_false() {
8031 assert_eq!(ev("{} ? a.b.c"), Value::Bool(false));
8032 }
8033
8034 #[test]
8037 fn list_with_function_value() {
8038 let v = ev("[(x: x + 1)]");
8039 if let Value::List(items) = v {
8040 assert_eq!(items.len(), 1);
8041 let forced = force_value(&items[0]).unwrap();
8043 assert!(matches!(forced, Value::Lambda(_)));
8044 } else {
8045 panic!("expected list");
8046 }
8047 }
8048
8049 #[test]
8052 fn inherit_unknown_name_errors() {
8053 let result = eval("let x = 1; in let inherit nonexistent; in nonexistent");
8054 assert!(result.is_err());
8055 }
8056
8057 #[test]
8060 fn string_concat_no_context_when_both_plain() {
8061 let v = ev(r#""abc" + "def""#);
8062 if let Value::String(ns) = v {
8063 assert_eq!(ns.chars, "abcdef");
8064 assert!(!ns.has_context());
8065 } else {
8066 panic!("expected string");
8067 }
8068 }
8069
8070 #[test]
8073 fn parens_around_expression() {
8074 assert_eq!(ev("(1 + 2)"), Value::Int(3));
8075 }
8076
8077 #[test]
8078 fn nested_parens() {
8079 assert_eq!(ev("(((42)))"), Value::Int(42));
8080 }
8081
8082 #[test]
8085 fn throw_propagates_as_error() {
8086 let result = eval(r#"builtins.throw "kaboom""#);
8087 match result {
8088 Err(EvalError::Throw(s)) => assert!(s.contains("kaboom")),
8089 other => panic!("expected Throw, got {other:?}"),
8090 }
8091 }
8092
8093 #[test]
8094 fn assert_failed_propagates_as_error() {
8095 let result = eval("assert false; 1");
8096 match result {
8097 Err(EvalError::AssertionFailed(_)) => {}
8098 other => panic!("expected AssertionFailed, got {other:?}"),
8099 }
8100 }
8101
8102 #[test]
8105 fn string_no_interp_yields_no_context() {
8106 let v = ev(r#""just literal""#);
8107 if let Value::String(ns) = v {
8108 assert!(!ns.has_context());
8109 } else {
8110 panic!("expected string");
8111 }
8112 }
8113
8114 #[test]
8123 fn interp_path_copies_to_store_byte_matches_cppnix() {
8124 let dir = std::env::temp_dir().join(format!("sui-r5-interp-{}", std::process::id()));
8125 let _ = std::fs::remove_dir_all(&dir);
8126 std::fs::create_dir_all(&dir).unwrap();
8127 let f = dir.join("data.txt");
8128 std::fs::write(&f, b"hello\n").unwrap();
8129 let expr = format!(r#""${{{}}}""#, f.display());
8130 let v = eval(&expr).unwrap();
8131 if let Value::String(ns) = v {
8132 assert_eq!(
8133 ns.chars.to_string(),
8134 "/nix/store/y9dmvfhip31hg8ia4njwjz9vfa3ndphr-data.txt",
8135 );
8136 assert!(ns.has_context());
8137 } else {
8138 panic!("expected string");
8139 }
8140 let _ = std::fs::remove_dir_all(&dir);
8141 }
8142
8143 #[test]
8152 fn parse_error_unbalanced_braces() {
8153 let result = eval("{ a = 1");
8154 assert!(result.is_err());
8155 let err = result.unwrap_err();
8156 assert!(matches!(err, EvalError::ParseError(_)));
8157 }
8158
8159 #[test]
8160 fn parse_error_dangling_let() {
8161 let result = eval("let in");
8162 assert!(result.is_err());
8163 }
8164
8165 #[test]
8166 fn parse_error_empty_input() {
8167 let result = eval("");
8168 assert!(result.is_err());
8169 }
8170
8171 #[test]
8174 fn float_int_subtraction() {
8175 assert_eq!(ev("3.5 - 1"), Value::Float(2.5));
8176 }
8177
8178 #[test]
8179 fn int_float_subtraction() {
8180 assert_eq!(ev("3 - 0.5"), Value::Float(2.5));
8181 }
8182
8183 #[test]
8184 fn float_float_division() {
8185 assert_eq!(ev("6.0 / 2.0"), Value::Float(3.0));
8186 }
8187
8188 #[test]
8189 fn int_float_multiplication() {
8190 assert_eq!(ev("3 * 2.5"), Value::Float(7.5));
8191 }
8192
8193 #[test]
8196 fn compare_int_float_less() {
8197 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
8198 }
8199
8200 #[test]
8201 fn compare_float_int_more() {
8202 assert_eq!(ev("3.5 > 3"), Value::Bool(true));
8203 }
8204
8205 #[test]
8206 fn compare_equal_int_float() {
8207 assert_eq!(ev("3 <= 3.0"), Value::Bool(true));
8208 }
8209
8210 #[test]
8213 fn equal_lists_same() {
8214 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
8215 }
8216
8217 #[test]
8218 fn equal_lists_diff_length() {
8219 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
8220 }
8221
8222 #[test]
8223 fn not_equal_lists() {
8224 assert_eq!(ev("[1] != [2]"), Value::Bool(true));
8225 }
8226
8227 #[test]
8228 fn equal_attrsets_same() {
8229 assert_eq!(ev("{a = 1; b = 2;} == {b = 2; a = 1;}"), Value::Bool(true));
8230 }
8231
8232 #[test]
8239 fn lambda_self_equality_in_attrset() {
8240 assert_eq!(
8242 ev("let f = x: x; in { a = 1; inherit f; } == { a = 1; inherit f; }"),
8243 Value::Bool(true),
8244 );
8245 }
8246
8247 #[test]
8248 fn lambda_self_reference_attrset_equality() {
8249 assert_eq!(
8251 ev("let x = { a = 1; f = y: y; }; in x == x"),
8252 Value::Bool(true),
8253 );
8254 }
8255
8256 #[test]
8257 fn lambda_different_closures_not_equal() {
8258 assert_eq!(
8260 ev("{ f = x: x; } == { f = x: x; }"),
8261 Value::Bool(false),
8262 );
8263 }
8264
8265 #[test]
8266 fn lambda_ne_does_not_force_unused_branch() {
8267 assert_eq!(
8270 ev("let ls = { a = 1; f = x: x; }; in if ls != ls then builtins.throw \"bug\" else 42"),
8271 Value::Int(42),
8272 );
8273 }
8274
8275 #[test]
8278 fn force_value_through_thunk() {
8279 let root = rnix::Root::parse("1 + 2");
8280 let expr = root.tree().expr().unwrap();
8281 let thunk = Thunk::new_suspended(expr, Env::new());
8282 let val = Value::Thunk(thunk);
8283 assert_eq!(force_value(&val).unwrap(), Value::Int(3));
8284 }
8285
8286 #[test]
8289 fn try_eval_catches_thrown_error() {
8290 let v = ev(r#"(builtins.tryEval (builtins.throw "oops")).success"#);
8292 assert_eq!(v, Value::Bool(false));
8293 }
8294
8295 #[test]
8296 fn try_eval_returns_value_on_success() {
8297 let v = ev("(builtins.tryEval 42).value");
8298 assert_eq!(v, Value::Int(42));
8299 }
8300
8301 #[test]
8304 fn legacy_let_returns_body_attr() {
8305 assert_eq!(ev("let { x = 1; body = x + 41; }"), Value::Int(42));
8309 }
8310
8311 #[test]
8312 fn legacy_let_missing_body_errors() {
8313 let result = eval("let { x = 1; }");
8314 assert!(result.is_err());
8315 }
8316
8317 #[test]
8318 fn legacy_let_with_inherit_from_scope() {
8319 assert_eq!(
8320 ev("let outer = 5; in let { inherit outer; body = outer * 2; }"),
8321 Value::Int(10),
8322 );
8323 }
8324
8325 #[test]
8328 fn interp_with_string_concat_preserves_order() {
8329 assert_eq!(
8330 ev(r#"let a = "x"; b = "y"; in "${a}-${b}""#),
8331 Value::string("x-y"),
8332 );
8333 }
8334
8335 #[test]
8336 fn interp_only_literal_part() {
8337 assert_eq!(ev(r#""no interp here""#), Value::string("no interp here"));
8338 }
8339
8340 #[test]
8343 fn dynamic_attr_via_string_key_in_set() {
8344 assert_eq!(ev(r#"{ "a" = 1; }.a"#), Value::Int(1));
8346 }
8347
8348 #[test]
8349 fn dynamic_attr_via_interpolated_key() {
8350 let v = ev(r#"let k = "foo"; in { ${k} = 99; }.foo"#);
8351 assert_eq!(v, Value::Int(99));
8352 }
8353
8354 #[test]
8357 fn select_with_string_key() {
8358 let v = ev(r#"{ a = 42; }."a""#);
8359 assert_eq!(v, Value::Int(42));
8360 }
8361
8362 #[test]
8365 fn apply_attrset_with_functor_works() {
8366 let v = ev("let s = { __functor = self: x: x + 1; }; in s 5");
8367 assert_eq!(v, Value::Int(6));
8368 }
8369
8370 #[test]
8373 fn double_negate_int() {
8374 assert_eq!(ev("- (-5)"), Value::Int(5));
8375 }
8376
8377 #[test]
8380 fn inherit_in_let_makes_name_available() {
8381 assert_eq!(
8382 ev("let src = { a = 7; }; in let inherit (src) a; in a"),
8383 Value::Int(7),
8384 );
8385 }
8386
8387 #[test]
8390 fn path_plus_string_yields_path() {
8391 let v = ev(r#"/foo + "/bar""#);
8392 match v {
8393 Value::Path(p) => assert_eq!(&*p, "/foo/bar"),
8394 _ => panic!("expected path"),
8395 }
8396 }
8397
8398 #[test]
8401 fn attrset_value_not_forced_unless_selected() {
8402 assert_eq!(
8405 ev(r#"{ bad = builtins.throw "boom"; good = 42; }.good"#),
8406 Value::Int(42),
8407 );
8408 }
8409
8410 #[test]
8413 fn lambda_recursive_via_let() {
8414 assert_eq!(
8416 ev("let fact = n: if n == 0 then 1 else n * fact (n - 1); in fact 5"),
8417 Value::Int(120),
8418 );
8419 }
8420
8421 #[test]
8424 fn select_with_dynamic_key_via_var() {
8425 assert_eq!(ev(r#"let k = { x = 1; }; in k.x"#), Value::Int(1));
8428 }
8429
8430 #[test]
8433 fn compare_string_lex_greater_or_equal() {
8434 assert_eq!(ev(r#""b" >= "a""#), Value::Bool(true));
8435 assert_eq!(ev(r#""a" >= "a""#), Value::Bool(true));
8436 assert_eq!(ev(r#""a" >= "b""#), Value::Bool(false));
8437 }
8438
8439 #[test]
8442 fn equal_int_string_false() {
8443 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
8444 }
8445
8446 #[test]
8447 fn equal_null_int_false() {
8448 assert_eq!(ev("null == 0"), Value::Bool(false));
8449 }
8450
8451 #[test]
8454 fn update_with_let_bound_operands() {
8455 assert_eq!(
8456 ev("let a = { x = 1; }; b = { y = 2; }; in (a // b).y"),
8457 Value::Int(2),
8458 );
8459 }
8460
8461 #[test]
8464 fn concat_lists_from_let() {
8465 assert_eq!(
8466 ev("let a = [1 2]; b = [3 4]; in builtins.length (a ++ b)"),
8467 Value::Int(4),
8468 );
8469 }
8470
8471 #[test]
8474 fn interp_list_coerces_with_spaces() {
8475 assert_eq!(
8478 ev(r#""${toString [1 2 3]}""#),
8479 Value::string("1 2 3"),
8480 );
8481 }
8482
8483 #[test]
8484 fn interp_list_directly_coerces() {
8485 assert_eq!(
8487 ev(r#""${[1 2]}""#),
8488 Value::string("1 2"),
8489 );
8490 }
8491
8492 #[test]
8495 fn interp_outpath_attrset() {
8496 assert_eq!(
8497 ev(r#"let x = { outPath = "/nix/store/abc"; }; in "${x}""#),
8498 Value::string("/nix/store/abc"),
8499 );
8500 }
8501
8502 #[test]
8503 fn interp_tostring_takes_priority_over_outpath() {
8504 assert_eq!(
8505 ev(r#"let x = { __toString = self: "custom"; outPath = "/ignored"; }; in "${x}""#),
8506 Value::string("custom"),
8507 );
8508 }
8509
8510 #[test]
8511 fn interp_derivation_coerces_to_outpath() {
8512 let result = eval(r#"
8514 let drv = builtins.derivation {
8515 name = "test";
8516 system = "x86_64-linux";
8517 builder = "/bin/sh";
8518 };
8519 in "${drv}"
8520 "#).unwrap();
8521 if let Value::String(s) = result {
8522 assert!(s.chars.starts_with("/nix/store/"), "got: {}", s.chars);
8523 } else {
8524 panic!("expected string");
8525 }
8526 }
8527
8528 #[test]
8531 fn interp_lambda_errors() {
8532 let result = eval(r#""${x: x}""#);
8533 assert!(result.is_err());
8534 }
8535
8536 #[test]
8539 fn force_value_int_returns_same() {
8540 let v = Value::Int(42);
8541 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
8542 }
8543
8544 #[test]
8545 fn force_value_bool_returns_same() {
8546 let v = Value::Bool(true);
8547 assert_eq!(force_value(&v).unwrap(), Value::Bool(true));
8548 }
8549
8550 #[test]
8551 fn force_value_string_returns_same() {
8552 let v = Value::string("hello");
8553 assert_eq!(force_value(&v).unwrap(), Value::string("hello"));
8554 }
8555
8556 #[test]
8557 fn force_value_attrs_returns_same() {
8558 let mut a = NixAttrs::new();
8559 a.insert("x".to_string(), Value::Int(1));
8560 let v = Value::Attrs(Rc::new(a.clone()));
8561 assert_eq!(force_value(&v).unwrap(), Value::Attrs(Rc::new(a)));
8562 }
8563
8564 #[test]
8565 fn force_value_list_returns_same() {
8566 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
8567 assert_eq!(
8568 force_value(&v).unwrap(),
8569 Value::list(vec![Value::Int(1), Value::Int(2)]),
8570 );
8571 }
8572
8573 #[test]
8574 fn force_value_null_returns_null() {
8575 let v = Value::Null;
8576 assert_eq!(force_value(&v).unwrap(), Value::Null);
8577 }
8578
8579 #[test]
8580 fn force_value_evaluated_thunk_returns_cached() {
8581 let v = ev("let x = 1 + 2; in x");
8583 assert_eq!(v, Value::Int(3));
8584 assert_eq!(force_value(&v).unwrap(), Value::Int(3));
8586 }
8587
8588 #[test]
8591 fn tco_if_true_condition() {
8592 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
8593 }
8594
8595 #[test]
8596 fn tco_if_false_condition() {
8597 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
8598 }
8599
8600 #[test]
8601 fn tco_deeply_nested_if_else_chain() {
8602 let mut expr = String::from("150");
8605 for i in (1..150).rev() {
8606 expr = format!("if false then {} else {}", i, expr);
8607 }
8608 let v = ev(&expr);
8609 assert_eq!(v, Value::Int(150));
8610 }
8611
8612 #[test]
8613 fn tco_assert_true_passes_through() {
8614 assert_eq!(ev("assert true; 42"), Value::Int(42));
8615 }
8616
8617 #[test]
8618 fn tco_assert_false_throws_assertion_failed() {
8619 let result = eval("assert false; 42");
8620 assert!(result.is_err());
8621 let err = result.unwrap_err();
8622 assert!(
8623 matches!(err, EvalError::AssertionFailed(_)),
8624 "expected AssertionFailed, got: {err}",
8625 );
8626 }
8627
8628 #[test]
8629 fn tco_with_makes_scope_available() {
8630 assert_eq!(ev("with { x = 10; y = 20; }; x + y"), Value::Int(30));
8631 }
8632
8633 #[test]
8634 fn tco_let_in_creates_bindings() {
8635 assert_eq!(ev("let a = 5; in a"), Value::Int(5));
8636 }
8637
8638 #[test]
8639 fn tco_let_in_multiple_bindings() {
8640 assert_eq!(ev("let a = 1; b = 2; c = 3; in a + b + c"), Value::Int(6));
8641 }
8642
8643 #[test]
8646 fn eval_attrset_empty() {
8647 let v = ev("{}");
8648 if let Value::Attrs(attrs) = v {
8649 assert!(attrs.is_empty(), "expected empty attrset");
8650 } else {
8651 panic!("expected attrset, got {v:?}");
8652 }
8653 }
8654
8655 #[test]
8656 fn eval_attrset_simple_kv() {
8657 let v = ev("{ a = 1; b = 2; }");
8658 if let Value::Attrs(attrs) = v {
8659 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8660 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8661 } else {
8662 panic!("expected attrset, got {v:?}");
8663 }
8664 }
8665
8666 #[test]
8667 fn eval_attrset_recursive() {
8668 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
8669 assert_eq!(ev("(rec { a = 1; b = a + 1; }).a"), Value::Int(1));
8670 }
8671
8672 #[test]
8673 fn eval_attrset_inherit_from_scope() {
8674 assert_eq!(ev("let x = 1; in { inherit x; }.x"), Value::Int(1));
8675 }
8676
8677 #[test]
8678 fn eval_attrset_inherit_from_expr() {
8679 assert_eq!(
8680 ev("{ inherit (builtins) true; }.true"),
8681 Value::Bool(true),
8682 );
8683 }
8684
8685 #[test]
8686 fn eval_attrset_dotted_path() {
8687 assert_eq!(ev("{ a.b.c = 1; }.a.b.c"), Value::Int(1));
8688 }
8689
8690 #[test]
8691 fn eval_attrset_update_merge() {
8692 let v = ev("{ a = 1; } // { b = 2; }");
8693 if let Value::Attrs(attrs) = v {
8694 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8695 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8696 } else {
8697 panic!("expected attrset, got {v:?}");
8698 }
8699 }
8700
8701 #[test]
8704 fn eval_apply_simple_function() {
8705 assert_eq!(ev("(x: x + 1) 2"), Value::Int(3));
8706 }
8707
8708 #[test]
8709 fn eval_apply_pattern_destructuring() {
8710 assert_eq!(ev("({a, b}: a + b) { a = 1; b = 2; }"), Value::Int(3));
8711 }
8712
8713 #[test]
8714 fn eval_apply_default_arguments() {
8715 assert_eq!(ev("({a, b ? 0}: a + b) { a = 1; }"), Value::Int(1));
8716 }
8717
8718 #[test]
8719 fn eval_apply_ellipsis() {
8720 assert_eq!(ev("({a, ...}: a) { a = 1; b = 2; }"), Value::Int(1));
8721 }
8722
8723 #[test]
8726 fn eval_select_single_key() {
8727 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
8728 }
8729
8730 #[test]
8731 fn eval_select_multi_level() {
8732 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
8733 }
8734
8735 #[test]
8736 fn eval_select_with_or_default() {
8737 assert_eq!(ev("{}.a or 42"), Value::Int(42));
8738 }
8739
8740 #[test]
8741 fn eval_select_missing_key_without_default_throws() {
8742 let result = eval("{}.a");
8743 assert!(result.is_err());
8744 }
8745
8746 #[test]
8749 fn binop_add_ints() {
8750 assert_eq!(ev("1 + 2"), Value::Int(3));
8751 }
8752
8753 #[test]
8754 fn binop_sub_ints() {
8755 assert_eq!(ev("3 - 1"), Value::Int(2));
8756 }
8757
8758 #[test]
8759 fn binop_mul_ints() {
8760 assert_eq!(ev("2 * 3"), Value::Int(6));
8761 }
8762
8763 #[test]
8764 fn binop_div_ints() {
8765 assert_eq!(ev("6 / 2"), Value::Int(3));
8766 }
8767
8768 #[test]
8769 fn binop_float_arithmetic() {
8770 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
8771 }
8772
8773 #[test]
8774 fn binop_string_concat() {
8775 assert_eq!(
8776 ev(r#""hello" + " " + "world""#),
8777 Value::string("hello world"),
8778 );
8779 }
8780
8781 #[test]
8782 fn binop_list_concat() {
8783 assert_eq!(
8784 ev("[1 2] ++ [3 4]"),
8785 Value::list(vec![
8786 Value::Int(1),
8787 Value::Int(2),
8788 Value::Int(3),
8789 Value::Int(4),
8790 ]),
8791 );
8792 }
8793
8794 #[test]
8795 fn binop_attrset_update() {
8796 let v = ev("{ a = 1; } // { b = 2; }");
8797 if let Value::Attrs(attrs) = v {
8798 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8799 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8800 } else {
8801 panic!("expected attrset, got {v:?}");
8802 }
8803 }
8804
8805 #[test]
8806 fn binop_less_than() {
8807 assert_eq!(ev("1 < 2"), Value::Bool(true));
8808 assert_eq!(ev("2 < 1"), Value::Bool(false));
8809 }
8810
8811 #[test]
8812 fn binop_greater_than() {
8813 assert_eq!(ev("2 > 1"), Value::Bool(true));
8814 assert_eq!(ev("1 > 2"), Value::Bool(false));
8815 }
8816
8817 #[test]
8818 fn binop_equal() {
8819 assert_eq!(ev("1 == 1"), Value::Bool(true));
8820 assert_eq!(ev("1 == 2"), Value::Bool(false));
8821 }
8822
8823 #[test]
8824 fn binop_not_equal() {
8825 assert_eq!(ev("1 != 2"), Value::Bool(true));
8826 assert_eq!(ev("1 != 1"), Value::Bool(false));
8827 }
8828
8829 #[test]
8830 fn binop_logical_and() {
8831 assert_eq!(ev("true && false"), Value::Bool(false));
8832 assert_eq!(ev("true && true"), Value::Bool(true));
8833 }
8834
8835 #[test]
8836 fn binop_logical_or() {
8837 assert_eq!(ev("true || false"), Value::Bool(true));
8838 assert_eq!(ev("false || false"), Value::Bool(false));
8839 }
8840
8841 #[test]
8842 fn binop_logical_not() {
8843 assert_eq!(ev("!true"), Value::Bool(false));
8844 assert_eq!(ev("!false"), Value::Bool(true));
8845 }
8846
8847 #[test]
8848 fn binop_implication() {
8849 assert_eq!(ev("false -> true"), Value::Bool(true));
8850 assert_eq!(ev("false -> false"), Value::Bool(true));
8851 assert_eq!(ev("true -> true"), Value::Bool(true));
8852 assert_eq!(ev("true -> false"), Value::Bool(false));
8853 }
8854}