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 {
772 static LEVEL: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
773 *LEVEL.get_or_init(
774 || match std::env::var("SUI_SCOPE_NARROW").ok().as_deref() {
775 Some("1") => 1,
776 Some("2") => 2,
777 _ => 0,
778 },
779 )
780}
781
782#[inline]
784fn scope_narrow_enabled() -> bool {
785 scope_narrow_level() >= 1
786}
787
788#[inline]
790fn scope_cluster_enabled() -> bool {
791 scope_narrow_level() >= 2
792}
793
794fn referenced_idents(value_expr: &ast::Expr) -> HashSet<SmolStr> {
819 use rnix::SyntaxKind;
820 let perf_on = crate::perf::enabled();
826 let t0 = if perf_on {
827 Some(std::time::Instant::now())
828 } else {
829 None
830 };
831 crate::perf::inc(crate::perf::Counter::SelfRecWalkCalls);
832 let mut nodes_walked: u64 = 0;
833 let mut set: HashSet<SmolStr> = HashSet::new();
834 for node in value_expr.syntax().descendants() {
835 nodes_walked += 1;
836 if node.kind() == SyntaxKind::NODE_IDENT
837 && node
838 .parent()
839 .is_none_or(|p| p.kind() != SyntaxKind::NODE_ATTRPATH)
840 && let Some(i) = ast::Ident::cast(node)
841 {
842 set.insert(SmolStr::from(ident_text(&i).as_str()));
843 }
844 }
845 crate::perf::add(crate::perf::Counter::SelfRecWalkNodes, nodes_walked);
846 if let Some(t0) = t0 {
847 crate::trace::add_self_rec_walk_nanos(t0.elapsed().as_nanos());
848 }
849 set
850}
851
852fn is_self_recursive_binding(value_expr: &ast::Expr, name: &str) -> bool {
856 referenced_idents(value_expr).contains(name)
857}
858
859fn maybe_thunk(
860 expr: &ast::Expr,
861 env: &Env,
862 is_rec: bool,
863 defined_so_far: Option<&HashSet<String>>,
864) -> Value {
865 match expr {
866 ast::Expr::Literal(lit) => eval_literal(lit).unwrap_or_else(|_| {
868 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
869 }),
870 ast::Expr::Ident(ident) if !is_rec => {
877 let sym = {
887 let src_id = env.source_id();
888 let offset = u32::from(ident.syntax().text_range().start());
889 crate::value::intern_cached_with(src_id, offset, || {
890 crate::value::intern(&ident_text(ident))
891 })
892 };
893 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
895 "true" => Some(Value::Bool(true)),
896 "false" => Some(Value::Bool(false)),
897 "null" => Some(Value::Null),
898 _ => None,
899 }) {
900 return kw;
901 }
902 {
903 {
904 if let Some(v) = env.lookup_fast(sym, "") {
908 return v;
909 }
910 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
913 return Value::Thunk(Thunk::new_with_ident(
914 SmolStr::from(ident_text(ident).as_str()),
915 scope_cache,
916 scope_value,
917 env.clone(),
918 ));
919 }
920 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
921 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
922 }
923 }
924 }
925 ast::Expr::Ident(ident) if is_rec => {
929 let name = ident_text(ident);
930 match name.as_str() {
931 "true" => Value::Bool(true),
932 "false" => Value::Bool(false),
933 "null" => Value::Null,
934 _ => {
935 if defined_so_far.map_or(false, |d| d.contains(&name)) {
938 env.lookup(&name).unwrap_or_else(|| {
939 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
940 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
941 })
942 } else {
943 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeIdent);
945 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
946 }
947 }
948 }
949 }
950 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
955 let text = crate::path::canon_abs(&p.syntax().text().to_string());
961 Value::Path(Box::new(SmolStr::from(text.as_str())))
962 }
963 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
964 let text = p.syntax().text().to_string();
965 Value::Path(Box::new(SmolStr::from(text.as_str())))
966 }
967 ast::Expr::Str(st) if !str_has_interpolation(st) => {
980 eval_str(st, env).unwrap_or_else(|_| {
981 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
982 })
983 }
984 ast::Expr::Lambda(lam) if !is_rec => {
988 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
989 Value::Lambda(Rc::new(Closure {
990 param,
991 body,
992 env: env.clone(),
993 }))
994 } else {
995 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
996 }
997 }
998 _ => {
1009 crate::perf::inc(crate::perf::Counter::ThunkSiteMaybeOther);
1010 if crate::perf::enabled() {
1011 let kind = match expr {
1012 ast::Expr::Select(_) => "Select",
1013 ast::Expr::Apply(_) => "Apply",
1014 ast::Expr::BinOp(_) => "BinOp",
1015 ast::Expr::IfElse(_) => "IfElse",
1016 ast::Expr::Str(_) => "Str",
1017 ast::Expr::List(_) => "List",
1018 ast::Expr::With(_) => "With",
1019 ast::Expr::Assert(_) => "Assert",
1020 ast::Expr::HasAttr(_) => "HasAttr",
1021 ast::Expr::UnaryOp(_) => "UnaryOp",
1022 ast::Expr::Paren(_) => "Paren",
1023 ast::Expr::LetIn(_) => "LetIn",
1024 ast::Expr::AttrSet(_) => "AttrSet",
1025 ast::Expr::Ident(_) => "Ident(rec)",
1026 ast::Expr::Lambda(_) => "Lambda(rec)",
1027 ast::Expr::LegacyLet(_) => "LegacyLet",
1028 ast::Expr::PathAbs(_)
1029 | ast::Expr::PathHome(_)
1030 | ast::Expr::PathRel(_)
1031 | ast::Expr::PathSearch(_) => "Path(interp)",
1032 _ => "Other",
1033 };
1034 crate::trace::inc_maybe_other_kind(kind);
1035 }
1036 Value::Thunk(Thunk::new_suspended(expr.clone(), env.clone()))
1037 }
1038 }
1039}
1040
1041#[inline(always)]
1052pub fn eval_expr(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1053 match expr {
1056 ast::Expr::Ident(ident) => {
1057 crate::perf::inc(crate::perf::Counter::EvalExpr);
1058 if crate::perf::enabled() {
1059 crate::perf::inc(crate::perf::Counter::ExprIdent);
1060 }
1061 if crate::resolve_env::enabled() {
1074 let src_id = CURRENT_SOURCE_ID.with(std::cell::Cell::get);
1075 let offset = u32::from(ident.syntax().text_range().start());
1076 if let sui_resolve::Resolution::Lexical { sym } =
1077 crate::resolve_env::resolution_for(src_id, offset)
1078 {
1079 if let Some(v) = env.lookup_lexical_sym(sym) {
1080 return Ok(v);
1081 }
1082 }
1083 }
1085 let sym = {
1119 let src_id = env.source_id();
1120 let offset = u32::from(ident.syntax().text_range().start());
1121 crate::value::intern_cached_with(src_id, offset, || {
1122 crate::value::intern(&ident_text(ident))
1123 })
1124 };
1125 if let Some(kw) = crate::value::with_resolved(sym, |s| match s {
1129 "true" => Some(Value::Bool(true)),
1130 "false" => Some(Value::Bool(false)),
1131 "null" => Some(Value::Null),
1132 _ => None,
1133 }) {
1134 return Ok(kw);
1135 }
1136 return {
1137 {
1138 if let Some(v) = env.lookup_fast(sym, "") {
1142 Ok(v)
1143 } else {
1144 let name = ident_text(ident);
1145 let fresh = crate::value::intern(name.as_str());
1164 if fresh != sym {
1165 if let Some(v) = env.lookup_fast(fresh, name.as_str()) {
1166 return Ok(v);
1167 }
1168 }
1169 if env.with_scope_count() > 0 {
1170 if let Some((scope_cache, scope_value)) = env.innermost_with_scope() {
1174 Ok(Value::Thunk(Thunk::new_with_ident(
1175 SmolStr::from(name.as_str()),
1176 scope_cache,
1177 scope_value,
1178 env.clone(),
1179 )))
1180 } else if crate::value::in_promise_eval() {
1181 Ok(Value::Null)
1191 } else {
1192 Err(EvalError::UndefinedVar(
1193 format!("'{name}'{}", eval_file_ctx()),
1194 ))
1195 }
1196 } else {
1197 if let Ok(dbg_var) = std::env::var("SUI_DEBUG_VAR") {
1198 if dbg_var == name || dbg_var == "*" {
1199 eprintln!(
1200 "[sui-debug] UndefinedVar '{name}' in {}\n\
1201 [sui-debug] env bindings ({} total): {:?}\n\
1202 [sui-debug] with_scopes: {}",
1203 eval_file_ctx(),
1204 env.binding_count(),
1205 env.binding_names_preview(20),
1206 env.with_scope_count(),
1207 );
1208 }
1209 }
1210 if crate::value::in_promise_eval() {
1211 return Ok(Value::Null);
1214 }
1215 Err(EvalError::UndefinedVar(
1216 format!("'{name}'{}", eval_file_ctx()),
1217 ))
1218 }
1219 }
1220 }
1221 };
1222 }
1223 ast::Expr::Literal(lit) => {
1224 crate::perf::inc(crate::perf::Counter::EvalExpr);
1225 if crate::perf::enabled() {
1226 crate::perf::inc(crate::perf::Counter::ExprLiteral);
1227 }
1228 return eval_literal(lit);
1229 }
1230 ast::Expr::Paren(p) => {
1231 if let Some(inner) = p.expr() {
1232 return eval_expr(&inner, env);
1233 }
1234 }
1235 ast::Expr::Root(r) => {
1236 if let Some(inner) = r.expr() {
1237 return eval_expr(&inner, env);
1238 }
1239 }
1240 ast::Expr::Lambda(lam) => {
1242 crate::perf::inc(crate::perf::Counter::EvalExpr);
1243 if crate::perf::enabled() {
1244 crate::perf::inc(crate::perf::Counter::ExprLambda);
1245 }
1246 if let (Some(param), Some(body)) = (lam.param(), lam.body()) {
1247 return Ok(Value::Lambda(Rc::new(Closure {
1248 param,
1249 body,
1250 env: env.clone(),
1251 })));
1252 }
1253 }
1254 _ => {}
1255 }
1256 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || {
1258 eval_expr_inner(expr, env)
1259 })
1260}
1261
1262fn eval_expr_inner(expr: &ast::Expr, env: &Env) -> Result<Value, EvalError> {
1270 let mut cur_expr = expr.clone();
1273 let mut cur_env = env.clone();
1274
1275 loop {
1276 crate::perf::inc(crate::perf::Counter::EvalExpr);
1277 if crate::perf::enabled() {
1279 use crate::perf::Counter;
1280 let c = match &cur_expr {
1281 ast::Expr::Ident(_) => Counter::ExprIdent,
1282 ast::Expr::Literal(_) => Counter::ExprLiteral,
1283 ast::Expr::Str(_) => Counter::ExprStr,
1284 ast::Expr::List(_) => Counter::ExprList,
1285 ast::Expr::AttrSet(_) => Counter::ExprAttrs,
1286 ast::Expr::Select(_) => Counter::ExprSelect,
1287 ast::Expr::Apply(_) => Counter::ExprApply,
1288 ast::Expr::LetIn(_) => Counter::ExprLetIn,
1289 ast::Expr::IfElse(_) => Counter::ExprIfElse,
1290 ast::Expr::With(_) => Counter::ExprWith,
1291 ast::Expr::Lambda(_) => Counter::ExprLambda,
1292 ast::Expr::BinOp(_) => Counter::ExprBinOp,
1293 ast::Expr::HasAttr(_) => Counter::ExprHasAttr,
1294 ast::Expr::UnaryOp(_) => Counter::ExprUnaryOp,
1295 ast::Expr::Assert(_) => Counter::ExprAssert,
1296 ast::Expr::PathAbs(_) | ast::Expr::PathRel(_)
1297 | ast::Expr::PathHome(_) | ast::Expr::PathSearch(_) => Counter::ExprPath,
1298 _ => Counter::ExprOther,
1299 };
1300 crate::perf::inc(c);
1301 }
1302 let _guard = DepthGuard::enter()?;
1303 let env = &cur_env;
1304 match &cur_expr {
1305 ast::Expr::Literal(lit) => return eval_literal(lit),
1306
1307 ast::Expr::Str(s) => return eval_str(s, env),
1308
1309 ast::Expr::PathAbs(p) => {
1310 let parts = p.parts();
1313 if parts_have_interpolation(&parts) {
1314 return eval_interpol_path_parts(&parts, PathKind::Abs, env);
1315 }
1316 let text = crate::path::canon_abs(&p.syntax().text().to_string());
1319 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1320 }
1321 ast::Expr::PathRel(p) => {
1322 let parts = p.parts();
1333 if parts_have_interpolation(&parts) {
1334 return eval_interpol_path_parts(&parts, PathKind::Rel, env);
1335 }
1336 let text = p.syntax().text().to_string();
1337 let resolved = if let Some(dir) = current_eval_dir() {
1338 let joined = dir.join(&text);
1339 let norm = normalize_path(&joined);
1343 crate::path::dematerialize(&norm)
1353 .to_string_lossy()
1354 .into_owned()
1355 } else {
1356 text.clone()
1357 };
1358 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1359 }
1360 ast::Expr::PathHome(p) => {
1361 let parts = p.parts();
1362 if parts_have_interpolation(&parts) {
1363 return eval_interpol_path_parts(&parts, PathKind::Home, env);
1364 }
1365 let text = p.syntax().text().to_string();
1366 return Ok(Value::Path(Box::new(SmolStr::from(text.as_str()))));
1367 }
1368 ast::Expr::PathSearch(p) => {
1369 let text = p.syntax().text().to_string();
1374 let inner = text
1375 .strip_prefix('<')
1376 .and_then(|s| s.strip_suffix('>'))
1377 .unwrap_or(&text);
1378 if let Some(resolved) = crate::builtins::resolve_search_path(inner) {
1379 return Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))));
1380 }
1381 return Err(EvalError::Throw(
1385 format!("search path '{text}' not in NIX_PATH"),
1386 ));
1387 }
1388
1389 ast::Expr::Ident(ident) => {
1390 let name = ident_text(ident);
1391 return match name.as_str() {
1392 "true" => Ok(Value::Bool(true)),
1393 "false" => Ok(Value::Bool(false)),
1394 "null" => Ok(Value::Null),
1395 _ => {
1396 env.lookup(&name)
1397 .ok_or_else(|| EvalError::UndefinedVar(
1398 format!("'{name}'{}", eval_file_ctx()),
1399 ))
1400 }
1401 };
1402 }
1403
1404 ast::Expr::List(list) => {
1405 let values: Vec<Value> = list.items()
1410 .map(|e| maybe_thunk(&e, env, false, None))
1411 .collect();
1412 return Ok(Value::list(values));
1413 }
1414
1415 ast::Expr::AttrSet(set) => return eval_attrset(set, env),
1416
1417 ast::Expr::Select(sel) => return eval_select(sel, env),
1418
1419 ast::Expr::HasAttr(ha) => return eval_has_attr(ha, env),
1420
1421 ast::Expr::UnaryOp(op) => return eval_unary_op(op, env),
1422
1423 ast::Expr::BinOp(binop) => {
1424 let lhs_expr = binop
1425 .lhs()
1426 .ok_or_else(|| EvalError::ParseError("binop missing lhs".to_string()))?;
1427 let rhs_expr = binop
1428 .rhs()
1429 .ok_or_else(|| EvalError::ParseError("binop missing rhs".to_string()))?;
1430 let kind = binop
1431 .operator()
1432 .ok_or_else(|| EvalError::ParseError("binop missing operator".to_string()))?;
1433 return eval_binop(kind, &lhs_expr, &rhs_expr, env);
1434 }
1435
1436 ast::Expr::Apply(app) => return eval_apply(app, env),
1437
1438 ast::Expr::IfElse(ie) => {
1439 let cond = ie
1440 .condition()
1441 .ok_or_else(|| EvalError::ParseError("if missing condition".to_string()))?;
1442 let body = ie
1443 .body()
1444 .ok_or_else(|| EvalError::ParseError("if missing then body".to_string()))?;
1445 let else_body = ie
1446 .else_body()
1447 .ok_or_else(|| EvalError::ParseError("if missing else body".to_string()))?;
1448 if force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1449 cur_expr = body;
1450 } else {
1451 cur_expr = else_body;
1452 }
1453 continue;
1455 }
1456
1457 ast::Expr::Assert(assert) => {
1458 let cond = assert
1459 .condition()
1460 .ok_or_else(|| EvalError::ParseError("assert missing condition".to_string()))?;
1461 let body = assert
1462 .body()
1463 .ok_or_else(|| EvalError::ParseError("assert missing body".to_string()))?;
1464 if !force_concrete(&eval_expr(&cond, env)?)?.as_bool()? {
1465 return Err(EvalError::AssertionFailed(eval_file_ctx()));
1466 }
1467 cur_expr = body;
1468 continue;
1469 }
1470
1471 ast::Expr::With(with) => {
1472 let ns = with
1473 .namespace()
1474 .ok_or_else(|| EvalError::ParseError("with missing namespace".to_string()))?;
1475 let body = with
1476 .body()
1477 .ok_or_else(|| EvalError::ParseError("with missing body".to_string()))?;
1478 let scope_val = maybe_thunk(&ns, env, false, None);
1504 let new_env = env.child().with_scope(scope_val);
1505 cur_expr = body;
1506 cur_env = new_env;
1507 continue;
1508 }
1509
1510 ast::Expr::LetIn(letin) => {
1511 let mut new_env = env.child();
1512
1513 let mut thunks: Vec<(String, Thunk)> = Vec::new();
1516
1517 let mut defined_so_far: HashSet<String> = HashSet::new();
1521
1522 let mut dotted_attrs: NixAttrs = NixAttrs::new();
1526
1527 let mut names_complete = true;
1546 let let_scope_names: HashSet<String> = {
1547 let mut s = HashSet::new();
1548 for entry in letin.entries() {
1549 match entry {
1550 ast::Entry::AttrpathValue(apv) => {
1551 if let Some(attrpath) = apv.attrpath() {
1552 if let Some(first) = attrpath.attrs().next() {
1553 if let ast::Attr::Dynamic(_) = &first {
1554 names_complete = false;
1555 }
1556 if let Ok(name) = eval_attr(&first, env) {
1557 s.insert(name);
1558 } else {
1559 names_complete = false;
1560 }
1561 } else {
1562 names_complete = false;
1563 }
1564 } else {
1565 names_complete = false;
1566 }
1567 }
1568 ast::Entry::Inherit(inherit) => {
1569 for attr in inherit.attrs() {
1570 if let ast::Attr::Dynamic(_) = &attr {
1571 names_complete = false;
1572 }
1573 if let Ok(name) = eval_attr(&attr, env) {
1574 s.insert(name);
1575 } else {
1576 names_complete = false;
1577 }
1578 }
1579 }
1580 }
1581 }
1582 s
1583 };
1584 let narrow = scope_narrow_enabled() && names_complete;
1585
1586 let cluster = narrow && scope_cluster_enabled();
1604 let mut all_bound: Vec<(String, Value)> = Vec::new();
1607 let mut pinned_names: HashSet<String> = HashSet::new();
1614 let mut pinned_refs: Vec<HashSet<SmolStr>> = Vec::new();
1615 let mut has_dotted = false;
1620
1621 for entry in letin.entries() {
1622 match entry {
1623 ast::Entry::AttrpathValue(ref apv) => {
1624 let attrpath = apv.attrpath().ok_or_else(|| {
1625 EvalError::ParseError("binding missing attrpath".to_string())
1626 })?;
1627 let value_expr = apv.value().ok_or_else(|| {
1628 EvalError::ParseError("binding missing value".to_string())
1629 })?;
1630 let mut path_keys: Vec<String> = attrpath
1631 .attrs()
1632 .map(|a| eval_attr(&a, env))
1633 .collect::<Result<_, _>>()?;
1634 if path_keys.len() == 1 {
1635 let key = path_keys.pop().unwrap();
1636 let referenced = referenced_idents(&value_expr);
1659 let in_mutual_cycle = std::iter::once(&key)
1660 .chain(let_scope_names.iter())
1661 .any(|n| referenced.contains(n.as_str()));
1662 let value = if in_mutual_cycle {
1663 Value::Thunk(Thunk::new_suspended_recursive(
1664 value_expr.clone(),
1665 env.clone(),
1666 ))
1667 } else {
1668 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
1669 };
1670 new_env.bind(key.clone(), value.clone());
1671 if cluster {
1672 all_bound.push((key.clone(), value.clone()));
1673 }
1674 if let Value::Thunk(t) = &value {
1675 if in_mutual_cycle || !narrow {
1695 thunks.push((key.clone(), t.clone()));
1696 if cluster {
1697 pinned_names.insert(key.clone());
1698 pinned_refs.push(referenced);
1699 }
1700 crate::value::census::scope_pinned();
1701 } else {
1702 crate::value::census::scope_narrowed();
1703 }
1704 }
1705 defined_so_far.insert(key);
1706 } else if path_keys.len() > 1 {
1707 has_dotted = true;
1712 let key = path_keys[0].clone();
1713 let value = build_nested_attr_thunk(
1714 &path_keys[1..],
1715 &value_expr,
1716 env,
1717 &mut thunks,
1718 );
1719 merge_nested_insert(&mut dotted_attrs, key, value);
1720 }
1721 }
1722 ast::Entry::Inherit(ref inherit) => {
1723 if let Some(from) = inherit.from() {
1724 let source_expr = from.expr().ok_or_else(|| {
1725 EvalError::ParseError(
1726 "inherit from missing expr".to_string(),
1727 )
1728 })?;
1729 let source_refs: Option<HashSet<SmolStr>> = if narrow {
1739 Some(referenced_idents(&source_expr))
1740 } else {
1741 None
1742 };
1743 let source_needs_scope = match &source_refs {
1744 Some(refs) => let_scope_names
1745 .iter()
1746 .any(|n| refs.contains(n.as_str())),
1747 None => true,
1748 };
1749 let source_thunk = Thunk::new_suspended(
1754 source_expr, env.clone(),
1755 );
1756 for attr in inherit.attrs() {
1757 let name = eval_attr(&attr, env)?;
1758 let thunk = Thunk::new_inherit_select(
1759 source_thunk.clone(),
1760 name.clone(),
1761 );
1762 new_env.bind(name.clone(), Value::Thunk(thunk.clone()));
1763 if cluster {
1764 all_bound.push((
1765 name.clone(),
1766 Value::Thunk(thunk.clone()),
1767 ));
1768 }
1769 if source_needs_scope {
1770 if cluster {
1771 pinned_names.insert(name.clone());
1772 }
1773 thunks.push((name, thunk));
1774 crate::value::census::scope_pinned();
1775 } else {
1776 crate::value::census::scope_narrowed();
1777 }
1778 }
1779 if cluster
1782 && source_needs_scope
1783 && let Some(refs) = source_refs
1784 {
1785 pinned_refs.push(refs);
1786 }
1787 } else {
1788 for attr in inherit.attrs() {
1793 let name = eval_attr(&attr, env)?;
1794 let value = env.lookup(&name).ok_or_else(|| {
1795 EvalError::UndefinedVar(
1796 format!("'{name}'{}", eval_file_ctx()),
1797 )
1798 })?;
1799 if cluster {
1800 all_bound.push((name.clone(), value.clone()));
1801 }
1802 new_env.bind(name, value);
1803 }
1804 }
1805 }
1806 }
1807 }
1808
1809 for (key, value) in dotted_attrs.iter() {
1814 new_env.bind(key.clone(), value.clone());
1815 if cluster {
1816 all_bound.push((key.clone(), value.clone()));
1817 }
1818 }
1819
1820 let fix_env: Option<Env> = if cluster && !has_dotted && !thunks.is_empty() {
1825 let mut pin = pinned_names;
1832 for refs in &pinned_refs {
1833 for n in &let_scope_names {
1834 if refs.contains(n.as_str()) {
1835 pin.insert(n.clone());
1836 }
1837 }
1838 }
1839 if pin.len() < all_bound.len() {
1840 let mut fe = env.child();
1841 for (name, value) in &all_bound {
1842 if pin.contains(name) {
1843 fe.bind(name.clone(), value.clone());
1844 }
1845 }
1846 Some(fe)
1847 } else {
1848 None
1849 }
1850 } else {
1851 None
1852 };
1853
1854 let phase2_env: &Env = fix_env.as_ref().unwrap_or(&new_env);
1857 for (_key, thunk) in &thunks {
1858 thunk.update_env(phase2_env);
1859 }
1860
1861 let body = letin
1862 .body()
1863 .ok_or_else(|| EvalError::ParseError("let missing body".to_string()))?;
1864 cur_expr = body;
1865 cur_env = new_env;
1866 continue;
1867 }
1868
1869 ast::Expr::Lambda(lam) => {
1870 let param = lam
1871 .param()
1872 .ok_or_else(|| EvalError::ParseError("lambda missing param".to_string()))?;
1873 let body = lam
1874 .body()
1875 .ok_or_else(|| EvalError::ParseError("lambda missing body".to_string()))?;
1876 return Ok(Value::Lambda(Rc::new(Closure {
1877 param,
1878 body,
1879 env: env.clone(),
1880 })));
1881 }
1882
1883 ast::Expr::Paren(p) => {
1884 let inner = p
1885 .expr()
1886 .ok_or_else(|| EvalError::ParseError("paren missing expr".to_string()))?;
1887 cur_expr = inner;
1888 continue;
1889 }
1890
1891 ast::Expr::Root(r) => {
1892 let inner = r
1893 .expr()
1894 .ok_or_else(|| EvalError::ParseError("root missing expr".to_string()))?;
1895 cur_expr = inner;
1896 continue;
1897 }
1898
1899 ast::Expr::LegacyLet(ll) => {
1900 let mut new_env = env.child();
1901 eval_entries(ll, &mut new_env)?;
1902 return new_env
1904 .lookup("body")
1905 .ok_or_else(|| EvalError::AttrNotFound(
1906 format!("'body' in legacy let{}", eval_file_ctx()),
1907 ));
1908 }
1909
1910 ast::Expr::CurPos(_) => return Err(EvalError::NotImplemented("__curPos".to_string())),
1911 ast::Expr::Error(_) => return Err(EvalError::ParseError("parse error node".to_string())),
1912 } } }
1915
1916fn eval_literal(lit: &ast::Literal) -> Result<Value, EvalError> {
1917 use ast::LiteralKind;
1918 match lit.kind() {
1919 LiteralKind::Integer(tok) => {
1920 let n = tok
1921 .value()
1922 .map_err(|e| EvalError::ParseError(format!("invalid integer: {e}")))?;
1923 Ok(Value::Int(n))
1924 }
1925 LiteralKind::Float(tok) => {
1926 let f = tok
1927 .value()
1928 .map_err(|e| EvalError::ParseError(format!("invalid float: {e}")))?;
1929 Ok(Value::Float(f))
1930 }
1931 LiteralKind::Uri(tok) => Ok(Value::string(tok.syntax().text().to_string())),
1932 }
1933}
1934
1935enum TraverseResult {
1937 Found(Value),
1939 Missing(String),
1941 NotAttrs(Value),
1943}
1944
1945fn traverse_attrpath(
1950 base: Value,
1951 attrpath: &rnix::ast::Attrpath,
1952 env: &Env,
1953) -> Result<TraverseResult, EvalError> {
1954 let attrs: Vec<_> = attrpath.attrs().collect();
1955 let mut value = base;
1956 for (i, attr) in attrs.iter().enumerate() {
1957 let key = eval_attr(attr, env)?;
1958 let forced = force_value(&value)?;
1960 match forced {
1961 Value::Attrs(ref a) => match a.get(&key) {
1962 Some(v) => {
1963 if i < attrs.len() - 1 {
1964 value = force_value(v)?;
1966 } else {
1967 value = v.clone();
1970 }
1971 }
1972 None => return Ok(TraverseResult::Missing(key)),
1973 },
1974 _ => return Ok(TraverseResult::NotAttrs(forced)),
1975 }
1976 }
1977 Ok(TraverseResult::Found(value))
1978}
1979
1980fn eval_select(sel: &ast::Select, env: &Env) -> Result<Value, EvalError> {
1981 crate::perf::inc(crate::perf::Counter::Select);
1982 let base_expr = sel.expr().ok_or_else(|| {
1983 EvalError::ParseError("select missing expression".to_string())
1984 })?;
1985 let base_result = eval_expr(&base_expr, env)
1994 .and_then(|v| force_concrete(&v).map(Concrete::into_value));
1995 let base = match base_result {
1996 Ok(v) => v,
1997 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
1998 return eval_expr(&sel.default_expr().expect("checked"), env);
1999 }
2000 Err(e) => return Err(e),
2001 };
2002 let base_type = base.type_name();
2003 let attrpath = sel.attrpath().ok_or_else(|| {
2004 EvalError::ParseError("select missing attrpath".to_string())
2005 })?;
2006 let bridge_active = std::env::var_os("SUI_BLACKHOLE_AS_EMPTY_ATTRS").is_some()
2028 || std::env::var_os("SUI_BLACKHOLE_AS_NULL").is_some();
2029 let traversal = traverse_attrpath(base, &attrpath, env);
2030 match traversal {
2031 Ok(TraverseResult::Found(v)) => Ok(v),
2032 Ok(TraverseResult::Missing(key)) => {
2033 if let Some(def) = sel.default_expr() {
2034 eval_expr(&def, env)
2035 } else if bridge_active {
2036 if std::env::var_os("SUI_M26_SELTRACE").is_some() {
2037 let path: Vec<String> = sel.attrpath().map(|ap|
2038 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2039 ).unwrap_or_default();
2040 eprintln!("[M26 SEL-MISS→null] base_type={base_type} path={path:?} missing-key={key}{}", eval_file_ctx());
2041 }
2042 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
2043 let path: Vec<String> = sel.attrpath().map(|ap|
2044 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2045 ).unwrap_or_default();
2046 if path.iter().any(|p| p.contains(&filt)) {
2047 return Err(EvalError::type_error(format!(
2048 "M26-HARDSOFTEN path={path:?} key={key}"
2049 )));
2050 }
2051 }
2052 Ok(Value::Null)
2053 } else {
2054 Err(EvalError::AttrNotFound(
2055 format!("'{key}'{}", eval_file_ctx()),
2056 ))
2057 }
2058 }
2059 Ok(TraverseResult::NotAttrs(forced)) => {
2060 if let Some(def) = sel.default_expr() {
2066 eval_expr(&def, env)
2067 } else if bridge_active {
2068 if let Ok(filt) = std::env::var("SUI_M26_HARDSOFTEN") {
2069 let path: Vec<String> = sel.attrpath().map(|ap|
2070 ap.attrs().map(|a| a.syntax().text().to_string()).collect()
2071 ).unwrap_or_default();
2072 if path.iter().any(|p| p.contains(&filt)) {
2073 return Err(EvalError::type_error(format!(
2074 "M26-HARDSOFTEN-NOTATTRS path={path:?} base_type={base_type}"
2075 )));
2076 }
2077 }
2078 return Ok(Value::Null);
2079 } else {
2080 if std::env::var("SUI_DEBUG_SELECT").is_ok() {
2081 let path: Vec<String> = sel.attrpath().map(|ap|
2082 ap.attrs().filter_map(|a| match a {
2083 ast::Attr::Ident(i) => Some(i.to_string()),
2084 ast::Attr::Str(s) => Some(format!("\"{}\"", s.syntax().text())),
2085 ast::Attr::Dynamic(_) => Some("<dyn>".into()),
2086 }).collect()
2087 ).unwrap_or_default();
2088 let dbg = format!("{:?}", forced);
2089 let truncated = if dbg.len() > 200 { format!("{}…", &dbg[..200]) } else { dbg };
2090 eprintln!("[SUI_DEBUG_SELECT] base_type={base_type} path={path:?} base={truncated}{}", eval_file_ctx());
2091 }
2092 Err(attach_trace(EvalError::type_error(
2093 format!("cannot select from {base_type}"),
2094 )))
2095 }
2096 }
2097 Err(EvalError::InfiniteRecursion(_)) if sel.default_expr().is_some() => {
2102 eval_expr(&sel.default_expr().expect("checked"), env)
2103 }
2104 Err(e) => Err(e),
2105 }
2106}
2107
2108fn eval_has_attr(ha: &ast::HasAttr, env: &Env) -> Result<Value, EvalError> {
2110 let base_expr = ha.expr().ok_or_else(|| {
2111 EvalError::ParseError("hasattr missing expression".to_string())
2112 })?;
2113 let base = force_concrete(&eval_expr(&base_expr, env)?)?.into_value();
2114 let attrpath = ha.attrpath().ok_or_else(|| {
2115 EvalError::ParseError("hasattr missing attrpath".to_string())
2116 })?;
2117 match traverse_attrpath(base, &attrpath, env)? {
2118 TraverseResult::Found(_) => Ok(Value::Bool(true)),
2119 TraverseResult::Missing(_) | TraverseResult::NotAttrs(_) => Ok(Value::Bool(false)),
2120 }
2121}
2122
2123fn eval_unary_op(op: &ast::UnaryOp, env: &Env) -> Result<Value, EvalError> {
2124 let inner = op
2125 .expr()
2126 .ok_or_else(|| EvalError::ParseError("unary op missing expr".to_string()))?;
2127 let val = force_value(&eval_expr(&inner, env)?)?;
2128 let kind = op
2129 .operator()
2130 .ok_or_else(|| EvalError::ParseError("unary op missing operator".to_string()))?;
2131 match kind {
2132 ast::UnaryOpKind::Negate => match val {
2133 Value::Int(n) => Ok(Value::Int(-n)),
2134 Value::Float(f) => Ok(Value::Float(-f)),
2135 _ => Err(EvalError::type_error(
2136 format!("cannot negate {}", val.type_name()),
2137 )),
2138 },
2139 ast::UnaryOpKind::Invert => Ok(Value::Bool(!val.as_bool()?)),
2140 }
2141}
2142
2143#[inline]
2154pub(crate) fn builtin_takes_lazy_arg(name: &str) -> bool {
2155 matches!(
2156 name,
2157 "tryEval" | "addErrorContext<partial>" | "seq<partial>" | "deepSeq<partial>" | "foldl'<p1>"
2158 )
2159}
2160
2161fn eval_apply(app: &ast::Apply, env: &Env) -> Result<Value, EvalError> {
2162 let func_expr = app
2163 .lambda()
2164 .ok_or_else(|| EvalError::ParseError("apply missing function".to_string()))?;
2165 let arg_expr = app
2166 .argument()
2167 .ok_or_else(|| EvalError::ParseError("apply missing argument".to_string()))?;
2168 let func = force_value(&eval_expr(&func_expr, env)?)?;
2169 let arg = match &func {
2177 Value::Lambda(_) => {
2178 if let Some(v) = eval_pure_constant_arg(&arg_expr) {
2187 v
2188 } else {
2189 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
2190 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
2191 }
2192 }
2193 Value::Builtin(b) if builtin_takes_lazy_arg(&b.name) => {
2194 crate::perf::inc(crate::perf::Counter::ThunkSiteApplyArg);
2199 Value::Thunk(Thunk::new_suspended(arg_expr.clone(), env.clone()))
2200 }
2201 _ => eval_expr(&arg_expr, env)?,
2202 };
2203 apply(func, arg)
2204}
2205
2206fn eval_pure_constant_arg(arg_expr: &ast::Expr) -> Option<Value> {
2221 match arg_expr {
2222 ast::Expr::Literal(lit) => eval_literal(lit).ok(),
2223 ast::Expr::Str(st) if !str_has_interpolation(st) => {
2224 eval_str(st, &Env::new()).ok()
2226 }
2227 ast::Expr::PathAbs(p) if !parts_have_interpolation(&p.parts()) => {
2228 let text = crate::path::canon_abs(&p.syntax().text().to_string());
2229 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2230 }
2231 ast::Expr::PathHome(p) if !parts_have_interpolation(&p.parts()) => {
2232 let text = p.syntax().text().to_string();
2233 Some(Value::Path(Box::new(SmolStr::from(text.as_str()))))
2234 }
2235 _ => None,
2236 }
2237}
2238
2239fn eval_str(s: &ast::Str, env: &Env) -> Result<Value, EvalError> {
2240 let mut result = String::new();
2241 let mut ctx = StringContext::new();
2242 for part in s.normalized_parts() {
2243 match part {
2244 InterpolPart::Literal(text) => result.push_str(&text),
2245 InterpolPart::Interpolation(interpol) => {
2246 let expr = interpol.expr().ok_or_else(|| {
2247 EvalError::ParseError("interpolation missing expr".to_string())
2248 })?;
2249 let val = force_value(&eval_expr(&expr, env)?)?;
2250 let (s, c) = val.coerce_to_string_copy_to_store()?;
2255 result.push_str(&s);
2256 ctx.merge(&c);
2257 }
2258 }
2259 }
2260 Ok(Value::String(Rc::new(NixString::with_context(result, ctx))))
2261}
2262
2263fn parts_have_interpolation(parts: &[InterpolPart<rnix::ast::PathContent>]) -> bool {
2267 parts
2268 .iter()
2269 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2270}
2271
2272fn str_has_interpolation(s: &ast::Str) -> bool {
2276 s.normalized_parts()
2277 .iter()
2278 .any(|p| matches!(p, InterpolPart::Interpolation(_)))
2279}
2280
2281fn eval_interpol_path_parts(
2296 parts: &[InterpolPart<rnix::ast::PathContent>],
2297 kind: PathKind,
2298 env: &Env,
2299) -> Result<Value, EvalError> {
2300 let mut text = String::new();
2301 for part in parts {
2302 match part {
2303 InterpolPart::Literal(content) => text.push_str(content.text()),
2304 InterpolPart::Interpolation(interpol) => {
2305 let expr = interpol.expr().ok_or_else(|| {
2306 EvalError::ParseError("path interpolation missing expr".to_string())
2307 })?;
2308 let val = force_value(&eval_expr(&expr, env)?)?;
2309 let (s, _ctx) = val.coerce_to_string()?;
2313 text.push_str(&s);
2314 }
2315 }
2316 }
2317 let resolved = match kind {
2318 PathKind::Rel => {
2321 if let Some(dir) = current_eval_dir() {
2322 let norm = normalize_path(&dir.join(&text));
2323 crate::path::dematerialize(&norm).to_string_lossy().into_owned()
2332 } else {
2333 text
2337 }
2338 }
2339 PathKind::Abs => crate::path::canon_abs(&text),
2347 PathKind::Home => normalize_path(std::path::Path::new(&text))
2350 .to_string_lossy()
2351 .into_owned(),
2352 };
2353 Ok(Value::Path(Box::new(SmolStr::from(resolved.as_str()))))
2354}
2355
2356#[derive(Clone, Copy)]
2359enum PathKind {
2360 Abs,
2361 Rel,
2362 Home,
2363}
2364
2365fn eval_attr(attr: &ast::Attr, env: &Env) -> Result<String, EvalError> {
2368 eval_attr_maybe_null(attr, env)?
2369 .ok_or_else(|| EvalError::TypeError("null dynamic attribute name".into()))
2370}
2371
2372fn eval_attr_maybe_null(attr: &ast::Attr, env: &Env) -> Result<Option<String>, EvalError> {
2375 match attr {
2376 ast::Attr::Ident(ident) => Ok(Some(ident_text(ident))),
2377 ast::Attr::Dynamic(dyn_) => {
2378 let expr = dyn_
2379 .expr()
2380 .ok_or_else(|| EvalError::ParseError("dynamic attr missing expr".to_string()))?;
2381 let val = force_value(&eval_expr(&expr, env)?)?;
2382 if val == Value::Null {
2385 return Ok(None);
2386 }
2387 Ok(Some(val.as_string()?.to_string()))
2388 }
2389 ast::Attr::Str(s) => {
2390 let val = eval_str(s, env)?;
2391 Ok(Some(val.as_string()?.to_string()))
2392 }
2393 }
2394}
2395
2396fn ident_text(ident: &ast::Ident) -> String {
2398 match ident.ident_token() {
2406 Some(tok) => tok.text().to_string(),
2407 None => ident.syntax().text().to_string(),
2408 }
2409}
2410
2411fn static_attr_offset(attr: &ast::Attr) -> Option<u32> {
2418 let node = match attr {
2419 ast::Attr::Ident(i) => i.syntax(),
2420 ast::Attr::Str(s) => s.syntax(),
2421 ast::Attr::Dynamic(_) => return None,
2422 };
2423 Some(u32::from(node.text_range().start()))
2424}
2425
2426fn attach_attrset_positions(set: &ast::AttrSet, attrs: &mut NixAttrs, env: &Env) {
2433 let mut table = crate::pos::AttrPositions::new(current_eval_file());
2440 for entry in set.entries() {
2441 if let ast::Entry::AttrpathValue(apv) = entry {
2442 let Some(attrpath) = apv.attrpath() else { continue };
2443 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2444 let Some(head) = path_attrs.first() else { continue };
2452 let Some(offset) = static_attr_offset(head) else { continue };
2453 if let Ok(Some(name)) = eval_attr_maybe_null(&path_attrs[0], env) {
2456 table.insert(intern(&name), offset);
2457 }
2458 } else if let ast::Entry::Inherit(inh) = entry {
2459 for attr in inh.attrs() {
2473 let Some(offset) = static_attr_offset(&attr) else { continue };
2474 if let Ok(Some(name)) = eval_attr_maybe_null(&attr, env) {
2475 table.insert(intern(&name), offset);
2476 }
2477 }
2478 }
2479 }
2480 if !table.is_empty() {
2481 attrs.set_positions(std::rc::Rc::new(table));
2482 }
2483}
2484
2485fn eval_attrset(set: &ast::AttrSet, env: &Env) -> Result<Value, EvalError> {
2486 crate::perf::inc(crate::perf::Counter::Attrset);
2487 let mut attrs = NixAttrs::new();
2488 let is_rec = set.rec_token().is_some();
2489
2490 if is_rec {
2491 let mut rec_env = env.child();
2492 let mut thunks: Vec<(String, Thunk)> = Vec::new();
2493
2494 let mut defined_so_far: HashSet<String> = HashSet::new();
2498
2499 let mut dotted_attrs: NixAttrs = NixAttrs::new();
2505
2506 let mut names_complete = scope_narrow_enabled();
2528 let rec_scope_names: HashSet<String> = if names_complete {
2529 let mut s = HashSet::new();
2530 for entry in set.entries() {
2531 match entry {
2532 ast::Entry::AttrpathValue(apv) => {
2533 match apv.attrpath().and_then(|p| p.attrs().next()) {
2534 Some(ast::Attr::Ident(i)) => {
2535 s.insert(ident_text(&i));
2536 }
2537 _ => names_complete = false,
2538 }
2539 }
2540 ast::Entry::Inherit(inh) => {
2541 for attr in inh.attrs() {
2542 match attr {
2543 ast::Attr::Ident(i) => {
2544 s.insert(ident_text(&i));
2545 }
2546 _ => names_complete = false,
2547 }
2548 }
2549 }
2550 }
2551 }
2552 s
2553 } else {
2554 HashSet::new()
2555 };
2556 let narrow = names_complete;
2557
2558 for entry in set.entries() {
2560 match entry {
2561 ast::Entry::AttrpathValue(apv) => {
2562 let attrpath = apv.attrpath().ok_or_else(|| {
2563 EvalError::ParseError("binding missing attrpath".to_string())
2564 })?;
2565 let value_expr = apv.value().ok_or_else(|| {
2566 EvalError::ParseError("binding missing value".to_string())
2567 })?;
2568 let mut path_keys: Vec<String> = attrpath
2569 .attrs()
2570 .filter_map(|a| eval_attr_maybe_null(&a, env).transpose())
2571 .collect::<Result<_, _>>()?;
2572 if path_keys.is_empty() { continue; }
2574 if path_keys.len() == 1 {
2575 let key = path_keys.pop().unwrap();
2576 let referenced = referenced_idents(&value_expr);
2593 let is_recursive_binding = referenced.contains(key.as_str())
2594 || defined_so_far
2595 .iter()
2596 .any(|n| referenced.contains(n.as_str()));
2597 let value = if is_recursive_binding {
2598 Value::Thunk(Thunk::new_suspended_recursive(
2599 value_expr.clone(),
2600 env.clone(),
2601 ))
2602 } else {
2603 maybe_thunk(&value_expr, env, true, Some(&defined_so_far))
2609 };
2610 let needs_scope = !narrow
2616 || is_recursive_binding
2617 || rec_scope_names
2618 .iter()
2619 .any(|n| referenced.contains(n.as_str()));
2620 rec_env.bind(key.clone(), value.clone());
2621 attrs.insert(key.clone(), value.clone());
2622 if let Value::Thunk(t) = &value {
2623 if needs_scope {
2624 thunks.push((key.clone(), t.clone()));
2625 crate::value::census::scope_pinned();
2626 } else {
2627 crate::value::census::scope_narrowed();
2628 }
2629 }
2630 defined_so_far.insert(key);
2631 } else {
2632 let key = path_keys[0].clone();
2636 let value =
2637 build_nested_attr_thunk(&path_keys[1..], &value_expr, env, &mut thunks);
2638 merge_nested_insert(&mut dotted_attrs, key, value);
2639 }
2640 }
2641 ast::Entry::Inherit(inherit) => {
2642 eval_inherit(&inherit, env, &mut attrs, Some(&mut rec_env), Some(&mut thunks))?;
2643 }
2644 }
2645 }
2646
2647 for (key, value) in dotted_attrs.iter() {
2652 attrs.insert(key.clone(), value.clone());
2653 rec_env.bind(key.clone(), value.clone());
2654 }
2655
2656 for (_key, thunk) in &thunks {
2659 thunk.update_env(&rec_env);
2660 }
2661 } else {
2662 for entry in set.entries() {
2663 match entry {
2664 ast::Entry::AttrpathValue(apv) => {
2665 let attrpath = apv.attrpath().ok_or_else(|| {
2666 EvalError::ParseError("binding missing attrpath".to_string())
2667 })?;
2668 let value_expr = apv.value().ok_or_else(|| {
2669 EvalError::ParseError("binding missing value".to_string())
2670 })?;
2671 let path_attrs: Vec<ast::Attr> = attrpath.attrs().collect();
2672 let tail_is_dynamic =
2682 path_attrs.len() > 1 && attrs_have_dynamic(&path_attrs[1..]);
2683 let head_key = match eval_attr_maybe_null(&path_attrs[0], env)? {
2684 Some(k) => k,
2685 None => continue,
2687 };
2688 if tail_is_dynamic && attrs.get(&head_key).is_none() {
2689 let value =
2690 build_deferred_tail_attr(&path_attrs[1..], &value_expr, env);
2691 attrs.insert(head_key, value);
2692 continue;
2693 }
2694 if tail_is_dynamic {
2708 if let Some(existing) = attrs.get(&head_key).cloned() {
2709 let merged = merge_deferred_dynamic_tail(
2710 existing,
2711 &path_attrs[1..],
2712 &value_expr,
2713 env,
2714 )?;
2715 attrs.insert(head_key, merged);
2716 continue;
2717 }
2718 }
2719 let mut path_keys: Vec<String> = {
2722 let mut v = Vec::with_capacity(path_attrs.len());
2723 v.push(head_key);
2724 let mut skip = false;
2725 for a in &path_attrs[1..] {
2726 match eval_attr_maybe_null(a, env)? {
2727 Some(k) => v.push(k),
2728 None => { skip = true; break; }
2729 }
2730 }
2731 if skip { v.clear(); }
2732 v
2733 };
2734 if path_keys.is_empty() { continue; }
2736 if path_keys.len() == 1 {
2737 let key = path_keys.pop().unwrap();
2738 let value = maybe_thunk(&value_expr, env, false, None);
2741 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2766 let existing = attrs.get(&key).cloned().unwrap();
2767 let forced_existing = force_value(&existing)?;
2768 attrs.insert(key.clone(), forced_existing);
2769 }
2770 if matches!(attrs.get(&key), Some(Value::Attrs(_))) {
2771 let forced = force_value(&value)?;
2772 merge_nested_insert(&mut attrs, key, forced);
2773 } else {
2774 attrs.insert(key, value);
2775 }
2776 } else {
2777 let key = path_keys[0].clone();
2778 let value = build_nested_attr(&path_keys[1..], &value_expr, env)?;
2779 if matches!(attrs.get(&key), Some(Value::Thunk(_))) {
2793 let existing = attrs.get(&key).cloned().unwrap();
2794 let forced = force_value(&existing)?;
2795 attrs.insert(key.clone(), forced);
2796 }
2797 merge_nested_insert(&mut attrs, key, value);
2798 }
2799 }
2800 ast::Entry::Inherit(inherit) => {
2801 eval_inherit(&inherit, env, &mut attrs, None, None)?;
2802 }
2803 }
2804 }
2805 }
2806
2807 attach_attrset_positions(set, &mut attrs, env);
2813
2814 Ok(Value::Attrs(Rc::new(attrs)))
2815}
2816
2817fn eval_inherit(
2818 inherit: &ast::Inherit,
2819 env: &Env,
2820 attrs: &mut NixAttrs,
2821 bind_env: Option<&mut Env>,
2822 mut thunks: Option<&mut Vec<(String, Thunk)>>,
2823) -> Result<(), EvalError> {
2824 if let Some(from) = inherit.from() {
2825 let source_expr = from
2845 .expr()
2846 .ok_or_else(|| EvalError::ParseError("inherit from missing expr".to_string()))?;
2847 let source_thunk = Thunk::new_suspended(source_expr, env.clone());
2851 let mut be = bind_env;
2852 for attr in inherit.attrs() {
2853 let name = eval_attr(&attr, env)?;
2854 let thunk = Thunk::new_inherit_select(source_thunk.clone(), name.clone());
2855 let value = Value::Thunk(thunk.clone());
2856 attrs.insert(name.clone(), value.clone());
2857 if let Some(ref mut e) = be {
2858 e.bind(name.clone(), value);
2859 }
2860 if let Some(ref mut t) = thunks {
2861 t.push((name, thunk));
2862 }
2863 }
2864 } else {
2865 let mut be = bind_env;
2881 for attr in inherit.attrs() {
2882 let name = eval_attr(&attr, env)?;
2883 let sym = crate::value::intern(&name);
2884 let value = if let Some(v) = env.lookup_fast(sym, &name) {
2885 v
2886 } else if let Some((scope_cache, scope_value)) =
2887 env.innermost_with_scope()
2888 {
2889 Value::Thunk(Thunk::new_with_ident(
2890 SmolStr::from(name.as_str()),
2891 scope_cache,
2892 scope_value,
2893 env.clone(),
2894 ))
2895 } else {
2896 return Err(EvalError::UndefinedVar(format!(
2897 "'{name}'{}",
2898 eval_file_ctx()
2899 )));
2900 };
2901 attrs.insert(name.clone(), value.clone());
2902 if let Some(ref mut e) = be {
2903 e.bind(name, value);
2904 }
2905 }
2906 }
2907 Ok(())
2908}
2909
2910fn build_nested_attr(
2911 path: &[String],
2912 expr: &ast::Expr,
2913 env: &Env,
2914) -> Result<Value, EvalError> {
2915 if path.is_empty() {
2916 return Ok(maybe_thunk(expr, env, false, None));
2921 }
2922 let key = path[0].clone();
2923 let inner = build_nested_attr(&path[1..], expr, env)?;
2924 let mut attrs = NixAttrs::new();
2925 attrs.insert(key, inner);
2926 Ok(Value::Attrs(Rc::new(attrs)))
2927}
2928
2929fn attr_is_dynamic(attr: &ast::Attr) -> bool {
2950 match attr {
2951 ast::Attr::Dynamic(_) => true,
2952 ast::Attr::Str(s) => s
2955 .normalized_parts()
2956 .iter()
2957 .any(|p| matches!(p, InterpolPart::Interpolation(_))),
2958 ast::Attr::Ident(_) => false,
2959 }
2960}
2961
2962fn attrs_have_dynamic(attrs: &[ast::Attr]) -> bool {
2970 attrs.iter().any(attr_is_dynamic)
2971}
2972
2973fn build_deferred_tail_attr(
2986 tail: &[ast::Attr],
2987 value_expr: &ast::Expr,
2988 env: &Env,
2989) -> Value {
2990 let tail: Vec<ast::Attr> = tail.to_vec();
2991 let value_expr = value_expr.clone();
2992 let env = env.clone();
2993 Value::Thunk(Thunk::new_native(move || {
2994 build_tail_attrs_now(&tail, &value_expr, &env)
2995 }))
2996}
2997
2998fn build_tail_attrs_now(
3019 tail: &[ast::Attr],
3020 value_expr: &ast::Expr,
3021 env: &Env,
3022) -> Result<Value, EvalError> {
3023 if tail.is_empty() {
3024 return Ok(maybe_thunk(value_expr, env, false, None));
3025 }
3026 if std::env::var_os("SUI_M26_TAILTRACE").is_some() {
3027 let t: String = tail[0].syntax().text().to_string().chars().take(40).collect();
3028 eprintln!("[M26 TAIL-RESOLVE] forcing dynamic tail key `{t}`");
3029 if attrs_have_dynamic(&tail[..1]) {
3030 crate::trace::dump_force_stack_ids();
3031 }
3032 }
3033 let key = match eval_attr_maybe_null(&tail[0], env)? {
3034 Some(k) => k,
3035 None => return Ok(Value::Attrs(Rc::new(NixAttrs::new()))),
3038 };
3039 let inner = if tail.len() == 1 {
3045 maybe_thunk(value_expr, env, false, None)
3046 } else {
3047 build_deferred_tail_attr(&tail[1..], value_expr, env)
3048 };
3049 let mut attrs = NixAttrs::new();
3050 attrs.insert(key, inner);
3051 Ok(Value::Attrs(Rc::new(attrs)))
3052}
3053
3054fn merge_deferred_dynamic_tail(
3072 existing: Value,
3073 tail: &[ast::Attr],
3074 value_expr: &ast::Expr,
3075 env: &Env,
3076) -> Result<Value, EvalError> {
3077 debug_assert!(!tail.is_empty());
3080
3081 if attr_is_dynamic(&tail[0]) {
3086 let deferred = build_deferred_tail_attr(tail, value_expr, env);
3087 return Ok(lazy_overlay_merge(existing, deferred));
3088 }
3089
3090 let key = match eval_attr_maybe_null(&tail[0], env)? {
3093 Some(k) => k,
3094 None => return Ok(existing),
3095 };
3096
3097 let existing_forced = force_value(&existing)?;
3101 let mut base = match existing_forced {
3102 Value::Attrs(a) => (*a).clone(),
3103 _ => {
3108 let deferred = build_deferred_tail_attr(tail, value_expr, env);
3109 return Ok(deferred);
3110 }
3111 };
3112
3113 let child_existing = base.get(&key).cloned();
3115 let new_child = match child_existing {
3116 Some(child) if tail.len() > 1 => {
3117 merge_deferred_dynamic_tail(child, &tail[1..], value_expr, env)?
3119 }
3120 Some(child) => {
3121 let leaf = maybe_thunk(value_expr, env, false, None);
3124 lazy_overlay_merge(child, leaf)
3125 }
3126 None if tail.len() > 1 => {
3127 build_deferred_tail_attr(&tail[1..], value_expr, env)
3131 }
3132 None => maybe_thunk(value_expr, env, false, None),
3133 };
3134 base.insert(key, new_child);
3135 Ok(Value::Attrs(Rc::new(base)))
3136}
3137
3138fn lazy_overlay_merge(left: Value, right: Value) -> Value {
3145 match (&left, &right) {
3146 (Value::Attrs(la), Value::Attrs(_)) => {
3147 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
3148 let mut merged = (**la).clone();
3149 if let Value::Attrs(ra) = &right {
3150 for (k, v) in ra.iter_unsorted() {
3154 merge_nested_insert(&mut merged, k.clone(), v.clone());
3155 }
3156 }
3157 Value::Attrs(Rc::new(merged))
3158 }
3159 _ => {
3160 Value::Thunk(Thunk::new_native(move || {
3164 let lf = force_value(&left)?;
3165 let rf = force_value(&right)?;
3166 let la = lf.as_attrs()?;
3167 let ra = rf.as_attrs()?;
3168 crate::perf::inc(crate::perf::Counter::SlashDeferredTailClone);
3169 let mut merged = (*la).clone();
3170 for (k, v) in ra.iter_unsorted() {
3171 merge_nested_insert(&mut merged, k.clone(), v.clone());
3172 }
3173 Ok(Value::Attrs(Rc::new(merged)))
3174 }))
3175 }
3176 }
3177}
3178
3179fn build_nested_attr_thunk(
3187 path: &[String],
3188 expr: &ast::Expr,
3189 env: &Env,
3190 thunks: &mut Vec<(String, Thunk)>,
3191) -> Value {
3192 if path.is_empty() {
3193 let thunk = Thunk::new_suspended(expr.clone(), env.clone());
3194 let val = Value::Thunk(thunk.clone());
3195 thunks.push((String::new(), thunk));
3196 return val;
3197 }
3198 let key = path[0].clone();
3199 let inner = build_nested_attr_thunk(&path[1..], expr, env, thunks);
3200 let mut attrs = NixAttrs::new();
3201 attrs.insert(key, inner);
3202 Value::Attrs(Rc::new(attrs))
3203}
3204
3205fn merge_nested_insert(target: &mut NixAttrs, key: String, value: Value) {
3212 let existing = match target.get(&key) {
3216 Some(e) => e.clone(),
3217 None => {
3218 target.insert(key, value);
3219 return;
3220 }
3221 };
3222 let value = match value {
3246 Value::Thunk(_) => match force_value(&value) {
3247 Ok(v @ Value::Attrs(_)) => v,
3248 _ => value,
3249 },
3250 other => other,
3251 };
3252 if !matches!(value, Value::Attrs(_)) {
3253 target.insert(key, value);
3254 return;
3255 }
3256 let existing_concrete = match &existing {
3259 Value::Attrs(_) => existing.clone(),
3260 Value::Thunk(_) => match force_value(&existing) {
3261 Ok(v @ Value::Attrs(_)) => v,
3262 _ => {
3263 target.insert(key, value);
3264 return;
3265 }
3266 },
3267 _ => {
3268 target.insert(key, value);
3269 return;
3270 }
3271 };
3272 let mut existing_attrs = match existing_concrete {
3276 Value::Attrs(a) => (*a).clone(),
3277 _ => unreachable!(),
3278 };
3279 let new_attrs = match value {
3280 Value::Attrs(ref a) => a,
3281 _ => unreachable!(),
3282 };
3283 for (k, v) in new_attrs.iter_unsorted() {
3284 merge_nested_insert(&mut existing_attrs, k.clone(), v.clone());
3285 }
3286 target.insert(key, Value::Attrs(Rc::new(existing_attrs)));
3287}
3288
3289fn eval_entries<N: HasEntry + AstNode>(node: &N, env: &mut Env) -> Result<(), EvalError> {
3291 for entry in node.entries() {
3292 match entry {
3293 ast::Entry::AttrpathValue(apv) => {
3294 let attrpath = apv.attrpath().ok_or_else(|| {
3295 EvalError::ParseError("binding missing attrpath".to_string())
3296 })?;
3297 let value_expr = apv.value().ok_or_else(|| {
3298 EvalError::ParseError("binding missing value".to_string())
3299 })?;
3300 let mut path_keys: Vec<String> = attrpath
3301 .attrs()
3302 .map(|a| eval_attr(&a, env))
3303 .collect::<Result<_, _>>()?;
3304 if path_keys.len() == 1 {
3305 let key = path_keys.pop().unwrap();
3306 let value = eval_expr(&value_expr, env)?;
3307 env.bind(key, value);
3308 }
3309 }
3311 ast::Entry::Inherit(inherit) => {
3312 if let Some(from) = inherit.from() {
3313 let source_expr = from.expr().ok_or_else(|| {
3314 EvalError::ParseError("inherit from missing expr".to_string())
3315 })?;
3316 let source = force_value(&eval_expr(&source_expr, env)?)?;
3317 let source_attrs = source.as_attrs()?;
3318 for attr in inherit.attrs() {
3319 let name = eval_attr(&attr, env)?;
3320 let value = source_attrs
3321 .get(&name)
3322 .cloned()
3323 .ok_or_else(|| EvalError::AttrNotFound(
3324 format!("'{name}' in inherit{}", eval_file_ctx()),
3325 ))?;
3326 env.bind(name, value);
3327 }
3328 } else {
3329 for attr in inherit.attrs() {
3330 let name = eval_attr(&attr, env)?;
3331 let value = env
3332 .lookup(&name)
3333 .ok_or_else(|| EvalError::UndefinedVar(
3334 format!("'{name}'{}", eval_file_ctx()),
3335 ))?;
3336 env.bind(name, value);
3337 }
3338 }
3339 }
3340 }
3341 }
3342 Ok(())
3343}
3344
3345fn eval_binop(
3346 op: ast::BinOpKind,
3347 lhs: &ast::Expr,
3348 rhs: &ast::Expr,
3349 env: &Env,
3350) -> Result<Value, EvalError> {
3351 match op {
3353 ast::BinOpKind::And => {
3354 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3355 if !l {
3356 return Ok(Value::Bool(false));
3357 }
3358 return eval_expr(rhs, env);
3359 }
3360 ast::BinOpKind::Or => {
3361 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3362 if l {
3363 return Ok(Value::Bool(true));
3364 }
3365 return eval_expr(rhs, env);
3366 }
3367 ast::BinOpKind::Implication => {
3368 let l = force_value(&eval_expr(lhs, env)?)?.as_bool()?;
3369 if !l {
3370 return Ok(Value::Bool(true));
3371 }
3372 return eval_expr(rhs, env);
3373 }
3374 _ => {}
3375 }
3376
3377 let lc = force_concrete(&eval_expr(lhs, env)?)?;
3378 let rc = force_concrete(&eval_expr(rhs, env)?)?;
3379 let l = lc.into_value();
3386 let r = rc.into_value();
3387
3388 match op {
3389 ast::BinOpKind::Add => match (&l, &r) {
3390 (Value::Int(a), Value::Int(b)) => a
3391 .checked_add(*b)
3392 .map(Value::Int)
3393 .ok_or_else(|| int_overflow("adding", *a, '+', *b)),
3394 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
3395 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
3396 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(a + *b as f64)),
3397 (Value::String(a), Value::String(b)) => {
3398 let mut ctx = a.context.clone();
3399 ctx.merge(&b.context);
3400 let mut s = String::with_capacity(a.chars.len() + b.chars.len());
3408 s.push_str(&a.chars);
3409 s.push_str(&b.chars);
3410 Ok(Value::String(Rc::new(NixString::with_context(s, ctx))))
3411 }
3412 (Value::Path(a), Value::String(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}{}", b.chars).as_str())))),
3413 (Value::Path(a), Value::Path(b)) => Ok(Value::Path(Box::new(SmolStr::from(format!("{a}/{b}").as_str())))),
3414 (Value::Attrs(_), _) | (_, Value::Attrs(_)) => {
3416 let (ls, lctx) = l.coerce_to_string()?;
3417 let (rs, rctx) = r.coerce_to_string()?;
3418 let mut ctx = lctx;
3419 ctx.merge(&rctx);
3420 Ok(Value::String(Rc::new(NixString::with_context(
3421 format!("{ls}{rs}"),
3422 ctx,
3423 ))))
3424 }
3425 _ => Err(EvalError::op_type("add", l.type_name(), r.type_name())),
3426 },
3427 ast::BinOpKind::Sub => num_op(
3428 &l,
3429 &r,
3430 |a, b| a.checked_sub(b),
3431 |a, b| a - b,
3432 |a, b| int_overflow("subtracting", a, '-', b),
3433 ),
3434 ast::BinOpKind::Mul => num_op(
3435 &l,
3436 &r,
3437 |a, b| a.checked_mul(b),
3438 |a, b| a * b,
3439 |a, b| int_overflow("multiplying", a, '*', b),
3440 ),
3441 ast::BinOpKind::Div => {
3442 let rhs_is_zero = match &r {
3451 Value::Int(0) => true,
3452 Value::Float(f) => *f == 0.0,
3453 _ => false,
3454 };
3455 if rhs_is_zero {
3456 return Err(EvalError::DivisionByZero);
3457 }
3458 num_op(
3459 &l,
3460 &r,
3461 |a, b| a.checked_div(b),
3462 |a, b| a / b,
3463 |a, b| int_overflow("dividing", a, '/', b),
3464 )
3465 }
3466 ast::BinOpKind::Equal => Ok(Value::Bool(l == r)),
3467 ast::BinOpKind::NotEqual => Ok(Value::Bool(l != r)),
3468 ast::BinOpKind::Less => compare(&l, &r, |o| o == std::cmp::Ordering::Less),
3469 ast::BinOpKind::LessOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Greater),
3470 ast::BinOpKind::More => compare(&l, &r, |o| o == std::cmp::Ordering::Greater),
3471 ast::BinOpKind::MoreOrEq => compare(&l, &r, |o| o != std::cmp::Ordering::Less),
3472 ast::BinOpKind::Update => {
3473 let la = l.to_attrs()?;
3474 let ra = r.to_attrs()?;
3475 Ok(Value::Attrs(Rc::new(la.overlay(ra))))
3477 }
3478 ast::BinOpKind::Concat => {
3479 crate::value::concat_lists(l, r.as_list()?)
3489 }
3490 ast::BinOpKind::And | ast::BinOpKind::Or | ast::BinOpKind::Implication => {
3491 unreachable!("handled above")
3492 }
3493 ast::BinOpKind::PipeRight | ast::BinOpKind::PipeLeft => {
3494 Err(EvalError::NotImplemented("pipe operators".to_string()))
3495 }
3496 }
3497}
3498
3499#[inline]
3504fn int_overflow(verb: &str, a: i64, sym: char, b: i64) -> EvalError {
3505 EvalError::Abort(format!("integer overflow in {verb} {a} {sym} {b}"))
3506}
3507
3508fn num_op(
3509 l: &Value,
3510 r: &Value,
3511 int_op: impl Fn(i64, i64) -> Option<i64>,
3512 float_op: impl Fn(f64, f64) -> f64,
3513 overflow: impl Fn(i64, i64) -> EvalError,
3514) -> Result<Value, EvalError> {
3515 match (l, r) {
3516 (Value::Int(a), Value::Int(b)) => {
3517 int_op(*a, *b).map(Value::Int).ok_or_else(|| overflow(*a, *b))
3518 }
3519 (Value::Float(a), Value::Float(b)) => Ok(Value::Float(float_op(*a, *b))),
3520 (Value::Int(a), Value::Float(b)) => Ok(Value::Float(float_op(*a as f64, *b))),
3521 (Value::Float(a), Value::Int(b)) => Ok(Value::Float(float_op(*a, *b as f64))),
3522 _ => Err(EvalError::op_type("perform arithmetic on", l.type_name(), r.type_name())),
3523 }
3524}
3525
3526fn compare(
3527 l: &Value,
3528 r: &Value,
3529 pred: impl Fn(std::cmp::Ordering) -> bool,
3530) -> Result<Value, EvalError> {
3531 let ord = match (l, r) {
3532 (Value::Int(a), Value::Int(b)) => a.cmp(b),
3533 (Value::Float(a), Value::Float(b)) => {
3534 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
3535 }
3536 (Value::Int(a), Value::Float(b)) => (*a as f64)
3537 .partial_cmp(b)
3538 .unwrap_or(std::cmp::Ordering::Equal),
3539 (Value::Float(a), Value::Int(b)) => a
3540 .partial_cmp(&(*b as f64))
3541 .unwrap_or(std::cmp::Ordering::Equal),
3542 (Value::String(a), Value::String(b)) => a.chars.cmp(&b.chars),
3543 _ => {
3544 return Err(EvalError::op_type("compare", l.type_name(), r.type_name()));
3545 }
3546 };
3547 Ok(Value::Bool(pred(ord)))
3548}
3549
3550pub fn apply_and_force(func: Value, arg: Value) -> Result<Value, EvalError> {
3564 force_value(&apply(func, arg)?)
3565}
3566
3567pub fn apply(func: Value, arg: Value) -> Result<Value, EvalError> {
3568 stacker::maybe_grow(64 * 1024, 2 * 1024 * 1024, || apply_inner(func, arg))
3569}
3570
3571fn apply_inner(func: Value, arg: Value) -> Result<Value, EvalError> {
3572 crate::perf::inc(crate::perf::Counter::Apply);
3573 let func = force_concrete(&func)?.into_value();
3574 match func {
3575 Value::Lambda(closure) => {
3576 if crate::perf::enabled() {
3578 APPLY_SITES.with(|sites| {
3579 let file = closure.env.eval_file()
3580 .map(|p| p.display().to_string())
3581 .unwrap_or_else(|| "<eval>".into());
3582 let param_name = match &closure.param {
3584 rnix::ast::Param::IdentParam(ip) => ip.ident().map(|i| ident_text(&i)).unwrap_or_default(),
3585 rnix::ast::Param::Pattern(pat) => {
3586 let mut names: Vec<String> = pat.pat_entries()
3587 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3588 .take(3)
3589 .collect();
3590 if pat.pat_entries().count() > 3 { names.push("...".to_string()); }
3591 format!("{{{}}}", names.join(","))
3592 }
3593 };
3594 let key = format!("{}:{}", file.rsplit_once("-source/").map_or(file.as_str(), |(_,s)| s), param_name);
3595 *sites.borrow_mut().entry(key).or_insert(0u64) += 1;
3596 });
3597 }
3598 let mut call_env = closure.env.child();
3599 let _file_guard = push_eval_frame(closure.env.eval_file().cloned());
3605 let _trace = push_nix_trace_lambda(&closure.env);
3611 match &closure.param {
3612 rnix::ast::Param::IdentParam(_) => {
3613 bind_param(&closure.param, &arg, &mut call_env)?;
3616 }
3617 rnix::ast::Param::Pattern(_) => {
3618 let forced_arg = force_concrete(&arg)?.into_value();
3620 bind_param(&closure.param, &forced_arg, &mut call_env)?;
3621 }
3622 }
3623 eval_expr(&closure.body, &call_env)
3624 }
3625 Value::Builtin(b) => {
3626 let _trace = push_nix_trace(format!("while calling the '{}' builtin", b.name));
3627 if builtin_takes_lazy_arg(&b.name) {
3637 (b.func)(&[arg])
3638 } else {
3639 let forced_arg = force_value(&arg)?;
3640 (b.func)(&[forced_arg])
3641 }
3642 }
3643 Value::Attrs(ref attrs) => {
3644 if let Some(functor) = attrs.get("__functor") {
3645 let functor = force_value(functor)?;
3646 let partial = apply(functor, func.clone())?;
3648 apply(partial, arg)
3649 } else if crate::value::in_promise_eval() {
3650 Ok(Value::Null)
3655 } else {
3656 Err(EvalError::type_error(
3657 format!("cannot call {} (missing __functor){}", func.type_name(), eval_file_ctx()),
3658 ))
3659 }
3660 }
3661 _ if crate::value::in_promise_eval() => {
3662 Ok(Value::Null)
3667 }
3668 _ => Err(EvalError::type_error(
3669 format!("cannot call {}{}", func.type_name(), eval_file_ctx()),
3670 )),
3671 }
3672}
3673
3674static SUI_BATCH_BIND: std::sync::LazyLock<bool> =
3684 std::sync::LazyLock::new(|| std::env::var_os("SUI_BATCH_BIND").is_some());
3685
3686fn bind_param(param: &ast::Param, arg: &Value, env: &mut Env) -> Result<(), EvalError> {
3687 match param {
3688 ast::Param::IdentParam(ip) => {
3689 let ident = ip
3690 .ident()
3691 .ok_or_else(|| EvalError::ParseError("ident param missing ident".to_string()))?;
3692 let name = ident_text(&ident);
3693 env.bind(name, arg.clone());
3694 }
3695 ast::Param::Pattern(pat) => {
3696 let attrs = arg.as_attrs()?;
3697
3698 if let Some(pat_bind) = pat.pat_bind()
3700 && let Some(ident) = pat_bind.ident()
3701 {
3702 let name = ident_text(&ident);
3703 env.bind(name, arg.clone());
3704 }
3705
3706 let has_ellipsis = pat.ellipsis_token().is_some();
3707 let entries: Vec<ast::PatEntry> = pat.pat_entries().collect();
3708
3709 let mut default_thunks: Vec<Thunk> = Vec::new();
3716 let use_batch = *SUI_BATCH_BIND;
3726 let mut pairs: Vec<(String, Value)> =
3727 if use_batch { Vec::with_capacity(entries.len()) } else { Vec::new() };
3728
3729 let narrow = scope_narrow_enabled();
3754 let default_names: HashSet<String> = if narrow {
3757 entries
3758 .iter()
3759 .filter(|e| e.default().is_some())
3760 .filter_map(ast::PatEntry::ident)
3761 .map(|i| ident_text(&i))
3762 .filter(|n| attrs.get(n).is_none())
3763 .collect()
3764 } else {
3765 HashSet::new()
3766 };
3767
3768 if narrow {
3769 let mut deferred: Vec<(String, ast::Expr)> =
3773 Vec::with_capacity(default_names.len());
3774 for entry in &entries {
3775 let ident = entry.ident().ok_or_else(|| {
3776 EvalError::ParseError("pat entry missing ident".to_string())
3777 })?;
3778 let name = ident_text(&ident);
3779 if let Some(v) = attrs.get(&name) {
3780 env.bind(name, v.clone());
3781 } else if let Some(default_expr) = entry.default() {
3782 deferred.push((
3783 name,
3784 ast::Expr::cast(default_expr.syntax().clone()).unwrap(),
3785 ));
3786 } else {
3787 return Err(EvalError::type_error(
3788 format!("missing argument '{name}'{}", eval_file_ctx()),
3789 ));
3790 }
3791 }
3792 for (name, default_expr) in deferred {
3795 let thunk =
3796 Thunk::new_suspended(default_expr.clone(), env.clone());
3797 let referenced = referenced_idents(&default_expr);
3798 if default_names.iter().any(|n| referenced.contains(n.as_str())) {
3799 default_thunks.push(thunk.clone());
3803 crate::value::census::scope_pinned();
3804 } else {
3805 crate::value::census::scope_narrowed();
3806 }
3807 env.bind(name, Value::Thunk(thunk));
3808 }
3809 } else {
3810 for entry in &entries {
3811 let ident = entry.ident().ok_or_else(|| {
3812 EvalError::ParseError("pat entry missing ident".to_string())
3813 })?;
3814 let name = ident_text(&ident);
3815 let value = if let Some(v) = attrs.get(&name) {
3816 v.clone()
3817 } else if let Some(default_expr) = entry.default() {
3818 let thunk = Thunk::new_suspended(
3824 ast::Expr::cast(default_expr.syntax().clone()).unwrap(),
3825 env.clone(),
3826 );
3827 default_thunks.push(thunk.clone());
3828 Value::Thunk(thunk)
3829 } else {
3830 return Err(EvalError::type_error(
3831 format!("missing argument '{name}'{}", eval_file_ctx()),
3832 ));
3833 };
3834 if use_batch {
3835 pairs.push((name, value));
3836 } else {
3837 env.bind(name, value);
3838 }
3839 }
3840 if use_batch {
3841 env.bind_many(pairs);
3842 }
3843 }
3844
3845 for thunk in &default_thunks {
3847 thunk.update_env(env);
3848 }
3849
3850 if !has_ellipsis {
3851 let entry_names: std::collections::HashSet<String> = entries
3852 .iter()
3853 .filter_map(|e| e.ident().map(|i| ident_text(&i)))
3854 .collect();
3855 for key in attrs.keys() {
3856 if !entry_names.contains(key.as_str()) {
3857 return Err(EvalError::type_error(
3858 format!("unexpected argument '{key}'{}", eval_file_ctx()),
3859 ));
3860 }
3861 }
3862 }
3863 }
3864 }
3865 Ok(())
3866}
3867
3868#[cfg(test)]
3869mod tests {
3870 use super::*;
3871
3872 fn ev(input: &str) -> Value {
3873 eval(input).unwrap()
3874 }
3875
3876 #[test]
3883 fn is_self_recursive_binding_ignores_attribute_names() {
3884 fn expr(s: &str) -> ast::Expr {
3885 rnix::Root::parse(s).tree().expr().expect("parse")
3886 }
3887 assert!(!is_self_recursive_binding(&expr("lhs.placeholder"), "placeholder"));
3889 assert!(!is_self_recursive_binding(&expr("{ placeholder = 1; }"), "placeholder"));
3890 assert!(!is_self_recursive_binding(
3891 &expr("if lhs.placeholder == rhs.placeholder then lhs.placeholder else null"),
3892 "placeholder",
3893 ));
3894 assert!(is_self_recursive_binding(&expr("placeholder + 1"), "placeholder"));
3896 assert!(is_self_recursive_binding(
3897 &expr("if placeholder then 1 else 2"),
3898 "placeholder"
3899 ));
3900 }
3901
3902 #[test]
3906 fn maybe_thunk_eager_constant_str_is_byte_identical() {
3907 fn expr(s: &str) -> ast::Expr {
3908 rnix::Root::parse(s).tree().expr().expect("parse")
3909 }
3910 let env = Env::new();
3911 let v = maybe_thunk(&expr(r#""abc""#), &env, false, None);
3913 assert!(matches!(v, Value::String(_)), "constant str should be eager, got {v:?}");
3914 assert_eq!(force_value(&v).unwrap(), Value::string("abc"));
3915 let vi = maybe_thunk(&expr(r#""a${b}c""#), &env, false, None);
3917 assert!(matches!(vi, Value::Thunk(_)), "interpolated str must stay thunked");
3918 }
3919
3920 #[test]
3924 fn eval_pure_constant_arg_classification() {
3925 fn expr(s: &str) -> ast::Expr {
3926 rnix::Root::parse(s).tree().expr().expect("parse")
3927 }
3928 assert!(eval_pure_constant_arg(&expr("42")).is_some());
3930 assert!(eval_pure_constant_arg(&expr("3.14")).is_some());
3931 assert!(eval_pure_constant_arg(&expr(r#""const""#)).is_some());
3932 assert!(eval_pure_constant_arg(&expr("/abs/path")).is_some());
3933 assert!(eval_pure_constant_arg(&expr(r#""a${b}c""#)).is_none(), "interpolated str");
3935 assert!(eval_pure_constant_arg(&expr("true")).is_none(), "bool is an ident");
3938 assert!(eval_pure_constant_arg(&expr("x")).is_none(), "ident (with-scope force)");
3939 assert!(eval_pure_constant_arg(&expr("a.b")).is_none(), "select (fixpoint)");
3940 assert!(eval_pure_constant_arg(&expr("f x")).is_none(), "apply (may throw)");
3941 assert!(eval_pure_constant_arg(&expr("1 + 1")).is_none(), "binop (may throw)");
3942 assert!(eval_pure_constant_arg(&expr("throw \"x\"")).is_none(), "throw stays lazy");
3943 }
3944
3945 #[test]
3949 fn ignored_throwing_arg_stays_lazy() {
3950 assert_eq!(ev(r#"(x: 7) (throw "boom")"#), Value::Int(7));
3951 assert_eq!(ev(r#"(x: 7) "const""#), Value::Int(7));
3953 assert_eq!(ev(r#"(x: x) "used""#), Value::string("used"));
3955 }
3956
3957 #[test]
3958 fn eval_int() { assert_eq!(ev("42"), Value::Int(42)); }
3959
3960 #[test]
3961 fn eval_float() { assert_eq!(ev("3.14"), Value::Float(3.14)); }
3962
3963 #[test]
3964 fn eval_string() { assert_eq!(ev(r#""hello""#), Value::string("hello")); }
3965
3966 #[test]
3967 fn eval_bool() { assert_eq!(ev("true"), Value::Bool(true)); }
3968
3969 #[test]
3970 fn eval_null() { assert_eq!(ev("null"), Value::Null); }
3971
3972 #[test]
3973 fn eval_arithmetic() {
3974 assert_eq!(ev("1 + 2"), Value::Int(3));
3975 assert_eq!(ev("10 - 3"), Value::Int(7));
3976 assert_eq!(ev("2 * 3"), Value::Int(6));
3977 assert_eq!(ev("10 / 3"), Value::Int(3));
3978 }
3979
3980 #[test]
3981 fn eval_precedence() {
3982 assert_eq!(ev("1 + 2 * 3"), Value::Int(7));
3983 assert_eq!(ev("(1 + 2) * 3"), Value::Int(9));
3984 }
3985
3986 #[test]
3987 fn eval_comparison() {
3988 assert_eq!(ev("1 == 1"), Value::Bool(true));
3989 assert_eq!(ev("1 == 2"), Value::Bool(false));
3990 assert_eq!(ev("1 < 2"), Value::Bool(true));
3991 assert_eq!(ev("2 <= 2"), Value::Bool(true));
3992 }
3993
3994 #[test]
3995 fn eval_logic() {
3996 assert_eq!(ev("true && false"), Value::Bool(false));
3997 assert_eq!(ev("true || false"), Value::Bool(true));
3998 assert_eq!(ev("!true"), Value::Bool(false));
3999 }
4000
4001 #[test]
4002 fn eval_string_concat() {
4003 assert_eq!(ev(r#""hello" + " " + "world""#), Value::string("hello world"));
4004 }
4005
4006 #[test]
4007 fn eval_if() {
4008 assert_eq!(ev("if true then 1 else 2"), Value::Int(1));
4009 assert_eq!(ev("if false then 1 else 2"), Value::Int(2));
4010 }
4011
4012 #[test]
4013 fn eval_let() {
4014 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
4015 assert_eq!(ev("let x = 1; y = 2; in x + y"), Value::Int(3));
4016 }
4017
4018 #[test]
4019 fn eval_let_dotted_simple() {
4020 assert_eq!(ev("let a.b = 1; a.c = 2; in a.b + a.c"), Value::Int(3));
4022 }
4023
4024 #[test]
4025 fn eval_let_dotted_deep() {
4026 assert_eq!(ev("let a.b.c = 1; in a.b.c"), Value::Int(1));
4028 }
4029
4030 #[test]
4031 fn eval_let_dotted_mixed() {
4032 assert_eq!(
4034 ev("let a.x = 1; b = 2; a.y = 3; in a.x + a.y + b"),
4035 Value::Int(6),
4036 );
4037 }
4038
4039 #[test]
4040 fn eval_let_dotted_produces_attrset() {
4041 let v = ev("let a.b = 1; a.c = 2; in a");
4043 if let Value::Attrs(attrs) = v {
4044 assert_eq!(attrs.get("b"), Some(&Value::Int(1)));
4045 assert_eq!(attrs.get("c"), Some(&Value::Int(2)));
4046 } else {
4047 panic!("expected Attrs, got {v:?}");
4048 }
4049 }
4050
4051 #[test]
4059 fn dynamic_inner_attr_key_is_lazy_on_sibling_read() {
4060 assert_eq!(
4062 ev(r#"let s = { a.${throw "KEYFORCED"} = 7; other = 9; }; in s.other"#),
4063 Value::Int(9),
4064 );
4065 }
4066
4067 #[test]
4068 fn dynamic_inner_attr_key_resolves_on_head_demand() {
4069 let v = ev(r#"let u = "bob"; s = { homes.${u} = 7; }; in s.homes"#);
4071 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4072 assert_eq!(attrs.get("bob"), Some(&Value::Int(7)));
4073 } else {
4074 panic!("expected Attrs");
4075 }
4076 }
4077
4078 #[test]
4079 fn dynamic_inner_attr_key_merges_with_static_sibling() {
4080 let v = ev(r#"let u = "x"; s = { a.${u} = 1; a.b = 2; }; in s.a"#);
4082 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4083 assert_eq!(attrs.get("x"), Some(&Value::Int(1)));
4084 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4085 } else {
4086 panic!("expected Attrs");
4087 }
4088 }
4089
4090 #[test]
4091 fn dynamic_inner_attr_key_null_skips_binding() {
4092 let v = ev(
4095 r#"let c = true; s = { a.${if c then null else "n"} = 5; b = 1; }; in s.b"#,
4096 );
4097 assert_eq!(v, Value::Int(1));
4098 }
4099
4100 #[test]
4106 fn interpolated_string_attr_key_is_lazy_on_sibling_read() {
4107 assert_eq!(
4108 ev(r#"let s = { a."p/${throw "KEYFORCED"}" = 7; other = 9; }; in s.other"#),
4109 Value::Int(9),
4110 );
4111 }
4112
4113 #[test]
4114 fn interpolated_string_attr_key_resolves_on_head_demand() {
4115 let v = ev(r#"let u = "bob"; s = { homes."u/${u}" = 7; }; in s.homes"#);
4117 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4118 assert_eq!(attrs.get("u/bob"), Some(&Value::Int(7)));
4119 } else {
4120 panic!("expected Attrs");
4121 }
4122 }
4123
4124 #[test]
4125 fn purely_literal_string_attr_key_stays_eager_static() {
4126 let v = ev(r#"let s = { a."foo bar" = 1; a.b = 2; }; in s.a"#);
4129 if let Value::Attrs(attrs) = force_value(&v).unwrap() {
4130 assert_eq!(attrs.get("foo bar"), Some(&Value::Int(1)));
4131 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4132 } else {
4133 panic!("expected Attrs");
4134 }
4135 }
4136
4137 #[test]
4140 fn dynamic_tail_key_under_colliding_head_is_lazy() {
4141 let v = ev(
4144 r#"let s = { sd.services.x = 1; sd.tmpfiles.${throw "KEYFORCED"}.d = 2; }; in s.sd.services.x"#,
4145 );
4146 assert_eq!(v, Value::Int(1));
4147 }
4148
4149 #[test]
4150 fn dynamic_tail_key_under_colliding_head_resolves_and_merges() {
4151 let v = ev(
4154 r#"let k = "z"; s = { sd.services.x = 1; sd.tmpfiles.${k}.d = 2; }; in s.sd"#,
4155 );
4156 let sd = force_value(&v).unwrap();
4157 if let Value::Attrs(sd_attrs) = &sd {
4158 let services = force_value(sd_attrs.get("services").unwrap()).unwrap();
4160 if let Value::Attrs(a) = &services {
4161 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4162 } else { panic!("expected services attrs"); }
4163 let tmpfiles = force_value(sd_attrs.get("tmpfiles").unwrap()).unwrap();
4165 if let Value::Attrs(a) = &tmpfiles {
4166 let z = force_value(a.get("z").unwrap()).unwrap();
4167 if let Value::Attrs(zd) = &z {
4168 assert_eq!(force_value(zd.get("d").unwrap()).unwrap(), Value::Int(2));
4169 } else { panic!("expected z attrs"); }
4170 } else { panic!("expected tmpfiles attrs"); }
4171 } else {
4172 panic!("expected sd attrs");
4173 }
4174 }
4175
4176 #[test]
4185 fn with_namespace_is_lazy_on_body_whnf() {
4186 let v = ev(r#"builtins.attrNames (with (throw "WITH-FORCED"); { a = 1; b = 2; })"#);
4187 if let Value::List(items) = force_value(&v).unwrap() {
4188 let names: Vec<String> = items
4189 .iter()
4190 .map(|i| match force_value(i).unwrap() {
4191 Value::String(s) => s.as_str().to_string(),
4192 other => panic!("expected string, got {}", other.type_name()),
4193 })
4194 .collect();
4195 assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
4196 } else {
4197 panic!("expected list");
4198 }
4199 }
4200
4201 #[test]
4202 fn with_namespace_forces_only_on_fallthrough() {
4203 assert_eq!(ev(r#"with { x = 42; }; x"#), Value::Int(42));
4207 assert_eq!(ev(r#"let x = 7; in with (throw "NS"); x"#), Value::Int(7));
4210 }
4211
4212 #[test]
4223 fn dotted_fullset_leaf_deep_merges_with_deeper_sibling() {
4224 let v = ev(r#"{ o.a = { x = 1; }; o.a.y = 2; }.o.a"#);
4225 if let Value::Attrs(a) = force_value(&v).unwrap() {
4226 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4227 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
4228 } else {
4229 panic!("expected attrs");
4230 }
4231 }
4232
4233 #[test]
4234 fn dotted_fullset_leaf_deep_merge_reverse_order() {
4235 let v = ev(r#"{ o.a.y = 2; o.a = { x = 1; }; }.o.a"#);
4238 if let Value::Attrs(a) = force_value(&v).unwrap() {
4239 assert_eq!(force_value(a.get("x").unwrap()).unwrap(), Value::Int(1));
4240 assert_eq!(force_value(a.get("y").unwrap()).unwrap(), Value::Int(2));
4241 } else {
4242 panic!("expected attrs");
4243 }
4244 }
4245
4246 #[test]
4247 fn dotted_fullset_leaf_merge_preserves_leaf_laziness() {
4248 assert_eq!(ev(r#"{ o.a = { x = throw "X-NEVER"; }; o.a.y = 2; }.o.a.y"#), Value::Int(2));
4252 }
4253
4254 #[test]
4255 fn eval_nested_let() {
4256 assert_eq!(ev("let a = 1; b = let c = 2; in c; in a + b"), Value::Int(3));
4257 }
4258
4259 #[test]
4260 fn eval_lambda() {
4261 assert_eq!(ev("(x: x + 1) 41"), Value::Int(42));
4262 }
4263
4264 #[test]
4265 fn eval_lambda_multi_arg() {
4266 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
4267 }
4268
4269 #[test]
4270 fn eval_list() {
4271 let v = ev("[1 2 3]");
4272 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]));
4273 }
4274
4275 #[test]
4276 fn eval_list_concat() {
4277 let v = ev("[1 2] ++ [3 4]");
4278 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]));
4279 }
4280
4281 #[test]
4282 fn eval_attrset() {
4283 let v = ev("{ a = 1; b = 2; }");
4284 if let Value::Attrs(attrs) = v {
4285 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4286 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4287 } else {
4288 panic!("expected attrset");
4289 }
4290 }
4291
4292 #[test]
4293 fn eval_select() {
4294 assert_eq!(ev("{ a = 42; }.a"), Value::Int(42));
4295 }
4296
4297 #[test]
4298 fn eval_select_or() {
4299 assert_eq!(ev("{ a = 42; }.b or 0"), Value::Int(0));
4300 }
4301
4302 #[test]
4303 fn eval_has_attr() {
4304 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
4305 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
4306 }
4307
4308 #[test]
4309 fn eval_update() {
4310 let v = ev("{ a = 1; b = 2; } // { b = 3; c = 4; }");
4311 if let Value::Attrs(attrs) = v {
4312 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4313 assert_eq!(attrs.get("b"), Some(&Value::Int(3)));
4314 assert_eq!(attrs.get("c"), Some(&Value::Int(4)));
4315 } else {
4316 panic!("expected attrset");
4317 }
4318 }
4319
4320 #[test]
4321 fn eval_with() {
4322 assert_eq!(ev("with { x = 42; }; x"), Value::Int(42));
4323 }
4324
4325 #[test]
4326 fn eval_assert() {
4327 assert_eq!(ev("assert true; 42"), Value::Int(42));
4328 assert!(eval("assert false; 42").is_err());
4329 }
4330
4331 #[test]
4332 fn eval_formals() {
4333 assert_eq!(ev("({ a, b }: a + b) { a = 1; b = 2; }"), Value::Int(3));
4334 }
4335
4336 #[test]
4337 fn eval_formals_default() {
4338 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 1; }"), Value::Int(11));
4339 }
4340
4341 #[test]
4342 fn eval_formals_ellipsis() {
4343 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; }"), Value::Int(1));
4344 }
4345
4346 #[test]
4347 fn eval_named_formals() {
4348 assert_eq!(ev("(args @ { a }: args.a) { a = 42; }"), Value::Int(42));
4349 }
4350
4351 #[test]
4352 fn eval_rec_attrset() {
4353 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
4354 }
4355
4356 #[test]
4357 fn eval_negation() {
4358 assert_eq!(ev("-42"), Value::Int(-42));
4359 }
4360
4361 #[test]
4362 fn eval_float_arithmetic() {
4363 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
4364 assert_eq!(ev("1 + 1.5"), Value::Float(2.5));
4365 }
4366
4367 #[test]
4368 fn eval_division_by_zero() {
4369 assert!(eval("1 / 0").is_err());
4370 }
4371
4372 #[test]
4373 fn eval_builtins_available() {
4374 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
4375 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
4376 }
4377
4378 #[test]
4379 fn eval_builtins_length() {
4380 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
4381 }
4382
4383 #[test]
4384 fn eval_builtins_head_tail() {
4385 assert_eq!(ev("builtins.head [1 2 3]"), Value::Int(1));
4386 assert_eq!(ev("builtins.length (builtins.tail [1 2 3])"), Value::Int(2));
4387 }
4388
4389 #[test]
4390 fn eval_builtins_add() {
4391 assert_eq!(ev("builtins.add 1 2"), Value::Int(3));
4392 }
4393
4394 #[test]
4395 fn eval_builtins_to_string() {
4396 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
4397 }
4398
4399 #[test]
4400 fn eval_implication() {
4401 assert_eq!(ev("false -> true"), Value::Bool(true));
4402 assert_eq!(ev("true -> false"), Value::Bool(false));
4403 assert_eq!(ev("true -> true"), Value::Bool(true));
4404 }
4405
4406 #[test]
4409 fn eval_error_undefined_variable() {
4410 let result = eval("nonexistent");
4411 assert!(result.is_err());
4412 let msg = format!("{}", result.unwrap_err());
4413 assert!(msg.contains("undefined variable"));
4414 }
4415
4416 #[test]
4417 fn eval_error_type_mismatch_arithmetic() {
4418 let result = eval(r#"1 + "hello""#);
4419 assert!(result.is_err());
4420 let msg = format!("{}", result.unwrap_err());
4421 assert!(msg.contains("cannot add") || msg.contains("type"));
4422 }
4423
4424 #[test]
4425 fn eval_error_unexpected_argument() {
4426 let result = eval("({ a }: a) { a = 1; b = 2; }");
4427 assert!(result.is_err());
4428 let msg = format!("{}", result.unwrap_err());
4429 assert!(msg.contains("unexpected argument"));
4430 }
4431
4432 #[test]
4433 fn eval_error_missing_required_argument() {
4434 let result = eval("({ a, b }: a + b) { a = 1; }");
4435 assert!(result.is_err());
4436 let msg = format!("{}", result.unwrap_err());
4437 assert!(msg.contains("missing argument"));
4438 }
4439
4440 #[test]
4441 fn eval_builtins_attr_names_sorted() {
4442 let v = ev("builtins.attrNames { z = 1; a = 2; m = 3; }");
4443 assert_eq!(
4445 v,
4446 Value::list(vec![
4447 Value::string("a"),
4448 Value::string("m"),
4449 Value::string("z"),
4450 ]),
4451 );
4452 }
4453
4454 #[test]
4455 fn eval_builtins_attr_values() {
4456 let v = ev("builtins.attrValues { a = 1; b = 2; }");
4457 assert_eq!(v, Value::list(vec![Value::Int(1), Value::Int(2)]));
4459 }
4460
4461 #[test]
4462 fn eval_builtins_is_null() {
4463 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
4464 assert_eq!(ev("builtins.isNull 1"), Value::Bool(false));
4465 }
4466
4467 #[test]
4468 fn eval_builtins_is_int() {
4469 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
4470 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
4471 }
4472
4473 #[test]
4474 fn eval_builtins_is_bool() {
4475 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
4476 assert_eq!(ev("builtins.isBool 0"), Value::Bool(false));
4477 }
4478
4479 #[test]
4480 fn eval_builtins_is_string() {
4481 assert_eq!(ev(r#"builtins.isString "hi""#), Value::Bool(true));
4482 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
4483 }
4484
4485 #[test]
4486 fn eval_builtins_is_list() {
4487 assert_eq!(ev("builtins.isList [1 2]"), Value::Bool(true));
4488 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
4489 }
4490
4491 #[test]
4492 fn eval_builtins_is_attrs() {
4493 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
4494 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
4495 }
4496
4497 #[test]
4498 fn eval_builtins_string_length() {
4499 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
4500 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
4501 }
4502
4503 #[test]
4504 fn eval_builtins_to_json_roundtrip() {
4505 assert_eq!(
4507 ev(r#"builtins.fromJSON (builtins.toJSON 42)"#),
4508 Value::Int(42),
4509 );
4510 assert_eq!(
4511 ev(r#"builtins.fromJSON (builtins.toJSON [1 2 3])"#),
4512 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4513 );
4514 }
4515
4516 #[test]
4517 fn eval_builtins_from_json() {
4518 assert_eq!(
4519 ev(r#"builtins.fromJSON "{\"a\": 1}""#),
4520 {
4521 let mut attrs = NixAttrs::new();
4522 attrs.insert("a".to_string(), Value::Int(1));
4523 Value::Attrs(Rc::new(attrs))
4524 },
4525 );
4526 assert_eq!(ev(r#"builtins.fromJSON "null""#), Value::Null);
4527 assert_eq!(ev(r#"builtins.fromJSON "true""#), Value::Bool(true));
4528 }
4529
4530 #[test]
4531 fn eval_nested_function_application() {
4532 assert_eq!(ev("(x: y: x + y) 1 2"), Value::Int(3));
4534 assert_eq!(ev("((x: y: x + y) 1) 2"), Value::Int(3));
4536 }
4537
4538 #[test]
4539 fn eval_recursive_let() {
4540 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
4541 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
4542 }
4543
4544 #[test]
4545 fn eval_string_comparison() {
4546 assert_eq!(ev(r#""a" < "b""#), Value::Bool(true));
4547 assert_eq!(ev(r#""b" < "a""#), Value::Bool(false));
4548 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4549 assert_eq!(ev(r#""abc" != "def""#), Value::Bool(true));
4550 }
4551
4552 #[test]
4553 fn eval_list_in_attrset() {
4554 let v = ev("{ x = [1 2 3]; }.x");
4555 assert_eq!(
4556 v,
4557 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4558 );
4559 }
4560
4561 #[test]
4562 fn eval_nested_attrset_select() {
4563 assert_eq!(ev("{ a = { b = 42; }; }.a.b"), Value::Int(42));
4564 }
4565
4566 #[test]
4567 fn eval_let_shadows_outer() {
4568 assert_eq!(
4569 ev("let x = 1; in let x = 2; in x"),
4570 Value::Int(2),
4571 );
4572 }
4573
4574 #[test]
4575 fn eval_with_provides_scope() {
4576 assert_eq!(
4578 ev("with { x = 42; y = 10; }; x + y"),
4579 Value::Int(52),
4580 );
4581 }
4582
4583 #[test]
4584 fn eval_list_equality() {
4585 assert_eq!(ev("[1 2] == [1 2]"), Value::Bool(true));
4586 assert_eq!(ev("[1 2] == [1 3]"), Value::Bool(false));
4587 }
4588
4589 #[test]
4590 fn eval_attrset_equality() {
4591 assert_eq!(ev("{ a = 1; } == { a = 1; }"), Value::Bool(true));
4592 assert_eq!(ev("{ a = 1; } == { a = 2; }"), Value::Bool(false));
4593 }
4594
4595 #[test]
4600 fn literal_int_large_zero_negative() {
4601 assert_eq!(ev("9223372036854775807"), Value::Int(i64::MAX));
4603 assert_eq!(ev("0"), Value::Int(0));
4605 assert_eq!(ev("-1"), Value::Int(-1));
4607 assert_eq!(ev("-999999"), Value::Int(-999999));
4608 }
4609
4610 #[test]
4611 fn literal_float_small_large() {
4612 assert_eq!(ev("0.001"), Value::Float(0.001));
4613 assert_eq!(ev("999999.999"), Value::Float(999999.999));
4614 assert_eq!(ev("1.0e3"), Value::Float(1000.0));
4616 assert_eq!(ev("1.5e2"), Value::Float(150.0));
4617 }
4618
4619 #[test]
4620 fn literal_string_empty_and_escapes() {
4621 assert_eq!(ev(r#""""#), Value::string(""));
4622 assert_eq!(ev(r#""hello\nworld""#), Value::string("hello\nworld"));
4624 assert_eq!(ev(r#""tab\there""#), Value::string("tab\there"));
4625 }
4626
4627 #[test]
4628 fn literal_multiline_string() {
4629 assert_eq!(
4631 ev("''hello''"),
4632 Value::string("hello"),
4633 );
4634 assert_eq!(
4636 ev("''\n line1\n line2\n''"),
4637 Value::string("line1\nline2\n"),
4638 );
4639 }
4640
4641 #[test]
4642 fn literal_paths() {
4643 assert_eq!(ev("./foo"), Value::Path(Box::new(SmolStr::from("./foo"))));
4645 assert_eq!(ev("/nix/store/abc"), Value::Path(Box::new(SmolStr::from("/nix/store/abc"))));
4647 assert_eq!(ev("~/myfile"), Value::Path(Box::new(SmolStr::from("~/myfile"))));
4649 }
4650
4651 #[test]
4661 fn interp_path_abs_splices_and_types_path() {
4662 let v = ev(r#"let x = "foo"; in /a/${x}/b"#);
4664 assert_eq!(v, Value::Path(Box::new(SmolStr::from("/a/foo/b"))));
4665 }
4666
4667 #[test]
4668 fn interp_path_abs_multi_and_slash_in_value() {
4669 assert_eq!(
4671 ev(r#"let a = "x"; b = "y/z"; in /p/${a}/${b}.nix"#),
4672 Value::Path(Box::new(SmolStr::from("/p/x/y/z.nix"))),
4673 );
4674 }
4675
4676 #[test]
4677 fn interp_path_abs_normalizes_double_slash_seam() {
4678 assert_eq!(
4681 ev(r#"/bar/${/tmp/foo}"#),
4682 Value::Path(Box::new(SmolStr::from("/bar/tmp/foo"))),
4683 );
4684 }
4685
4686 #[test]
4687 fn interp_path_rel_resolves_against_eval_dir() {
4688 let _g = push_eval_file(std::path::PathBuf::from("/tmp/example/default.nix"));
4692 assert_eq!(
4693 ev(r#"let x = "foo"; in ./${x}.nix"#),
4694 Value::Path(Box::new(SmolStr::from("/tmp/example/foo.nix"))),
4695 );
4696 }
4697
4698 #[test]
4699 fn interp_path_rel_no_eval_dir_keeps_relative_text() {
4700 assert_eq!(
4703 ev(r#"let x = "foo"; in ./${x}.nix"#),
4704 Value::Path(Box::new(SmolStr::from("./foo.nix"))),
4705 );
4706 }
4707
4708 #[test]
4709 fn interp_path_home_splices_leading_tilde_preserved() {
4710 assert_eq!(
4714 ev(r#"let x = "foo"; in ~/${x}/bar"#),
4715 Value::Path(Box::new(SmolStr::from("~/foo/bar"))),
4716 );
4717 }
4718
4719 #[test]
4720 fn interp_path_non_interpolated_still_raw() {
4721 assert_eq!(ev("/a/b/c"), Value::Path(Box::new(SmolStr::from("/a/b/c"))));
4724 assert_eq!(ev("~/plain"), Value::Path(Box::new(SmolStr::from("~/plain"))));
4725 }
4726
4727 #[test]
4728 fn literal_null_true_false_standalone() {
4729 assert_eq!(ev("null"), Value::Null);
4730 assert_eq!(ev("true"), Value::Bool(true));
4731 assert_eq!(ev("false"), Value::Bool(false));
4732 }
4733
4734 #[test]
4739 fn op_arithmetic_int() {
4740 assert_eq!(ev("100 + 200"), Value::Int(300));
4741 assert_eq!(ev("50 - 30"), Value::Int(20));
4742 assert_eq!(ev("7 * 8"), Value::Int(56));
4743 assert_eq!(ev("17 / 3"), Value::Int(5)); }
4745
4746 #[test]
4747 fn op_arithmetic_float() {
4748 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
4749 assert_eq!(ev("5.0 - 1.5"), Value::Float(3.5));
4750 assert_eq!(ev("2.0 * 3.0"), Value::Float(6.0));
4751 assert_eq!(ev("7.0 / 2.0"), Value::Float(3.5));
4752 }
4753
4754 #[test]
4755 fn op_arithmetic_mixed_int_float() {
4756 assert_eq!(ev("1 + 2.5"), Value::Float(3.5));
4758 assert_eq!(ev("2.5 + 1"), Value::Float(3.5));
4759 assert_eq!(ev("2 * 1.5"), Value::Float(3.0));
4761 assert_eq!(ev("5.5 - 2"), Value::Float(3.5));
4763 }
4764
4765 #[test]
4766 fn op_string_concat() {
4767 assert_eq!(ev(r#""foo" + "bar""#), Value::string("foobar"));
4768 assert_eq!(ev(r#""" + "x""#), Value::string("x"));
4769 assert_eq!(ev(r#""a" + "" + "b""#), Value::string("ab"));
4770 }
4771
4772 #[test]
4773 fn op_path_concat() {
4774 assert_eq!(ev(r#"./foo + "/bar""#), Value::Path(Box::new(SmolStr::from("./foo/bar"))));
4776 assert_eq!(ev("./a + ./b"), Value::Path(Box::new(SmolStr::from("./a/./b"))));
4778 }
4779
4780 #[test]
4781 fn op_comparison_ints() {
4782 assert_eq!(ev("1 < 2"), Value::Bool(true));
4783 assert_eq!(ev("2 < 1"), Value::Bool(false));
4784 assert_eq!(ev("2 > 1"), Value::Bool(true));
4785 assert_eq!(ev("1 > 2"), Value::Bool(false));
4786 assert_eq!(ev("2 <= 2"), Value::Bool(true));
4787 assert_eq!(ev("3 <= 2"), Value::Bool(false));
4788 assert_eq!(ev("2 >= 2"), Value::Bool(true));
4789 assert_eq!(ev("1 >= 2"), Value::Bool(false));
4790 }
4791
4792 #[test]
4793 fn op_comparison_floats() {
4794 assert_eq!(ev("1.5 < 2.5"), Value::Bool(true));
4795 assert_eq!(ev("2.5 > 1.5"), Value::Bool(true));
4796 assert_eq!(ev("1.5 <= 1.5"), Value::Bool(true));
4797 assert_eq!(ev("1.5 >= 1.5"), Value::Bool(true));
4798 }
4799
4800 #[test]
4801 fn op_comparison_strings() {
4802 assert_eq!(ev(r#""apple" < "banana""#), Value::Bool(true));
4803 assert_eq!(ev(r#""banana" > "apple""#), Value::Bool(true));
4804 assert_eq!(ev(r#""abc" == "abc""#), Value::Bool(true));
4805 assert_eq!(ev(r#""abc" != "xyz""#), Value::Bool(true));
4806 assert_eq!(ev(r#""abc" <= "abd""#), Value::Bool(true));
4807 assert_eq!(ev(r#""abc" >= "abb""#), Value::Bool(true));
4808 }
4809
4810 #[test]
4811 fn op_equality_various_types() {
4812 assert_eq!(ev("null == null"), Value::Bool(true));
4813 assert_eq!(ev("true == true"), Value::Bool(true));
4814 assert_eq!(ev("false == false"), Value::Bool(true));
4815 assert_eq!(ev("true == false"), Value::Bool(false));
4816 assert_eq!(ev("1 == 1"), Value::Bool(true));
4817 assert_eq!(ev("1 != 2"), Value::Bool(true));
4818 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
4820 assert_eq!(ev("null == false"), Value::Bool(false));
4821 }
4822
4823 #[test]
4824 fn op_logic_short_circuit() {
4825 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
4827 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
4829 }
4830
4831 #[test]
4832 fn op_logic_full() {
4833 assert_eq!(ev("true && true"), Value::Bool(true));
4834 assert_eq!(ev("true && false"), Value::Bool(false));
4835 assert_eq!(ev("false && true"), Value::Bool(false));
4836 assert_eq!(ev("false && false"), Value::Bool(false));
4837 assert_eq!(ev("true || true"), Value::Bool(true));
4838 assert_eq!(ev("true || false"), Value::Bool(true));
4839 assert_eq!(ev("false || true"), Value::Bool(true));
4840 assert_eq!(ev("false || false"), Value::Bool(false));
4841 assert_eq!(ev("!true"), Value::Bool(false));
4842 assert_eq!(ev("!false"), Value::Bool(true));
4843 }
4844
4845 #[test]
4846 fn op_implication_truth_table() {
4847 assert_eq!(ev("false -> false"), Value::Bool(true));
4849 assert_eq!(ev("false -> true"), Value::Bool(true));
4850 assert_eq!(ev("true -> true"), Value::Bool(true));
4852 assert_eq!(ev("true -> false"), Value::Bool(false));
4853 }
4854
4855 #[test]
4856 fn op_implication_short_circuit() {
4857 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
4859 }
4860
4861 #[test]
4862 fn op_update_merge() {
4863 let v = ev("{ a = 1; } // { b = 2; }");
4864 if let Value::Attrs(attrs) = v {
4865 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4866 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4867 } else {
4868 panic!("expected attrs");
4869 }
4870 }
4871
4872 #[test]
4873 fn op_update_right_wins() {
4874 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
4875 }
4876
4877 #[test]
4878 fn op_list_concat() {
4879 assert_eq!(
4880 ev("[1 2] ++ [3 4]"),
4881 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]),
4882 );
4883 assert_eq!(ev("[] ++ [1]"), Value::list(vec![Value::Int(1)]));
4885 assert_eq!(ev("[1] ++ []"), Value::list(vec![Value::Int(1)]));
4886 }
4887
4888 #[test]
4889 fn op_has_attr_present_and_absent() {
4890 assert_eq!(ev("{ x = 1; y = 2; } ? x"), Value::Bool(true));
4891 assert_eq!(ev("{ x = 1; } ? z"), Value::Bool(false));
4892 assert_eq!(ev("{} ? anything"), Value::Bool(false));
4893 }
4894
4895 #[test]
4896 fn op_unary_negate() {
4897 assert_eq!(ev("-42"), Value::Int(-42));
4898 assert_eq!(ev("-3.14"), Value::Float(-3.14));
4899 assert_eq!(ev("- -5"), Value::Int(5));
4901 }
4902
4903 #[test]
4908 fn control_if_true_branch() {
4909 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
4910 }
4911
4912 #[test]
4913 fn control_if_false_branch() {
4914 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
4915 }
4916
4917 #[test]
4918 fn control_if_nested() {
4919 assert_eq!(
4920 ev("if true then (if false then 1 else 2) else 3"),
4921 Value::Int(2),
4922 );
4923 assert_eq!(
4924 ev("if false then 1 else (if true then 2 else 3)"),
4925 Value::Int(2),
4926 );
4927 }
4928
4929 #[test]
4930 fn control_assert_passing() {
4931 assert_eq!(ev("assert 1 == 1; 42"), Value::Int(42));
4932 assert_eq!(ev("assert true; true"), Value::Bool(true));
4933 }
4934
4935 #[test]
4936 fn control_assert_failing() {
4937 assert!(eval("assert false; 42").is_err());
4938 assert!(eval("assert 1 == 2; 42").is_err());
4939 }
4940
4941 #[test]
4942 fn control_with_basic_scope() {
4943 assert_eq!(ev("with { a = 1; b = 2; }; a + b"), Value::Int(3));
4944 }
4945
4946 #[test]
4947 fn control_with_lexical_precedence() {
4948 assert_eq!(
4950 ev("let x = 10; in with { x = 99; }; x"),
4951 Value::Int(10),
4952 );
4953 }
4954
4955 #[test]
4956 fn control_with_nested() {
4957 assert_eq!(
4958 ev("with { a = 1; }; with { b = 2; }; a + b"),
4959 Value::Int(3),
4960 );
4961 }
4962
4963 #[test]
4964 fn control_with_lazy_fix_self() {
4965 let result = eval(
4970 "let fix = f: let x = f x; in x; in fix (self: with self; { a = 1; b = a + 1; })"
4971 );
4972 assert!(result.is_ok(), "fix with self should work: {:?}", result);
4973 if let Ok(Value::Attrs(attrs)) = result {
4974 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
4975 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
4976 } else {
4977 panic!("expected Attrs, got {:?}", result);
4978 }
4979 }
4980
4981 #[test]
4982 fn control_with_lazy_fix_self_lib_pattern() {
4983 let result = eval(r#"
4986 let fix = f: let x = f x; in x;
4987 in (fix (self: with self; {
4988 lib = { version = "1.0"; };
4989 hello = "hello ${lib.version}";
4990 })).hello
4991 "#);
4992 assert!(result.is_ok(), "nixpkgs-style lib pattern: {:?}", result);
4993 assert_eq!(
4994 result.unwrap(),
4995 Value::String(Rc::new(NixString::plain("hello 1.0"))),
4996 );
4997 }
4998
4999 #[test]
5000 fn control_with_non_attrset_errors() {
5001 let result = eval("with 42; 1");
5003 assert_eq!(result.unwrap(), Value::Int(1));
5006 }
5007
5008 #[test]
5009 fn control_with_non_attrset_lookup_falls_through() {
5010 let result = eval("let x = 1; in with 42; x");
5013 assert_eq!(result.unwrap(), Value::Int(1));
5014 }
5015
5016 #[test]
5017 fn control_let_simple_and_multiple() {
5018 assert_eq!(ev("let x = 5; in x"), Value::Int(5));
5019 assert_eq!(ev("let x = 1; y = 2; z = 3; in x + y + z"), Value::Int(6));
5020 }
5021
5022 #[test]
5023 fn control_let_shadow_outer() {
5024 assert_eq!(
5025 ev("let x = 1; in let x = 2; in x"),
5026 Value::Int(2),
5027 );
5028 }
5029
5030 #[test]
5031 fn control_let_recursive_reference() {
5032 assert_eq!(ev("let a = 1; b = a + 1; in b"), Value::Int(2));
5033 assert_eq!(ev("let a = 1; b = a + 1; c = b + 1; in c"), Value::Int(3));
5034 }
5035
5036 #[test]
5037 fn control_nested_let_expression() {
5038 assert_eq!(
5039 ev("let a = let b = 1; in b; in a"),
5040 Value::Int(1),
5041 );
5042 assert_eq!(
5043 ev("let a = let b = 10; in b + 5; in a * 2"),
5044 Value::Int(30),
5045 );
5046 }
5047
5048 #[test]
5053 fn func_identity_lambda() {
5054 assert_eq!(ev("(x: x) 42"), Value::Int(42));
5055 assert_eq!(ev(r#"(x: x) "hello""#), Value::string("hello"));
5056 }
5057
5058 #[test]
5059 fn func_curried_two_args() {
5060 assert_eq!(ev("(x: y: x + y) 3 4"), Value::Int(7));
5061 }
5062
5063 #[test]
5064 fn func_curried_three_args() {
5065 assert_eq!(ev("(a: b: c: a + b + c) 1 2 3"), Value::Int(6));
5066 }
5067
5068 #[test]
5069 fn func_formals_basic() {
5070 assert_eq!(ev("({ a, b }: a + b) { a = 3; b = 7; }"), Value::Int(10));
5071 }
5072
5073 #[test]
5074 fn func_formals_with_defaults() {
5075 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; }"), Value::Int(15));
5076 assert_eq!(ev("({ a, b ? 10 }: a + b) { a = 5; b = 20; }"), Value::Int(25));
5078 }
5079
5080 #[test]
5081 fn func_formals_with_ellipsis() {
5082 assert_eq!(ev("({ a, ... }: a) { a = 1; b = 2; c = 3; }"), Value::Int(1));
5083 }
5084
5085 #[test]
5086 fn func_named_formals_at_before() {
5087 assert_eq!(
5089 ev("(args @ { a, b }: args.a + args.b) { a = 3; b = 4; }"),
5090 Value::Int(7),
5091 );
5092 }
5093
5094 #[test]
5095 fn func_named_formals_at_after() {
5096 assert_eq!(
5098 ev("({ a, b } @ args: args.a + args.b) { a = 10; b = 20; }"),
5099 Value::Int(30),
5100 );
5101 }
5102
5103 #[test]
5104 fn func_nested_application() {
5105 assert_eq!(ev("((x: y: x * y) 3) 4"), Value::Int(12));
5107 }
5108
5109 #[test]
5110 fn func_higher_order_map() {
5111 assert_eq!(
5112 ev("builtins.map (x: x * 2) [1 2 3]"),
5113 Value::list(vec![Value::Int(2), Value::Int(4), Value::Int(6)]),
5114 );
5115 }
5116
5117 #[test]
5118 fn func_higher_order_filter() {
5119 assert_eq!(
5120 ev("builtins.filter (x: x > 2) [1 2 3 4 5]"),
5121 Value::list(vec![Value::Int(3), Value::Int(4), Value::Int(5)]),
5122 );
5123 }
5124
5125 #[test]
5126 fn func_higher_order_foldl() {
5127 assert_eq!(
5129 ev("builtins.foldl' (acc: x: acc + x) 0 [1 2 3 4]"),
5130 Value::Int(10),
5131 );
5132 }
5133
5134 #[test]
5135 fn func_as_attrset_value() {
5136 assert_eq!(
5137 ev("let s = { f = x: x + 1; }; in s.f 5"),
5138 Value::Int(6),
5139 );
5140 }
5141
5142 #[test]
5143 fn func_immediate_application() {
5144 assert_eq!(ev("(x: x * x) 7"), Value::Int(49));
5145 }
5146
5147 #[test]
5148 fn func_in_let_binding() {
5149 assert_eq!(
5150 ev("let double = x: x * 2; in double 21"),
5151 Value::Int(42),
5152 );
5153 }
5154
5155 #[test]
5160 fn attrs_empty_set() {
5161 let v = ev("{}");
5162 if let Value::Attrs(attrs) = v {
5163 assert!(attrs.is_empty());
5164 } else {
5165 panic!("expected attrs");
5166 }
5167 }
5168
5169 #[test]
5170 fn attrs_simple() {
5171 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
5172 }
5173
5174 #[test]
5175 fn attrs_nested_access() {
5176 assert_eq!(ev("{ a = { b = { c = 42; }; }; }.a.b.c"), Value::Int(42));
5177 }
5178
5179 #[test]
5180 fn attrs_recursive_set() {
5181 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
5182 }
5183
5184 #[test]
5185 fn attrs_update_disjoint() {
5186 let v = ev("{ a = 1; } // { b = 2; }");
5187 if let Value::Attrs(attrs) = v {
5188 assert_eq!(attrs.len(), 2);
5189 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5190 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
5191 } else {
5192 panic!("expected attrs");
5193 }
5194 }
5195
5196 #[test]
5197 fn attrs_update_override() {
5198 assert_eq!(ev("({ a = 1; } // { a = 2; }).a"), Value::Int(2));
5199 }
5200
5201 #[test]
5202 fn attrs_has_attr_operator() {
5203 assert_eq!(ev("{ a = 1; } ? a"), Value::Bool(true));
5204 assert_eq!(ev("{ a = 1; } ? b"), Value::Bool(false));
5205 }
5206
5207 #[test]
5208 fn attrs_select_with_default() {
5209 assert_eq!(ev("{ a = 1; }.a or 99"), Value::Int(1));
5210 assert_eq!(ev("{}.missing or 99"), Value::Int(99));
5211 assert_eq!(ev("{ a = 1; }.b or 42"), Value::Int(42));
5212 }
5213
5214 #[test]
5215 fn attrs_nested_attr_path_in_binding() {
5216 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
5218 }
5219
5220 #[test]
5221 fn attrs_inherit_from_scope() {
5222 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.x"), Value::Int(1));
5223 assert_eq!(ev("let x = 1; y = 2; in { inherit x y; }.y"), Value::Int(2));
5224 }
5225
5226 #[test]
5227 fn attrs_inherit_from_expr() {
5228 assert_eq!(
5229 ev("{ inherit ({ a = 42; b = 10; }) a; }.a"),
5230 Value::Int(42),
5231 );
5232 }
5233
5234 #[test]
5235 fn attrs_dynamic_attr_name() {
5236 assert_eq!(
5237 ev(r#"let name = "x"; in { ${name} = 42; }.x"#),
5238 Value::Int(42),
5239 );
5240 }
5241
5242 #[test]
5243 fn attrs_attr_names_sorted() {
5244 assert_eq!(
5245 ev("builtins.attrNames { z = 1; m = 2; a = 3; }"),
5246 Value::list(vec![
5247 Value::string("a"),
5248 Value::string("m"),
5249 Value::string("z"),
5250 ]),
5251 );
5252 }
5253
5254 #[test]
5255 fn attrs_attr_values_follow_key_order() {
5256 assert_eq!(
5258 ev("builtins.attrValues { c = 3; a = 1; b = 2; }"),
5259 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5260 );
5261 }
5262
5263 #[test]
5264 fn attrs_update_is_shallow() {
5265 assert_eq!(
5267 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a ? x"),
5268 Value::Bool(false),
5269 );
5270 assert_eq!(
5271 ev("({ a = { x = 1; }; } // { a = { y = 2; }; }).a.y"),
5272 Value::Int(2),
5273 );
5274 }
5275
5276 #[test]
5281 fn list_empty() {
5282 assert_eq!(ev("[]"), Value::list(vec![]));
5283 }
5284
5285 #[test]
5286 fn list_single_element() {
5287 assert_eq!(ev("[1]"), Value::list(vec![Value::Int(1)]));
5288 }
5289
5290 #[test]
5291 fn list_mixed_types() {
5292 assert_eq!(
5293 ev(r#"[1 "two" true null]"#),
5294 Value::list(vec![
5295 Value::Int(1),
5296 Value::string("two"),
5297 Value::Bool(true),
5298 Value::Null,
5299 ]),
5300 );
5301 }
5302
5303 #[test]
5304 fn list_nested() {
5305 assert_eq!(
5306 ev("[[1 2] [3 4]]"),
5307 Value::list(vec![
5308 Value::list(vec![Value::Int(1), Value::Int(2)]),
5309 Value::list(vec![Value::Int(3), Value::Int(4)]),
5310 ]),
5311 );
5312 }
5313
5314 #[test]
5315 fn list_concat_operator() {
5316 assert_eq!(
5317 ev("[1] ++ [2] ++ [3]"),
5318 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5319 );
5320 }
5321
5322 #[test]
5323 fn list_builtins_length() {
5324 assert_eq!(ev("builtins.length [1 2 3]"), Value::Int(3));
5325 assert_eq!(ev("builtins.length []"), Value::Int(0));
5326 }
5327
5328 #[test]
5329 fn list_builtins_elem_at() {
5330 assert_eq!(ev("builtins.elemAt [10 20 30] 0"), Value::Int(10));
5331 assert_eq!(ev("builtins.elemAt [10 20 30] 1"), Value::Int(20));
5332 assert_eq!(ev("builtins.elemAt [10 20 30] 2"), Value::Int(30));
5333 }
5334
5335 #[test]
5336 fn list_equality() {
5337 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
5338 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
5339 assert_eq!(ev("[] == []"), Value::Bool(true));
5340 }
5341
5342 #[test]
5347 fn interp_simple_variable() {
5348 assert_eq!(
5349 ev(r#"let name = "world"; in "hello ${name}""#),
5350 Value::string("hello world"),
5351 );
5352 }
5353
5354 #[test]
5355 fn interp_nested_expression() {
5356 assert_eq!(
5357 ev(r#""result: ${builtins.toString (1 + 2)}""#),
5358 Value::string("result: 3"),
5359 );
5360 }
5361
5362 #[test]
5363 fn interp_int_coercion() {
5364 assert_eq!(
5366 ev(r#"let x = 42; in "count: ${builtins.toString x}""#),
5367 Value::string("count: 42"),
5368 );
5369 }
5370
5371 #[test]
5372 fn interp_multiple() {
5373 assert_eq!(
5374 ev(r#"let a = "foo"; b = "bar"; in "${a} and ${b}""#),
5375 Value::string("foo and bar"),
5376 );
5377 }
5378
5379 #[test]
5380 fn interp_in_let() {
5381 assert_eq!(
5382 ev(r#"let x = "world"; in "hello ${x}""#),
5383 Value::string("hello world"),
5384 );
5385 }
5386
5387 #[test]
5388 fn interp_empty_result() {
5389 assert_eq!(
5390 ev(r#"let x = ""; in "a${x}b""#),
5391 Value::string("ab"),
5392 );
5393 }
5394
5395 #[test]
5396 fn interp_path_in_string_context() {
5397 assert!(eval(r#""path: ${./foo-nonexistent-xyz}""#).is_err());
5403 }
5404
5405 #[test]
5406 fn interp_adjacent_interpolations() {
5407 assert_eq!(
5408 ev(r#"let a = "x"; b = "y"; in "${a}${b}""#),
5409 Value::string("xy"),
5410 );
5411 }
5412
5413 #[test]
5418 fn builtins_map_filter_foldl() {
5419 assert_eq!(
5421 ev("builtins.map (x: x + 10) [1 2 3]"),
5422 Value::list(vec![Value::Int(11), Value::Int(12), Value::Int(13)]),
5423 );
5424 assert_eq!(
5426 ev("builtins.filter (x: x > 1) [1 2 3]"),
5427 Value::list(vec![Value::Int(2), Value::Int(3)]),
5428 );
5429 assert_eq!(
5431 ev("builtins.foldl' (a: b: a * b) 1 [2 3 4]"),
5432 Value::Int(24),
5433 );
5434 }
5435
5436 #[test]
5437 fn builtins_map_attrs() {
5438 assert_eq!(
5439 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).a"),
5440 Value::Int(2),
5441 );
5442 assert_eq!(
5443 ev("(builtins.mapAttrs (name: value: value * 2) { a = 1; b = 2; }).b"),
5444 Value::Int(4),
5445 );
5446 }
5447
5448 #[test]
5449 fn builtins_list_to_attrs() {
5450 assert_eq!(
5451 ev(r#"(builtins.listToAttrs [{ name = "x"; value = 1; } { name = "y"; value = 2; }]).x"#),
5452 Value::Int(1),
5453 );
5454 }
5455
5456 #[test]
5457 fn builtins_list_to_attrs_duplicate_key_first_wins() {
5458 assert_eq!(
5467 ev(r#"(builtins.listToAttrs [{ name = "k"; value = 1; } { name = "k"; value = 2; }]).k"#),
5468 Value::Int(1),
5469 );
5470 }
5471
5472 #[test]
5473 fn builtins_concat_map() {
5474 assert_eq!(
5475 ev("builtins.concatMap (x: [x (x * 2)]) [1 2 3]"),
5476 Value::list(vec![
5477 Value::Int(1), Value::Int(2),
5478 Value::Int(2), Value::Int(4),
5479 Value::Int(3), Value::Int(6),
5480 ]),
5481 );
5482 }
5483
5484 #[test]
5485 fn builtins_concat_lists() {
5486 assert_eq!(
5487 ev("builtins.concatLists [[1 2] [3] [4 5]]"),
5488 Value::list(vec![
5489 Value::Int(1), Value::Int(2), Value::Int(3),
5490 Value::Int(4), Value::Int(5),
5491 ]),
5492 );
5493 }
5494
5495 #[test]
5496 fn builtins_concat_strings_sep() {
5497 assert_eq!(
5498 ev(r#"builtins.concatStringsSep ", " ["a" "b" "c"]"#),
5499 Value::string("a, b, c"),
5500 );
5501 assert_eq!(
5502 ev(r#"builtins.concatStringsSep "" ["x" "y"]"#),
5503 Value::string("xy"),
5504 );
5505 }
5506
5507 #[test]
5508 fn builtins_replace_strings() {
5509 assert_eq!(
5510 ev(r#"builtins.replaceStrings ["o"] ["0"] "foobar""#),
5511 Value::string("f00bar"),
5512 );
5513 assert_eq!(
5514 ev(r#"builtins.replaceStrings ["hello"] ["goodbye"] "hello world""#),
5515 Value::string("goodbye world"),
5516 );
5517 }
5518
5519 #[test]
5520 fn builtins_has_prefix_has_suffix() {
5521 assert_eq!(ev(r#"builtins.hasPrefix "he" "hello""#), Value::Bool(true));
5522 assert_eq!(ev(r#"builtins.hasPrefix "xx" "hello""#), Value::Bool(false));
5523 assert_eq!(ev(r#"builtins.hasSuffix "lo" "hello""#), Value::Bool(true));
5524 assert_eq!(ev(r#"builtins.hasSuffix "xx" "hello""#), Value::Bool(false));
5525 }
5526
5527 #[test]
5528 fn builtins_all_any() {
5529 assert_eq!(ev("builtins.all (x: x > 0) [1 2 3]"), Value::Bool(true));
5530 assert_eq!(ev("builtins.all (x: x > 1) [1 2 3]"), Value::Bool(false));
5531 assert_eq!(ev("builtins.any (x: x > 2) [1 2 3]"), Value::Bool(true));
5532 assert_eq!(ev("builtins.any (x: x > 5) [1 2 3]"), Value::Bool(false));
5533 }
5534
5535 #[test]
5536 fn builtins_sort() {
5537 assert_eq!(
5538 ev("builtins.sort (a: b: a < b) [3 1 2]"),
5539 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5540 );
5541 }
5542
5543 #[test]
5544 fn builtins_remove_attrs() {
5545 let v = ev(r#"builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b" "c"]"#);
5546 if let Value::Attrs(attrs) = v {
5547 assert_eq!(attrs.len(), 1);
5548 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
5549 assert!(attrs.get("b").is_none());
5550 } else {
5551 panic!("expected attrs");
5552 }
5553 }
5554
5555 #[test]
5556 fn builtins_intersect_attrs() {
5557 let v = ev("builtins.intersectAttrs { a = 1; b = 2; } { b = 20; c = 30; }");
5558 if let Value::Attrs(attrs) = v {
5559 assert_eq!(attrs.len(), 1);
5560 assert_eq!(attrs.get("b"), Some(&Value::Int(20)));
5562 } else {
5563 panic!("expected attrs");
5564 }
5565 }
5566
5567 #[test]
5568 fn builtins_type_of_all_types() {
5569 assert_eq!(ev("builtins.typeOf null"), Value::string("null"));
5570 assert_eq!(ev("builtins.typeOf true"), Value::string("bool"));
5571 assert_eq!(ev("builtins.typeOf 42"), Value::string("int"));
5572 assert_eq!(ev("builtins.typeOf 3.14"), Value::string("float"));
5573 assert_eq!(ev(r#"builtins.typeOf "hi""#), Value::string("string"));
5574 assert_eq!(ev("builtins.typeOf [1]"), Value::string("list"));
5575 assert_eq!(ev("builtins.typeOf {}"), Value::string("set"));
5576 assert_eq!(ev("builtins.typeOf (x: x)"), Value::string("lambda"));
5577 }
5578
5579 #[test]
5580 fn builtins_is_type_checks() {
5581 assert_eq!(ev("builtins.isNull null"), Value::Bool(true));
5582 assert_eq!(ev("builtins.isNull 0"), Value::Bool(false));
5583 assert_eq!(ev("builtins.isInt 42"), Value::Bool(true));
5584 assert_eq!(ev("builtins.isInt 3.14"), Value::Bool(false));
5585 assert_eq!(ev("builtins.isBool true"), Value::Bool(true));
5586 assert_eq!(ev("builtins.isBool 1"), Value::Bool(false));
5587 assert_eq!(ev(r#"builtins.isString "x""#), Value::Bool(true));
5588 assert_eq!(ev("builtins.isString 1"), Value::Bool(false));
5589 assert_eq!(ev("builtins.isList []"), Value::Bool(true));
5590 assert_eq!(ev("builtins.isList {}"), Value::Bool(false));
5591 assert_eq!(ev("builtins.isAttrs {}"), Value::Bool(true));
5592 assert_eq!(ev("builtins.isAttrs []"), Value::Bool(false));
5593 assert_eq!(ev("builtins.isFunction (x: x)"), Value::Bool(true));
5594 assert_eq!(ev("builtins.isFunction 1"), Value::Bool(false));
5595 assert_eq!(ev("builtins.isFloat 3.14"), Value::Bool(true));
5596 assert_eq!(ev("builtins.isFloat 1"), Value::Bool(false));
5597 }
5598
5599 #[test]
5600 fn builtins_to_json_from_json_roundtrip() {
5601 assert_eq!(ev("builtins.fromJSON (builtins.toJSON 42)"), Value::Int(42));
5603 assert_eq!(
5605 ev(r#"builtins.fromJSON (builtins.toJSON "hello")"#),
5606 Value::string("hello"),
5607 );
5608 assert_eq!(
5610 ev("builtins.fromJSON (builtins.toJSON [1 2 3])"),
5611 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
5612 );
5613 assert_eq!(ev("builtins.fromJSON (builtins.toJSON null)"), Value::Null);
5615 assert_eq!(ev("builtins.fromJSON (builtins.toJSON true)"), Value::Bool(true));
5617 }
5618
5619 #[test]
5620 fn builtins_to_string_various() {
5621 assert_eq!(ev("builtins.toString 42"), Value::string("42"));
5622 assert_eq!(ev("builtins.toString true"), Value::string("1"));
5623 assert_eq!(ev("builtins.toString false"), Value::string(""));
5624 assert_eq!(ev("builtins.toString null"), Value::string(""));
5625 assert_eq!(ev(r#"builtins.toString "hello""#), Value::string("hello"));
5626 }
5627
5628 #[test]
5629 fn builtins_function_args() {
5630 let v = ev("builtins.functionArgs ({ a, b ? 1 }: a)");
5631 if let Value::Attrs(attrs) = v {
5632 assert_eq!(attrs.get("a"), Some(&Value::Bool(false))); assert_eq!(attrs.get("b"), Some(&Value::Bool(true))); } else {
5635 panic!("expected attrs");
5636 }
5637 }
5638
5639 #[test]
5640 fn builtins_gen_list() {
5641 assert_eq!(
5642 ev("builtins.genList (x: x * x) 5"),
5643 Value::list(vec![
5644 Value::Int(0), Value::Int(1), Value::Int(4),
5645 Value::Int(9), Value::Int(16),
5646 ]),
5647 );
5648 assert_eq!(ev("builtins.genList (x: x) 0"), Value::list(vec![]));
5649 }
5650
5651 #[test]
5652 fn builtins_elem() {
5653 assert_eq!(ev("builtins.elem 2 [1 2 3]"), Value::Bool(true));
5654 assert_eq!(ev("builtins.elem 5 [1 2 3]"), Value::Bool(false));
5655 assert_eq!(ev("builtins.elem 1 []"), Value::Bool(false));
5656 }
5657
5658 #[test]
5659 fn builtins_head_tail() {
5660 assert_eq!(ev("builtins.head [10 20 30]"), Value::Int(10));
5661 assert_eq!(
5662 ev("builtins.tail [10 20 30]"),
5663 Value::list(vec![Value::Int(20), Value::Int(30)]),
5664 );
5665 }
5666
5667 #[test]
5668 fn builtins_string_length() {
5669 assert_eq!(ev(r#"builtins.stringLength "hello""#), Value::Int(5));
5670 assert_eq!(ev(r#"builtins.stringLength """#), Value::Int(0));
5671 assert_eq!(ev(r#"builtins.stringLength "abc def""#), Value::Int(7));
5672 }
5673
5674 #[test]
5675 fn builtins_ceil_floor() {
5676 assert_eq!(ev("builtins.ceil 2.3"), Value::Int(3));
5677 assert_eq!(ev("builtins.ceil 2.0"), Value::Int(2));
5678 assert_eq!(ev("builtins.floor 2.9"), Value::Int(2));
5679 assert_eq!(ev("builtins.floor 2.0"), Value::Int(2));
5680 assert_eq!(ev("builtins.ceil 5"), Value::Int(5));
5682 assert_eq!(ev("builtins.floor 5"), Value::Int(5));
5683 }
5684
5685 #[test]
5686 fn builtins_try_eval() {
5687 let v = ev("builtins.tryEval 42");
5688 if let Value::Attrs(attrs) = v {
5689 assert_eq!(attrs.get("success"), Some(&Value::Bool(true)));
5690 assert_eq!(attrs.get("value"), Some(&Value::Int(42)));
5691 } else {
5692 panic!("expected attrs");
5693 }
5694 }
5695
5696 #[test]
5697 fn builtins_throw() {
5698 let result = eval(r#"builtins.throw "oops""#);
5699 assert!(result.is_err());
5700 let msg = format!("{}", result.unwrap_err());
5701 assert!(msg.contains("oops"));
5702 }
5703
5704 #[test]
5705 fn builtins_seq_deep_seq() {
5706 assert_eq!(ev("builtins.seq 1 42"), Value::Int(42));
5708 assert_eq!(ev("builtins.deepSeq [1 2 3] 99"), Value::Int(99));
5710 }
5711
5712 #[test]
5713 fn builtins_current_system() {
5714 let v = ev("builtins.currentSystem");
5715 if let Value::String(ns) = v {
5716 let s = &ns.chars;
5717 assert!(
5719 s == "aarch64-darwin"
5720 || s == "x86_64-darwin"
5721 || s == "aarch64-linux"
5722 || s == "x86_64-linux",
5723 "unexpected system: {s}",
5724 );
5725 } else {
5726 panic!("expected string");
5727 }
5728 }
5729
5730 #[test]
5735 fn pattern_mkif_like() {
5736 assert_eq!(
5738 ev("(if true then { x = 1; } else {}).x"),
5739 Value::Int(1),
5740 );
5741 let v = ev("if false then { x = 1; } else {}");
5742 if let Value::Attrs(attrs) = v {
5743 assert!(attrs.is_empty());
5744 } else {
5745 panic!("expected attrs");
5746 }
5747 }
5748
5749 #[test]
5750 fn pattern_optional_attrs() {
5751 assert_eq!(
5753 ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in (optionalAttrs true { a = 1; }).a"),
5754 Value::Int(1),
5755 );
5756 let v = ev("let optionalAttrs = cond: attrs: if cond then attrs else {}; in optionalAttrs false { a = 1; }");
5757 if let Value::Attrs(attrs) = v {
5758 assert!(attrs.is_empty());
5759 } else {
5760 panic!("expected attrs");
5761 }
5762 }
5763
5764 #[test]
5765 fn pattern_filter_attrs_via_remove() {
5766 assert_eq!(
5768 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]).a"#),
5769 Value::Int(1),
5770 );
5771 assert_eq!(
5772 ev(r#"(builtins.removeAttrs { a = 1; b = 2; c = 3; } ["b"]) ? b"#),
5773 Value::Bool(false),
5774 );
5775 }
5776
5777 #[test]
5778 fn pattern_override() {
5779 let v = ev(r#"
5781 let
5782 defaults = { debug = false; port = 8080; host = "localhost"; };
5783 overrides = { debug = true; port = 9090; };
5784 in defaults // overrides
5785 "#);
5786 if let Value::Attrs(attrs) = v {
5787 assert_eq!(attrs.get("debug"), Some(&Value::Bool(true)));
5788 assert_eq!(attrs.get("port"), Some(&Value::Int(9090)));
5789 assert_eq!(attrs.get("host"), Some(&Value::string("localhost")));
5790 } else {
5791 panic!("expected attrs");
5792 }
5793 }
5794
5795 #[test]
5796 fn pattern_functor() {
5797 assert_eq!(
5799 ev("let s = { __functor = self: x: self.value + x; value = 10; }; in s 5"),
5800 Value::Int(15),
5801 );
5802 }
5803
5804 #[test]
5805 fn pattern_platform_check() {
5806 let v = ev(r#"if builtins.currentSystem == "aarch64-darwin" then "arm" else "other""#);
5808 if let Value::String(_) = v {
5810 } else {
5812 panic!("expected string");
5813 }
5814 }
5815
5816 #[test]
5817 fn pattern_recursive_overlay_lambda_structure() {
5818 let v = ev("let overlay = self: super: { pkg = 42; }; in overlay {} {}");
5820 if let Value::Attrs(attrs) = v {
5821 assert_eq!(attrs.get("pkg"), Some(&Value::Int(42)));
5822 } else {
5823 panic!("expected attrs");
5824 }
5825 }
5826
5827 #[test]
5828 fn pattern_call_package_simplified() {
5829 assert_eq!(
5831 ev("let callPkg = f: f { lib = { id = x: x; }; }; lib = { id = x: x; }; in callPkg ({ lib }: lib.id 42)"),
5832 Value::Int(42),
5833 );
5834 }
5835
5836 #[test]
5837 fn pattern_derivation_like_attrset() {
5838 let v = ev(r#"{ type = "derivation"; name = "hello"; system = builtins.currentSystem; builder = "/bin/sh"; }"#);
5839 if let Value::Attrs(attrs) = v {
5840 assert_eq!(attrs.get("type"), Some(&Value::string("derivation")));
5841 assert_eq!(attrs.get("name"), Some(&Value::string("hello")));
5842 assert_eq!(attrs.get("builder"), Some(&Value::string("/bin/sh")));
5843 let system = force_value(attrs.get("system").unwrap()).unwrap();
5845 assert!(matches!(system, Value::String(_)), "expected string, got {system:?}");
5846 } else {
5847 panic!("expected attrs");
5848 }
5849 }
5850
5851 #[test]
5852 fn pattern_module_system_simplified() {
5853 assert_eq!(
5855 ev(r#"
5856 let
5857 eval = m: m { config = {}; lib = { mkDefault = x: x; }; };
5858 in eval ({ config, lib }: { result = lib.mkDefault 42; })
5859 "#),
5860 {
5861 let mut attrs = NixAttrs::new();
5862 attrs.insert("result".to_string(), Value::Int(42));
5863 Value::Attrs(Rc::new(attrs))
5864 },
5865 );
5866 }
5867
5868 #[test]
5873 fn error_undefined_variable() {
5874 let result = eval("nonexistent_var");
5875 assert!(result.is_err());
5876 let msg = format!("{}", result.unwrap_err());
5877 assert!(msg.contains("undefined variable") || msg.contains("nonexistent_var"));
5878 }
5879
5880 #[test]
5881 fn error_type_mismatch_arithmetic() {
5882 let result = eval(r#"1 + "hello""#);
5883 assert!(result.is_err());
5884 }
5885
5886 #[test]
5887 fn error_missing_attribute() {
5888 let result = eval("{}.nonexistent");
5889 assert!(result.is_err());
5890 let msg = format!("{}", result.unwrap_err());
5891 assert!(msg.contains("nonexistent") || msg.contains("not found"));
5892 }
5893
5894 #[test]
5895 fn error_division_by_zero() {
5896 assert!(eval("1 / 0").is_err());
5897 assert!(eval("100 / 0").is_err());
5898 }
5899
5900 #[test]
5901 fn error_missing_required_function_arg() {
5902 let result = eval("({ a, b }: a + b) { a = 1; }");
5903 assert!(result.is_err());
5904 let msg = format!("{}", result.unwrap_err());
5905 assert!(msg.contains("missing argument"));
5906 }
5907
5908 #[test]
5909 fn error_unexpected_function_arg() {
5910 let result = eval("({ a }: a) { a = 1; b = 2; }");
5911 assert!(result.is_err());
5912 let msg = format!("{}", result.unwrap_err());
5913 assert!(msg.contains("unexpected argument"));
5914 }
5915
5916 #[test]
5917 fn error_assertion_failure() {
5918 assert!(eval("assert false; 1").is_err());
5919 assert!(eval("assert 1 == 2; 1").is_err());
5920 }
5921
5922 #[test]
5923 fn error_infinite_recursion() {
5924 let result = eval("let x = x; in x");
5927 assert!(result.is_err());
5928 }
5929
5930 #[test]
5931 fn error_infinite_recursion_via_lambda() {
5932 let result = eval("let f = x: f x; in f 1");
5934 assert!(result.is_err());
5935 let msg = format!("{}", result.unwrap_err());
5936 assert!(
5937 msg.contains("infinite recursion") || msg.contains("eval depth") || msg.contains("undefined"),
5938 );
5939 }
5940
5941 #[test]
5946 fn integration_let_with_function_returning_attrset() {
5947 assert_eq!(
5948 ev("let mkPkg = name: { inherit name; version = 1; }; in (mkPkg \"hello\").name"),
5949 Value::string("hello"),
5950 );
5951 }
5952
5953 #[test]
5954 fn integration_chained_updates() {
5955 assert_eq!(
5956 ev("({ a = 1; } // { b = 2; } // { c = 3; }).c"),
5957 Value::Int(3),
5958 );
5959 }
5960
5961 #[test]
5962 fn integration_map_over_attrnames() {
5963 assert_eq!(
5965 ev(r#"
5966 let
5967 set = { a = 1; b = 2; };
5968 names = builtins.attrNames set;
5969 in builtins.length names
5970 "#),
5971 Value::Int(2),
5972 );
5973 }
5974
5975 #[test]
5976 fn integration_compose_functions() {
5977 assert_eq!(
5979 ev("let compose = f: g: x: f (g x); double = x: x * 2; inc = x: x + 1; in compose double inc 5"),
5980 Value::Int(12), );
5982 }
5983
5984 #[test]
5985 fn integration_recursive_list_building() {
5986 assert_eq!(
5988 ev("builtins.map (x: x * x) (builtins.genList (x: x + 1) 4)"),
5989 Value::list(vec![Value::Int(1), Value::Int(4), Value::Int(9), Value::Int(16)]),
5990 );
5991 }
5992
5993 #[test]
5994 fn integration_attrset_from_list() {
5995 let v = ev(r#"
5997 builtins.listToAttrs (builtins.map (x: { name = x; value = true; }) ["a" "b" "c"])
5998 "#);
5999 if let Value::Attrs(attrs) = v {
6000 assert_eq!(attrs.get("a"), Some(&Value::Bool(true)));
6001 assert_eq!(attrs.get("b"), Some(&Value::Bool(true)));
6002 assert_eq!(attrs.get("c"), Some(&Value::Bool(true)));
6003 } else {
6004 panic!("expected attrs");
6005 }
6006 }
6007
6008 #[test]
6009 fn integration_nested_with_and_let() {
6010 assert_eq!(
6011 ev("let x = 10; in with { y = 20; }; x + y"),
6012 Value::Int(30),
6013 );
6014 }
6015
6016 #[test]
6017 fn integration_complex_pattern_match() {
6018 assert_eq!(
6020 ev("(args @ { a, b ? 5, ... }: a + b + (if args ? c then args.c else 0)) { a = 1; c = 10; }"),
6021 Value::Int(16), );
6023 }
6024
6025 #[test]
6026 fn integration_substring() {
6027 assert_eq!(
6028 ev(r#"builtins.substring 0 5 "hello world""#),
6029 Value::string("hello"),
6030 );
6031 assert_eq!(
6032 ev(r#"builtins.substring 6 5 "hello world""#),
6033 Value::string("world"),
6034 );
6035 }
6036
6037 #[test]
6038 fn integration_has_attr_on_nested() {
6039 assert_eq!(ev("{ a = { b = 1; }; } ? a"), Value::Bool(true));
6041 assert_eq!(
6042 ev("({ a = { b = 1; }; }.a) ? b"),
6043 Value::Bool(true),
6044 );
6045 }
6046
6047 #[test]
6048 fn integration_cat_attrs() {
6049 assert_eq!(
6050 ev(r#"builtins.catAttrs "x" [{ x = 1; } { y = 2; } { x = 3; }]"#),
6051 Value::list(vec![Value::Int(1), Value::Int(3)]),
6052 );
6053 }
6054
6055 #[test]
6056 fn integration_get_attr_builtin() {
6057 assert_eq!(
6058 ev(r#"builtins.getAttr "a" { a = 42; b = 10; }"#),
6059 Value::Int(42),
6060 );
6061 }
6062
6063 #[test]
6064 fn integration_has_attr_builtin() {
6065 assert_eq!(
6066 ev(r#"builtins.hasAttr "a" { a = 1; }"#),
6067 Value::Bool(true),
6068 );
6069 assert_eq!(
6070 ev(r#"builtins.hasAttr "z" { a = 1; }"#),
6071 Value::Bool(false),
6072 );
6073 }
6074
6075 #[test]
6076 fn integration_is_path() {
6077 assert_eq!(ev("builtins.isPath ./foo"), Value::Bool(true));
6078 assert_eq!(ev("builtins.isPath 42"), Value::Bool(false));
6079 }
6080
6081 #[test]
6082 fn integration_builtins_trace() {
6083 assert_eq!(ev(r#"builtins.trace "debug msg" 42"#), Value::Int(42));
6085 }
6086
6087 #[test]
6088 fn integration_builtins_split() {
6089 assert_eq!(
6093 ev(r#"builtins.split "/" "a/b/c""#),
6094 Value::list(vec![
6095 Value::string("a"),
6096 Value::list(vec![]),
6097 Value::string("b"),
6098 Value::list(vec![]),
6099 Value::string("c"),
6100 ]),
6101 );
6102 assert_eq!(
6105 ev(r#"builtins.split "(/)" "a/b/c""#),
6106 Value::list(vec![
6107 Value::string("a"),
6108 Value::list(vec![Value::string("/")]),
6109 Value::string("b"),
6110 Value::list(vec![Value::string("/")]),
6111 Value::string("c"),
6112 ]),
6113 );
6114 }
6115
6116 #[test]
6117 fn integration_builtins_split_no_capture_groups() {
6118 assert_eq!(
6123 ev(r#"builtins.split "-" "aarch64-darwin""#),
6124 Value::list(vec![
6125 Value::string("aarch64"),
6126 Value::list(vec![]),
6127 Value::string("darwin"),
6128 ]),
6129 );
6130 }
6131
6132 #[test]
6133 fn integration_builtins_split_system_string_filter() {
6134 assert_eq!(
6137 ev(r#"builtins.filter builtins.isString (builtins.split "-" "aarch64-darwin")"#),
6138 Value::list(vec![
6139 Value::string("aarch64"),
6140 Value::string("darwin"),
6141 ]),
6142 );
6143 }
6144
6145 #[test]
6146 fn integration_deeply_nested_let() {
6147 assert_eq!(
6149 ev("let a = let b = let c = 10; in c * 2; in b + 1; in a"),
6150 Value::Int(21),
6151 );
6152 }
6153
6154 #[test]
6155 fn integration_if_in_attrset_value() {
6156 assert_eq!(
6157 ev("{ x = if true then 1 else 2; }.x"),
6158 Value::Int(1),
6159 );
6160 }
6161
6162 #[test]
6163 fn integration_lambda_in_list() {
6164 assert_eq!(
6166 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 0) 5"),
6167 Value::Int(6),
6168 );
6169 assert_eq!(
6170 ev("let fs = [(x: x + 1) (x: x * 2)]; in (builtins.elemAt fs 1) 5"),
6171 Value::Int(10),
6172 );
6173 }
6174
6175 #[test]
6176 fn integration_nixpkgs_lib_id() {
6177 assert_eq!(
6179 ev("let lib = { id = x: x; const = a: b: a; }; in lib.id 42"),
6180 Value::Int(42),
6181 );
6182 assert_eq!(
6183 ev("let lib = { id = x: x; const = a: b: a; }; in lib.const 1 2"),
6184 Value::Int(1),
6185 );
6186 }
6187
6188 #[test]
6189 fn integration_multiple_inherit() {
6190 assert_eq!(
6191 ev("let a = 1; b = 2; c = 3; in { inherit a b c; }.b"),
6192 Value::Int(2),
6193 );
6194 }
6195
6196 #[test]
6197 fn integration_rec_set_with_builtins() {
6198 assert_eq!(
6199 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
6200 Value::Int(5),
6201 );
6202 }
6203
6204 #[test]
6209 fn functor_simple_callable_attrset() {
6210 assert_eq!(
6211 ev("let s = { __functor = self: x: x + 1; }; in s 41"),
6212 Value::Int(42),
6213 );
6214 }
6215
6216 #[test]
6217 fn functor_with_self_reference() {
6218 assert_eq!(
6219 ev("let s = { __functor = self: x: self.base + x; base = 100; }; in s 23"),
6220 Value::Int(123),
6221 );
6222 }
6223
6224 #[test]
6225 fn functor_updated_attrset() {
6226 assert_eq!(
6228 ev(r#"
6229 let
6230 mk = { __functor = self: x: self.n + x; n = 0; };
6231 s = mk // { n = 50; };
6232 in s 7
6233 "#),
6234 Value::Int(57),
6235 );
6236 }
6237
6238 #[test]
6239 fn functor_error_on_non_callable_attrset() {
6240 let result = eval("let s = { a = 1; }; in s 5");
6242 assert!(result.is_err());
6243 }
6244
6245 #[test]
6250 fn to_string_protocol_in_interpolation() {
6251 assert_eq!(
6252 ev(r#"let s = { __toString = self: "world"; }; in "hello ${s}""#),
6253 Value::string("hello world"),
6254 );
6255 }
6256
6257 #[test]
6258 fn to_string_protocol_accesses_self() {
6259 assert_eq!(
6260 ev(r#"let s = { __toString = self: self.val; val = "abc"; }; in "${s}""#),
6261 Value::string("abc"),
6262 );
6263 }
6264
6265 #[test]
6266 fn to_string_protocol_via_builtin_to_string() {
6267 assert_eq!(
6268 ev(r#"builtins.toString { __toString = self: "via-builtin"; }"#),
6269 Value::string("via-builtin"),
6270 );
6271 }
6272
6273 #[test]
6274 fn to_string_protocol_attrset_without_toString_fails() {
6275 let result = eval(r#""${{}}"#);
6277 assert!(result.is_err());
6278 }
6279
6280 #[test]
6285 fn eval_builtins_concat_strings() {
6286 assert_eq!(
6287 ev(r#"builtins.concatStrings ["a" "b" "c"]"#),
6288 Value::string("abc"),
6289 );
6290 assert_eq!(
6291 ev(r#"builtins.concatStrings []"#),
6292 Value::string(""),
6293 );
6294 }
6295
6296 #[test]
6297 fn eval_builtins_partition() {
6298 let v = ev("builtins.partition (x: x > 3) [1 2 3 4 5]");
6299 if let Value::Attrs(a) = v {
6300 assert_eq!(a.get("right"), Some(&Value::list(vec![Value::Int(4), Value::Int(5)])));
6301 assert_eq!(a.get("wrong"), Some(&Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)])));
6302 } else {
6303 panic!("expected attrs");
6304 }
6305 }
6306
6307 #[test]
6308 fn eval_builtins_group_by() {
6309 let v = ev(r#"builtins.groupBy (x: if x > 0 then "pos" else "neg") [1 (0 - 2) 3 (0 - 4)]"#);
6310 if let Value::Attrs(a) = v {
6311 assert_eq!(a.get("pos"), Some(&Value::list(vec![Value::Int(1), Value::Int(3)])));
6312 assert_eq!(a.get("neg"), Some(&Value::list(vec![Value::Int(-2), Value::Int(-4)])));
6313 } else {
6314 panic!("expected attrs");
6315 }
6316 }
6317
6318 #[test]
6319 fn eval_builtins_zip_attrs_with() {
6320 let v = ev("builtins.zipAttrsWith (n: vs: builtins.head vs) [{ a = 1; } { a = 2; b = 3; }]");
6321 if let Value::Attrs(a) = v {
6322 assert_eq!(a.get("a"), Some(&Value::Int(1)));
6323 assert_eq!(a.get("b"), Some(&Value::Int(3)));
6324 } else {
6325 panic!("expected attrs");
6326 }
6327 }
6328
6329 #[test]
6330 fn eval_builtins_compare_versions() {
6331 assert_eq!(ev(r#"builtins.compareVersions "2.0" "1.0""#), Value::Int(1));
6332 assert_eq!(ev(r#"builtins.compareVersions "1.0" "2.0""#), Value::Int(-1));
6333 assert_eq!(ev(r#"builtins.compareVersions "1.0" "1.0""#), Value::Int(0));
6334 }
6335
6336 #[test]
6337 fn eval_builtins_parse_drv_name() {
6338 let v = ev(r#"builtins.parseDrvName "nix-2.3.4""#);
6339 if let Value::Attrs(a) = v {
6340 assert_eq!(a.get("name"), Some(&Value::string("nix")));
6341 assert_eq!(a.get("version"), Some(&Value::string("2.3.4")));
6342 } else {
6343 panic!("expected attrs");
6344 }
6345 }
6346
6347 #[test]
6348 fn eval_builtins_base_name_of() {
6349 assert_eq!(
6350 ev(r#"builtins.baseNameOf "/foo/bar/baz""#),
6351 Value::string("baz"),
6352 );
6353 }
6354
6355 #[test]
6356 fn eval_builtins_dir_of() {
6357 assert_eq!(
6358 ev(r#"builtins.dirOf "/foo/bar/baz""#),
6359 Value::string("/foo/bar"),
6360 );
6361 }
6362
6363 #[test]
6364 fn eval_builtins_add_error_context() {
6365 assert_eq!(
6366 ev(r#"builtins.addErrorContext "some context" 42"#),
6367 Value::Int(42),
6368 );
6369 }
6370
6371 #[test]
6372 fn eval_builtins_abort() {
6373 let result = eval(r#"builtins.abort "fatal error""#);
6374 assert!(result.is_err());
6375 let msg = format!("{}", result.unwrap_err());
6376 assert!(msg.contains("fatal error"));
6377 }
6378
6379 #[test]
6384 fn indented_string_simple() {
6385 assert_eq!(ev("''hello''"), Value::string("hello"));
6386 }
6387
6388 #[test]
6389 fn indented_string_multiline_strips_indent() {
6390 assert_eq!(
6391 ev("''\n line1\n line2\n''"),
6392 Value::string("line1\nline2\n"),
6393 );
6394 }
6395
6396 #[test]
6397 fn indented_string_with_interpolation() {
6398 let code = "let x = \"world\"; in ''hello ${x}''";
6399 assert_eq!(
6400 ev(code),
6401 Value::string("hello world"),
6402 );
6403 }
6404
6405 #[test]
6406 fn indented_string_deeper_indent_preserved() {
6407 assert_eq!(
6409 ev("''\n a\n b\n''"),
6410 Value::string("a\n b\n"),
6411 );
6412 }
6413
6414 #[test]
6419 fn dynamic_attr_name_in_set() {
6420 assert_eq!(
6421 ev(r#"let key = "mykey"; in { ${key} = 42; }.mykey"#),
6422 Value::Int(42),
6423 );
6424 }
6425
6426 #[test]
6427 fn dynamic_attr_name_with_expression() {
6428 assert_eq!(
6429 ev(r#"let prefix = "foo"; in { ${"${prefix}bar"} = 1; }.foobar"#),
6430 Value::Int(1),
6431 );
6432 }
6433
6434 #[test]
6439 fn eval_builtins_match() {
6440 assert_eq!(
6441 ev(r#"builtins.match "([0-9]+)" "42""#),
6442 Value::list(vec![Value::string("42")]),
6443 );
6444 }
6445
6446 #[test]
6447 fn eval_builtins_hash_string() {
6448 let v = ev(r#"builtins.hashString "sha256" "hello""#);
6449 if let Value::String(ns) = v {
6450 assert_eq!(ns.chars.len(), 64);
6451 } else {
6452 panic!("expected string");
6453 }
6454 }
6455
6456 #[test]
6457 fn eval_builtins_import() {
6458 let dir = std::env::temp_dir();
6459 let path = dir.join("sui_eval_test_import_eval.nix");
6460 std::fs::write(&path, "42").unwrap();
6461 let expr = format!(r#"import "{}""#, path.display());
6462 let v = eval(&expr).unwrap();
6463 assert_eq!(v, Value::Int(42));
6464 std::fs::remove_file(&path).ok();
6465 }
6466
6467 #[test]
6468 fn eval_builtins_derivation() {
6469 let v = eval(r#"builtins.derivation { name = "test"; system = "x86_64-linux"; builder = "/bin/sh"; }"#).unwrap();
6470 if let Value::Attrs(a) = v {
6471 assert_eq!(a.get("type"), Some(&Value::string("derivation")));
6472 } else {
6473 panic!("expected attrs");
6474 }
6475 }
6476
6477 #[test]
6478 fn eval_mutual_recursive_let() {
6479 let v = eval("let a = { x = b; }; b = { y = a; }; in a.x.y");
6486 assert!(v.is_ok(), "mutual recursive let should not error: {v:?}");
6487 let val = v.unwrap();
6489 assert!(
6490 matches!(val, Value::Attrs(_)),
6491 "a.x.y should be an attrset, got: {val:?}",
6492 );
6493 }
6494
6495 #[test]
6496 fn eval_mutual_recursive_let_simple() {
6497 let v = eval("let a = b; b = 42; in a");
6499 assert!(v.is_ok());
6500 assert_eq!(v.unwrap(), Value::Int(42));
6503 }
6504
6505 #[test]
6506 fn eval_builtins_read_dir() {
6507 let dir = std::env::temp_dir().join("sui_eval_test_readdir_eval");
6508 let _ = std::fs::remove_dir_all(&dir);
6509 std::fs::create_dir_all(&dir).unwrap();
6510 std::fs::write(dir.join("a.txt"), "").unwrap();
6511 let expr = format!(r#"builtins.readDir "{}""#, dir.display());
6512 let v = eval(&expr).unwrap();
6513 if let Value::Attrs(a) = v {
6514 assert_eq!(a.get("a.txt"), Some(&Value::string("regular")));
6515 } else {
6516 panic!("expected attrs");
6517 }
6518 let _ = std::fs::remove_dir_all(&dir);
6519 }
6520
6521 #[test]
6526 fn thunk_basic_let() {
6527 assert_eq!(ev("let x = 1; in x"), Value::Int(1));
6529 }
6530
6531 #[test]
6532 fn thunk_forward_ref() {
6533 assert_eq!(ev("let a = b; b = 1; in a"), Value::Int(1));
6535 }
6536
6537 #[test]
6538 fn thunk_mutual_rec_attrset_in_let() {
6539 assert_eq!(ev("let a = { x = b; }; b = { y = 1; }; in a.x.y"), Value::Int(1));
6541 }
6542
6543 #[test]
6544 fn thunk_rec_attrset() {
6545 assert_eq!(ev("(rec { a = b; b = 1; }).a"), Value::Int(1));
6547 }
6548
6549 #[test]
6550 fn thunk_rec_attrset_chain() {
6551 assert_eq!(ev("(rec { a = 1; b = a + 1; c = b + 1; }).c"), Value::Int(3));
6553 }
6554
6555 #[test]
6556 fn thunk_fixpoint() {
6557 assert_eq!(
6559 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; })).b"),
6560 Value::Int(2),
6561 );
6562 }
6563
6564 #[test]
6565 fn thunk_blackhole_self_reference() {
6566 let result = eval("let x = x; in x");
6568 assert!(result.is_err());
6569 let msg = format!("{}", result.unwrap_err());
6570 assert!(
6571 msg.contains("infinite recursion") || msg.contains("blackhole"),
6572 "expected blackhole error, got: {msg}",
6573 );
6574 }
6575
6576 #[test]
6577 fn thunk_mutual_blackhole() {
6578 let result = eval("let a = b; b = a; in a");
6580 assert!(result.is_err());
6581 }
6582
6583 #[test]
6584 fn thunk_let_body_forces_correctly() {
6585 assert_eq!(ev("let a = 10; b = 20; in a + b"), Value::Int(30));
6587 }
6588
6589 #[test]
6590 fn thunk_only_forced_when_needed() {
6591 assert_eq!(ev("let bad = 1 / 0; good = 42; in good"), Value::Int(42));
6593 }
6594
6595 #[test]
6596 fn thunk_forward_ref_in_function_body() {
6597 assert_eq!(
6599 ev("let f = x: x + b; b = 10; in f 5"),
6600 Value::Int(15),
6601 );
6602 }
6603
6604 #[test]
6605 fn thunk_rec_set_self_ref_through_self() {
6606 assert_eq!(
6608 ev(r#"(rec { a = "hello"; b = builtins.stringLength a; }).b"#),
6609 Value::Int(5),
6610 );
6611 }
6612
6613 #[test]
6614 fn thunk_nested_let_forward_ref() {
6615 assert_eq!(
6617 ev("let a = b + 1; b = 2; in a"),
6618 Value::Int(3),
6619 );
6620 }
6621
6622 #[test]
6623 fn thunk_deep_chain() {
6624 assert_eq!(
6626 ev("let a = 1; b = a; c = b; d = c; e = d; in e"),
6627 Value::Int(1),
6628 );
6629 }
6630
6631 #[test]
6632 fn thunk_rec_set_fixpoint() {
6633 assert_eq!(
6635 ev("let fix = f: let x = f x; in x; in (fix (self: { a = 1; b = self.a + 1; c = self.b + 1; })).c"),
6636 Value::Int(3),
6637 );
6638 }
6639
6640 #[test]
6641 fn thunk_let_with_inherit() {
6642 assert_eq!(
6644 ev("let a = 1; in let inherit a; b = a + 1; in b"),
6645 Value::Int(2),
6646 );
6647 }
6648
6649 #[test]
6650 fn thunk_attrset_value_lazy() {
6651 assert_eq!(
6654 ev("let x = 42; in { a = x; }.a"),
6655 Value::Int(42),
6656 );
6657 }
6658
6659 #[test]
6660 fn thunk_unused_error_not_forced() {
6661 assert_eq!(
6663 ev(r#"let bad = builtins.throw "boom"; ok = 1; in ok"#),
6664 Value::Int(1),
6665 );
6666 }
6667
6668 #[test]
6669 fn thunk_rec_set_mutual_reference() {
6670 let v = ev("rec { a = { val = b.val + 1; }; b = { val = 10; }; }");
6672 if let Value::Attrs(attrs) = v {
6673 let a = attrs.get("a").unwrap();
6674 let a_forced = force_value(a).unwrap();
6675 if let Value::Attrs(a_attrs) = a_forced {
6676 assert_eq!(a_attrs.get("val"), Some(&Value::Int(11)));
6677 } else {
6678 panic!("expected attrs for a");
6679 }
6680 } else {
6681 panic!("expected attrs");
6682 }
6683 }
6684
6685 #[test]
6688 fn let_rec_self_reference_simple() {
6689 assert_eq!(
6690 ev("let x = 1; y = x + 1; in y"),
6691 Value::Int(2),
6692 );
6693 }
6694
6695 #[test]
6696 fn let_rec_self_reference_chain() {
6697 assert_eq!(
6698 ev("let a = 1; b = a + 1; c = b + 1; in c"),
6699 Value::Int(3),
6700 );
6701 }
6702
6703 #[test]
6704 fn let_rec_self_reference_with_function() {
6705 assert_eq!(
6706 ev("let f = x: x + 1; y = f 10; in y"),
6707 Value::Int(11),
6708 );
6709 }
6710
6711 #[test]
6712 fn let_rec_mutual_recursion_via_if() {
6713 assert_eq!(
6714 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"),
6715 Value::Bool(true),
6716 );
6717 }
6718
6719 #[test]
6720 fn let_rec_forward_ref_in_list() {
6721 assert_eq!(
6722 ev("let xs = [a b]; a = 1; b = 2; in builtins.length xs"),
6723 Value::Int(2),
6724 );
6725 }
6726
6727 #[test]
6730 fn with_shadowing_let_wins_over_with() {
6731 assert_eq!(
6732 ev("let x = 1; in with { x = 2; }; x"),
6733 Value::Int(1),
6734 );
6735 }
6736
6737 #[test]
6738 fn with_shadowing_inner_with_wins() {
6739 assert_eq!(
6740 ev("with { x = 1; }; with { x = 2; }; x"),
6741 Value::Int(2),
6742 );
6743 }
6744
6745 #[test]
6746 fn with_shadowing_outer_provides_missing() {
6747 assert_eq!(
6748 ev("with { x = 1; y = 10; }; with { x = 2; }; x + y"),
6749 Value::Int(12),
6750 );
6751 }
6752
6753 #[test]
6754 fn with_shadowing_lambda_arg_wins() {
6755 assert_eq!(
6756 ev("(x: with { x = 99; }; x) 42"),
6757 Value::Int(42),
6758 );
6759 }
6760
6761 #[test]
6762 fn with_shadowing_nested_let_wins_over_with() {
6763 assert_eq!(
6764 ev("with { x = 1; }; let x = 2; in x"),
6765 Value::Int(2),
6766 );
6767 }
6768
6769 #[test]
6770 fn with_scope_dynamic_attrs() {
6771 assert_eq!(
6772 ev(r#"with { x = 1; y = 2; z = 3; }; x + y + z"#),
6773 Value::Int(6),
6774 );
6775 }
6776
6777 #[test]
6778 fn with_scope_over_lazy_thunk_chain_resolves() {
6779 assert_eq!(
6788 ev(r#"let outer = if true then (if true then { unix = 42; } else {}) else {};
6789 # force a two-deep lazy wrap of the with-head
6790 head = (x: x) ((y: y) outer);
6791 in with head; unix"#),
6792 Value::Int(42),
6793 );
6794 }
6795
6796 #[test]
6797 fn with_scope_head_from_deep_select_resolves() {
6798 assert_eq!(
6801 ev(r#"let a = { b = { c = { key = 7; }; }; }; in with a.b.c; key"#),
6802 Value::Int(7),
6803 );
6804 }
6805
6806 #[test]
6809 fn attrset_deep_merge_simple() {
6810 let v = ev("{ a.b = 1; a.c = 2; }");
6811 if let Value::Attrs(attrs) = v {
6812 let a = force_value(attrs.get("a").unwrap()).unwrap();
6813 if let Value::Attrs(inner) = a {
6814 assert_eq!(force_value(inner.get("b").unwrap()).unwrap(), Value::Int(1));
6815 assert_eq!(force_value(inner.get("c").unwrap()).unwrap(), Value::Int(2));
6816 } else {
6817 panic!("expected nested attrs");
6818 }
6819 } else {
6820 panic!("expected attrs");
6821 }
6822 }
6823
6824 #[test]
6825 fn attrset_deep_merge_three_levels() {
6826 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
6827 if let Value::Attrs(attrs) = v {
6828 let a = force_value(attrs.get("a").unwrap()).unwrap();
6829 if let Value::Attrs(a_inner) = a {
6830 let e = force_value(a_inner.get("e").unwrap()).unwrap();
6831 assert_eq!(e, Value::Int(3));
6832 let b = force_value(a_inner.get("b").unwrap()).unwrap();
6833 if let Value::Attrs(b_inner) = b {
6834 assert_eq!(force_value(b_inner.get("c").unwrap()).unwrap(), Value::Int(1));
6835 assert_eq!(force_value(b_inner.get("d").unwrap()).unwrap(), Value::Int(2));
6836 } else {
6837 panic!("expected nested attrs for b");
6838 }
6839 } else {
6840 panic!("expected nested attrs for a");
6841 }
6842 } else {
6843 panic!("expected attrs");
6844 }
6845 }
6846
6847 #[test]
6848 fn attrset_deep_merge_preserves_siblings() {
6849 assert_eq!(
6850 ev("{ a.x = 1; b = 2; a.y = 3; }.b"),
6851 Value::Int(2),
6852 );
6853 }
6854
6855 #[test]
6856 fn attrset_deep_merge_in_let() {
6857 let v = ev("let s = { a.b = 1; a.c = 2; }; in s.a.b + s.a.c");
6858 assert_eq!(v, Value::Int(3));
6859 }
6860
6861 #[test]
6862 fn attrset_deep_merge_fullset_then_dotted() {
6863 let v = ev("let s = { a = { x = 1; }; a.y = 2; }; in s.a.x + s.a.y");
6870 assert_eq!(v, Value::Int(3));
6871 let both = ev("let s = { a = { x = 1; }; a.y = 2; }; in [ s.a.x s.a.y ]");
6873 if let Value::List(items) = both {
6874 assert_eq!(force_value(&items[0]).unwrap(), Value::Int(1));
6875 assert_eq!(force_value(&items[1]).unwrap(), Value::Int(2));
6876 } else {
6877 panic!("expected list");
6878 }
6879 }
6880
6881 #[test]
6884 fn inherit_from_basic() {
6885 assert_eq!(
6886 ev("let s = { x = 1; y = 2; }; in let inherit (s) x y; in x + y"),
6887 Value::Int(3),
6888 );
6889 }
6890
6891 #[test]
6892 fn inherit_from_with_shadowing() {
6893 assert_eq!(
6894 ev("let x = 10; in let inherit ({ x = 20; }) x; in x"),
6895 Value::Int(20),
6896 );
6897 }
6898
6899 #[test]
6900 fn inherit_from_in_attrset() {
6901 let v = ev(r#"let s = { a = 1; b = 2; }; in { inherit (s) a b; c = 3; }"#);
6902 if let Value::Attrs(attrs) = v {
6903 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
6904 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
6905 assert_eq!(force_value(attrs.get("c").unwrap()).unwrap(), Value::Int(3));
6906 } else {
6907 panic!("expected attrs");
6908 }
6909 }
6910
6911 #[test]
6912 fn inherit_from_rec_set() {
6913 assert_eq!(
6914 ev("rec { inherit ({ x = 42; }) x; y = x; }.y"),
6915 Value::Int(42),
6916 );
6917 }
6918
6919 #[test]
6920 fn inherit_plain_from_scope() {
6921 assert_eq!(
6922 ev("let x = 1; in { inherit x; }.x"),
6923 Value::Int(1),
6924 );
6925 }
6926
6927 #[test]
6936 fn inherit_plain_from_with_scope_lazy() {
6937 assert_eq!(
6941 ev("let fix = f: let x = f x; in x;
6942 self = fix (self: with self; {
6943 a = use { inherit cp; };
6944 use = { cp }: cp 5;
6945 cp = x: x + 100;
6946 });
6947 in self.a"),
6948 Value::Int(105),
6949 );
6950 assert_eq!(
6952 ev("with { y = 7; }; { inherit y; }.y"),
6953 Value::Int(7),
6954 );
6955 }
6956
6957 #[test]
6958 fn inherit_multiple_from_expr() {
6959 assert_eq!(
6960 ev("let s = { a = 10; b = 20; c = 30; }; in let inherit (s) a b c; in a + b + c"),
6961 Value::Int(60),
6962 );
6963 }
6964
6965 #[test]
6968 fn interp_nested_attrset_access() {
6969 assert_eq!(
6970 ev(r#"let x = { a = "hello"; }; in "${x.a} world""#),
6971 Value::string("hello world"),
6972 );
6973 }
6974
6975 #[test]
6976 fn interp_with_let_expression() {
6977 assert_eq!(
6978 ev(r#""${let x = "inner"; in x}""#),
6979 Value::string("inner"),
6980 );
6981 }
6982
6983 #[test]
6984 fn interp_float_coercion() {
6985 assert_eq!(
6987 ev(r#""${toString 3.14}""#),
6988 Value::string("3.140000"),
6989 );
6990 }
6991
6992 #[test]
6995 fn compare_mixed_int_float() {
6996 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
6997 assert_eq!(ev("1.5 > 1"), Value::Bool(true));
6998 assert_eq!(ev("2.0 == 2"), Value::Bool(true));
6999 }
7000
7001 #[test]
7002 fn compare_string_lexicographic() {
7003 assert_eq!(ev(r#""abc" < "abd""#), Value::Bool(true));
7004 assert_eq!(ev(r#""abc" < "abc""#), Value::Bool(false));
7005 assert_eq!(ev(r#""abc" <= "abc""#), Value::Bool(true));
7006 }
7007
7008 #[test]
7011 fn update_empty_sets() {
7012 let v = ev("{} // {}");
7013 if let Value::Attrs(a) = v { assert!(a.is_empty()); } else { panic!(); }
7014 }
7015
7016 #[test]
7017 fn update_right_overrides_completely() {
7018 assert_eq!(
7019 ev("{ a = 1; b = 2; } // { a = 10; c = 30; }"),
7020 ev("{ a = 10; b = 2; c = 30; }"),
7021 );
7022 }
7023
7024 #[test]
7025 fn update_chained() {
7026 assert_eq!(
7027 ev("{ a = 1; } // { b = 2; } // { c = 3; }"),
7028 ev("{ a = 1; b = 2; c = 3; }"),
7029 );
7030 }
7031
7032 #[test]
7035 fn force_value_concrete_unchanged() {
7036 let v = Value::Int(42);
7037 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
7038 }
7039
7040 #[test]
7041 fn force_value_null() {
7042 assert_eq!(force_value(&Value::Null).unwrap(), Value::Null);
7043 }
7044
7045 #[test]
7048 fn eval_with_file_none() {
7049 let result = eval_with_file("1 + 2", None).unwrap();
7050 assert_eq!(result, Value::Int(3));
7051 }
7052
7053 #[test]
7056 fn error_type_mismatch_in_comparison() {
7057 let result = eval(r#"1 < "a""#);
7058 assert!(result.is_err());
7059 }
7060
7061 #[test]
7062 fn error_select_from_non_set() {
7063 let result = eval("42.x");
7064 assert!(result.is_err());
7065 }
7066
7067 #[test]
7068 fn error_call_non_function() {
7069 let result = eval("42 1");
7070 assert!(result.is_err());
7071 }
7072
7073 #[test]
7074 fn error_negate_string() {
7075 let result = eval(r#"-"hello""#);
7076 assert!(result.is_err());
7077 }
7078
7079 #[test]
7082 fn multiline_string_empty() {
7083 assert_eq!(ev("''''"), Value::string(""));
7084 }
7085
7086 #[test]
7087 fn multiline_string_with_trailing_newline() {
7088 let v = ev("''\n hello\n''");
7089 assert_eq!(v, Value::string("hello\n"));
7090 }
7091
7092 #[test]
7095 fn list_concat_empty_left() {
7096 assert_eq!(ev("[] ++ [1 2]"), Value::list(vec![Value::Int(1), Value::Int(2)]));
7097 }
7098
7099 #[test]
7100 fn list_concat_empty_right() {
7101 assert_eq!(ev("[1 2] ++ []"), Value::list(vec![Value::Int(1), Value::Int(2)]));
7102 }
7103
7104 #[test]
7105 fn list_concat_both_empty() {
7106 assert_eq!(ev("[] ++ []"), Value::list(vec![]));
7107 }
7108
7109 #[test]
7112 fn formals_at_pattern_accessible() {
7113 assert_eq!(
7114 ev("({ x, ... } @ args: builtins.length (builtins.attrNames args)) { x = 1; y = 2; z = 3; }"),
7115 Value::Int(3),
7116 );
7117 }
7118
7119 #[test]
7120 fn formals_default_uses_other_arg() {
7121 assert_eq!(
7122 ev("({ x, y ? x + 1 }: y) { x = 10; }"),
7123 Value::Int(11),
7124 );
7125 }
7126
7127 #[test]
7128 fn formals_default_lazy_assert_false() {
7129 assert_eq!(
7133 ev("({ cpu, vendor ? assert false; null, kernel } @ args: if args ? vendor then vendor else \"inferred\") { cpu = \"x86_64\"; kernel = \"linux\"; }"),
7134 Value::String(Rc::new(NixString::plain("inferred"))),
7135 );
7136 }
7137
7138 #[test]
7139 fn formals_default_lazy_only_forced_when_accessed() {
7140 assert_eq!(
7142 ev("({ a, b ? 42 }: b) { a = 1; }"),
7143 Value::Int(42),
7144 );
7145 }
7146
7147 #[test]
7148 fn formals_ellipsis_ignores_extra() {
7149 assert_eq!(
7150 ev("({ x, ... }: x) { x = 1; y = 2; z = 3; }"),
7151 Value::Int(1),
7152 );
7153 }
7154
7155 #[test]
7158 fn pure_mode_roundtrip() {
7159 let was_pure = is_pure_mode();
7160 set_pure_mode(true);
7161 assert!(is_pure_mode());
7162 set_pure_mode(false);
7163 assert!(!is_pure_mode());
7164 set_pure_mode(was_pure);
7165 }
7166
7167 #[test]
7170 fn path_concat_with_string() {
7171 assert_eq!(
7172 ev(r#"/foo + "bar""#),
7173 Value::Path(Box::new(SmolStr::from("/foobar"))),
7174 );
7175 }
7176
7177 #[test]
7178 fn path_concat_with_path() {
7179 assert_eq!(
7180 ev("/foo + /bar"),
7181 Value::Path(Box::new(SmolStr::from("/foo//bar"))),
7182 );
7183 }
7184
7185 #[test]
7188 fn current_eval_dir_empty_when_no_file_pushed() {
7189 let snapshot = current_eval_dir();
7193 let _ = snapshot;
7195 }
7196
7197 #[test]
7198 fn push_eval_file_sets_current_dir() {
7199 let p = std::path::PathBuf::from("/tmp/example/file.nix");
7200 {
7201 let _g = push_eval_file(p.clone());
7202 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/tmp/example")));
7203 }
7204 }
7208
7209 #[test]
7210 fn push_eval_file_nested_stack() {
7211 let outer = std::path::PathBuf::from("/a/x.nix");
7212 let inner = std::path::PathBuf::from("/b/y.nix");
7213 {
7214 let _g_outer = push_eval_file(outer.clone());
7215 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
7216 {
7217 let _g_inner = push_eval_file(inner.clone());
7218 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/b")));
7219 }
7220 assert_eq!(current_eval_dir(), Some(std::path::PathBuf::from("/a")));
7222 }
7223 }
7224
7225 #[test]
7233 fn fileless_frame_masks_parent_file() {
7234 let outer = std::path::PathBuf::from("/a/x.nix");
7235 let _g_outer = push_eval_file(outer.clone());
7236 assert_eq!(current_eval_file(), Some(outer.clone()));
7237 {
7238 let _g_none = push_eval_frame(None);
7239 assert_eq!(current_eval_file(), None);
7241 assert_eq!(current_eval_dir(), None);
7242 assert_eq!(eval_file_stack_snapshot().last().map(String::as_str), Some("<no-file>"));
7243 }
7244 assert_eq!(current_eval_file(), Some(outer));
7246 }
7247
7248 #[test]
7251 fn error_undefined_var_includes_file_context() {
7252 let p = std::path::PathBuf::from("/nix/store/abc-default.nix");
7253 let _g = push_eval_file(p);
7254 let result = eval("nonexistent_xyz");
7255 let msg = format!("{}", result.unwrap_err());
7256 assert!(msg.contains("undefined variable"), "msg: {msg}");
7257 assert!(msg.contains("nonexistent_xyz"), "msg: {msg}");
7258 assert!(msg.contains("abc-default.nix"), "msg: {msg}");
7259 }
7260
7261 #[test]
7262 fn error_attr_not_found_includes_file_context() {
7263 let p = std::path::PathBuf::from("/nix/store/xyz-module.nix");
7264 let _g = push_eval_file(p);
7265 let result = eval("{}.missing_key");
7266 let msg = format!("{}", result.unwrap_err());
7267 assert!(msg.contains("not found") || msg.contains("missing_key"), "msg: {msg}");
7268 assert!(msg.contains("xyz-module.nix"), "msg: {msg}");
7269 }
7270
7271 #[test]
7272 fn error_assertion_failed_includes_file_context() {
7273 let p = std::path::PathBuf::from("/nix/store/test-assert.nix");
7274 let _g = push_eval_file(p);
7275 let result = eval("assert false; 1");
7276 let msg = format!("{}", result.unwrap_err());
7277 assert!(msg.contains("assertion failed"), "msg: {msg}");
7278 assert!(msg.contains("test-assert.nix"), "msg: {msg}");
7279 }
7280
7281 #[test]
7296 fn inherit_bindings_carry_positions() {
7297 let dir = tempfile::tempdir().unwrap();
7298 let body = "{ inherit ({ x = 1; }) x; }\n";
7303 let f = dir.path().join("inh.nix");
7304 std::fs::write(&f, body).unwrap();
7305 let v = eval(&format!("builtins.unsafeGetAttrPos \"x\" (import {})", f.display())).unwrap();
7306 let attrs = match v {
7307 Value::Attrs(a) => a,
7308 Value::Null => panic!("null — the inherit binding carried no position"),
7309 o => panic!("expected attrs, got {o:?}"),
7310 };
7311 let off = body.rfind("x; }").unwrap();
7315 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
7316 assert_eq!(*attrs.get("line").unwrap(), Value::Int(1));
7317 assert_eq!(*attrs.get("column").unwrap(), Value::Int((off - bol) as i64 + 1));
7318 }
7319
7320 #[test]
7337 fn every_binding_form_carries_a_position() {
7338 let dir = tempfile::tempdir().unwrap();
7339 let body = concat!(
7341 "let src = { i = 1; j = 2; }; in {\n",
7342 " plain = 1;\n",
7343 " \"quoted\" = 2;\n",
7344 " inherit (src) i;\n",
7345 " inherit src;\n",
7346 " nested.deep = 3;\n",
7347 "}\n",
7348 );
7349 let f = dir.path().join("forms.nix");
7350 std::fs::write(&f, body).unwrap();
7351
7352 let keys = ["plain", "quoted", "i", "src", "nested"];
7354 let probe = keys
7355 .iter()
7356 .map(|k| format!(
7357 "(let q = builtins.unsafeGetAttrPos \"{k}\" t; \
7358 in if q == null then \"{k}=NULL\" \
7359 else \"{k}=${{toString q.line}}:${{toString q.column}}\")"
7360 ))
7361 .collect::<Vec<_>>()
7362 .join(" + \" \" + ");
7363 let got = eval(&format!("let t = import {}; in {probe}", f.display()))
7364 .unwrap()
7365 .as_string()
7366 .unwrap()
7367 .to_string();
7368
7369 assert!(!got.contains("NULL"), "a binding form lost its position: {got}");
7370 let rows: Vec<&str> = got.split(' ').collect();
7371 assert_eq!(rows.len(), keys.len(), "corpus shrank — gate would be vacuous: {got}");
7372
7373 for (k, row) in keys.iter().zip(&rows) {
7375 let needle = match *k {
7376 "quoted" => "\"quoted\"".to_string(),
7377 "i" => "i;".to_string(),
7378 "src" => "src;".to_string(),
7379 "nested" => "nested.".to_string(),
7382 other => format!("{other} ="),
7383 };
7384 let off = body.find(&needle).unwrap();
7385 let bol = body[..off].rfind('\n').map_or(0, |i| i + 1);
7386 let line = 1 + body[..off].matches('\n').count();
7387 let col = off - bol + 1;
7388 assert_eq!(*row, format!("{k}={line}:{col}"), "wrong position for `{k}` in:\n{body}");
7389 }
7390 }
7391
7392 #[test]
7405 fn error_missing_argument_includes_file_context() {
7406 let p = std::path::PathBuf::from("/nix/store/func.nix");
7407 let result = eval_with_file("({ a, b }: a) { a = 1; }", Some(p));
7408 let msg = format!("{}", result.unwrap_err());
7409 assert!(msg.contains("missing argument"), "msg: {msg}");
7410 assert!(msg.contains("func.nix"), "msg: {msg}");
7411 }
7412
7413 #[test]
7414 fn error_cannot_call_includes_file_context() {
7415 let p = std::path::PathBuf::from("/nix/store/call.nix");
7416 let _g = push_eval_file(p);
7417 let result = eval("42 99");
7418 let msg = format!("{}", result.unwrap_err());
7419 assert!(msg.contains("cannot call"), "msg: {msg}");
7420 assert!(msg.contains("call.nix"), "msg: {msg}");
7421 }
7422
7423 #[test]
7424 fn error_without_file_has_no_in_prefix() {
7425 let result = eval("nonexistent_xyz");
7428 let msg = format!("{}", result.unwrap_err());
7429 assert!(msg.contains("undefined variable"), "msg: {msg}");
7430 assert!(!msg.contains(", in"), "msg should not contain file context: {msg}");
7431 }
7432
7433 #[test]
7436 fn pure_mode_set_get_independence() {
7437 let was = is_pure_mode();
7438 set_pure_mode(true);
7439 assert!(is_pure_mode());
7440 set_pure_mode(false);
7441 assert!(!is_pure_mode());
7442 set_pure_mode(was);
7443 }
7444
7445 #[test]
7448 fn eval_with_file_some_path_arithmetic() {
7449 let p = std::path::PathBuf::from("/tmp/imaginary.nix");
7450 let result = eval_with_file("1 + 2", Some(p)).unwrap();
7451 assert_eq!(result, Value::Int(3));
7452 }
7453
7454 #[test]
7462 fn unsafe_get_attr_pos_reports_file_and_offset_column() {
7463 let dir = tempfile::tempdir().unwrap();
7475 let file_body = "{ a = 1;\n b = 2; }\n";
7477 let f = dir.path().join("lit.nix");
7478 std::fs::write(&f, file_body).unwrap();
7479 let src = format!("builtins.unsafeGetAttrPos \"b\" (import {})", f.display());
7480 let v = eval(&src).unwrap();
7481 let attrs = match v { Value::Attrs(a) => a, other => panic!("expected attrs, got {other:?}") };
7482 assert_eq!(
7483 attrs.get("file").unwrap().as_string().unwrap(),
7484 f.to_string_lossy(),
7485 );
7486 let off = file_body.find("b = 2").unwrap();
7488 let bol = file_body[..off].rfind('\n').map_or(0, |i| i + 1);
7489 let expected_line = 1 + file_body[..off].matches('\n').count() as i64;
7490 let expected_col = (off - bol) as i64 + 1;
7491 assert_eq!(expected_line, 2, "fixture must put `b` on line 2");
7492 assert_eq!(*attrs.get("line").unwrap(), Value::Int(expected_line));
7493 let col = match attrs.get("column").unwrap() { Value::Int(n) => *n, o => panic!("{o:?}") };
7494 assert_eq!(col, expected_col, "column must be the 1-based BYTE column");
7495 }
7496
7497 #[test]
7498 fn unsafe_get_attr_pos_null_for_string_origin() {
7499 let v = eval("builtins.unsafeGetAttrPos \"a\" { a = 1; }").unwrap();
7501 assert_eq!(v, Value::Null);
7502 }
7503
7504 #[test]
7505 fn unsafe_get_attr_pos_null_for_missing_key() {
7506 let dir = tempfile::tempdir().unwrap();
7508 let f = dir.path().join("lit.nix");
7509 std::fs::write(&f, "{ a = 1; }\n").unwrap();
7510 let src = format!("builtins.unsafeGetAttrPos \"zzz\" (import {})", f.display());
7511 let v = eval(&src).unwrap();
7512 assert_eq!(v, Value::Null);
7513 }
7514
7515 #[test]
7518 fn interp_int_into_string() {
7519 assert_eq!(ev(r#""val=${toString 42}""#), Value::string("val=42"));
7521 }
7522
7523 #[test]
7524 fn interp_bool_true_becomes_one() {
7525 let v = ev(r#"let x = true; in "${builtins.toString x}""#);
7527 assert_eq!(v, Value::string("1"));
7528 }
7529
7530 #[test]
7531 fn interp_null_becomes_empty() {
7532 let v = ev(r#"let x = null; in "${builtins.toString x}""#);
7534 assert_eq!(v, Value::string(""));
7535 }
7536
7537 #[test]
7538 fn interp_attrset_without_to_string_errors() {
7539 let result = eval(r#"let s = { x = 1; }; in "${s}""#);
7541 assert!(result.is_err());
7542 }
7543
7544 #[test]
7545 fn interp_attrset_with_to_string_protocol() {
7546 let v = ev(r#""${{ __toString = self: "ok"; }}""#);
7548 assert_eq!(v, Value::string("ok"));
7549 }
7550
7551 #[test]
7554 fn eval_path_absolute_literal() {
7555 let v = ev("/tmp/foo");
7556 match v {
7557 Value::Path(p) => assert!(p.contains("/tmp/foo")),
7558 _ => panic!("expected Path"),
7559 }
7560 }
7561
7562 #[test]
7563 fn eval_path_home_literal() {
7564 let v = ev("~/foo.nix");
7565 match v {
7566 Value::Path(p) => assert!(p.contains("~/foo.nix") || p.ends_with("foo.nix")),
7567 _ => panic!("expected Path"),
7568 }
7569 }
7570
7571 #[test]
7574 fn path_search_unmatched_errors() {
7575 let saved = std::env::var("NIX_PATH").ok();
7578 unsafe {
7582 std::env::remove_var("NIX_PATH");
7583 }
7584 let result = eval("<this_should_not_resolve>");
7585 if let Some(v) = saved {
7586 unsafe {
7587 std::env::set_var("NIX_PATH", v);
7588 }
7589 }
7590 assert!(result.is_err());
7591 }
7592
7593 #[test]
7596 fn unary_negate_int() {
7597 assert_eq!(ev("-7"), Value::Int(-7));
7598 }
7599
7600 #[test]
7601 fn unary_negate_float() {
7602 assert_eq!(ev("-2.5"), Value::Float(-2.5));
7603 }
7604
7605 #[test]
7606 fn unary_invert_true() {
7607 assert_eq!(ev("!true"), Value::Bool(false));
7608 }
7609
7610 #[test]
7611 fn unary_invert_false() {
7612 assert_eq!(ev("!false"), Value::Bool(true));
7613 }
7614
7615 #[test]
7616 fn unary_negate_bool_errors() {
7617 let result = eval("-true");
7618 assert!(result.is_err());
7619 }
7620
7621 #[test]
7622 fn unary_invert_int_errors() {
7623 let result = eval("!42");
7624 assert!(result.is_err());
7625 }
7626
7627 #[test]
7630 fn binop_add_attrs_errors() {
7631 let result = eval("{a=1;} + {b=2;}");
7632 assert!(result.is_err());
7633 }
7634
7635 #[test]
7636 fn binop_sub_string_errors() {
7637 let result = eval(r#""a" - "b""#);
7638 assert!(result.is_err());
7639 }
7640
7641 #[test]
7642 fn binop_mul_string_errors() {
7643 let result = eval(r#""a" * "b""#);
7644 assert!(result.is_err());
7645 }
7646
7647 #[test]
7648 fn binop_div_string_errors() {
7649 let result = eval(r#""a" / "b""#);
7650 assert!(result.is_err());
7651 }
7652
7653 #[test]
7654 fn binop_compare_attrs_errors() {
7655 let result = eval("{a=1;} < {b=2;}");
7656 assert!(result.is_err());
7657 }
7658
7659 #[test]
7660 fn binop_div_float_by_zero_int() {
7661 let result = eval("1.0 / 0");
7665 let _ = result;
7668 }
7669
7670 #[test]
7671 fn binop_int_div_zero_is_division_by_zero() {
7672 let result = eval("5 / 0");
7673 match result {
7674 Err(EvalError::DivisionByZero) => {}
7675 other => panic!("expected DivisionByZero, got {other:?}"),
7676 }
7677 }
7678
7679 #[test]
7682 fn if_else_only_chosen_branch_evaluated_then() {
7683 assert_eq!(ev("if true then 42 else 1 / 0"), Value::Int(42));
7686 }
7687
7688 #[test]
7689 fn if_else_only_chosen_branch_evaluated_else() {
7690 assert_eq!(ev("if false then 1 / 0 else 99"), Value::Int(99));
7691 }
7692
7693 #[test]
7694 fn if_condition_must_be_bool() {
7695 let result = eval("if 1 then 1 else 2");
7696 assert!(result.is_err());
7697 }
7698
7699 #[test]
7700 fn if_condition_lazy_does_not_force_unused() {
7701 assert_eq!(
7704 ev("let bad = 1 / 0; in if true then 42 else bad"),
7705 Value::Int(42),
7706 );
7707 }
7708
7709 #[test]
7712 fn and_short_circuits_on_false() {
7713 assert_eq!(ev("false && (1 / 0 == 0)"), Value::Bool(false));
7715 }
7716
7717 #[test]
7718 fn or_short_circuits_on_true() {
7719 assert_eq!(ev("true || (1 / 0 == 0)"), Value::Bool(true));
7720 }
7721
7722 #[test]
7723 fn implication_short_circuits_on_false_lhs() {
7724 assert_eq!(ev("false -> (1 / 0 == 0)"), Value::Bool(true));
7726 }
7727
7728 #[test]
7731 fn lambda_fix_combinator_returns_attrset() {
7732 let v = ev(
7734 "let fix = f: let x = f x; in x; in
7735 (fix (self: { val = 1; double = self.val * 2; })).double",
7736 );
7737 assert_eq!(v, Value::Int(2));
7738 }
7739
7740 #[test]
7743 fn rec_attrset_self_reference() {
7744 let v = ev("(rec { a = b; b = 1; }).a");
7746 assert_eq!(v, Value::Int(1));
7747 }
7748
7749 #[test]
7750 fn rec_attrset_inherit_from_uses_outer_scope() {
7751 let v = ev(
7755 "let src = { a = 10; }; in
7756 rec {
7757 inherit (src) a;
7758 b = a + 1;
7759 }",
7760 );
7761 if let Value::Attrs(attrs) = v {
7762 let b = attrs.get("b").unwrap();
7763 let b_forced = force_value(b).unwrap();
7764 assert_eq!(b_forced, Value::Int(11));
7765 } else {
7766 panic!("expected attrs");
7767 }
7768 }
7769
7770 #[test]
7771 fn nonrec_attrset_no_self_reference() {
7772 let result = eval("({ a = 1; b = a + 1; }).b");
7775 assert!(result.is_err());
7776 }
7777
7778 #[test]
7781 fn dotted_binding_three_segments_then_sibling() {
7782 let v = ev("{ a.b.c = 1; a.b.d = 2; a.e = 3; }");
7783 if let Value::Attrs(attrs) = v {
7784 let a = attrs.get("a").unwrap();
7785 let a_forced = force_value(a).unwrap();
7786 if let Value::Attrs(a_attrs) = a_forced {
7787 let b = a_attrs.get("b").unwrap();
7788 let b_forced = force_value(b).unwrap();
7789 if let Value::Attrs(b_attrs) = b_forced {
7790 assert_eq!(force_value(b_attrs.get("c").unwrap()).unwrap(), Value::Int(1));
7791 assert_eq!(force_value(b_attrs.get("d").unwrap()).unwrap(), Value::Int(2));
7792 } else {
7793 panic!("expected b to be attrs");
7794 }
7795 assert_eq!(force_value(a_attrs.get("e").unwrap()).unwrap(), Value::Int(3));
7796 } else {
7797 panic!("expected a to be attrs");
7798 }
7799 } else {
7800 panic!("expected outer attrs");
7801 }
7802 }
7803
7804 #[test]
7807 fn rec_dotted_bindings_visible_to_siblings() {
7808 let v = ev("rec { types.openSB = 1; types.openCpu = 2; foo = types.openSB; }.foo");
7811 assert_eq!(v, Value::Int(1));
7812 }
7813
7814 #[test]
7815 fn rec_dotted_leaf_uses_rec_scope() {
7816 let v = ev("rec { types.a = f 1; f = x: x + 1; }.types.a");
7819 assert_eq!(v, Value::Int(2));
7820 }
7821
7822 #[test]
7823 fn rec_dotted_multiple_keys_merge() {
7824 let v = ev("rec { types.a = 1; types.b = 2; x = types; }.x");
7826 if let Value::Attrs(attrs) = v {
7827 assert_eq!(force_value(attrs.get("a").unwrap()).unwrap(), Value::Int(1));
7828 assert_eq!(force_value(attrs.get("b").unwrap()).unwrap(), Value::Int(2));
7829 } else {
7830 panic!("expected attrs");
7831 }
7832 }
7833
7834 #[test]
7835 fn rec_nixpkgs_parse_pattern() {
7836 let v = ev(r#"
7840 let
7841 mkOptionType = x: x;
7842 mergeOneOption = "merge";
7843 attrValues = builtins.attrValues;
7844 setType = name: value: { __type = name; } // value;
7845 mapAttrs = builtins.mapAttrs;
7846 enum = xs: mkOptionType { name = "enum"; check = x: builtins.elem x xs; };
7847 setTypes = type: mapAttrs (name: value: setType type.name ({ inherit name; } // value));
7848 in
7849 rec {
7850 types.openSB = mkOptionType { name = "sb"; merge = mergeOneOption; };
7851 types.significantByte = enum (attrValues significantBytes);
7852 significantBytes = setTypes types.openSB { bigEndian = {}; littleEndian = {}; };
7853 types.openCpuType = mkOptionType { name = "cpu-type"; };
7854 types.cpuType = enum (attrValues cpuTypes);
7855 cpuTypes = setTypes types.openCpuType { arm = { bits = 32; }; };
7856 }.types.openCpuType
7857 "#);
7858 if let Value::Attrs(attrs) = v {
7859 assert_eq!(
7860 force_value(attrs.get("name").unwrap()).unwrap(),
7861 Value::string("cpu-type")
7862 );
7863 } else {
7864 panic!("expected attrs");
7865 }
7866 }
7867
7868 #[test]
7869 fn let_dotted_leaf_uses_let_scope() {
7870 let v = ev("let a.x = f 1; f = x: x + 1; in a.x");
7872 assert_eq!(v, Value::Int(2));
7873 }
7874
7875 #[test]
7876 fn let_inherit_from_plus_dotted_overrides() {
7877 let v = ev(r#"
7883 let
7884 src = { types = { existing = true; }; };
7885 inherit (src) types;
7886 types.added = true;
7887 in types
7888 "#);
7889 if let Value::Attrs(attrs) = v {
7890 assert_eq!(
7892 force_value(attrs.get("added").unwrap()).unwrap(),
7893 Value::Bool(true)
7894 );
7895 assert!(attrs.get("existing").is_none());
7897 } else {
7898 panic!("expected attrs");
7899 }
7900 }
7901
7902 #[test]
7905 fn pattern_empty_no_args_no_ellipsis() {
7906 assert_eq!(ev("({}: 1) {}"), Value::Int(1));
7908 }
7909
7910 #[test]
7911 fn pattern_empty_with_ellipsis_accepts_extra() {
7912 assert_eq!(ev("({...}: 1) { a = 1; b = 2; }"), Value::Int(1));
7913 }
7914
7915 #[test]
7916 fn pattern_all_defaults() {
7917 assert_eq!(
7918 ev("({a ? 1, b ? 2}: a + b) {}"),
7919 Value::Int(3),
7920 );
7921 }
7922
7923 #[test]
7924 fn pattern_at_bind_before() {
7925 assert_eq!(ev("(args @ { x }: args.x) { x = 7; }"), Value::Int(7));
7927 }
7928
7929 #[test]
7930 fn pattern_at_bind_after() {
7931 assert_eq!(ev("({ x } @ args: args.x) { x = 7; }"), Value::Int(7));
7933 }
7934
7935 #[test]
7936 fn pattern_default_references_other_arg() {
7937 assert_eq!(ev("({a, b ? a + 1}: b) {a = 10;}"), Value::Int(11));
7939 }
7940
7941 #[test]
7942 fn pattern_required_missing_errors() {
7943 let result = eval("({ a, b }: a) { a = 1; }");
7944 assert!(result.is_err());
7945 }
7946
7947 #[test]
7948 fn pattern_unexpected_errors_without_ellipsis() {
7949 let result = eval("({ a }: a) { a = 1; b = 2; }");
7950 assert!(result.is_err());
7951 }
7952
7953 #[test]
7956 fn apply_int_errors() {
7957 let result = eval("42 5");
7958 assert!(result.is_err());
7959 }
7960
7961 #[test]
7962 fn apply_string_errors() {
7963 let result = eval(r#""hi" 5"#);
7964 assert!(result.is_err());
7965 }
7966
7967 #[test]
7968 fn apply_attrset_without_functor_errors() {
7969 let result = eval("{ x = 1; } 5");
7970 assert!(result.is_err());
7971 let msg = format!("{}", result.unwrap_err());
7972 assert!(msg.contains("__functor") || msg.contains("cannot call"));
7973 }
7974
7975 #[test]
7978 fn select_multi_segment_with_default() {
7979 assert_eq!(ev("{ a = { b = 1; }; }.a.c or 99"), Value::Int(99));
7981 }
7982
7983 #[test]
7984 fn select_from_int_errors() {
7985 let result = eval("(1).x");
7986 assert!(result.is_err());
7987 }
7988
7989 #[test]
7992 fn has_attr_on_non_set_returns_false() {
7993 assert_eq!(ev("1 ? x"), Value::Bool(false));
7995 }
7996
7997 #[test]
7998 fn has_attr_nested_path_present() {
7999 assert_eq!(ev("{ a = { b = 1; }; } ? a.b"), Value::Bool(true));
8000 }
8001
8002 #[test]
8003 fn has_attr_nested_path_missing() {
8004 assert_eq!(ev("{ a = { b = 1; }; } ? a.c"), Value::Bool(false));
8005 }
8006
8007 #[test]
8008 fn has_attr_intermediate_missing_returns_false() {
8009 assert_eq!(ev("{} ? a.b.c"), Value::Bool(false));
8010 }
8011
8012 #[test]
8015 fn list_with_function_value() {
8016 let v = ev("[(x: x + 1)]");
8017 if let Value::List(items) = v {
8018 assert_eq!(items.len(), 1);
8019 let forced = force_value(&items[0]).unwrap();
8021 assert!(matches!(forced, Value::Lambda(_)));
8022 } else {
8023 panic!("expected list");
8024 }
8025 }
8026
8027 #[test]
8030 fn inherit_unknown_name_errors() {
8031 let result = eval("let x = 1; in let inherit nonexistent; in nonexistent");
8032 assert!(result.is_err());
8033 }
8034
8035 #[test]
8038 fn string_concat_no_context_when_both_plain() {
8039 let v = ev(r#""abc" + "def""#);
8040 if let Value::String(ns) = v {
8041 assert_eq!(ns.chars, "abcdef");
8042 assert!(!ns.has_context());
8043 } else {
8044 panic!("expected string");
8045 }
8046 }
8047
8048 #[test]
8051 fn parens_around_expression() {
8052 assert_eq!(ev("(1 + 2)"), Value::Int(3));
8053 }
8054
8055 #[test]
8056 fn nested_parens() {
8057 assert_eq!(ev("(((42)))"), Value::Int(42));
8058 }
8059
8060 #[test]
8063 fn throw_propagates_as_error() {
8064 let result = eval(r#"builtins.throw "kaboom""#);
8065 match result {
8066 Err(EvalError::Throw(s)) => assert!(s.contains("kaboom")),
8067 other => panic!("expected Throw, got {other:?}"),
8068 }
8069 }
8070
8071 #[test]
8072 fn assert_failed_propagates_as_error() {
8073 let result = eval("assert false; 1");
8074 match result {
8075 Err(EvalError::AssertionFailed(_)) => {}
8076 other => panic!("expected AssertionFailed, got {other:?}"),
8077 }
8078 }
8079
8080 #[test]
8083 fn string_no_interp_yields_no_context() {
8084 let v = ev(r#""just literal""#);
8085 if let Value::String(ns) = v {
8086 assert!(!ns.has_context());
8087 } else {
8088 panic!("expected string");
8089 }
8090 }
8091
8092 #[test]
8101 fn interp_path_copies_to_store_byte_matches_cppnix() {
8102 let dir = std::env::temp_dir().join(format!("sui-r5-interp-{}", std::process::id()));
8103 let _ = std::fs::remove_dir_all(&dir);
8104 std::fs::create_dir_all(&dir).unwrap();
8105 let f = dir.join("data.txt");
8106 std::fs::write(&f, b"hello\n").unwrap();
8107 let expr = format!(r#""${{{}}}""#, f.display());
8108 let v = eval(&expr).unwrap();
8109 if let Value::String(ns) = v {
8110 assert_eq!(
8111 ns.chars.to_string(),
8112 "/nix/store/y9dmvfhip31hg8ia4njwjz9vfa3ndphr-data.txt",
8113 );
8114 assert!(ns.has_context());
8115 } else {
8116 panic!("expected string");
8117 }
8118 let _ = std::fs::remove_dir_all(&dir);
8119 }
8120
8121 #[test]
8130 fn parse_error_unbalanced_braces() {
8131 let result = eval("{ a = 1");
8132 assert!(result.is_err());
8133 let err = result.unwrap_err();
8134 assert!(matches!(err, EvalError::ParseError(_)));
8135 }
8136
8137 #[test]
8138 fn parse_error_dangling_let() {
8139 let result = eval("let in");
8140 assert!(result.is_err());
8141 }
8142
8143 #[test]
8144 fn parse_error_empty_input() {
8145 let result = eval("");
8146 assert!(result.is_err());
8147 }
8148
8149 #[test]
8152 fn float_int_subtraction() {
8153 assert_eq!(ev("3.5 - 1"), Value::Float(2.5));
8154 }
8155
8156 #[test]
8157 fn int_float_subtraction() {
8158 assert_eq!(ev("3 - 0.5"), Value::Float(2.5));
8159 }
8160
8161 #[test]
8162 fn float_float_division() {
8163 assert_eq!(ev("6.0 / 2.0"), Value::Float(3.0));
8164 }
8165
8166 #[test]
8167 fn int_float_multiplication() {
8168 assert_eq!(ev("3 * 2.5"), Value::Float(7.5));
8169 }
8170
8171 #[test]
8174 fn compare_int_float_less() {
8175 assert_eq!(ev("1 < 1.5"), Value::Bool(true));
8176 }
8177
8178 #[test]
8179 fn compare_float_int_more() {
8180 assert_eq!(ev("3.5 > 3"), Value::Bool(true));
8181 }
8182
8183 #[test]
8184 fn compare_equal_int_float() {
8185 assert_eq!(ev("3 <= 3.0"), Value::Bool(true));
8186 }
8187
8188 #[test]
8191 fn equal_lists_same() {
8192 assert_eq!(ev("[1 2 3] == [1 2 3]"), Value::Bool(true));
8193 }
8194
8195 #[test]
8196 fn equal_lists_diff_length() {
8197 assert_eq!(ev("[1 2] == [1 2 3]"), Value::Bool(false));
8198 }
8199
8200 #[test]
8201 fn not_equal_lists() {
8202 assert_eq!(ev("[1] != [2]"), Value::Bool(true));
8203 }
8204
8205 #[test]
8206 fn equal_attrsets_same() {
8207 assert_eq!(ev("{a = 1; b = 2;} == {b = 2; a = 1;}"), Value::Bool(true));
8208 }
8209
8210 #[test]
8217 fn lambda_self_equality_in_attrset() {
8218 assert_eq!(
8220 ev("let f = x: x; in { a = 1; inherit f; } == { a = 1; inherit f; }"),
8221 Value::Bool(true),
8222 );
8223 }
8224
8225 #[test]
8226 fn lambda_self_reference_attrset_equality() {
8227 assert_eq!(
8229 ev("let x = { a = 1; f = y: y; }; in x == x"),
8230 Value::Bool(true),
8231 );
8232 }
8233
8234 #[test]
8235 fn lambda_different_closures_not_equal() {
8236 assert_eq!(
8238 ev("{ f = x: x; } == { f = x: x; }"),
8239 Value::Bool(false),
8240 );
8241 }
8242
8243 #[test]
8244 fn lambda_ne_does_not_force_unused_branch() {
8245 assert_eq!(
8248 ev("let ls = { a = 1; f = x: x; }; in if ls != ls then builtins.throw \"bug\" else 42"),
8249 Value::Int(42),
8250 );
8251 }
8252
8253 #[test]
8256 fn force_value_through_thunk() {
8257 let root = rnix::Root::parse("1 + 2");
8258 let expr = root.tree().expr().unwrap();
8259 let thunk = Thunk::new_suspended(expr, Env::new());
8260 let val = Value::Thunk(thunk);
8261 assert_eq!(force_value(&val).unwrap(), Value::Int(3));
8262 }
8263
8264 #[test]
8267 fn try_eval_catches_thrown_error() {
8268 let v = ev(r#"(builtins.tryEval (builtins.throw "oops")).success"#);
8270 assert_eq!(v, Value::Bool(false));
8271 }
8272
8273 #[test]
8274 fn try_eval_returns_value_on_success() {
8275 let v = ev("(builtins.tryEval 42).value");
8276 assert_eq!(v, Value::Int(42));
8277 }
8278
8279 #[test]
8282 fn legacy_let_returns_body_attr() {
8283 assert_eq!(ev("let { x = 1; body = x + 41; }"), Value::Int(42));
8287 }
8288
8289 #[test]
8290 fn legacy_let_missing_body_errors() {
8291 let result = eval("let { x = 1; }");
8292 assert!(result.is_err());
8293 }
8294
8295 #[test]
8296 fn legacy_let_with_inherit_from_scope() {
8297 assert_eq!(
8298 ev("let outer = 5; in let { inherit outer; body = outer * 2; }"),
8299 Value::Int(10),
8300 );
8301 }
8302
8303 #[test]
8306 fn interp_with_string_concat_preserves_order() {
8307 assert_eq!(
8308 ev(r#"let a = "x"; b = "y"; in "${a}-${b}""#),
8309 Value::string("x-y"),
8310 );
8311 }
8312
8313 #[test]
8314 fn interp_only_literal_part() {
8315 assert_eq!(ev(r#""no interp here""#), Value::string("no interp here"));
8316 }
8317
8318 #[test]
8321 fn dynamic_attr_via_string_key_in_set() {
8322 assert_eq!(ev(r#"{ "a" = 1; }.a"#), Value::Int(1));
8324 }
8325
8326 #[test]
8327 fn dynamic_attr_via_interpolated_key() {
8328 let v = ev(r#"let k = "foo"; in { ${k} = 99; }.foo"#);
8329 assert_eq!(v, Value::Int(99));
8330 }
8331
8332 #[test]
8335 fn select_with_string_key() {
8336 let v = ev(r#"{ a = 42; }."a""#);
8337 assert_eq!(v, Value::Int(42));
8338 }
8339
8340 #[test]
8343 fn apply_attrset_with_functor_works() {
8344 let v = ev("let s = { __functor = self: x: x + 1; }; in s 5");
8345 assert_eq!(v, Value::Int(6));
8346 }
8347
8348 #[test]
8351 fn double_negate_int() {
8352 assert_eq!(ev("- (-5)"), Value::Int(5));
8353 }
8354
8355 #[test]
8358 fn inherit_in_let_makes_name_available() {
8359 assert_eq!(
8360 ev("let src = { a = 7; }; in let inherit (src) a; in a"),
8361 Value::Int(7),
8362 );
8363 }
8364
8365 #[test]
8368 fn path_plus_string_yields_path() {
8369 let v = ev(r#"/foo + "/bar""#);
8370 match v {
8371 Value::Path(p) => assert_eq!(&*p, "/foo/bar"),
8372 _ => panic!("expected path"),
8373 }
8374 }
8375
8376 #[test]
8379 fn attrset_value_not_forced_unless_selected() {
8380 assert_eq!(
8383 ev(r#"{ bad = builtins.throw "boom"; good = 42; }.good"#),
8384 Value::Int(42),
8385 );
8386 }
8387
8388 #[test]
8391 fn lambda_recursive_via_let() {
8392 assert_eq!(
8394 ev("let fact = n: if n == 0 then 1 else n * fact (n - 1); in fact 5"),
8395 Value::Int(120),
8396 );
8397 }
8398
8399 #[test]
8402 fn select_with_dynamic_key_via_var() {
8403 assert_eq!(ev(r#"let k = { x = 1; }; in k.x"#), Value::Int(1));
8406 }
8407
8408 #[test]
8411 fn compare_string_lex_greater_or_equal() {
8412 assert_eq!(ev(r#""b" >= "a""#), Value::Bool(true));
8413 assert_eq!(ev(r#""a" >= "a""#), Value::Bool(true));
8414 assert_eq!(ev(r#""a" >= "b""#), Value::Bool(false));
8415 }
8416
8417 #[test]
8420 fn equal_int_string_false() {
8421 assert_eq!(ev(r#"1 == "1""#), Value::Bool(false));
8422 }
8423
8424 #[test]
8425 fn equal_null_int_false() {
8426 assert_eq!(ev("null == 0"), Value::Bool(false));
8427 }
8428
8429 #[test]
8432 fn update_with_let_bound_operands() {
8433 assert_eq!(
8434 ev("let a = { x = 1; }; b = { y = 2; }; in (a // b).y"),
8435 Value::Int(2),
8436 );
8437 }
8438
8439 #[test]
8442 fn concat_lists_from_let() {
8443 assert_eq!(
8444 ev("let a = [1 2]; b = [3 4]; in builtins.length (a ++ b)"),
8445 Value::Int(4),
8446 );
8447 }
8448
8449 #[test]
8452 fn interp_list_coerces_with_spaces() {
8453 assert_eq!(
8456 ev(r#""${toString [1 2 3]}""#),
8457 Value::string("1 2 3"),
8458 );
8459 }
8460
8461 #[test]
8462 fn interp_list_directly_coerces() {
8463 assert_eq!(
8465 ev(r#""${[1 2]}""#),
8466 Value::string("1 2"),
8467 );
8468 }
8469
8470 #[test]
8473 fn interp_outpath_attrset() {
8474 assert_eq!(
8475 ev(r#"let x = { outPath = "/nix/store/abc"; }; in "${x}""#),
8476 Value::string("/nix/store/abc"),
8477 );
8478 }
8479
8480 #[test]
8481 fn interp_tostring_takes_priority_over_outpath() {
8482 assert_eq!(
8483 ev(r#"let x = { __toString = self: "custom"; outPath = "/ignored"; }; in "${x}""#),
8484 Value::string("custom"),
8485 );
8486 }
8487
8488 #[test]
8489 fn interp_derivation_coerces_to_outpath() {
8490 let result = eval(r#"
8492 let drv = builtins.derivation {
8493 name = "test";
8494 system = "x86_64-linux";
8495 builder = "/bin/sh";
8496 };
8497 in "${drv}"
8498 "#).unwrap();
8499 if let Value::String(s) = result {
8500 assert!(s.chars.starts_with("/nix/store/"), "got: {}", s.chars);
8501 } else {
8502 panic!("expected string");
8503 }
8504 }
8505
8506 #[test]
8509 fn interp_lambda_errors() {
8510 let result = eval(r#""${x: x}""#);
8511 assert!(result.is_err());
8512 }
8513
8514 #[test]
8517 fn force_value_int_returns_same() {
8518 let v = Value::Int(42);
8519 assert_eq!(force_value(&v).unwrap(), Value::Int(42));
8520 }
8521
8522 #[test]
8523 fn force_value_bool_returns_same() {
8524 let v = Value::Bool(true);
8525 assert_eq!(force_value(&v).unwrap(), Value::Bool(true));
8526 }
8527
8528 #[test]
8529 fn force_value_string_returns_same() {
8530 let v = Value::string("hello");
8531 assert_eq!(force_value(&v).unwrap(), Value::string("hello"));
8532 }
8533
8534 #[test]
8535 fn force_value_attrs_returns_same() {
8536 let mut a = NixAttrs::new();
8537 a.insert("x".to_string(), Value::Int(1));
8538 let v = Value::Attrs(Rc::new(a.clone()));
8539 assert_eq!(force_value(&v).unwrap(), Value::Attrs(Rc::new(a)));
8540 }
8541
8542 #[test]
8543 fn force_value_list_returns_same() {
8544 let v = Value::list(vec![Value::Int(1), Value::Int(2)]);
8545 assert_eq!(
8546 force_value(&v).unwrap(),
8547 Value::list(vec![Value::Int(1), Value::Int(2)]),
8548 );
8549 }
8550
8551 #[test]
8552 fn force_value_null_returns_null() {
8553 let v = Value::Null;
8554 assert_eq!(force_value(&v).unwrap(), Value::Null);
8555 }
8556
8557 #[test]
8558 fn force_value_evaluated_thunk_returns_cached() {
8559 let v = ev("let x = 1 + 2; in x");
8561 assert_eq!(v, Value::Int(3));
8562 assert_eq!(force_value(&v).unwrap(), Value::Int(3));
8564 }
8565
8566 #[test]
8569 fn tco_if_true_condition() {
8570 assert_eq!(ev("if true then 42 else 0"), Value::Int(42));
8571 }
8572
8573 #[test]
8574 fn tco_if_false_condition() {
8575 assert_eq!(ev("if false then 42 else 0"), Value::Int(0));
8576 }
8577
8578 #[test]
8579 fn tco_deeply_nested_if_else_chain() {
8580 let mut expr = String::from("150");
8583 for i in (1..150).rev() {
8584 expr = format!("if false then {} else {}", i, expr);
8585 }
8586 let v = ev(&expr);
8587 assert_eq!(v, Value::Int(150));
8588 }
8589
8590 #[test]
8591 fn tco_assert_true_passes_through() {
8592 assert_eq!(ev("assert true; 42"), Value::Int(42));
8593 }
8594
8595 #[test]
8596 fn tco_assert_false_throws_assertion_failed() {
8597 let result = eval("assert false; 42");
8598 assert!(result.is_err());
8599 let err = result.unwrap_err();
8600 assert!(
8601 matches!(err, EvalError::AssertionFailed(_)),
8602 "expected AssertionFailed, got: {err}",
8603 );
8604 }
8605
8606 #[test]
8607 fn tco_with_makes_scope_available() {
8608 assert_eq!(ev("with { x = 10; y = 20; }; x + y"), Value::Int(30));
8609 }
8610
8611 #[test]
8612 fn tco_let_in_creates_bindings() {
8613 assert_eq!(ev("let a = 5; in a"), Value::Int(5));
8614 }
8615
8616 #[test]
8617 fn tco_let_in_multiple_bindings() {
8618 assert_eq!(ev("let a = 1; b = 2; c = 3; in a + b + c"), Value::Int(6));
8619 }
8620
8621 #[test]
8624 fn eval_attrset_empty() {
8625 let v = ev("{}");
8626 if let Value::Attrs(attrs) = v {
8627 assert!(attrs.is_empty(), "expected empty attrset");
8628 } else {
8629 panic!("expected attrset, got {v:?}");
8630 }
8631 }
8632
8633 #[test]
8634 fn eval_attrset_simple_kv() {
8635 let v = ev("{ a = 1; b = 2; }");
8636 if let Value::Attrs(attrs) = v {
8637 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8638 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8639 } else {
8640 panic!("expected attrset, got {v:?}");
8641 }
8642 }
8643
8644 #[test]
8645 fn eval_attrset_recursive() {
8646 assert_eq!(ev("(rec { a = 1; b = a + 1; }).b"), Value::Int(2));
8647 assert_eq!(ev("(rec { a = 1; b = a + 1; }).a"), Value::Int(1));
8648 }
8649
8650 #[test]
8651 fn eval_attrset_inherit_from_scope() {
8652 assert_eq!(ev("let x = 1; in { inherit x; }.x"), Value::Int(1));
8653 }
8654
8655 #[test]
8656 fn eval_attrset_inherit_from_expr() {
8657 assert_eq!(
8658 ev("{ inherit (builtins) true; }.true"),
8659 Value::Bool(true),
8660 );
8661 }
8662
8663 #[test]
8664 fn eval_attrset_dotted_path() {
8665 assert_eq!(ev("{ a.b.c = 1; }.a.b.c"), Value::Int(1));
8666 }
8667
8668 #[test]
8669 fn eval_attrset_update_merge() {
8670 let v = ev("{ a = 1; } // { b = 2; }");
8671 if let Value::Attrs(attrs) = v {
8672 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8673 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8674 } else {
8675 panic!("expected attrset, got {v:?}");
8676 }
8677 }
8678
8679 #[test]
8682 fn eval_apply_simple_function() {
8683 assert_eq!(ev("(x: x + 1) 2"), Value::Int(3));
8684 }
8685
8686 #[test]
8687 fn eval_apply_pattern_destructuring() {
8688 assert_eq!(ev("({a, b}: a + b) { a = 1; b = 2; }"), Value::Int(3));
8689 }
8690
8691 #[test]
8692 fn eval_apply_default_arguments() {
8693 assert_eq!(ev("({a, b ? 0}: a + b) { a = 1; }"), Value::Int(1));
8694 }
8695
8696 #[test]
8697 fn eval_apply_ellipsis() {
8698 assert_eq!(ev("({a, ...}: a) { a = 1; b = 2; }"), Value::Int(1));
8699 }
8700
8701 #[test]
8704 fn eval_select_single_key() {
8705 assert_eq!(ev("{ a = 1; }.a"), Value::Int(1));
8706 }
8707
8708 #[test]
8709 fn eval_select_multi_level() {
8710 assert_eq!(ev("{ a.b = 1; }.a.b"), Value::Int(1));
8711 }
8712
8713 #[test]
8714 fn eval_select_with_or_default() {
8715 assert_eq!(ev("{}.a or 42"), Value::Int(42));
8716 }
8717
8718 #[test]
8719 fn eval_select_missing_key_without_default_throws() {
8720 let result = eval("{}.a");
8721 assert!(result.is_err());
8722 }
8723
8724 #[test]
8727 fn binop_add_ints() {
8728 assert_eq!(ev("1 + 2"), Value::Int(3));
8729 }
8730
8731 #[test]
8732 fn binop_sub_ints() {
8733 assert_eq!(ev("3 - 1"), Value::Int(2));
8734 }
8735
8736 #[test]
8737 fn binop_mul_ints() {
8738 assert_eq!(ev("2 * 3"), Value::Int(6));
8739 }
8740
8741 #[test]
8742 fn binop_div_ints() {
8743 assert_eq!(ev("6 / 2"), Value::Int(3));
8744 }
8745
8746 #[test]
8747 fn binop_float_arithmetic() {
8748 assert_eq!(ev("1.5 + 2.5"), Value::Float(4.0));
8749 }
8750
8751 #[test]
8752 fn binop_string_concat() {
8753 assert_eq!(
8754 ev(r#""hello" + " " + "world""#),
8755 Value::string("hello world"),
8756 );
8757 }
8758
8759 #[test]
8760 fn binop_list_concat() {
8761 assert_eq!(
8762 ev("[1 2] ++ [3 4]"),
8763 Value::list(vec![
8764 Value::Int(1),
8765 Value::Int(2),
8766 Value::Int(3),
8767 Value::Int(4),
8768 ]),
8769 );
8770 }
8771
8772 #[test]
8773 fn binop_attrset_update() {
8774 let v = ev("{ a = 1; } // { b = 2; }");
8775 if let Value::Attrs(attrs) = v {
8776 assert_eq!(attrs.get("a"), Some(&Value::Int(1)));
8777 assert_eq!(attrs.get("b"), Some(&Value::Int(2)));
8778 } else {
8779 panic!("expected attrset, got {v:?}");
8780 }
8781 }
8782
8783 #[test]
8784 fn binop_less_than() {
8785 assert_eq!(ev("1 < 2"), Value::Bool(true));
8786 assert_eq!(ev("2 < 1"), Value::Bool(false));
8787 }
8788
8789 #[test]
8790 fn binop_greater_than() {
8791 assert_eq!(ev("2 > 1"), Value::Bool(true));
8792 assert_eq!(ev("1 > 2"), Value::Bool(false));
8793 }
8794
8795 #[test]
8796 fn binop_equal() {
8797 assert_eq!(ev("1 == 1"), Value::Bool(true));
8798 assert_eq!(ev("1 == 2"), Value::Bool(false));
8799 }
8800
8801 #[test]
8802 fn binop_not_equal() {
8803 assert_eq!(ev("1 != 2"), Value::Bool(true));
8804 assert_eq!(ev("1 != 1"), Value::Bool(false));
8805 }
8806
8807 #[test]
8808 fn binop_logical_and() {
8809 assert_eq!(ev("true && false"), Value::Bool(false));
8810 assert_eq!(ev("true && true"), Value::Bool(true));
8811 }
8812
8813 #[test]
8814 fn binop_logical_or() {
8815 assert_eq!(ev("true || false"), Value::Bool(true));
8816 assert_eq!(ev("false || false"), Value::Bool(false));
8817 }
8818
8819 #[test]
8820 fn binop_logical_not() {
8821 assert_eq!(ev("!true"), Value::Bool(false));
8822 assert_eq!(ev("!false"), Value::Bool(true));
8823 }
8824
8825 #[test]
8826 fn binop_implication() {
8827 assert_eq!(ev("false -> true"), Value::Bool(true));
8828 assert_eq!(ev("false -> false"), Value::Bool(true));
8829 assert_eq!(ev("true -> true"), Value::Bool(true));
8830 assert_eq!(ev("true -> false"), Value::Bool(false));
8831 }
8832}