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