1use bstr::{ByteSlice, ByteVec};
7use builtin_macros::builtins;
8use genawaiter::rc::Gen;
9use regex::Regex;
10use rustc_hash::FxHashMap;
11use std::cell::RefCell;
12use std::cmp::{self, Ordering};
13use std::collections::BTreeMap;
14use std::collections::VecDeque;
15use std::collections::hash_map::Entry;
16use std::path::PathBuf;
17use std::rc::Rc;
18
19use crate::value::PointerEquality;
20use crate::vm::generators::{self, GenCo};
21use crate::warnings::WarningKind;
22use crate::{
23 self as snix_eval,
24 builtins::hash::hash_nix_string,
25 errors::{CatchableErrorKind, ErrorKind},
26 value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
27};
28use crate::{arithmetic_op, try_cek};
29
30use self::versions::{VersionPart, VersionPartsIter};
31
32mod hash;
33mod to_xml;
34mod versions;
35
36#[cfg(test)]
37pub use to_xml::value_to_xml;
38
39#[cfg(feature = "impure")]
40mod impure;
41
42#[cfg(feature = "impure")]
43pub use impure::impure_builtins;
44
45pub const CURRENT_PLATFORM: &str = env!("SNIX_CURRENT_SYSTEM");
47
48pub async fn coerce_value_to_path(
56 co: &GenCo,
57 v: Value,
58) -> Result<Result<PathBuf, CatchableErrorKind>, ErrorKind> {
59 let value = generators::request_force(co, v).await;
60 if let Value::Path(p) = value {
61 return Ok(Ok(*p));
62 }
63
64 let vs = try_cek!(
65 generators::request_string_coerce(
66 co,
67 value,
68 CoercionKind {
69 strong: false,
70 import_paths: false,
71 },
72 )
73 .await
74 );
75
76 let path = vs.to_path()?.to_owned();
77 if path.is_absolute() {
78 Ok(Ok(path))
79 } else {
80 Err(ErrorKind::NotAnAbsolutePath(path))
81 }
82}
83
84#[derive(Debug, Default)]
85struct BuiltinState {
86 regex_cache: RefCell<FxHashMap<String, Regex>>,
87}
88
89impl BuiltinState {
90 fn get_regex(&self, pattern: &str) -> Result<Regex, regex::Error> {
91 let mut map = self.regex_cache.borrow_mut();
92 Ok(match map.entry(pattern.to_string()) {
93 Entry::Occupied(occupied) => occupied.get().clone(),
94 Entry::Vacant(vacant) => {
95 let re = Regex::new(pattern)?;
96 vacant.insert(re).clone()
97 }
98 })
99 }
100}
101
102#[builtins(state = "Rc<BuiltinState>")]
103mod pure_builtins {
104 use std::ffi::OsString;
105
106 use bstr::{B, BString, ByteSlice};
107 use itertools::Itertools;
108 use os_str_bytes::OsStringBytes;
109 use rustc_hash::{FxHashMap, FxHashSet};
110
111 use crate::{
112 AddContext, NixContext, NixContextElement, try_cek_to_value, value::PointerEquality,
113 };
114
115 use super::*;
116
117 macro_rules! try_value {
118 ($value:expr) => {{
119 let val = $value;
120 if val.is_catchable() {
121 return Ok(val);
122 }
123 val
124 }};
125 }
126
127 #[builtin("abort")]
128 async fn builtin_abort(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
129 Err(ErrorKind::Abort(message.to_contextful_str()?.to_string()))
135 }
136
137 #[builtin("add")]
138 async fn builtin_add(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
139 arithmetic_op!(&x, &y, +)
140 }
141
142 #[builtin("all")]
143 async fn builtin_all(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
144 for value in list.to_list()?.into_iter() {
145 let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
146 let pred_result = try_value!(generators::request_force(&co, pred_result).await);
147
148 if !pred_result.as_bool()? {
149 return Ok(Value::Bool(false));
150 }
151 }
152
153 Ok(Value::Bool(true))
154 }
155
156 #[builtin("any")]
157 async fn builtin_any(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
158 for value in list.to_list()?.into_iter() {
159 let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
160 let pred_result = try_value!(generators::request_force(&co, pred_result).await);
161
162 if pred_result.as_bool()? {
163 return Ok(Value::Bool(true));
164 }
165 }
166
167 Ok(Value::Bool(false))
168 }
169
170 #[builtin("attrNames")]
171 async fn builtin_attr_names(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
172 let xs = set.to_attrs()?;
173 let mut output = Vec::with_capacity(xs.len());
174
175 for (key, _val) in xs.iter_sorted() {
176 output.push(Value::from(key.clone()));
177 }
178
179 Ok(Value::List(NixList::construct(output.len(), output)))
180 }
181
182 #[builtin("attrValues")]
183 async fn builtin_attr_values(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
184 let xs = set.to_attrs()?;
185 let mut output = Vec::with_capacity(xs.len());
186
187 for (_key, val) in xs.iter_sorted() {
188 output.push(val.clone());
189 }
190
191 Ok(Value::List(NixList::construct(output.len(), output)))
192 }
193
194 #[builtin("baseNameOf")]
195 async fn builtin_base_name_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
196 let span = generators::request_span(&co).await;
197 let s = s
198 .coerce_to_string(
199 co,
200 CoercionKind {
201 strong: false,
202 import_paths: false,
203 },
204 span,
205 )
206 .await?
207 .to_contextful_str()?;
208
209 let mut bs = (**s).to_owned();
210 if let Some(last_slash) = bs.rfind_char('/') {
211 bs = bs[(last_slash + 1)..].into();
212 }
213 Ok(NixString::new_inherit_context_from(&s, bs).into())
214 }
215
216 #[builtin("bitAnd")]
217 async fn builtin_bit_and(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
218 Ok(Value::Integer(x.as_int()? & y.as_int()?))
219 }
220
221 #[builtin("bitOr")]
222 async fn builtin_bit_or(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
223 Ok(Value::Integer(x.as_int()? | y.as_int()?))
224 }
225
226 #[builtin("bitXor")]
227 async fn builtin_bit_xor(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
228 Ok(Value::Integer(x.as_int()? ^ y.as_int()?))
229 }
230
231 #[builtin("catAttrs")]
232 async fn builtin_cat_attrs(co: GenCo, key: Value, list: Value) -> Result<Value, ErrorKind> {
233 let key = key.to_str()?;
234 let list = list.to_list()?;
235 let mut output = vec![];
236
237 for item in list.into_iter() {
238 let set = generators::request_force(&co, item).await.to_attrs()?;
239
240 if let Some(value) = set.select(&key) {
241 output.push(value.clone());
242 }
243 }
244
245 Ok(Value::List(NixList::construct(output.len(), output)))
246 }
247
248 #[builtin("ceil")]
249 async fn builtin_ceil(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
250 Ok(Value::Integer(double.as_float()?.ceil() as i64))
251 }
252
253 #[builtin("compareVersions")]
254 async fn builtin_compare_versions(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
255 let s1 = x.to_str()?;
256 let s1 = VersionPartsIter::new_for_cmp((&s1).into());
257 let s2 = y.to_str()?;
258 let s2 = VersionPartsIter::new_for_cmp((&s2).into());
259
260 match s1.cmp(s2) {
261 std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
262 std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
263 std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
264 }
265 }
266
267 #[builtin("concatLists")]
268 async fn builtin_concat_lists(co: GenCo, lists: Value) -> Result<Value, ErrorKind> {
269 let mut out = Vec::new();
270
271 for value in lists.to_list()? {
272 let list = try_value!(generators::request_force(&co, value).await).to_list()?;
273 out.extend(list.into_iter());
274 }
275
276 Ok(Value::List(out.into()))
277 }
278
279 #[builtin("concatMap")]
280 async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
281 let list = list.to_list()?;
282 let mut res = Vec::new();
283 for val in list {
284 let out = generators::request_call_with(&co, f.clone(), [val]).await;
285 let out = try_value!(generators::request_force(&co, out).await);
286 res.extend(out.to_list()?);
287 }
288 Ok(Value::List(res.into()))
289 }
290
291 #[builtin("concatStringsSep")]
292 async fn builtin_concat_strings_sep(
293 co: GenCo,
294 separator: Value,
295 list: Value,
296 ) -> Result<Value, ErrorKind> {
297 let mut separator = separator.to_contextful_str()?;
298
299 let mut context = NixContext::new();
300 if let Some(sep_context) = separator.take_context() {
301 context.extend(sep_context.into_iter())
302 }
303 let list = list.to_list()?;
304 let mut res = BString::default();
305 for (i, val) in list.into_iter().enumerate() {
306 if i != 0 {
307 res.push_str(&separator);
308 }
309 let mut s = try_cek_to_value!(
310 generators::request_string_coerce(
311 &co,
312 val,
313 CoercionKind {
314 strong: false,
315 import_paths: true,
316 },
317 )
318 .await
319 );
320 res.push_str(&s);
321 if let Some(other_context) = s.take_context() {
322 context.extend(other_context.into_iter());
323 }
324 }
325 Ok(NixString::new_context_from(context, res).into())
327 }
328
329 #[builtin("deepSeq")]
330 async fn builtin_deep_seq(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
331 generators::request_deep_force(&co, x).await;
332 Ok(y)
333 }
334
335 #[builtin("div")]
336 async fn builtin_div(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
337 arithmetic_op!(&x, &y, /)
338 }
339
340 #[builtin("dirOf")]
341 async fn builtin_dir_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
342 let is_path = s.is_path();
343 let span = generators::request_span(&co).await;
344 let str = s
345 .coerce_to_string(
346 co,
347 CoercionKind {
348 strong: false,
349 import_paths: false,
350 },
351 span,
352 )
353 .await?
354 .to_contextful_str()?;
355 let result = str
356 .rfind_char('/')
357 .map(|last_slash| {
358 let x = &str[..last_slash];
359 if x.is_empty() { B("/") } else { x }
360 })
361 .unwrap_or(b".");
362 if is_path {
363 Ok(Value::Path(Box::new(PathBuf::from(
364 OsString::assert_from_raw_vec(result.to_owned()),
365 ))))
366 } else {
367 Ok(Value::from(NixString::new_inherit_context_from(
368 &str, result,
369 )))
370 }
371 }
372
373 #[builtin("elem")]
374 async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
375 for val in xs.to_list()? {
376 match try_cek_to_value!(
377 generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
378 ) {
379 true => return Ok(true.into()),
380 false => continue,
381 }
382 }
383 Ok(false.into())
384 }
385
386 #[builtin("elemAt")]
387 async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
388 let xs = xs.to_list()?;
389 let i = i.as_int()?;
390 if i < 0 {
391 Err(ErrorKind::IndexOutOfBounds { index: i })
392 } else {
393 match xs.get(i as usize) {
394 Some(x) => Ok(x.clone()),
395 None => Err(ErrorKind::IndexOutOfBounds { index: i }),
396 }
397 }
398 }
399
400 #[builtin("filter")]
401 async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
402 let list: NixList = list.to_list()?;
403 let mut out = Vec::new();
404
405 for value in list {
406 let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
407 let verdict = try_value!(generators::request_force(&co, result).await);
408 if verdict.as_bool()? {
409 out.push(value);
410 }
411 }
412
413 Ok(Value::List(out.into()))
414 }
415
416 #[builtin("floor")]
417 async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
418 Ok(Value::Integer(double.as_float()?.floor() as i64))
419 }
420
421 #[builtin("foldl'")]
422 async fn builtin_foldl(
423 co: GenCo,
424 op: Value,
425 #[lazy] nul: Value,
426 list: Value,
427 ) -> Result<Value, ErrorKind> {
428 let mut nul = nul;
429 let list = list.to_list()?;
430 for val in list {
431 nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
435 nul = generators::request_force(&co, nul).await;
436 if let c @ Value::Catchable(_) = nul {
437 return Ok(c);
438 }
439 }
440
441 Ok(nul)
442 }
443
444 #[builtin("functionArgs")]
445 async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
446 let lambda = &f.as_closure()?.lambda();
447 let formals = if let Some(formals) = &lambda.formals {
448 formals
449 } else {
450 return Ok(Value::attrs(NixAttrs::empty()));
451 };
452 Ok(Value::attrs(NixAttrs::from_iter(
453 formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
454 )))
455 }
456
457 #[builtin("fromJSON")]
458 async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
459 let json_str = json.to_str()?;
460 serde_json::from_slice(&json_str).map_err(|err| err.into())
461 }
462
463 #[builtin("toJSON")]
464 async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
465 match val.into_contextful_json(&co).await {
466 Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
467 Err(err) => Err(err),
468 Ok((json, context)) => {
469 let json_str = serde_json::to_string(&json)
470 .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
471 Ok(Value::String(NixString::new_context_from(
472 context, json_str,
473 )))
474 }
475 }
476 }
477
478 #[builtin("fromTOML")]
479 async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
480 let toml_str = toml.to_str()?;
481
482 toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
483 }
484
485 #[builtin("genericClosure")]
486 async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
487 let attrs = input.to_attrs()?;
488
489 let mut work_set: VecDeque<Value> =
492 generators::request_force(&co, attrs.select_required("startSet")?.clone())
493 .await
494 .to_list()?
495 .into_iter()
496 .collect();
497
498 let operator = attrs.select_required("operator")?;
499
500 let mut res = Vec::new();
501 let mut done_keys: Vec<Value> = vec![];
502
503 while let Some(val) = work_set.pop_front() {
504 let val = generators::request_force(&co, val).await;
505 let attrs = val.to_attrs()?;
506 let key = attrs.select_required("key")?;
507
508 let value_missing =
509 try_cek_to_value!(bgc_insert_key(&co, key.clone(), &mut done_keys).await?);
510
511 if !value_missing {
512 continue;
513 }
514
515 res.push(val.clone());
516
517 let op_result = generators::request_force(
518 &co,
519 generators::request_call_with(&co, operator.clone(), [val]).await,
520 )
521 .await;
522
523 work_set.extend(op_result.to_list()?.into_iter());
524 }
525
526 Ok(Value::List(NixList::from(res)))
527 }
528
529 #[builtin("genList")]
530 async fn builtin_gen_list(
531 co: GenCo,
532 #[lazy] generator: Value,
534 length: Value,
535 ) -> Result<Value, ErrorKind> {
536 let len = length.as_int()?;
537 let mut out = Vec::with_capacity(
538 len.try_into()
539 .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
540 );
541
542 let span = generators::request_span(&co).await;
544
545 for i in 0..len {
546 let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
547 out.push(val);
548 }
549
550 Ok(Value::List(out.into()))
551 }
552
553 #[builtin("getAttr")]
554 async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
555 let k = key.to_str()?;
556 let xs = set.to_attrs()?;
557
558 match xs.select(&k) {
559 Some(x) => Ok(x.clone()),
560 None => Err(ErrorKind::AttributeNotFound {
561 name: k.to_string(),
562 }),
563 }
564 }
565
566 #[builtin("groupBy")]
567 async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
568 let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
569 for val in list.to_list()? {
570 let key = try_value!(
571 generators::request_force(
572 &co,
573 generators::request_call_with(&co, f.clone(), [val.clone()]).await,
574 )
575 .await
576 )
577 .to_str()?;
578
579 res.entry(key).or_default().push(val);
580 }
581 Ok(Value::attrs(NixAttrs::from_iter(
582 res.into_iter()
583 .map(|(k, v)| (k, Value::List(NixList::from(v)))),
584 )))
585 }
586
587 #[builtin("hasAttr")]
588 async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
589 let k = key.to_str()?;
590 let xs = set.to_attrs()?;
591
592 Ok(Value::Bool(xs.contains(&k)))
593 }
594
595 #[builtin("hasContext")]
596 #[allow(non_snake_case)]
597 async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
598 if e.is_catchable() {
599 return Ok(e);
600 }
601
602 let v = e.to_contextful_str()?;
603 Ok(Value::Bool(v.has_context()))
604 }
605
606 #[builtin("getContext")]
607 #[allow(non_snake_case)]
608 async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
609 if e.is_catchable() {
610 return Ok(e);
611 }
612
613 let span = generators::request_span(&co).await;
615 let v = e
616 .coerce_to_string(
617 co,
618 CoercionKind {
619 strong: true,
620 import_paths: true,
621 },
622 span,
623 )
624 .await?;
625 let s = v.to_contextful_str()?;
626
627 let groups = s
628 .iter_context()
629 .flat_map(|context| context.iter())
630 .into_grouping_map_by(|ctx_element| match ctx_element {
636 NixContextElement::Plain(spath) => spath,
637 NixContextElement::Single { derivation, .. } => derivation,
638 NixContextElement::Derivation(drv_path) => drv_path,
639 })
640 .collect::<Vec<_>>();
641
642 let elements = groups
643 .into_iter()
644 .map(|(key, group)| {
645 let mut outputs: Vec<NixString> = Vec::new();
646 let mut is_path = false;
647 let mut all_outputs = false;
648
649 for ctx_element in group {
650 match ctx_element {
651 NixContextElement::Plain(spath) => {
652 debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
653 is_path = true;
654 }
655
656 NixContextElement::Single { name, derivation } => {
657 debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
658 outputs.push(name.clone().into());
659 }
660
661 NixContextElement::Derivation(drv_path) => {
662 debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
663 all_outputs = true;
664 }
665 }
666 }
667
668 let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
671
672 if is_path {
673 vec_attrs.push(("path", true.into()));
674 }
675
676 if all_outputs {
677 vec_attrs.push(("allOutputs", true.into()));
678 }
679
680 if !outputs.is_empty() {
681 outputs.sort();
682 vec_attrs.push(("outputs", Value::List(outputs
683 .into_iter()
684 .map(|s| s.into())
685 .collect::<Vec<Value>>()
686 .into()
687 )));
688 }
689
690 (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
691 });
692
693 Ok(Value::attrs(NixAttrs::from_iter(elements)))
694 }
695
696 #[builtin("appendContext")]
697 #[allow(non_snake_case)]
698 async fn builtin_appendContext(
699 co: GenCo,
700 origin: Value,
701 added_context: Value,
702 ) -> Result<Value, ErrorKind> {
703 let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
722 let span = generators::request_span(&co).await;
723 let origin = origin
724 .coerce_to_string(
725 co,
726 CoercionKind {
727 strong: true,
728 import_paths: true,
729 },
730 span,
731 )
732 .await?;
733 let mut origin = origin.to_contextful_str()?;
734
735 let added_context = added_context.to_attrs()?;
736 for (context_key, context_element) in added_context.into_iter() {
737 let context_element = context_element.to_attrs()?;
741 if let Some(path) = context_element.select("path") {
742 if path.as_bool()? {
743 ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
744 }
745 }
746 if let Some(all_outputs) = context_element.select("allOutputs") {
747 if all_outputs.as_bool()? {
748 ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
751 }
752 }
753 if let Some(some_outputs) = context_element.select("outputs") {
754 let some_outputs = some_outputs.to_list()?;
755 for output in some_outputs.into_iter() {
758 let output = output.to_str()?;
759 ctx_elements.insert(NixContextElement::Single {
760 derivation: context_key.to_string(),
761 name: output.to_string(),
762 });
763 }
764 }
765 }
766
767 if let Some(origin_ctx) = origin.context_mut() {
768 origin_ctx.extend(ctx_elements)
769 }
771
772 Ok(origin.into())
773 }
774
775 #[builtin("hashString")]
776 async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
777 hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
778 }
779
780 #[builtin("head")]
781 async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
782 if list.is_catchable() {
783 return Ok(list);
784 }
785
786 match list.to_list()?.get(0) {
787 Some(x) => Ok(x.clone()),
788 None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
789 }
790 }
791
792 #[builtin("intersectAttrs")]
793 async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
794 if x.is_catchable() {
795 return Ok(x);
796 }
797 if y.is_catchable() {
798 return Ok(y);
799 }
800 let left_set = x.to_attrs()?;
801 if left_set.is_empty() {
802 return Ok(Value::attrs(NixAttrs::empty()));
803 }
804
805 let right_set = y.to_attrs()?;
806
807 if right_set.is_empty() {
808 return Ok(Value::attrs(NixAttrs::empty()));
809 }
810
811 Ok(Value::attrs(left_set.intersect(&right_set)))
812 }
813
814 #[builtin("isAttrs")]
815 async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
816 if value.is_catchable() {
818 return Ok(value);
819 }
820
821 Ok(Value::Bool(matches!(value, Value::Attrs(_))))
822 }
823
824 #[builtin("isBool")]
825 async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
826 if value.is_catchable() {
827 return Ok(value);
828 }
829
830 Ok(Value::Bool(matches!(value, Value::Bool(_))))
831 }
832
833 #[builtin("isFloat")]
834 async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
835 if value.is_catchable() {
836 return Ok(value);
837 }
838
839 Ok(Value::Bool(matches!(value, Value::Float(_))))
840 }
841
842 #[builtin("isFunction")]
843 async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
844 if value.is_catchable() {
845 return Ok(value);
846 }
847
848 Ok(Value::Bool(matches!(
849 value,
850 Value::Closure(_) | Value::Builtin(_)
851 )))
852 }
853
854 #[builtin("isInt")]
855 async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
856 if value.is_catchable() {
857 return Ok(value);
858 }
859
860 Ok(Value::Bool(matches!(value, Value::Integer(_))))
861 }
862
863 #[builtin("isList")]
864 async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
865 if value.is_catchable() {
866 return Ok(value);
867 }
868
869 Ok(Value::Bool(matches!(value, Value::List(_))))
870 }
871
872 #[builtin("isNull")]
873 async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
874 if value.is_catchable() {
875 return Ok(value);
876 }
877
878 Ok(Value::Bool(matches!(value, Value::Null)))
879 }
880
881 #[builtin("isPath")]
882 async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
883 if value.is_catchable() {
884 return Ok(value);
885 }
886
887 Ok(Value::Bool(matches!(value, Value::Path(_))))
888 }
889
890 #[builtin("isString")]
891 async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
892 if value.is_catchable() {
893 return Ok(value);
894 }
895
896 Ok(Value::Bool(matches!(value, Value::String(_))))
897 }
898
899 #[builtin("length")]
900 async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
901 if list.is_catchable() {
902 return Ok(list);
903 }
904 Ok(Value::Integer(list.to_list()?.len() as i64))
905 }
906
907 #[builtin("lessThan")]
908 async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
909 let span = generators::request_span(&co).await;
910 match try_cek_to_value!(x.nix_cmp_ordering(y, co, span).await?) {
911 Ordering::Less => Ok(Value::Bool(true)),
912 _ => Ok(Value::Bool(false)),
913 }
914 }
915
916 #[builtin("listToAttrs")]
917 async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
918 let list = list.to_list()?;
919 let mut map = FxHashMap::default();
920 for val in list {
921 let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
922 let name = try_value!(
923 generators::request_force(&co, attrs.select_required("name")?.clone()).await
924 )
925 .to_str()?;
926 let value = attrs.select_required("value")?.clone();
927 map.entry(name).or_insert(value);
929 }
930 Ok(Value::attrs(NixAttrs::from(map)))
931 }
932
933 #[builtin("map")]
934 async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
935 let list = list_val.to_list()?;
936 let mut out = Vec::with_capacity(list.len());
937
938 let span = generators::request_span(&co).await;
940
941 for val in list {
942 let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
943 out.push(result)
944 }
945
946 Ok(Value::List(out.into()))
947 }
948
949 #[builtin("mapAttrs")]
950 async fn builtin_map_attrs(
951 co: GenCo,
952 #[lazy] f: Value,
953 attrs: Value,
954 ) -> Result<Value, ErrorKind> {
955 let attrs = attrs.to_attrs()?;
956 let mut out = FxHashMap::default();
957
958 let span = generators::request_span(&co).await;
960
961 for (key, value) in attrs.into_iter() {
962 let result = Value::Thunk(Thunk::new_suspended_call(
963 f.clone(),
964 key.clone().into(),
965 span,
966 ));
967 let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
968
969 out.insert(key, result);
970 }
971
972 Ok(Value::attrs(out.into()))
973 }
974
975 #[builtin("match")]
976 async fn builtin_match(
977 state: Rc<BuiltinState>,
978 co: GenCo,
979 regex: Value,
980 str: Value,
981 ) -> Result<Value, ErrorKind> {
982 let s = str;
983 if s.is_catchable() {
984 return Ok(s);
985 }
986 let s = s.to_contextful_str()?;
987 let re = regex;
988 if re.is_catchable() {
989 return Ok(re);
990 }
991 let re = re.to_str()?;
992 let re = re.to_str()?;
993 let re = state
994 .get_regex(&format!("^{re}$"))
995 .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
996
997 match re.captures(s.to_str()?) {
998 Some(caps) => Ok(Value::List(
999 caps.iter()
1000 .skip(1)
1001 .map(|grp| {
1002 grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
1010 })
1011 .collect::<Vec<Value>>()
1012 .into(),
1013 )),
1014 None => Ok(Value::Null),
1015 }
1016 }
1017
1018 #[builtin("mul")]
1019 async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1020 arithmetic_op!(&x, &y, *)
1021 }
1022
1023 #[builtin("parseDrvName")]
1024 async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1025 if s.is_catchable() {
1026 return Ok(s);
1027 }
1028
1029 let s = s.to_str()?;
1032 let slice: &[u8] = s.as_ref();
1033 let (name, dash_and_version) = slice.split_at(
1034 slice
1035 .windows(2)
1036 .enumerate()
1037 .find_map(|x| match x {
1038 (idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
1039 _ => None,
1040 })
1041 .unwrap_or(slice.len()),
1042 );
1043 let version = dash_and_version
1044 .split_first()
1045 .map(|x| core::str::from_utf8(x.1))
1046 .unwrap_or(Ok(""))?;
1047 Ok(Value::attrs(NixAttrs::from_iter(
1048 [("name", core::str::from_utf8(name)?), ("version", version)].into_iter(),
1049 )))
1050 }
1051
1052 #[builtin("partition")]
1053 async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
1054 let mut right: Vec<Value> = Default::default();
1055 let mut wrong: Vec<Value> = Default::default();
1056
1057 let list: NixList = list.to_list()?;
1058 for elem in list {
1059 let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
1060
1061 if try_value!(generators::request_force(&co, result).await).as_bool()? {
1062 right.push(elem);
1063 } else {
1064 wrong.push(elem);
1065 };
1066 }
1067
1068 let res = [
1069 ("right", Value::List(NixList::from(right))),
1070 ("wrong", Value::List(NixList::from(wrong))),
1071 ];
1072
1073 Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1074 }
1075
1076 #[builtin("removeAttrs")]
1077 async fn builtin_remove_attrs(
1078 co: GenCo,
1079 attrs: Value,
1080 keys: Value,
1081 ) -> Result<Value, ErrorKind> {
1082 let attrs = attrs.to_attrs()?;
1083 let keys = keys
1084 .to_list()?
1085 .into_iter()
1086 .map(|v| v.to_str())
1087 .collect::<Result<FxHashSet<_>, _>>()?;
1088 let res = attrs.iter().filter_map(|(k, v)| {
1089 if !keys.contains(k) {
1090 Some((k.clone(), v.clone()))
1091 } else {
1092 None
1093 }
1094 });
1095 Ok(Value::attrs(NixAttrs::from_iter(res)))
1096 }
1097
1098 #[builtin("replaceStrings")]
1099 async fn builtin_replace_strings(
1100 co: GenCo,
1101 from: Value,
1102 to: Value,
1103 s: Value,
1104 ) -> Result<Value, ErrorKind> {
1105 let from = from.to_list()?;
1106 for val in &from {
1107 try_value!(generators::request_force(&co, val.clone()).await);
1108 }
1109
1110 let to = to.to_list()?;
1111 for val in &to {
1112 try_value!(generators::request_force(&co, val.clone()).await);
1113 }
1114
1115 let mut string = s.to_contextful_str()?;
1116
1117 let mut res = BString::default();
1118
1119 let mut i: usize = 0;
1120 let mut empty_string_replace = false;
1121 let mut context = NixContext::new();
1122
1123 if let Some(string_context) = string.take_context() {
1124 context.extend(string_context.into_iter());
1125 }
1126
1127 'outer: while i < string.len() {
1134 for elem in std::iter::zip(from.iter(), to.iter()) {
1136 let from = elem.0.to_contextful_str()?;
1137 let mut to = elem.1.to_contextful_str()?;
1138
1139 if i + from.len() > string.len() {
1140 continue;
1141 }
1142
1143 if empty_string_replace && from.is_empty() {
1147 continue;
1148 }
1149
1150 if string[i..i + from.len()] == *from {
1152 res.push_str(&to);
1153 i += from.len();
1154 if let Some(to_ctx) = to.take_context() {
1155 context.extend(to_ctx.into_iter());
1156 }
1157
1158 empty_string_replace = from.is_empty();
1160
1161 continue 'outer;
1162 }
1163 }
1164
1165 res.push_str(&string[i..i + 1]);
1167 i += 1;
1168
1169 empty_string_replace = false;
1172 }
1173
1174 for elem in std::iter::zip(from.iter(), to.iter()) {
1177 let from = elem.0.to_contextful_str()?;
1178 let mut to = elem.1.to_contextful_str()?;
1183
1184 if from.is_empty() {
1185 res.push_str(&to);
1186 if let Some(to_ctx) = to.take_context() {
1187 context.extend(to_ctx.into_iter());
1188 }
1189 break;
1190 }
1191 }
1192
1193 Ok(Value::from(NixString::new_context_from(context, res)))
1194 }
1195
1196 #[builtin("seq")]
1197 async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
1198 Ok(y)
1201 }
1202
1203 #[builtin("split")]
1204 async fn builtin_split(
1205 state: Rc<BuiltinState>,
1206 co: GenCo,
1207 regex: Value,
1208 str: Value,
1209 ) -> Result<Value, ErrorKind> {
1210 if str.is_catchable() {
1211 return Ok(str);
1212 }
1213
1214 if regex.is_catchable() {
1215 return Ok(regex);
1216 }
1217
1218 let s = str.to_contextful_str()?;
1219 let text = s.to_str()?;
1220 let re = regex.to_str()?;
1221 let re = re.to_str()?;
1222 let re = state
1223 .get_regex(re)
1224 .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
1225 let mut capture_locations = re.capture_locations();
1226 let num_captures = capture_locations.len();
1227 let mut ret = Vec::new();
1228 let mut pos = 0;
1229
1230 while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1231 ret.push(Value::from(NixString::new_inherit_context_from(
1233 &s,
1234 &text[pos..thematch.start()],
1235 )));
1236
1237 let v: Vec<Value> = (1..num_captures)
1242 .map(|i| capture_locations.get(i))
1243 .map(|o| {
1244 o.map(|(start, end)| {
1245 Value::from(&text[start..end])
1248 })
1249 .unwrap_or(Value::Null)
1250 })
1251 .collect();
1252 ret.push(Value::List(NixList::from(v)));
1253 if pos == text.len() {
1254 break;
1255 }
1256 pos = thematch.end();
1257 }
1258
1259 ret.push(Value::from(&text[pos..]));
1263
1264 Ok(Value::List(NixList::from(ret)))
1265 }
1266
1267 #[builtin("sort")]
1268 async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1269 let list = list.to_list()?;
1270 let mut len = list.len();
1271 let mut data = list.into_inner();
1272
1273 loop {
1284 let mut new_len = 0;
1285 for i in 1..len {
1286 if try_value!(
1287 generators::request_force(
1288 &co,
1289 generators::request_call_with(
1290 &co,
1291 comparator.clone(),
1292 [data[i].clone(), data[i - 1].clone()],
1293 )
1294 .await,
1295 )
1296 .await
1297 )
1298 .as_bool()
1299 .context("evaluating comparator in `builtins.sort`")?
1300 {
1301 data.swap(i, i - 1);
1302 new_len = i;
1303 }
1304 }
1305
1306 if new_len == 0 {
1307 break;
1308 }
1309
1310 len = new_len;
1311 }
1312
1313 Ok(Value::List(data.into()))
1314 }
1315
1316 #[builtin("splitVersion")]
1317 async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1318 if s.is_catchable() {
1319 return Ok(s);
1320 }
1321 let s = s.to_str()?;
1322 let s = VersionPartsIter::new((&s).into());
1323
1324 let parts = s
1325 .map(|s| {
1326 Value::from(match s {
1327 VersionPart::Number(n) => n,
1328 VersionPart::Word(w) => w,
1329 })
1330 })
1331 .collect::<Vec<Value>>();
1332 Ok(Value::List(NixList::construct(parts.len(), parts)))
1333 }
1334
1335 #[builtin("stringLength")]
1336 async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1337 let span = generators::request_span(&co).await;
1339 let s = s
1340 .coerce_to_string(
1341 co,
1342 CoercionKind {
1343 strong: false,
1344 import_paths: true,
1345 },
1346 span,
1347 )
1348 .await?;
1349
1350 if s.is_catchable() {
1351 return Ok(s);
1352 }
1353
1354 Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1355 }
1356
1357 #[builtin("sub")]
1358 async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1359 arithmetic_op!(&x, &y, -)
1360 }
1361
1362 #[builtin("substring")]
1363 async fn builtin_substring(
1364 co: GenCo,
1365 start: Value,
1366 len: Value,
1367 s: Value,
1368 ) -> Result<Value, ErrorKind> {
1369 let beg = start.as_int()?;
1370 let len = len.as_int()?;
1371 let span = generators::request_span(&co).await;
1372 let x = s
1373 .coerce_to_string(
1374 co,
1375 CoercionKind {
1376 strong: false,
1377 import_paths: true,
1378 },
1379 span,
1380 )
1381 .await?;
1382 if x.is_catchable() {
1383 return Ok(x);
1384 }
1385 let x = x.to_contextful_str()?;
1386
1387 if beg < 0 {
1388 return Err(ErrorKind::IndexOutOfBounds { index: beg });
1389 }
1390 let beg = beg as usize;
1391
1392 if beg >= x.len() {
1396 return Ok(Value::from(NixString::new_inherit_context_from(
1397 &x,
1398 BString::default(),
1399 )));
1400 }
1401
1402 let end = if len < 0 {
1403 x.len()
1404 } else {
1405 cmp::min(beg + (len as usize), x.len())
1406 };
1407
1408 Ok(Value::from(NixString::new_inherit_context_from(
1409 &x,
1410 &x[beg..end],
1411 )))
1412 }
1413
1414 #[builtin("tail")]
1415 async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1416 if list.is_catchable() {
1417 return Ok(list);
1418 }
1419
1420 let xs = list.to_list()?;
1421
1422 if xs.is_empty() {
1423 Err(ErrorKind::TailEmptyList)
1424 } else {
1425 let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1426 Ok(Value::List(NixList::construct(output.len(), output)))
1427 }
1428 }
1429
1430 #[builtin("throw")]
1431 async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1432 if message.is_catchable() {
1434 return Ok(message);
1435 }
1436
1437 Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1440 }
1441
1442 #[builtin("toString")]
1443 async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1444 let span = generators::request_span(&co).await;
1449 x.coerce_to_string(
1450 co,
1451 CoercionKind {
1452 strong: true,
1453 import_paths: false,
1454 },
1455 span,
1456 )
1457 .await
1458 }
1459
1460 #[builtin("toXML")]
1461 async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1462 let value = generators::request_deep_force(&co, value).await;
1463 if value.is_catchable() {
1464 return Ok(value);
1465 }
1466
1467 let mut buf: Vec<u8> = vec![];
1468 let context = to_xml::value_to_xml(&mut buf, &value)?;
1469
1470 Ok(NixString::new_context_from(context, buf).into())
1471 }
1472
1473 #[builtin("trace")]
1474 async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1475 eprintln!("trace: {} :: {}", message, message.type_of());
1478 Ok(value)
1479 }
1480
1481 #[builtin("toPath")]
1482 async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1483 if s.is_catchable() {
1484 return Ok(s);
1485 }
1486
1487 let path = try_cek_to_value!(coerce_value_to_path(&co, s).await?);
1488 let path: Value = crate::value::canon_path(path).into();
1489 let span = generators::request_span(&co).await;
1490 path.coerce_to_string(
1491 co,
1492 CoercionKind {
1493 strong: false,
1494 import_paths: false,
1495 },
1496 span,
1497 )
1498 .await
1499 }
1500
1501 #[builtin("tryEval")]
1502 async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1503 let res = match generators::request_try_force(&co, e).await {
1504 Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1505 value => [("value", value), ("success", true.into())],
1506 };
1507
1508 Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1509 }
1510
1511 #[builtin("typeOf")]
1512 async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1513 if x.is_catchable() {
1514 return Ok(x);
1515 }
1516
1517 Ok(Value::from(x.type_of()))
1518 }
1519}
1520
1521async fn bgc_insert_key(
1524 co: &GenCo,
1525 key: Value,
1526 done: &mut Vec<Value>,
1527) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1528 for existing in done.iter() {
1529 if try_cek!(
1530 generators::check_equality(
1531 co,
1532 existing.clone(),
1533 key.clone(),
1534 PointerEquality::ForbidAll,
1536 )
1537 .await?
1538 ) {
1539 return Ok(Ok(false));
1540 }
1541 }
1542
1543 done.push(key);
1544 Ok(Ok(true))
1545}
1546
1547pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1550 let mut result = pure_builtins::builtins(Rc::new(BuiltinState::default()));
1551
1552 result.push(("nixVersion", Value::from("2.3.17-compat-snix-0.1")));
1554 result.push(("langVersion", Value::Integer(6)));
1555 result.push(("null", Value::Null));
1556 result.push(("true", Value::Bool(true)));
1557 result.push(("false", Value::Bool(false)));
1558
1559 result.push((
1560 "currentSystem",
1561 crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1562 ));
1563
1564 result.push((
1565 "__curPos",
1566 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1567 Ok(Value::attrs(NixAttrs::from_iter([
1569 ("line", 42.into()),
1570 ("column", 42.into()),
1571 ("file", Value::String("/deep/thought".into())),
1572 ])))
1573 }))),
1574 ));
1575
1576 result
1577}
1578
1579#[builtins]
1580mod placeholder_builtins {
1581 use crate::NixContext;
1582
1583 use super::*;
1584
1585 #[builtin("unsafeDiscardStringContext")]
1586 async fn builtin_unsafe_discard_string_context(
1587 co: GenCo,
1588 s: Value,
1589 ) -> Result<Value, ErrorKind> {
1590 let span = generators::request_span(&co).await;
1591 let mut v = s
1592 .coerce_to_string(
1593 co,
1594 CoercionKind {
1598 strong: false,
1599 import_paths: true,
1600 },
1601 span,
1602 )
1603 .await?
1604 .to_contextful_str()?;
1605 v.clear_context();
1606 Ok(Value::from(v))
1607 }
1608
1609 #[builtin("unsafeDiscardOutputDependency")]
1610 async fn builtin_unsafe_discard_output_dependency(
1611 co: GenCo,
1612 s: Value,
1613 ) -> Result<Value, ErrorKind> {
1614 let span = generators::request_span(&co).await;
1615 let mut v = s
1616 .coerce_to_string(
1617 co,
1618 CoercionKind {
1622 strong: false,
1623 import_paths: true,
1624 },
1625 span,
1626 )
1627 .await?
1628 .to_contextful_str()?;
1629
1630 if let Some(c) = v.take_context() {
1632 let mut context = NixContext::new();
1633 context.extend(c.into_iter().map(|elem| match elem {
1634 crate::NixContextElement::Derivation(drv_path) => {
1635 crate::NixContextElement::Plain(drv_path.to_string())
1636 }
1637 elem => elem.clone(),
1638 }));
1639
1640 return Ok(Value::String(NixString::new_context_from(context, v)));
1641 }
1642 Ok(Value::from(v))
1643 }
1644
1645 #[builtin("addErrorContext")]
1646 async fn builtin_add_error_context(
1647 co: GenCo,
1648 #[lazy] _context: Value,
1649 #[lazy] val: Value,
1650 ) -> Result<Value, ErrorKind> {
1651 generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1652 .await;
1653 Ok(val)
1654 }
1655
1656 #[builtin("unsafeGetAttrPos")]
1657 async fn builtin_unsafe_get_attr_pos(
1658 co: GenCo,
1659 _name: Value,
1660 _attrset: Value,
1661 ) -> Result<Value, ErrorKind> {
1662 generators::emit_warning_kind(
1664 &co,
1665 WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1666 )
1667 .await;
1668 let res = [
1669 ("line", 42.into()),
1670 ("column", 42.into()),
1671 ("file", Value::String("/deep/thought".into())),
1672 ];
1673 Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1674 }
1675}
1676
1677pub fn placeholders() -> Vec<(&'static str, Value)> {
1678 placeholder_builtins::builtins()
1679}