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::{BString, ByteSlice, B};
105 use itertools::Itertools;
106 use os_str_bytes::OsStringBytes;
107 use rustc_hash::{FxHashMap, FxHashSet};
108
109 use crate::{value::PointerEquality, AddContext, NixContext, NixContextElement};
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() {
359 B("/")
360 } else {
361 x
362 }
363 })
364 .unwrap_or(b".");
365 if is_path {
366 Ok(Value::Path(Box::new(PathBuf::from(
367 OsString::assert_from_raw_vec(result.to_owned()),
368 ))))
369 } else {
370 Ok(Value::from(NixString::new_inherit_context_from(
371 &str, result,
372 )))
373 }
374 }
375
376 #[builtin("elem")]
377 async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
378 for val in xs.to_list()? {
379 match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
380 {
381 Ok(true) => return Ok(true.into()),
382 Ok(false) => continue,
383 Err(cek) => return Ok(Value::from(cek)),
384 }
385 }
386 Ok(false.into())
387 }
388
389 #[builtin("elemAt")]
390 async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
391 let xs = xs.to_list()?;
392 let i = i.as_int()?;
393 if i < 0 {
394 Err(ErrorKind::IndexOutOfBounds { index: i })
395 } else {
396 match xs.get(i as usize) {
397 Some(x) => Ok(x.clone()),
398 None => Err(ErrorKind::IndexOutOfBounds { index: i }),
399 }
400 }
401 }
402
403 #[builtin("filter")]
404 async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
405 let list: NixList = list.to_list()?;
406 let mut out = Vec::new();
407
408 for value in list {
409 let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
410 let verdict = try_value!(generators::request_force(&co, result).await);
411 if verdict.as_bool()? {
412 out.push(value);
413 }
414 }
415
416 Ok(Value::List(out.into()))
417 }
418
419 #[builtin("floor")]
420 async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
421 Ok(Value::Integer(double.as_float()?.floor() as i64))
422 }
423
424 #[builtin("foldl'")]
425 async fn builtin_foldl(
426 co: GenCo,
427 op: Value,
428 #[lazy] nul: Value,
429 list: Value,
430 ) -> Result<Value, ErrorKind> {
431 let mut nul = nul;
432 let list = list.to_list()?;
433 for val in list {
434 nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
438 nul = generators::request_force(&co, nul).await;
439 if let c @ Value::Catchable(_) = nul {
440 return Ok(c);
441 }
442 }
443
444 Ok(nul)
445 }
446
447 #[builtin("functionArgs")]
448 async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
449 let lambda = &f.as_closure()?.lambda();
450 let formals = if let Some(formals) = &lambda.formals {
451 formals
452 } else {
453 return Ok(Value::attrs(NixAttrs::empty()));
454 };
455 Ok(Value::attrs(NixAttrs::from_iter(
456 formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
457 )))
458 }
459
460 #[builtin("fromJSON")]
461 async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
462 let json_str = json.to_str()?;
463 serde_json::from_slice(&json_str).map_err(|err| err.into())
464 }
465
466 #[builtin("toJSON")]
467 async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
468 match val.into_contextful_json(&co).await {
469 Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
470 Err(err) => Err(err),
471 Ok((json, context)) => {
472 let json_str = serde_json::to_string(&json)
473 .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
474 Ok(Value::String(NixString::new_context_from(
475 context, json_str,
476 )))
477 }
478 }
479 }
480
481 #[builtin("fromTOML")]
482 async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
483 let toml_str = toml.to_str()?;
484
485 toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
486 }
487
488 #[builtin("genericClosure")]
489 async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
490 let attrs = input.to_attrs()?;
491
492 let mut work_set: VecDeque<Value> =
495 generators::request_force(&co, attrs.select_required("startSet")?.clone())
496 .await
497 .to_list()?
498 .into_iter()
499 .collect();
500
501 let operator = attrs.select_required("operator")?;
502
503 let mut res = Vec::new();
504 let mut done_keys: Vec<Value> = vec![];
505
506 while let Some(val) = work_set.pop_front() {
507 let val = generators::request_force(&co, val).await;
508 let attrs = val.to_attrs()?;
509 let key = attrs.select_required("key")?;
510
511 let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?;
512
513 if let Err(cek) = value_missing {
514 return Ok(Value::Catchable(Box::new(cek)));
515 }
516
517 if let Ok(false) = value_missing {
518 continue;
519 }
520
521 res.push(val.clone());
522
523 let op_result = generators::request_force(
524 &co,
525 generators::request_call_with(&co, operator.clone(), [val]).await,
526 )
527 .await;
528
529 work_set.extend(op_result.to_list()?.into_iter());
530 }
531
532 Ok(Value::List(NixList::from(res)))
533 }
534
535 #[builtin("genList")]
536 async fn builtin_gen_list(
537 co: GenCo,
538 #[lazy] generator: Value,
540 length: Value,
541 ) -> Result<Value, ErrorKind> {
542 let len = length.as_int()?;
543 let mut out = Vec::with_capacity(
544 len.try_into()
545 .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
546 );
547
548 let span = generators::request_span(&co).await;
550
551 for i in 0..len {
552 let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
553 out.push(val);
554 }
555
556 Ok(Value::List(out.into()))
557 }
558
559 #[builtin("getAttr")]
560 async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
561 let k = key.to_str()?;
562 let xs = set.to_attrs()?;
563
564 match xs.select(&k) {
565 Some(x) => Ok(x.clone()),
566 None => Err(ErrorKind::AttributeNotFound {
567 name: k.to_string(),
568 }),
569 }
570 }
571
572 #[builtin("groupBy")]
573 async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
574 let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
575 for val in list.to_list()? {
576 let key = try_value!(
577 generators::request_force(
578 &co,
579 generators::request_call_with(&co, f.clone(), [val.clone()]).await,
580 )
581 .await
582 )
583 .to_str()?;
584
585 res.entry(key).or_default().push(val);
586 }
587 Ok(Value::attrs(NixAttrs::from_iter(
588 res.into_iter()
589 .map(|(k, v)| (k, Value::List(NixList::from(v)))),
590 )))
591 }
592
593 #[builtin("hasAttr")]
594 async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
595 let k = key.to_str()?;
596 let xs = set.to_attrs()?;
597
598 Ok(Value::Bool(xs.contains(&k)))
599 }
600
601 #[builtin("hasContext")]
602 #[allow(non_snake_case)]
603 async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
604 if e.is_catchable() {
605 return Ok(e);
606 }
607
608 let v = e.to_contextful_str()?;
609 Ok(Value::Bool(v.has_context()))
610 }
611
612 #[builtin("getContext")]
613 #[allow(non_snake_case)]
614 async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
615 if e.is_catchable() {
616 return Ok(e);
617 }
618
619 let span = generators::request_span(&co).await;
621 let v = e
622 .coerce_to_string(
623 co,
624 CoercionKind {
625 strong: true,
626 import_paths: true,
627 },
628 span,
629 )
630 .await?;
631 let s = v.to_contextful_str()?;
632
633 let groups = s
634 .iter_context()
635 .flat_map(|context| context.iter())
636 .into_grouping_map_by(|ctx_element| match ctx_element {
642 NixContextElement::Plain(spath) => spath,
643 NixContextElement::Single { derivation, .. } => derivation,
644 NixContextElement::Derivation(drv_path) => drv_path,
645 })
646 .collect::<Vec<_>>();
647
648 let elements = groups
649 .into_iter()
650 .map(|(key, group)| {
651 let mut outputs: Vec<NixString> = Vec::new();
652 let mut is_path = false;
653 let mut all_outputs = false;
654
655 for ctx_element in group {
656 match ctx_element {
657 NixContextElement::Plain(spath) => {
658 debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
659 is_path = true;
660 }
661
662 NixContextElement::Single { name, derivation } => {
663 debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
664 outputs.push(name.clone().into());
665 }
666
667 NixContextElement::Derivation(drv_path) => {
668 debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
669 all_outputs = true;
670 }
671 }
672 }
673
674 let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
677
678 if is_path {
679 vec_attrs.push(("path", true.into()));
680 }
681
682 if all_outputs {
683 vec_attrs.push(("allOutputs", true.into()));
684 }
685
686 if !outputs.is_empty() {
687 outputs.sort();
688 vec_attrs.push(("outputs", Value::List(outputs
689 .into_iter()
690 .map(|s| s.into())
691 .collect::<Vec<Value>>()
692 .into()
693 )));
694 }
695
696 (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
697 });
698
699 Ok(Value::attrs(NixAttrs::from_iter(elements)))
700 }
701
702 #[builtin("appendContext")]
703 #[allow(non_snake_case)]
704 async fn builtin_appendContext(
705 co: GenCo,
706 origin: Value,
707 added_context: Value,
708 ) -> Result<Value, ErrorKind> {
709 let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
728 let span = generators::request_span(&co).await;
729 let origin = origin
730 .coerce_to_string(
731 co,
732 CoercionKind {
733 strong: true,
734 import_paths: true,
735 },
736 span,
737 )
738 .await?;
739 let mut origin = origin.to_contextful_str()?;
740
741 let added_context = added_context.to_attrs()?;
742 for (context_key, context_element) in added_context.into_iter() {
743 let context_element = context_element.to_attrs()?;
747 if let Some(path) = context_element.select_str("path") {
748 if path.as_bool()? {
749 ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
750 }
751 }
752 if let Some(all_outputs) = context_element.select_str("allOutputs") {
753 if all_outputs.as_bool()? {
754 ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
757 }
758 }
759 if let Some(some_outputs) = context_element.select_str("outputs") {
760 let some_outputs = some_outputs.to_list()?;
761 for output in some_outputs.into_iter() {
764 let output = output.to_str()?;
765 ctx_elements.insert(NixContextElement::Single {
766 derivation: context_key.to_string(),
767 name: output.to_string(),
768 });
769 }
770 }
771 }
772
773 if let Some(origin_ctx) = origin.context_mut() {
774 origin_ctx.extend(ctx_elements)
775 }
777
778 Ok(origin.into())
779 }
780
781 #[builtin("hashString")]
782 async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
783 hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
784 }
785
786 #[builtin("head")]
787 async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
788 if list.is_catchable() {
789 return Ok(list);
790 }
791
792 match list.to_list()?.get(0) {
793 Some(x) => Ok(x.clone()),
794 None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
795 }
796 }
797
798 #[builtin("intersectAttrs")]
799 async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
800 if x.is_catchable() {
801 return Ok(x);
802 }
803 if y.is_catchable() {
804 return Ok(y);
805 }
806 let left_set = x.to_attrs()?;
807 if left_set.is_empty() {
808 return Ok(Value::attrs(NixAttrs::empty()));
809 }
810
811 let right_set = y.to_attrs()?;
812
813 if right_set.is_empty() {
814 return Ok(Value::attrs(NixAttrs::empty()));
815 }
816
817 Ok(Value::attrs(left_set.intersect(&right_set)))
818 }
819
820 #[builtin("isAttrs")]
821 async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
822 if value.is_catchable() {
824 return Ok(value);
825 }
826
827 Ok(Value::Bool(matches!(value, Value::Attrs(_))))
828 }
829
830 #[builtin("isBool")]
831 async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
832 if value.is_catchable() {
833 return Ok(value);
834 }
835
836 Ok(Value::Bool(matches!(value, Value::Bool(_))))
837 }
838
839 #[builtin("isFloat")]
840 async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
841 if value.is_catchable() {
842 return Ok(value);
843 }
844
845 Ok(Value::Bool(matches!(value, Value::Float(_))))
846 }
847
848 #[builtin("isFunction")]
849 async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
850 if value.is_catchable() {
851 return Ok(value);
852 }
853
854 Ok(Value::Bool(matches!(
855 value,
856 Value::Closure(_) | Value::Builtin(_)
857 )))
858 }
859
860 #[builtin("isInt")]
861 async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
862 if value.is_catchable() {
863 return Ok(value);
864 }
865
866 Ok(Value::Bool(matches!(value, Value::Integer(_))))
867 }
868
869 #[builtin("isList")]
870 async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
871 if value.is_catchable() {
872 return Ok(value);
873 }
874
875 Ok(Value::Bool(matches!(value, Value::List(_))))
876 }
877
878 #[builtin("isNull")]
879 async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
880 if value.is_catchable() {
881 return Ok(value);
882 }
883
884 Ok(Value::Bool(matches!(value, Value::Null)))
885 }
886
887 #[builtin("isPath")]
888 async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
889 if value.is_catchable() {
890 return Ok(value);
891 }
892
893 Ok(Value::Bool(matches!(value, Value::Path(_))))
894 }
895
896 #[builtin("isString")]
897 async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
898 if value.is_catchable() {
899 return Ok(value);
900 }
901
902 Ok(Value::Bool(matches!(value, Value::String(_))))
903 }
904
905 #[builtin("length")]
906 async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
907 if list.is_catchable() {
908 return Ok(list);
909 }
910 Ok(Value::Integer(list.to_list()?.len() as i64))
911 }
912
913 #[builtin("lessThan")]
914 async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
915 let span = generators::request_span(&co).await;
916 match x.nix_cmp_ordering(y, co, span).await? {
917 Err(cek) => Ok(Value::from(cek)),
918 Ok(Ordering::Less) => Ok(Value::Bool(true)),
919 Ok(_) => Ok(Value::Bool(false)),
920 }
921 }
922
923 #[builtin("listToAttrs")]
924 async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
925 let list = list.to_list()?;
926 let mut map = FxHashMap::default();
927 for val in list {
928 let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
929 let name = try_value!(
930 generators::request_force(&co, attrs.select_required("name")?.clone()).await
931 )
932 .to_str()?;
933 let value = attrs.select_required("value")?.clone();
934 map.entry(name).or_insert(value);
936 }
937 Ok(Value::attrs(NixAttrs::from(map)))
938 }
939
940 #[builtin("map")]
941 async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
942 let list = list_val.to_list()?;
943 let mut out = Vec::with_capacity(list.len());
944
945 let span = generators::request_span(&co).await;
947
948 for val in list {
949 let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
950 out.push(result)
951 }
952
953 Ok(Value::List(out.into()))
954 }
955
956 #[builtin("mapAttrs")]
957 async fn builtin_map_attrs(
958 co: GenCo,
959 #[lazy] f: Value,
960 attrs: Value,
961 ) -> Result<Value, ErrorKind> {
962 let attrs = attrs.to_attrs()?;
963 let mut out = FxHashMap::default();
964
965 let span = generators::request_span(&co).await;
967
968 for (key, value) in attrs.into_iter() {
969 let result = Value::Thunk(Thunk::new_suspended_call(
970 f.clone(),
971 key.clone().into(),
972 span,
973 ));
974 let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
975
976 out.insert(key, result);
977 }
978
979 Ok(Value::attrs(out.into()))
980 }
981
982 #[builtin("match")]
983 async fn builtin_match(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
984 let s = str;
985 if s.is_catchable() {
986 return Ok(s);
987 }
988 let s = s.to_contextful_str()?;
989 let re = regex;
990 if re.is_catchable() {
991 return Ok(re);
992 }
993 let re = re.to_str()?;
994 let re: Regex =
995 cached_regex(&format!("^{}$", re.to_str()?)).expect("TODO(tazjin): propagate error");
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(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
1205 if str.is_catchable() {
1206 return Ok(str);
1207 }
1208
1209 if regex.is_catchable() {
1210 return Ok(regex);
1211 }
1212
1213 let s = str.to_contextful_str()?;
1214 let text = s.to_str()?;
1215 let re = regex.to_str()?;
1216 let re = cached_regex(re.to_str()?).unwrap();
1217 let mut capture_locations = re.capture_locations();
1218 let num_captures = capture_locations.len();
1219 let mut ret = Vec::new();
1220 let mut pos = 0;
1221 let mut prev_match_end = 0;
1222
1223 while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1224 ret.push(Value::from(NixString::new_inherit_context_from(
1226 &s,
1227 &text[prev_match_end..thematch.start()],
1228 )));
1229
1230 let v: Vec<Value> = (1..num_captures)
1235 .map(|i| capture_locations.get(i))
1236 .map(|o| {
1237 o.map(|(start, end)| {
1238 Value::from(&text[start..end])
1241 })
1242 .unwrap_or(Value::Null)
1243 })
1244 .collect();
1245 ret.push(Value::List(NixList::from(v)));
1246 if pos == text.len() {
1247 break;
1248 }
1249
1250 prev_match_end = thematch.end();
1253 pos = std::cmp::max(pos + 1, prev_match_end);
1254 }
1255
1256 ret.push(Value::from(&text[pos..]));
1260
1261 Ok(Value::List(NixList::from(ret)))
1262 }
1263
1264 #[builtin("sort")]
1265 async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1266 let list = list.to_list()?;
1267 let mut len = list.len();
1268 let mut data = list.into_inner();
1269
1270 loop {
1281 let mut new_len = 0;
1282 for i in 1..len {
1283 if try_value!(
1284 generators::request_force(
1285 &co,
1286 generators::request_call_with(
1287 &co,
1288 comparator.clone(),
1289 [data[i].clone(), data[i - 1].clone()],
1290 )
1291 .await,
1292 )
1293 .await
1294 )
1295 .as_bool()
1296 .context("evaluating comparator in `builtins.sort`")?
1297 {
1298 data.swap(i, i - 1);
1299 new_len = i;
1300 }
1301 }
1302
1303 if new_len == 0 {
1304 break;
1305 }
1306
1307 len = new_len;
1308 }
1309
1310 Ok(Value::List(data.into()))
1311 }
1312
1313 #[builtin("splitVersion")]
1314 async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1315 if s.is_catchable() {
1316 return Ok(s);
1317 }
1318 let s = s.to_str()?;
1319 let s = VersionPartsIter::new((&s).into());
1320
1321 let parts = s
1322 .map(|s| {
1323 Value::from(match s {
1324 VersionPart::Number(n) => n,
1325 VersionPart::Word(w) => w,
1326 })
1327 })
1328 .collect::<Vec<Value>>();
1329 Ok(Value::List(NixList::construct(parts.len(), parts)))
1330 }
1331
1332 #[builtin("stringLength")]
1333 async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1334 let span = generators::request_span(&co).await;
1336 let s = s
1337 .coerce_to_string(
1338 co,
1339 CoercionKind {
1340 strong: false,
1341 import_paths: true,
1342 },
1343 span,
1344 )
1345 .await?;
1346
1347 if s.is_catchable() {
1348 return Ok(s);
1349 }
1350
1351 Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1352 }
1353
1354 #[builtin("sub")]
1355 async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1356 arithmetic_op!(&x, &y, -)
1357 }
1358
1359 #[builtin("substring")]
1360 async fn builtin_substring(
1361 co: GenCo,
1362 start: Value,
1363 len: Value,
1364 s: Value,
1365 ) -> Result<Value, ErrorKind> {
1366 let beg = start.as_int()?;
1367 let len = len.as_int()?;
1368 let span = generators::request_span(&co).await;
1369 let x = s
1370 .coerce_to_string(
1371 co,
1372 CoercionKind {
1373 strong: false,
1374 import_paths: true,
1375 },
1376 span,
1377 )
1378 .await?;
1379 if x.is_catchable() {
1380 return Ok(x);
1381 }
1382 let x = x.to_contextful_str()?;
1383
1384 if beg < 0 {
1385 return Err(ErrorKind::IndexOutOfBounds { index: beg });
1386 }
1387 let beg = beg as usize;
1388
1389 if beg >= x.len() {
1393 return Ok(Value::from(NixString::new_inherit_context_from(
1394 &x,
1395 BString::default(),
1396 )));
1397 }
1398
1399 let end = if len < 0 {
1400 x.len()
1401 } else {
1402 cmp::min(beg + (len as usize), x.len())
1403 };
1404
1405 Ok(Value::from(NixString::new_inherit_context_from(
1406 &x,
1407 &x[beg..end],
1408 )))
1409 }
1410
1411 #[builtin("tail")]
1412 async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1413 if list.is_catchable() {
1414 return Ok(list);
1415 }
1416
1417 let xs = list.to_list()?;
1418
1419 if xs.is_empty() {
1420 Err(ErrorKind::TailEmptyList)
1421 } else {
1422 let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1423 Ok(Value::List(NixList::construct(output.len(), output)))
1424 }
1425 }
1426
1427 #[builtin("throw")]
1428 async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1429 if message.is_catchable() {
1431 return Ok(message);
1432 }
1433
1434 Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1437 }
1438
1439 #[builtin("toString")]
1440 async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1441 let span = generators::request_span(&co).await;
1446 x.coerce_to_string(
1447 co,
1448 CoercionKind {
1449 strong: true,
1450 import_paths: false,
1451 },
1452 span,
1453 )
1454 .await
1455 }
1456
1457 #[builtin("toXML")]
1458 async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1459 let value = generators::request_deep_force(&co, value).await;
1460 if value.is_catchable() {
1461 return Ok(value);
1462 }
1463
1464 let mut buf: Vec<u8> = vec![];
1465 let context = to_xml::value_to_xml(&mut buf, &value)?;
1466
1467 Ok(NixString::new_context_from(context, buf).into())
1468 }
1469
1470 #[builtin("trace")]
1471 async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1472 eprintln!("trace: {} :: {}", message, message.type_of());
1475 Ok(value)
1476 }
1477
1478 #[builtin("toPath")]
1479 async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1480 if s.is_catchable() {
1481 return Ok(s);
1482 }
1483
1484 match coerce_value_to_path(&co, s).await? {
1485 Err(cek) => Ok(Value::from(cek)),
1486 Ok(path) => {
1487 let path: Value = crate::value::canon_path(path).into();
1488 let span = generators::request_span(&co).await;
1489 Ok(path
1490 .coerce_to_string(
1491 co,
1492 CoercionKind {
1493 strong: false,
1494 import_paths: false,
1495 },
1496 span,
1497 )
1498 .await?)
1499 }
1500 }
1501 }
1502
1503 #[builtin("tryEval")]
1504 async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1505 let res = match generators::request_try_force(&co, e).await {
1506 Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1507 value => [("value", value), ("success", true.into())],
1508 };
1509
1510 Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1511 }
1512
1513 #[builtin("typeOf")]
1514 async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1515 if x.is_catchable() {
1516 return Ok(x);
1517 }
1518
1519 Ok(Value::from(x.type_of()))
1520 }
1521}
1522
1523async fn bgc_insert_key(
1526 co: &GenCo,
1527 key: Value,
1528 done: &mut Vec<Value>,
1529) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1530 for existing in done.iter() {
1531 match generators::check_equality(
1532 co,
1533 existing.clone(),
1534 key.clone(),
1535 PointerEquality::ForbidAll,
1537 )
1538 .await?
1539 {
1540 Ok(true) => return Ok(Ok(false)),
1541 Ok(false) => (),
1542 Err(cek) => return Ok(Err(cek)),
1543 }
1544 }
1545
1546 done.push(key);
1547 Ok(Ok(true))
1548}
1549
1550pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1553 let mut result = pure_builtins::builtins();
1554
1555 result.push(("nixVersion", Value::from("2.18.3-compat-tvix-0.1")));
1557 result.push(("langVersion", Value::Integer(6)));
1558 result.push(("null", Value::Null));
1559 result.push(("true", Value::Bool(true)));
1560 result.push(("false", Value::Bool(false)));
1561
1562 result.push((
1563 "currentSystem",
1564 crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1565 ));
1566
1567 result.push((
1568 "__curPos",
1569 Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1570 Ok(Value::attrs(NixAttrs::from_iter([
1572 ("line", 42.into()),
1573 ("column", 42.into()),
1574 ("file", Value::String("/deep/thought".into())),
1575 ])))
1576 }))),
1577 ));
1578
1579 result
1580}
1581
1582#[builtins]
1583mod placeholder_builtins {
1584 use crate::NixContext;
1585
1586 use super::*;
1587
1588 #[builtin("unsafeDiscardStringContext")]
1589 async fn builtin_unsafe_discard_string_context(
1590 co: GenCo,
1591 s: Value,
1592 ) -> Result<Value, ErrorKind> {
1593 let span = generators::request_span(&co).await;
1594 let mut v = s
1595 .coerce_to_string(
1596 co,
1597 CoercionKind {
1601 strong: false,
1602 import_paths: true,
1603 },
1604 span,
1605 )
1606 .await?
1607 .to_contextful_str()?;
1608 v.clear_context();
1609 Ok(Value::from(v))
1610 }
1611
1612 #[builtin("unsafeDiscardOutputDependency")]
1613 async fn builtin_unsafe_discard_output_dependency(
1614 co: GenCo,
1615 s: Value,
1616 ) -> Result<Value, ErrorKind> {
1617 let span = generators::request_span(&co).await;
1618 let mut v = s
1619 .coerce_to_string(
1620 co,
1621 CoercionKind {
1625 strong: false,
1626 import_paths: true,
1627 },
1628 span,
1629 )
1630 .await?
1631 .to_contextful_str()?;
1632
1633 if let Some(c) = v.take_context() {
1635 let mut context = NixContext::new();
1636 context.extend(c.into_iter().map(|elem| match elem {
1637 crate::NixContextElement::Derivation(drv_path) => {
1638 crate::NixContextElement::Plain(drv_path.to_string())
1639 }
1640 elem => elem.clone(),
1641 }));
1642
1643 return Ok(Value::String(NixString::new_context_from(context, v)));
1644 }
1645 Ok(Value::from(v))
1646 }
1647
1648 #[builtin("addErrorContext")]
1649 async fn builtin_add_error_context(
1650 co: GenCo,
1651 #[lazy] _context: Value,
1652 #[lazy] val: Value,
1653 ) -> Result<Value, ErrorKind> {
1654 generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1655 .await;
1656 Ok(val)
1657 }
1658
1659 #[builtin("unsafeGetAttrPos")]
1660 async fn builtin_unsafe_get_attr_pos(
1661 co: GenCo,
1662 _name: Value,
1663 _attrset: Value,
1664 ) -> Result<Value, ErrorKind> {
1665 generators::emit_warning_kind(
1667 &co,
1668 WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1669 )
1670 .await;
1671 let res = [
1672 ("line", 42.into()),
1673 ("column", 42.into()),
1674 ("file", Value::String("/deep/thought".into())),
1675 ];
1676 Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1677 }
1678}
1679
1680pub fn placeholders() -> Vec<(&'static str, Value)> {
1681 placeholder_builtins::builtins()
1682}