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
36pub 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);
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)
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);
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 if matches!(f, Value::Builtin(_)) {
447 return Ok(Value::attrs(NixAttrs::empty()));
448 }
449
450 let lambda = &f.as_closure()?.lambda();
451 let formals = if let Some(formals) = &lambda.formals {
452 formals
453 } else {
454 return Ok(Value::attrs(NixAttrs::empty()));
455 };
456 Ok(Value::attrs(NixAttrs::from_iter(
457 formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
458 )))
459 }
460
461 #[builtin("fromJSON")]
462 async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
463 let json_str = json.to_str()?;
464 serde_json::from_slice(&json_str).map_err(|err| err.into())
465 }
466
467 #[builtin("toJSON")]
468 async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
469 match val.into_contextful_json(&co).await {
470 Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
471 Err(err) => Err(err),
472 Ok((json, context)) => {
473 let json_str = serde_json::to_string(&json)
474 .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
475 Ok(Value::String(NixString::new_context_from(
476 context, json_str,
477 )))
478 }
479 }
480 }
481
482 #[builtin("fromTOML")]
483 async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
484 let toml_str = toml.to_str()?;
485
486 toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
487 }
488
489 #[builtin("genericClosure")]
490 async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
491 let attrs = input.to_attrs()?;
492
493 let mut work_set: VecDeque<Value> =
496 generators::request_force(&co, attrs.select_required("startSet")?.clone())
497 .await
498 .to_list()?
499 .into_iter()
500 .collect();
501
502 let operator = attrs.select_required("operator")?;
503
504 let mut res = Vec::new();
505 let mut done_keys: Vec<Value> = vec![];
506
507 while let Some(val) = work_set.pop_front() {
508 let val = generators::request_force(&co, val).await;
509 let attrs = val.to_attrs()?;
510 let key = attrs.select_required("key")?;
511
512 let value_missing =
513 try_cek_to_value!(bgc_insert_key(&co, key.clone(), &mut done_keys).await?);
514
515 if !value_missing {
516 continue;
517 }
518
519 res.push(val.clone());
520
521 let op_result = generators::request_force(
522 &co,
523 generators::request_call_with(&co, operator.clone(), [val]).await,
524 )
525 .await;
526
527 work_set.extend(op_result.to_list()?);
528 }
529
530 Ok(Value::List(NixList::from(res)))
531 }
532
533 #[builtin("genList")]
534 async fn builtin_gen_list(
535 co: GenCo,
536 #[lazy] generator: Value,
538 length: Value,
539 ) -> Result<Value, ErrorKind> {
540 let len = length.as_int()?;
541 let mut out = Vec::with_capacity(
542 len.try_into()
543 .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
544 );
545
546 let span = generators::request_span(&co).await;
548
549 for i in 0..len {
550 let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
551 out.push(val);
552 }
553
554 Ok(Value::List(out.into()))
555 }
556
557 #[builtin("getAttr")]
558 async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
559 let k = key.to_str()?;
560 let xs = set.to_attrs()?;
561
562 match xs.select(&k) {
563 Some(x) => Ok(x.clone()),
564 None => Err(ErrorKind::AttributeNotFound {
565 name: k.to_string(),
566 }),
567 }
568 }
569
570 #[builtin("groupBy")]
571 async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
572 let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
573 for val in list.to_list()? {
574 let key = try_value!(
575 generators::request_force(
576 &co,
577 generators::request_call_with(&co, f.clone(), [val.clone()]).await,
578 )
579 .await
580 )
581 .to_str()?;
582
583 res.entry(key).or_default().push(val);
584 }
585 Ok(Value::attrs(NixAttrs::from_iter(
586 res.into_iter()
587 .map(|(k, v)| (k, Value::List(NixList::from(v)))),
588 )))
589 }
590
591 #[builtin("hasAttr")]
592 async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
593 let k = key.to_str()?;
594 let xs = set.to_attrs()?;
595
596 Ok(Value::Bool(xs.contains(&k)))
597 }
598
599 #[builtin("hasContext")]
600 #[allow(non_snake_case)]
601 async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
602 if e.is_catchable() {
603 return Ok(e);
604 }
605
606 let v = e.to_contextful_str()?;
607 Ok(Value::Bool(v.has_context()))
608 }
609
610 #[builtin("getContext")]
611 #[allow(non_snake_case)]
612 async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
613 if e.is_catchable() {
614 return Ok(e);
615 }
616
617 let s = e.to_contextful_str()?;
618
619 let groups = s
620 .iter_context()
621 .flat_map(|context| context.iter())
622 .into_grouping_map_by(|ctx_element| match ctx_element {
628 NixContextElement::Plain(spath) => spath,
629 NixContextElement::Single { derivation, .. } => derivation,
630 NixContextElement::Derivation(drv_path) => drv_path,
631 })
632 .collect::<Vec<_>>();
633
634 let elements = groups
635 .into_iter()
636 .map(|(key, group)| {
637 let mut outputs: Vec<NixString> = Vec::new();
638 let mut is_path = false;
639 let mut all_outputs = false;
640
641 for ctx_element in group {
642 match ctx_element {
643 NixContextElement::Plain(spath) => {
644 debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
645 is_path = true;
646 }
647
648 NixContextElement::Single { name, derivation } => {
649 debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
650 outputs.push(name.clone().into());
651 }
652
653 NixContextElement::Derivation(drv_path) => {
654 debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
655 all_outputs = true;
656 }
657 }
658 }
659
660 let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
663
664 if is_path {
665 vec_attrs.push(("path", true.into()));
666 }
667
668 if all_outputs {
669 vec_attrs.push(("allOutputs", true.into()));
670 }
671
672 if !outputs.is_empty() {
673 outputs.sort();
674 vec_attrs.push(("outputs", Value::List(outputs
675 .into_iter()
676 .map(|s| s.into())
677 .collect::<Vec<Value>>()
678 .into()
679 )));
680 }
681
682 (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs)))
683 });
684
685 Ok(Value::attrs(NixAttrs::from_iter(elements)))
686 }
687
688 #[builtin("appendContext")]
689 #[allow(non_snake_case)]
690 async fn builtin_appendContext(
691 co: GenCo,
692 origin: Value,
693 added_context: Value,
694 ) -> Result<Value, ErrorKind> {
695 let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
714 let span = generators::request_span(&co).await;
715 let origin = origin
716 .coerce_to_string(
717 co,
718 CoercionKind {
719 strong: true,
720 import_paths: true,
721 },
722 span,
723 )
724 .await?;
725 let mut origin = origin.to_contextful_str()?;
726
727 let added_context = added_context.to_attrs()?;
728 for (context_key, context_element) in added_context.into_iter() {
729 let context_element = context_element.to_attrs()?;
733 if let Some(path) = context_element.select("path")
734 && path.as_bool()?
735 {
736 ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
737 }
738 if let Some(all_outputs) = context_element.select("allOutputs")
739 && all_outputs.as_bool()?
740 {
741 ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
744 }
745 if let Some(some_outputs) = context_element.select("outputs") {
746 let some_outputs = some_outputs.to_list()?;
747 for output in some_outputs.into_iter() {
750 let output = output.to_str()?;
751 ctx_elements.insert(NixContextElement::Single {
752 derivation: context_key.to_string(),
753 name: output.to_string(),
754 });
755 }
756 }
757 }
758
759 if let Some(origin_ctx) = origin.context_mut() {
760 origin_ctx.extend(ctx_elements)
761 }
763
764 Ok(origin.into())
765 }
766
767 #[builtin("hashString")]
768 async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
769 hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
770 }
771
772 #[builtin("head")]
773 async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
774 if list.is_catchable() {
775 return Ok(list);
776 }
777
778 match list.to_list()?.get(0) {
779 Some(x) => Ok(x.clone()),
780 None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
781 }
782 }
783
784 #[builtin("intersectAttrs")]
785 async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
786 if x.is_catchable() {
787 return Ok(x);
788 }
789 if y.is_catchable() {
790 return Ok(y);
791 }
792 let left_set = x.to_attrs()?;
793 let right_set = y.to_attrs()?;
794
795 if left_set.is_empty() || right_set.is_empty() {
796 return Ok(Value::attrs(NixAttrs::empty()));
797 }
798
799 Ok(Value::attrs(left_set.intersect(&right_set)))
800 }
801
802 #[builtin("isAttrs")]
803 async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
804 if value.is_catchable() {
806 return Ok(value);
807 }
808
809 Ok(Value::Bool(matches!(value, Value::Attrs(_))))
810 }
811
812 #[builtin("isBool")]
813 async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
814 if value.is_catchable() {
815 return Ok(value);
816 }
817
818 Ok(Value::Bool(matches!(value, Value::Bool(_))))
819 }
820
821 #[builtin("isFloat")]
822 async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
823 if value.is_catchable() {
824 return Ok(value);
825 }
826
827 Ok(Value::Bool(matches!(value, Value::Float(_))))
828 }
829
830 #[builtin("isFunction")]
831 async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
832 if value.is_catchable() {
833 return Ok(value);
834 }
835
836 Ok(Value::Bool(matches!(
837 value,
838 Value::Closure(_) | Value::Builtin(_)
839 )))
840 }
841
842 #[builtin("isInt")]
843 async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
844 if value.is_catchable() {
845 return Ok(value);
846 }
847
848 Ok(Value::Bool(matches!(value, Value::Integer(_))))
849 }
850
851 #[builtin("isList")]
852 async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
853 if value.is_catchable() {
854 return Ok(value);
855 }
856
857 Ok(Value::Bool(matches!(value, Value::List(_))))
858 }
859
860 #[builtin("isNull")]
861 async fn builtin_is_null(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::Null)))
867 }
868
869 #[builtin("isPath")]
870 async fn builtin_is_path(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::Path(_))))
876 }
877
878 #[builtin("isString")]
879 async fn builtin_is_string(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::String(_))))
885 }
886
887 #[builtin("length")]
888 async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
889 if list.is_catchable() {
890 return Ok(list);
891 }
892 Ok(Value::Integer(list.to_list()?.len() as i64))
893 }
894
895 #[builtin("lessThan")]
896 async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
897 let span = generators::request_span(&co).await;
898 match try_cek_to_value!(x.nix_cmp_ordering(y, co, span).await?) {
899 Ordering::Less => Ok(Value::Bool(true)),
900 _ => Ok(Value::Bool(false)),
901 }
902 }
903
904 #[builtin("listToAttrs")]
905 async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
906 let list = list.to_list()?;
907 let mut map = FxHashMap::default();
908 for val in list {
909 let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
910 let name = try_value!(
911 generators::request_force(&co, attrs.select_required("name")?.clone()).await
912 )
913 .to_str()?;
914 let value = attrs.select_required("value")?.clone();
915 map.entry(name).or_insert(value);
917 }
918 Ok(Value::attrs(NixAttrs::from(map)))
919 }
920
921 #[builtin("map")]
922 async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
923 let list = list_val.to_list()?;
924 let mut out = Vec::with_capacity(list.len());
925
926 let span = generators::request_span(&co).await;
928
929 for val in list {
930 let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
931 out.push(result)
932 }
933
934 Ok(Value::List(out.into()))
935 }
936
937 #[builtin("mapAttrs")]
938 async fn builtin_map_attrs(
939 co: GenCo,
940 #[lazy] f: Value,
941 attrs: Value,
942 ) -> Result<Value, ErrorKind> {
943 let attrs = attrs.to_attrs()?;
944 let mut out = FxHashMap::default();
945
946 let span = generators::request_span(&co).await;
948
949 for (key, value) in attrs.into_iter() {
950 let result = Value::Thunk(Thunk::new_suspended_call(
951 f.clone(),
952 key.clone().into(),
953 span,
954 ));
955 let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
956
957 out.insert(key, result);
958 }
959
960 Ok(Value::attrs(out.into()))
961 }
962
963 #[builtin("match")]
964 async fn builtin_match(
965 state: Rc<BuiltinState>,
966 co: GenCo,
967 regex: Value,
968 str: Value,
969 ) -> Result<Value, ErrorKind> {
970 let s = str;
971 if s.is_catchable() {
972 return Ok(s);
973 }
974 let s = s.to_contextful_str()?;
975 let re = regex;
976 if re.is_catchable() {
977 return Ok(re);
978 }
979 let re = re.to_str()?;
980 let re = re.to_str()?;
981 let re = state
982 .get_regex(&format!("^{re}$"))
983 .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
984
985 match re.captures(s.to_str()?) {
986 Some(caps) => Ok(Value::List(
987 caps.iter()
988 .skip(1)
989 .map(|grp| {
990 grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
998 })
999 .collect::<Vec<Value>>()
1000 .into(),
1001 )),
1002 None => Ok(Value::Null),
1003 }
1004 }
1005
1006 #[builtin("mul")]
1007 async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1008 arithmetic_op!(&x, &y, *)
1009 }
1010
1011 #[builtin("parseDrvName")]
1012 async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1013 if s.is_catchable() {
1014 return Ok(s);
1015 }
1016
1017 let s = s.to_str()?;
1020 let slice: &[u8] = s.as_ref();
1021 let (name, dash_and_version) = slice.split_at(
1022 slice
1023 .windows(2)
1024 .enumerate()
1025 .find_map(|x| match x {
1026 (idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
1027 _ => None,
1028 })
1029 .unwrap_or(slice.len()),
1030 );
1031 let version = dash_and_version
1032 .split_first()
1033 .map(|x| core::str::from_utf8(x.1))
1034 .unwrap_or(Ok(""))?;
1035 Ok(Value::attrs(NixAttrs::from_iter([
1036 ("name", core::str::from_utf8(name)?),
1037 ("version", version),
1038 ])))
1039 }
1040
1041 #[builtin("partition")]
1042 async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
1043 let mut right: Vec<Value> = Default::default();
1044 let mut wrong: Vec<Value> = Default::default();
1045
1046 let list: NixList = list.to_list()?;
1047 for elem in list {
1048 let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
1049
1050 if try_value!(generators::request_force(&co, result).await).as_bool()? {
1051 right.push(elem);
1052 } else {
1053 wrong.push(elem);
1054 };
1055 }
1056
1057 let res = [
1058 ("right", Value::List(NixList::from(right))),
1059 ("wrong", Value::List(NixList::from(wrong))),
1060 ];
1061
1062 Ok(Value::attrs(NixAttrs::from_iter(res)))
1063 }
1064
1065 #[builtin("removeAttrs")]
1066 async fn builtin_remove_attrs(
1067 co: GenCo,
1068 attrs: Value,
1069 keys: Value,
1070 ) -> Result<Value, ErrorKind> {
1071 let attrs = attrs.to_attrs()?;
1072 let keys = keys
1073 .to_list()?
1074 .into_iter()
1075 .map(|v| v.to_str())
1076 .collect::<Result<FxHashSet<_>, _>>()?;
1077 let res = attrs.iter().filter_map(|(k, v)| {
1078 if !keys.contains(k) {
1079 Some((k.clone(), v.clone()))
1080 } else {
1081 None
1082 }
1083 });
1084 Ok(Value::attrs(NixAttrs::from_iter(res)))
1085 }
1086
1087 #[builtin("replaceStrings")]
1088 async fn builtin_replace_strings(
1089 co: GenCo,
1090 from: Value,
1091 to: Value,
1092 s: Value,
1093 ) -> Result<Value, ErrorKind> {
1094 let from = from.to_list()?;
1095 for val in &from {
1096 try_value!(generators::request_force(&co, val.clone()).await);
1097 }
1098
1099 let to = to.to_list()?;
1100 for val in &to {
1101 try_value!(generators::request_force(&co, val.clone()).await);
1102 }
1103
1104 let mut string = s.to_contextful_str()?;
1105
1106 let mut res = BString::default();
1107
1108 let mut i: usize = 0;
1109 let mut empty_string_replace = false;
1110 let mut context = NixContext::new();
1111
1112 if let Some(string_context) = string.take_context() {
1113 context.extend(*string_context);
1114 }
1115
1116 'outer: while i < string.len() {
1123 for elem in std::iter::zip(from.iter(), to.iter()) {
1125 let from = elem.0.to_contextful_str()?;
1126 let mut to = elem.1.to_contextful_str()?;
1127
1128 if i + from.len() > string.len() {
1129 continue;
1130 }
1131
1132 if empty_string_replace && from.is_empty() {
1136 continue;
1137 }
1138
1139 if string[i..i + from.len()] == *from {
1141 res.push_str(&to);
1142 i += from.len();
1143 if let Some(to_ctx) = to.take_context() {
1144 context.extend(*to_ctx);
1145 }
1146
1147 empty_string_replace = from.is_empty();
1149
1150 continue 'outer;
1151 }
1152 }
1153
1154 res.push_str(&string[i..i + 1]);
1156 i += 1;
1157
1158 empty_string_replace = false;
1161 }
1162
1163 for elem in std::iter::zip(from.iter(), to.iter()) {
1166 let from = elem.0.to_contextful_str()?;
1167 let mut to = elem.1.to_contextful_str()?;
1172
1173 if from.is_empty() {
1174 res.push_str(&to);
1175 if let Some(to_ctx) = to.take_context() {
1176 context.extend(*to_ctx);
1177 }
1178 break;
1179 }
1180 }
1181
1182 Ok(Value::from(NixString::new_context_from(context, res)))
1183 }
1184
1185 #[builtin("seq")]
1186 async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
1187 Ok(y)
1190 }
1191
1192 #[builtin("split")]
1193 async fn builtin_split(
1194 state: Rc<BuiltinState>,
1195 co: GenCo,
1196 regex: Value,
1197 str: Value,
1198 ) -> Result<Value, ErrorKind> {
1199 if str.is_catchable() {
1200 return Ok(str);
1201 }
1202
1203 if regex.is_catchable() {
1204 return Ok(regex);
1205 }
1206
1207 let s = str.to_contextful_str()?;
1208 let text = s.to_str()?;
1209 let re = regex.to_str()?;
1210 let re = re.to_str()?;
1211 let re = state
1212 .get_regex(re)
1213 .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
1214 let mut capture_locations = re.capture_locations();
1215 let num_captures = capture_locations.len();
1216 let mut ret = Vec::new();
1217 let mut pos = 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[pos..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 pos = thematch.end();
1246 }
1247
1248 ret.push(Value::from(&text[pos..]));
1252
1253 Ok(Value::List(NixList::from(ret)))
1254 }
1255
1256 #[builtin("sort")]
1257 async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1258 let list = list.to_list()?;
1259 let mut len = list.len();
1260 let mut data = list.into_inner();
1261
1262 loop {
1273 let mut new_len = 0;
1274 for i in 1..len {
1275 if try_value!(
1276 generators::request_force(
1277 &co,
1278 generators::request_call_with(
1279 &co,
1280 comparator.clone(),
1281 [data[i].clone(), data[i - 1].clone()],
1282 )
1283 .await,
1284 )
1285 .await
1286 )
1287 .as_bool()
1288 .context("evaluating comparator in `builtins.sort`")?
1289 {
1290 data.swap(i, i - 1);
1291 new_len = i;
1292 }
1293 }
1294
1295 if new_len == 0 {
1296 break;
1297 }
1298
1299 len = new_len;
1300 }
1301
1302 Ok(Value::List(data.into()))
1303 }
1304
1305 #[builtin("splitVersion")]
1306 async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1307 if s.is_catchable() {
1308 return Ok(s);
1309 }
1310 let s = s.to_str()?;
1311 let s = VersionPartsIter::new((&s).into());
1312
1313 let parts = s
1314 .map(|s| {
1315 Value::from(match s {
1316 VersionPart::Number(n) => n,
1317 VersionPart::Word(w) => w,
1318 })
1319 })
1320 .collect::<Vec<Value>>();
1321 Ok(Value::List(NixList::construct(parts.len(), parts)))
1322 }
1323
1324 #[builtin("stringLength")]
1325 async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1326 let span = generators::request_span(&co).await;
1328 let s = s
1329 .coerce_to_string(
1330 co,
1331 CoercionKind {
1332 strong: false,
1333 import_paths: true,
1334 },
1335 span,
1336 )
1337 .await?;
1338
1339 if s.is_catchable() {
1340 return Ok(s);
1341 }
1342
1343 Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1344 }
1345
1346 #[builtin("sub")]
1347 async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1348 arithmetic_op!(&x, &y, -)
1349 }
1350
1351 #[builtin("substring")]
1352 async fn builtin_substring(
1353 co: GenCo,
1354 start: Value,
1355 len: Value,
1356 s: Value,
1357 ) -> Result<Value, ErrorKind> {
1358 let beg = start.as_int()?;
1359 let len = len.as_int()?;
1360 let span = generators::request_span(&co).await;
1361 let x = s
1362 .coerce_to_string(
1363 co,
1364 CoercionKind {
1365 strong: false,
1366 import_paths: true,
1367 },
1368 span,
1369 )
1370 .await?;
1371 if x.is_catchable() {
1372 return Ok(x);
1373 }
1374 let x = x.to_contextful_str()?;
1375
1376 if beg < 0 {
1377 return Err(ErrorKind::IndexOutOfBounds { index: beg });
1378 }
1379 let beg = beg as usize;
1380
1381 if beg >= x.len() {
1385 return Ok(Value::from(NixString::new_inherit_context_from(
1386 &x,
1387 BString::default(),
1388 )));
1389 }
1390
1391 let end = if len < 0 {
1392 x.len()
1393 } else {
1394 cmp::min(beg + (len as usize), x.len())
1395 };
1396
1397 Ok(Value::from(NixString::new_inherit_context_from(
1398 &x,
1399 &x[beg..end],
1400 )))
1401 }
1402
1403 #[builtin("tail")]
1404 async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1405 if list.is_catchable() {
1406 return Ok(list);
1407 }
1408
1409 let xs = list.to_list()?;
1410
1411 if xs.is_empty() {
1412 Err(ErrorKind::TailEmptyList)
1413 } else {
1414 let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1415 Ok(Value::List(NixList::construct(output.len(), output)))
1416 }
1417 }
1418
1419 #[builtin("throw")]
1420 async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1421 if message.is_catchable() {
1423 return Ok(message);
1424 }
1425
1426 Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1429 }
1430
1431 #[builtin("toString")]
1432 async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1433 let span = generators::request_span(&co).await;
1438 x.coerce_to_string(
1439 co,
1440 CoercionKind {
1441 strong: true,
1442 import_paths: false,
1443 },
1444 span,
1445 )
1446 .await
1447 }
1448
1449 #[builtin("toXML")]
1450 async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1451 let value = generators::request_deep_force(&co, value).await;
1452 if value.is_catchable() {
1453 return Ok(value);
1454 }
1455
1456 let mut buf: Vec<u8> = vec![];
1457 let context = to_xml::value_to_xml(&mut buf, &value)?;
1458
1459 Ok(NixString::new_context_from(context, buf).into())
1460 }
1461
1462 #[builtin("trace")]
1463 async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1464 eprintln!("trace: {} :: {}", message, message.type_of());
1467 Ok(value)
1468 }
1469
1470 #[builtin("toPath")]
1471 async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1472 if s.is_catchable() {
1473 return Ok(s);
1474 }
1475
1476 let path = try_cek_to_value!(coerce_value_to_path(&co, s).await?);
1477 let path: Value = crate::value::canon_path(path).into();
1478 let span = generators::request_span(&co).await;
1479 path.coerce_to_string(
1480 co,
1481 CoercionKind {
1482 strong: false,
1483 import_paths: false,
1484 },
1485 span,
1486 )
1487 .await
1488 }
1489
1490 #[builtin("tryEval")]
1491 async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1492 let res = match generators::request_try_force(&co, e).await {
1493 Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1494 value => [("value", value), ("success", true.into())],
1495 };
1496
1497 Ok(Value::attrs(NixAttrs::from_iter(res)))
1498 }
1499
1500 #[builtin("typeOf")]
1501 async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1502 if x.is_catchable() {
1503 return Ok(x);
1504 }
1505
1506 Ok(Value::from(x.type_of()))
1507 }
1508
1509 #[builtin("zipAttrsWith")]
1510 async fn builtin_zip_attrs_with(
1511 co: GenCo,
1512 #[lazy] f: Value,
1513 list: Value,
1514 ) -> Result<Value, ErrorKind> {
1515 let mut merged: FxHashMap<NixString, Vec<Value>> = FxHashMap::default();
1516
1517 for set in list.to_list()? {
1519 let set = generators::request_force(&co, set).await;
1520 let set = set.to_attrs()?;
1521 for (key, val) in set {
1522 match merged.entry(key) {
1523 Entry::Occupied(mut occupied) => occupied.get_mut().push(val),
1524 Entry::Vacant(vacant) => {
1525 vacant.insert(vec![val]);
1526 }
1527 }
1528 }
1529 }
1530
1531 let span = generators::request_span(&co).await;
1532
1533 let attrs = merged.into_iter().map(|(key, vals)| {
1535 let val = Value::Thunk(Thunk::new_suspended_call(
1536 Value::Thunk(Thunk::new_suspended_call(
1537 f.clone(),
1538 key.clone().into(),
1539 span,
1540 )),
1541 Value::List(NixList::construct(vals.len(), vals)),
1542 span,
1543 ));
1544 (key, val)
1545 });
1546
1547 Ok(Value::attrs(NixAttrs::from_iter(attrs)))
1548 }
1549}
1550
1551async fn bgc_insert_key(
1554 co: &GenCo,
1555 key: Value,
1556 done: &mut Vec<Value>,
1557) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1558 for existing in done.iter() {
1559 if try_cek!(
1560 generators::check_equality(
1561 co,
1562 existing.clone(),
1563 key.clone(),
1564 PointerEquality::ForbidAll,
1566 )
1567 .await?
1568 ) {
1569 return Ok(Ok(false));
1570 }
1571 }
1572
1573 done.push(key);
1574 Ok(Ok(true))
1575}
1576
1577pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1580 let mut result = pure_builtins::builtins(Rc::new(BuiltinState::default()));
1581
1582 result.push(("nixVersion", Value::from("2.18.3-compat-snix-0.1")));
1584 result.push(("langVersion", Value::Integer(6)));
1585 result.push(("null", Value::Null));
1586 result.push(("true", Value::Bool(true)));
1587 result.push(("false", Value::Bool(false)));
1588
1589 result.push((
1590 "currentSystem",
1591 crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1592 ));
1593
1594 result
1595}
1596
1597#[builtins]
1598mod placeholder_builtins {
1599 use crate::NixContext;
1600
1601 use super::*;
1602
1603 #[builtin("unsafeDiscardStringContext")]
1604 async fn builtin_unsafe_discard_string_context(
1605 co: GenCo,
1606 s: Value,
1607 ) -> Result<Value, ErrorKind> {
1608 let span = generators::request_span(&co).await;
1609 let mut v = s
1610 .coerce_to_string(
1611 co,
1612 CoercionKind {
1616 strong: false,
1617 import_paths: true,
1618 },
1619 span,
1620 )
1621 .await?
1622 .to_contextful_str()?;
1623 v.clear_context();
1624 Ok(Value::from(v))
1625 }
1626
1627 #[builtin("unsafeDiscardOutputDependency")]
1628 async fn builtin_unsafe_discard_output_dependency(
1629 co: GenCo,
1630 s: Value,
1631 ) -> Result<Value, ErrorKind> {
1632 let span = generators::request_span(&co).await;
1633 let mut v = s
1634 .coerce_to_string(
1635 co,
1636 CoercionKind {
1640 strong: false,
1641 import_paths: true,
1642 },
1643 span,
1644 )
1645 .await?
1646 .to_contextful_str()?;
1647
1648 if let Some(c) = v.take_context() {
1650 let mut context = NixContext::new();
1651 context.extend(c.into_iter().map(|elem| match elem {
1652 crate::NixContextElement::Derivation(drv_path) => {
1653 crate::NixContextElement::Plain(drv_path.to_string())
1654 }
1655 elem => elem.clone(),
1656 }));
1657
1658 return Ok(Value::String(NixString::new_context_from(context, v)));
1659 }
1660 Ok(Value::from(v))
1661 }
1662
1663 #[builtin("addErrorContext")]
1664 async fn builtin_add_error_context(
1665 co: GenCo,
1666 #[lazy] _context: Value,
1667 #[lazy] val: Value,
1668 ) -> Result<Value, ErrorKind> {
1669 generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1670 .await;
1671 Ok(val)
1672 }
1673
1674 #[builtin("unsafeGetAttrPos")]
1675 async fn builtin_unsafe_get_attr_pos(
1676 co: GenCo,
1677 _name: Value,
1678 _attrset: Value,
1679 ) -> Result<Value, ErrorKind> {
1680 generators::emit_warning_kind(
1682 &co,
1683 WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1684 )
1685 .await;
1686 let res = [
1687 ("line", 42.into()),
1688 ("column", 42.into()),
1689 ("file", Value::String("/deep/thought".into())),
1690 ];
1691 Ok(Value::attrs(NixAttrs::from_iter(res)))
1692 }
1693}
1694
1695pub fn placeholders() -> Vec<(&'static str, Value)> {
1696 placeholder_builtins::builtins()
1697}