snix_eval/builtins/
mod.rs

1//! This module implements the builtins exposed in the Nix language.
2//!
3//! See //snix/eval/docs/builtins.md for a some context on the
4//! available builtins in Nix.
5
6use bstr::{ByteSlice, ByteVec};
7use builtin_macros::builtins;
8use genawaiter::rc::Gen;
9use regex::Regex;
10use rustc_hash::FxHashMap;
11use std::cell::RefCell;
12use std::cmp::{self, Ordering};
13use std::collections::BTreeMap;
14use std::collections::VecDeque;
15use std::collections::hash_map::Entry;
16use std::path::PathBuf;
17use std::rc::Rc;
18
19use crate::value::PointerEquality;
20use crate::vm::generators::{self, GenCo};
21use crate::warnings::WarningKind;
22use crate::{
23    self as snix_eval,
24    builtins::hash::hash_nix_string,
25    errors::{CatchableErrorKind, ErrorKind},
26    value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
27};
28use crate::{arithmetic_op, try_cek};
29
30use self::versions::{VersionPart, VersionPartsIter};
31
32mod hash;
33mod to_xml;
34mod versions;
35
36#[cfg(test)]
37pub use to_xml::value_to_xml;
38
39#[cfg(feature = "impure")]
40mod impure;
41
42#[cfg(feature = "impure")]
43pub use impure::impure_builtins;
44
45// we set SNIX_CURRENT_SYSTEM in build.rs
46pub const CURRENT_PLATFORM: &str = env!("SNIX_CURRENT_SYSTEM");
47
48/// Coerce a Nix Value to a plain path, e.g. in order to access the
49/// file it points to via either `builtins.toPath` or an impure
50/// builtin. This coercion can _never_ be performed in a Nix program
51/// without using builtins (i.e. the trick `path: /. + path` to
52/// convert from a string to a path wouldn't hit this code).
53///
54/// This operation doesn't import a Nix path value into the store.
55pub 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        // TODO(sterni): coerces to string
130        // Although `abort` does not make use of any context,
131        // we must still accept contextful strings as parameters.
132        // If `to_str` was used, this would err out with an unexpected type error.
133        // Therefore, we explicitly accept contextful strings and ignore their contexts.
134        Err(ErrorKind::Abort(message.to_contextful_str()?.to_string()))
135    }
136
137    #[builtin("add")]
138    async fn builtin_add(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
139        arithmetic_op!(&x, &y, +)
140    }
141
142    #[builtin("all")]
143    async fn builtin_all(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
144        for value in list.to_list()?.into_iter() {
145            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
146            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
147
148            if !pred_result.as_bool()? {
149                return Ok(Value::Bool(false));
150            }
151        }
152
153        Ok(Value::Bool(true))
154    }
155
156    #[builtin("any")]
157    async fn builtin_any(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
158        for value in list.to_list()?.into_iter() {
159            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
160            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
161
162            if pred_result.as_bool()? {
163                return Ok(Value::Bool(true));
164            }
165        }
166
167        Ok(Value::Bool(false))
168    }
169
170    #[builtin("attrNames")]
171    async fn builtin_attr_names(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
172        let xs = set.to_attrs()?;
173        let mut output = Vec::with_capacity(xs.len());
174
175        for (key, _val) in xs.iter_sorted() {
176            output.push(Value::from(key.clone()));
177        }
178
179        Ok(Value::List(NixList::construct(output.len(), output)))
180    }
181
182    #[builtin("attrValues")]
183    async fn builtin_attr_values(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
184        let xs = set.to_attrs()?;
185        let mut output = Vec::with_capacity(xs.len());
186
187        for (_key, val) in xs.iter_sorted() {
188            output.push(val.clone());
189        }
190
191        Ok(Value::List(NixList::construct(output.len(), output)))
192    }
193
194    #[builtin("baseNameOf")]
195    async fn builtin_base_name_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
196        let span = generators::request_span(&co).await;
197        let s = s
198            .coerce_to_string(
199                co,
200                CoercionKind {
201                    strong: false,
202                    import_paths: false,
203                },
204                span,
205            )
206            .await?
207            .to_contextful_str()?;
208
209        let mut bs = (**s).to_owned();
210        if let Some(last_slash) = bs.rfind_char('/') {
211            bs = bs[(last_slash + 1)..].into();
212        }
213        Ok(NixString::new_inherit_context_from(&s, bs).into())
214    }
215
216    #[builtin("bitAnd")]
217    async fn builtin_bit_and(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
218        Ok(Value::Integer(x.as_int()? & y.as_int()?))
219    }
220
221    #[builtin("bitOr")]
222    async fn builtin_bit_or(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
223        Ok(Value::Integer(x.as_int()? | y.as_int()?))
224    }
225
226    #[builtin("bitXor")]
227    async fn builtin_bit_xor(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
228        Ok(Value::Integer(x.as_int()? ^ y.as_int()?))
229    }
230
231    #[builtin("catAttrs")]
232    async fn builtin_cat_attrs(co: GenCo, key: Value, list: Value) -> Result<Value, ErrorKind> {
233        let key = key.to_str()?;
234        let list = list.to_list()?;
235        let mut output = vec![];
236
237        for item in list.into_iter() {
238            let set = generators::request_force(&co, item).await.to_attrs()?;
239
240            if let Some(value) = set.select(&key) {
241                output.push(value.clone());
242            }
243        }
244
245        Ok(Value::List(NixList::construct(output.len(), output)))
246    }
247
248    #[builtin("ceil")]
249    async fn builtin_ceil(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
250        Ok(Value::Integer(double.as_float()?.ceil() as i64))
251    }
252
253    #[builtin("compareVersions")]
254    async fn builtin_compare_versions(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
255        let s1 = x.to_str()?;
256        let s1 = VersionPartsIter::new_for_cmp((&s1).into());
257        let s2 = y.to_str()?;
258        let s2 = VersionPartsIter::new_for_cmp((&s2).into());
259
260        match s1.cmp(s2) {
261            std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
262            std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
263            std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
264        }
265    }
266
267    #[builtin("concatLists")]
268    async fn builtin_concat_lists(co: GenCo, lists: Value) -> Result<Value, ErrorKind> {
269        let mut out = Vec::new();
270
271        for value in lists.to_list()? {
272            let list = try_value!(generators::request_force(&co, value).await).to_list()?;
273            out.extend(list.into_iter());
274        }
275
276        Ok(Value::List(out.into()))
277    }
278
279    #[builtin("concatMap")]
280    async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
281        let list = list.to_list()?;
282        let mut res = Vec::new();
283        for val in list {
284            let out = generators::request_call_with(&co, f.clone(), [val]).await;
285            let out = try_value!(generators::request_force(&co, out).await);
286            res.extend(out.to_list()?);
287        }
288        Ok(Value::List(res.into()))
289    }
290
291    #[builtin("concatStringsSep")]
292    async fn builtin_concat_strings_sep(
293        co: GenCo,
294        separator: Value,
295        list: Value,
296    ) -> Result<Value, ErrorKind> {
297        let mut separator = separator.to_contextful_str()?;
298
299        let mut context = NixContext::new();
300        if let Some(sep_context) = separator.take_context() {
301            context.extend(sep_context.into_iter())
302        }
303        let list = list.to_list()?;
304        let mut res = BString::default();
305        for (i, val) in list.into_iter().enumerate() {
306            if i != 0 {
307                res.push_str(&separator);
308            }
309            let mut s = try_cek_to_value!(
310                generators::request_string_coerce(
311                    &co,
312                    val,
313                    CoercionKind {
314                        strong: false,
315                        import_paths: true,
316                    },
317                )
318                .await
319            );
320            res.push_str(&s);
321            if let Some(other_context) = s.take_context() {
322                context.extend(other_context.into_iter());
323            }
324        }
325        // FIXME: pass immediately the string res.
326        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            // Every call of `op` is forced immediately, but `nul` is not, see
432            // https://github.com/NixOS/nix/blob/940e9eb8/src/libexpr/primops.cc#L3069-L3070C36
433            // and our tests for foldl'.
434            nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
435            nul = generators::request_force(&co, nul).await;
436            if let c @ Value::Catchable(_) = nul {
437                return Ok(c);
438            }
439        }
440
441        Ok(nul)
442    }
443
444    #[builtin("functionArgs")]
445    async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
446        let lambda = &f.as_closure()?.lambda();
447        let formals = if let Some(formals) = &lambda.formals {
448            formals
449        } else {
450            return Ok(Value::attrs(NixAttrs::empty()));
451        };
452        Ok(Value::attrs(NixAttrs::from_iter(
453            formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
454        )))
455    }
456
457    #[builtin("fromJSON")]
458    async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
459        let json_str = json.to_str()?;
460        serde_json::from_slice(&json_str).map_err(|err| err.into())
461    }
462
463    #[builtin("toJSON")]
464    async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
465        match val.into_contextful_json(&co).await {
466            Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
467            Err(err) => Err(err),
468            Ok((json, context)) => {
469                let json_str = serde_json::to_string(&json)
470                    .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
471                Ok(Value::String(NixString::new_context_from(
472                    context, json_str,
473                )))
474            }
475        }
476    }
477
478    #[builtin("fromTOML")]
479    async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
480        let toml_str = toml.to_str()?;
481
482        toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
483    }
484
485    #[builtin("genericClosure")]
486    async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
487        let attrs = input.to_attrs()?;
488
489        // The work set is maintained as a VecDeque because new items
490        // are popped from the front.
491        let mut work_set: VecDeque<Value> =
492            generators::request_force(&co, attrs.select_required("startSet")?.clone())
493                .await
494                .to_list()?
495                .into_iter()
496                .collect();
497
498        let operator = attrs.select_required("operator")?;
499
500        let mut res = Vec::new();
501        let mut done_keys: Vec<Value> = vec![];
502
503        while let Some(val) = work_set.pop_front() {
504            let val = generators::request_force(&co, val).await;
505            let attrs = val.to_attrs()?;
506            let key = attrs.select_required("key")?;
507
508            let value_missing =
509                try_cek_to_value!(bgc_insert_key(&co, key.clone(), &mut done_keys).await?);
510
511            if !value_missing {
512                continue;
513            }
514
515            res.push(val.clone());
516
517            let op_result = generators::request_force(
518                &co,
519                generators::request_call_with(&co, operator.clone(), [val]).await,
520            )
521            .await;
522
523            work_set.extend(op_result.to_list()?.into_iter());
524        }
525
526        Ok(Value::List(NixList::from(res)))
527    }
528
529    #[builtin("genList")]
530    async fn builtin_gen_list(
531        co: GenCo,
532        // Nix 2.3 doesn't propagate failures here
533        #[lazy] generator: Value,
534        length: Value,
535    ) -> Result<Value, ErrorKind> {
536        let len = length.as_int()?;
537        let mut out = Vec::with_capacity(
538            len.try_into()
539                .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
540        );
541
542        // the best span we can get…
543        let span = generators::request_span(&co).await;
544
545        for i in 0..len {
546            let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
547            out.push(val);
548        }
549
550        Ok(Value::List(out.into()))
551    }
552
553    #[builtin("getAttr")]
554    async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
555        let k = key.to_str()?;
556        let xs = set.to_attrs()?;
557
558        match xs.select(&k) {
559            Some(x) => Ok(x.clone()),
560            None => Err(ErrorKind::AttributeNotFound {
561                name: k.to_string(),
562            }),
563        }
564    }
565
566    #[builtin("groupBy")]
567    async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
568        let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
569        for val in list.to_list()? {
570            let key = try_value!(
571                generators::request_force(
572                    &co,
573                    generators::request_call_with(&co, f.clone(), [val.clone()]).await,
574                )
575                .await
576            )
577            .to_str()?;
578
579            res.entry(key).or_default().push(val);
580        }
581        Ok(Value::attrs(NixAttrs::from_iter(
582            res.into_iter()
583                .map(|(k, v)| (k, Value::List(NixList::from(v)))),
584        )))
585    }
586
587    #[builtin("hasAttr")]
588    async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
589        let k = key.to_str()?;
590        let xs = set.to_attrs()?;
591
592        Ok(Value::Bool(xs.contains(&k)))
593    }
594
595    #[builtin("hasContext")]
596    #[allow(non_snake_case)]
597    async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
598        if e.is_catchable() {
599            return Ok(e);
600        }
601
602        let v = e.to_contextful_str()?;
603        Ok(Value::Bool(v.has_context()))
604    }
605
606    #[builtin("getContext")]
607    #[allow(non_snake_case)]
608    async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
609        if e.is_catchable() {
610            return Ok(e);
611        }
612
613        // also forces the value
614        let span = generators::request_span(&co).await;
615        let v = e
616            .coerce_to_string(
617                co,
618                CoercionKind {
619                    strong: true,
620                    import_paths: true,
621                },
622                span,
623            )
624            .await?;
625        let s = v.to_contextful_str()?;
626
627        let groups = s
628            .iter_context()
629            .flat_map(|context| context.iter())
630            // Do not think `group_by` works here.
631            // `group_by` works on consecutive elements of the iterator.
632            // Due to how `HashSet` works (ordering is not guaranteed),
633            // this can become a source of non-determinism if you `group_by` naively.
634            // I know I did.
635            .into_grouping_map_by(|ctx_element| match ctx_element {
636                NixContextElement::Plain(spath) => spath,
637                NixContextElement::Single { derivation, .. } => derivation,
638                NixContextElement::Derivation(drv_path) => drv_path,
639            })
640            .collect::<Vec<_>>();
641
642        let elements = groups
643            .into_iter()
644            .map(|(key, group)| {
645                let mut outputs: Vec<NixString> = Vec::new();
646                let mut is_path = false;
647                let mut all_outputs = false;
648
649                for ctx_element in group {
650                    match ctx_element {
651                        NixContextElement::Plain(spath) => {
652                            debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
653                            is_path = true;
654                        }
655
656                        NixContextElement::Single { name, derivation } => {
657                            debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
658                            outputs.push(name.clone().into());
659                        }
660
661                        NixContextElement::Derivation(drv_path) => {
662                            debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
663                            all_outputs = true;
664                        }
665                    }
666                }
667
668                // FIXME(raitobezarius): is there a better way to construct an attribute set
669                // conditionally?
670                let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
671
672                if is_path {
673                    vec_attrs.push(("path", true.into()));
674                }
675
676                if all_outputs {
677                    vec_attrs.push(("allOutputs", true.into()));
678                }
679
680                if !outputs.is_empty() {
681                    outputs.sort();
682                    vec_attrs.push(("outputs", Value::List(outputs
683                                .into_iter()
684                                .map(|s| s.into())
685                                .collect::<Vec<Value>>()
686                                .into()
687                    )));
688                }
689
690                (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
691            });
692
693        Ok(Value::attrs(NixAttrs::from_iter(elements)))
694    }
695
696    #[builtin("appendContext")]
697    #[allow(non_snake_case)]
698    async fn builtin_appendContext(
699        co: GenCo,
700        origin: Value,
701        added_context: Value,
702    ) -> Result<Value, ErrorKind> {
703        // `appendContext` is a "grow" context function.
704        // It cannot remove a context element, neither replace a piece of its contents.
705        //
706        // Growing context is always a safe operation, there's no loss of dependency tracking
707        // information.
708        //
709        // This is why this operation is not prefixed by `unsafe` and is deemed *safe*.
710        // Nonetheless, it is possible to craft nonsensical context elements referring
711        // to inexistent derivations, output paths or output names.
712        //
713        // In Nix, those nonsensical context elements are partially mitigated by checking
714        // that various parameters are indeed syntatically valid store paths in the context, i.e.
715        // starting with the same prefix as `builtins.storeDir`, or ending with `.drv`.
716        // In addition, if writing to the store is possible (evaluator not in read-only mode), Nix
717        // will realize some paths and ensures they are present in the store.
718        //
719        // In this implementation, we do none of that, no syntax checks, no realization.
720        // The next `TODO` are the checks that Nix implements.
721        let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
722        let span = generators::request_span(&co).await;
723        let origin = origin
724            .coerce_to_string(
725                co,
726                CoercionKind {
727                    strong: true,
728                    import_paths: true,
729                },
730                span,
731            )
732            .await?;
733        let mut origin = origin.to_contextful_str()?;
734
735        let added_context = added_context.to_attrs()?;
736        for (context_key, context_element) in added_context.into_iter() {
737            // Invariant checks:
738            // - TODO: context_key must be a syntactically valid store path.
739            // - Perform a deep force `context_element`.
740            let context_element = context_element.to_attrs()?;
741            if let Some(path) = context_element.select("path") {
742                if path.as_bool()? {
743                    ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
744                }
745            }
746            if let Some(all_outputs) = context_element.select("allOutputs") {
747                if all_outputs.as_bool()? {
748                    // TODO: check if `context_key` is a derivation path.
749                    // This may require realization.
750                    ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
751                }
752            }
753            if let Some(some_outputs) = context_element.select("outputs") {
754                let some_outputs = some_outputs.to_list()?;
755                // TODO: check if `context_key` is a derivation path.
756                // This may require realization.
757                for output in some_outputs.into_iter() {
758                    let output = output.to_str()?;
759                    ctx_elements.insert(NixContextElement::Single {
760                        derivation: context_key.to_string(),
761                        name: output.to_string(),
762                    });
763                }
764            }
765        }
766
767        if let Some(origin_ctx) = origin.context_mut() {
768            origin_ctx.extend(ctx_elements)
769            // TODO: didn't we forget cases where origin had no context?
770        }
771
772        Ok(origin.into())
773    }
774
775    #[builtin("hashString")]
776    async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
777        hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
778    }
779
780    #[builtin("head")]
781    async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
782        if list.is_catchable() {
783            return Ok(list);
784        }
785
786        match list.to_list()?.get(0) {
787            Some(x) => Ok(x.clone()),
788            None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
789        }
790    }
791
792    #[builtin("intersectAttrs")]
793    async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
794        if x.is_catchable() {
795            return Ok(x);
796        }
797        if y.is_catchable() {
798            return Ok(y);
799        }
800        let left_set = x.to_attrs()?;
801        if left_set.is_empty() {
802            return Ok(Value::attrs(NixAttrs::empty()));
803        }
804
805        let right_set = y.to_attrs()?;
806
807        if right_set.is_empty() {
808            return Ok(Value::attrs(NixAttrs::empty()));
809        }
810
811        Ok(Value::attrs(left_set.intersect(&right_set)))
812    }
813
814    #[builtin("isAttrs")]
815    async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
816        // TODO(edef): make this beautiful
817        if value.is_catchable() {
818            return Ok(value);
819        }
820
821        Ok(Value::Bool(matches!(value, Value::Attrs(_))))
822    }
823
824    #[builtin("isBool")]
825    async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
826        if value.is_catchable() {
827            return Ok(value);
828        }
829
830        Ok(Value::Bool(matches!(value, Value::Bool(_))))
831    }
832
833    #[builtin("isFloat")]
834    async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
835        if value.is_catchable() {
836            return Ok(value);
837        }
838
839        Ok(Value::Bool(matches!(value, Value::Float(_))))
840    }
841
842    #[builtin("isFunction")]
843    async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
844        if value.is_catchable() {
845            return Ok(value);
846        }
847
848        Ok(Value::Bool(matches!(
849            value,
850            Value::Closure(_) | Value::Builtin(_)
851        )))
852    }
853
854    #[builtin("isInt")]
855    async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
856        if value.is_catchable() {
857            return Ok(value);
858        }
859
860        Ok(Value::Bool(matches!(value, Value::Integer(_))))
861    }
862
863    #[builtin("isList")]
864    async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
865        if value.is_catchable() {
866            return Ok(value);
867        }
868
869        Ok(Value::Bool(matches!(value, Value::List(_))))
870    }
871
872    #[builtin("isNull")]
873    async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
874        if value.is_catchable() {
875            return Ok(value);
876        }
877
878        Ok(Value::Bool(matches!(value, Value::Null)))
879    }
880
881    #[builtin("isPath")]
882    async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
883        if value.is_catchable() {
884            return Ok(value);
885        }
886
887        Ok(Value::Bool(matches!(value, Value::Path(_))))
888    }
889
890    #[builtin("isString")]
891    async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
892        if value.is_catchable() {
893            return Ok(value);
894        }
895
896        Ok(Value::Bool(matches!(value, Value::String(_))))
897    }
898
899    #[builtin("length")]
900    async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
901        if list.is_catchable() {
902            return Ok(list);
903        }
904        Ok(Value::Integer(list.to_list()?.len() as i64))
905    }
906
907    #[builtin("lessThan")]
908    async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
909        let span = generators::request_span(&co).await;
910        match try_cek_to_value!(x.nix_cmp_ordering(y, co, span).await?) {
911            Ordering::Less => Ok(Value::Bool(true)),
912            _ => Ok(Value::Bool(false)),
913        }
914    }
915
916    #[builtin("listToAttrs")]
917    async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
918        let list = list.to_list()?;
919        let mut map = FxHashMap::default();
920        for val in list {
921            let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
922            let name = try_value!(
923                generators::request_force(&co, attrs.select_required("name")?.clone()).await
924            )
925            .to_str()?;
926            let value = attrs.select_required("value")?.clone();
927            // Map entries earlier in the list take precedence over entries later in the list
928            map.entry(name).or_insert(value);
929        }
930        Ok(Value::attrs(NixAttrs::from(map)))
931    }
932
933    #[builtin("map")]
934    async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
935        let list = list_val.to_list()?;
936        let mut out = Vec::with_capacity(list.len());
937
938        // the best span we can get…
939        let span = generators::request_span(&co).await;
940
941        for val in list {
942            let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
943            out.push(result)
944        }
945
946        Ok(Value::List(out.into()))
947    }
948
949    #[builtin("mapAttrs")]
950    async fn builtin_map_attrs(
951        co: GenCo,
952        #[lazy] f: Value,
953        attrs: Value,
954    ) -> Result<Value, ErrorKind> {
955        let attrs = attrs.to_attrs()?;
956        let mut out = FxHashMap::default();
957
958        // the best span we can get…
959        let span = generators::request_span(&co).await;
960
961        for (key, value) in attrs.into_iter() {
962            let result = Value::Thunk(Thunk::new_suspended_call(
963                f.clone(),
964                key.clone().into(),
965                span,
966            ));
967            let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
968
969            out.insert(key, result);
970        }
971
972        Ok(Value::attrs(out.into()))
973    }
974
975    #[builtin("match")]
976    async fn builtin_match(
977        state: Rc<BuiltinState>,
978        co: GenCo,
979        regex: Value,
980        str: Value,
981    ) -> Result<Value, ErrorKind> {
982        let s = str;
983        if s.is_catchable() {
984            return Ok(s);
985        }
986        let s = s.to_contextful_str()?;
987        let re = regex;
988        if re.is_catchable() {
989            return Ok(re);
990        }
991        let re = re.to_str()?;
992        let re = re.to_str()?;
993        let re = state
994            .get_regex(&format!("^{re}$"))
995            .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
996
997        match re.captures(s.to_str()?) {
998            Some(caps) => Ok(Value::List(
999                caps.iter()
1000                    .skip(1)
1001                    .map(|grp| {
1002                        // Surprisingly, Nix does not propagate
1003                        // the original context here.
1004                        // Though, it accepts contextful strings as an argument.
1005                        // An example of such behaviors in nixpkgs
1006                        // can be observed in make-initrd.nix when it comes
1007                        // to compressors which are matched over their full command
1008                        // and then a compressor name will be extracted from that.
1009                        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        // This replicates cppnix's (mis?)handling of codepoints
1030        // above U+007f following 0x2d ('-')
1031        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        // This can't be implemented using Rust's string.replace() as
1128        // well as a map because we need to handle errors with results
1129        // as well as "reset" the iterator to zero for the replacement
1130        // everytime there's a successful match.
1131        // Also, Rust's string.replace allocates a new string
1132        // on every call which is not preferable.
1133        'outer: while i < string.len() {
1134            // Try a match in all the from strings
1135            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                // We already applied a from->to with an empty from
1144                // transformation.
1145                // Let's skip it so that we don't loop infinitely
1146                if empty_string_replace && from.is_empty() {
1147                    continue;
1148                }
1149
1150                // if we match the `from` string, let's replace
1151                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                    // remember if we applied the empty from->to
1159                    empty_string_replace = from.is_empty();
1160
1161                    continue 'outer;
1162                }
1163            }
1164
1165            // If we don't match any `from`, we simply add a character
1166            res.push_str(&string[i..i + 1]);
1167            i += 1;
1168
1169            // Since we didn't apply anything transformation,
1170            // we reset the empty string replacement
1171            empty_string_replace = false;
1172        }
1173
1174        // Special case when the string is empty or at the string's end
1175        // and one of the from is also empty
1176        for elem in std::iter::zip(from.iter(), to.iter()) {
1177            let from = elem.0.to_contextful_str()?;
1178            // We mutate `to` by consuming its context
1179            // if we perform a successful replacement.
1180            // Therefore, it's fine if `to` was mutate and we reuse it here.
1181            // We don't need to merge again the context, it's already in the right state.
1182            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        // The builtin calling infra has already forced both args for us, so
1199        // we just return the second and ignore the first
1200        Ok(y)
1201    }
1202
1203    #[builtin("split")]
1204    async fn builtin_split(
1205        state: Rc<BuiltinState>,
1206        co: GenCo,
1207        regex: Value,
1208        str: Value,
1209    ) -> Result<Value, ErrorKind> {
1210        if str.is_catchable() {
1211            return Ok(str);
1212        }
1213
1214        if regex.is_catchable() {
1215            return Ok(regex);
1216        }
1217
1218        let s = str.to_contextful_str()?;
1219        let text = s.to_str()?;
1220        let re = regex.to_str()?;
1221        let re = re.to_str()?;
1222        let re = state
1223            .get_regex(re)
1224            .map_err(|_| ErrorKind::InvalidRegex(re.to_string()))?;
1225        let mut capture_locations = re.capture_locations();
1226        let num_captures = capture_locations.len();
1227        let mut ret = Vec::new();
1228        let mut pos = 0;
1229
1230        while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1231            // push the unmatched characters preceding the match
1232            ret.push(Value::from(NixString::new_inherit_context_from(
1233                &s,
1234                &text[pos..thematch.start()],
1235            )));
1236
1237            // Push a list with one element for each capture
1238            // group in the regex, containing the characters
1239            // matched by that capture group, or null if no match.
1240            // We skip capture 0; it represents the whole match.
1241            let v: Vec<Value> = (1..num_captures)
1242                .map(|i| capture_locations.get(i))
1243                .map(|o| {
1244                    o.map(|(start, end)| {
1245                        // Here, a surprising thing happens: we silently discard the original
1246                        // context. This is as intended, Nix does the same.
1247                        Value::from(&text[start..end])
1248                    })
1249                    .unwrap_or(Value::Null)
1250                })
1251                .collect();
1252            ret.push(Value::List(NixList::from(v)));
1253            if pos == text.len() {
1254                break;
1255            }
1256            pos = thematch.end();
1257        }
1258
1259        // push the unmatched characters following the last match
1260        // Here, a surprising thing happens: we silently discard the original
1261        // context. This is as intended, Nix does the same.
1262        ret.push(Value::from(&text[pos..]));
1263
1264        Ok(Value::List(NixList::from(ret)))
1265    }
1266
1267    #[builtin("sort")]
1268    async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1269        let list = list.to_list()?;
1270        let mut len = list.len();
1271        let mut data = list.into_inner();
1272
1273        // Asynchronous sorting algorithm in which the comparator can make use of
1274        // VM requests (required as `builtins.sort` uses comparators written in
1275        // Nix).
1276        //
1277        // This is a simple, optimised bubble sort implementation. The choice of
1278        // algorithm is constrained by the comparator in Nix not being able to
1279        // yield equality, and us being unable to use the standard library
1280        // implementation of sorting (which is a lot longer, but a lot more
1281        // efficient) here.
1282        // TODO(amjoseph): Investigate potential impl in Nix code, or Snix bytecode.
1283        loop {
1284            let mut new_len = 0;
1285            for i in 1..len {
1286                if try_value!(
1287                    generators::request_force(
1288                        &co,
1289                        generators::request_call_with(
1290                            &co,
1291                            comparator.clone(),
1292                            [data[i].clone(), data[i - 1].clone()],
1293                        )
1294                        .await,
1295                    )
1296                    .await
1297                )
1298                .as_bool()
1299                .context("evaluating comparator in `builtins.sort`")?
1300                {
1301                    data.swap(i, i - 1);
1302                    new_len = i;
1303                }
1304            }
1305
1306            if new_len == 0 {
1307                break;
1308            }
1309
1310            len = new_len;
1311        }
1312
1313        Ok(Value::List(data.into()))
1314    }
1315
1316    #[builtin("splitVersion")]
1317    async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1318        if s.is_catchable() {
1319            return Ok(s);
1320        }
1321        let s = s.to_str()?;
1322        let s = VersionPartsIter::new((&s).into());
1323
1324        let parts = s
1325            .map(|s| {
1326                Value::from(match s {
1327                    VersionPart::Number(n) => n,
1328                    VersionPart::Word(w) => w,
1329                })
1330            })
1331            .collect::<Vec<Value>>();
1332        Ok(Value::List(NixList::construct(parts.len(), parts)))
1333    }
1334
1335    #[builtin("stringLength")]
1336    async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1337        // also forces the value
1338        let span = generators::request_span(&co).await;
1339        let s = s
1340            .coerce_to_string(
1341                co,
1342                CoercionKind {
1343                    strong: false,
1344                    import_paths: true,
1345                },
1346                span,
1347            )
1348            .await?;
1349
1350        if s.is_catchable() {
1351            return Ok(s);
1352        }
1353
1354        Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1355    }
1356
1357    #[builtin("sub")]
1358    async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1359        arithmetic_op!(&x, &y, -)
1360    }
1361
1362    #[builtin("substring")]
1363    async fn builtin_substring(
1364        co: GenCo,
1365        start: Value,
1366        len: Value,
1367        s: Value,
1368    ) -> Result<Value, ErrorKind> {
1369        let beg = start.as_int()?;
1370        let len = len.as_int()?;
1371        let span = generators::request_span(&co).await;
1372        let x = s
1373            .coerce_to_string(
1374                co,
1375                CoercionKind {
1376                    strong: false,
1377                    import_paths: true,
1378                },
1379                span,
1380            )
1381            .await?;
1382        if x.is_catchable() {
1383            return Ok(x);
1384        }
1385        let x = x.to_contextful_str()?;
1386
1387        if beg < 0 {
1388            return Err(ErrorKind::IndexOutOfBounds { index: beg });
1389        }
1390        let beg = beg as usize;
1391
1392        // Nix doesn't assert that the length argument is
1393        // non-negative when the starting index is GTE the
1394        // string's length.
1395        if beg >= x.len() {
1396            return Ok(Value::from(NixString::new_inherit_context_from(
1397                &x,
1398                BString::default(),
1399            )));
1400        }
1401
1402        let end = if len < 0 {
1403            x.len()
1404        } else {
1405            cmp::min(beg + (len as usize), x.len())
1406        };
1407
1408        Ok(Value::from(NixString::new_inherit_context_from(
1409            &x,
1410            &x[beg..end],
1411        )))
1412    }
1413
1414    #[builtin("tail")]
1415    async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1416        if list.is_catchable() {
1417            return Ok(list);
1418        }
1419
1420        let xs = list.to_list()?;
1421
1422        if xs.is_empty() {
1423            Err(ErrorKind::TailEmptyList)
1424        } else {
1425            let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1426            Ok(Value::List(NixList::construct(output.len(), output)))
1427        }
1428    }
1429
1430    #[builtin("throw")]
1431    async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1432        // If it's already some error, let's propagate it immediately.
1433        if message.is_catchable() {
1434            return Ok(message);
1435        }
1436
1437        // TODO(sterni): coerce to string
1438        // We do not care about the context here explicitly.
1439        Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1440    }
1441
1442    #[builtin("toString")]
1443    async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1444        // TODO(edef): please fix me w.r.t. to catchability.
1445        // coerce_to_string forces for us
1446        // FIXME: should `coerce_to_string` preserve context?
1447        // it does for now.
1448        let span = generators::request_span(&co).await;
1449        x.coerce_to_string(
1450            co,
1451            CoercionKind {
1452                strong: true,
1453                import_paths: false,
1454            },
1455            span,
1456        )
1457        .await
1458    }
1459
1460    #[builtin("toXML")]
1461    async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1462        let value = generators::request_deep_force(&co, value).await;
1463        if value.is_catchable() {
1464            return Ok(value);
1465        }
1466
1467        let mut buf: Vec<u8> = vec![];
1468        let context = to_xml::value_to_xml(&mut buf, &value)?;
1469
1470        Ok(NixString::new_context_from(context, buf).into())
1471    }
1472
1473    #[builtin("trace")]
1474    async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1475        // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on
1476        // the VM
1477        eprintln!("trace: {} :: {}", message, message.type_of());
1478        Ok(value)
1479    }
1480
1481    #[builtin("toPath")]
1482    async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1483        if s.is_catchable() {
1484            return Ok(s);
1485        }
1486
1487        let path = try_cek_to_value!(coerce_value_to_path(&co, s).await?);
1488        let path: Value = crate::value::canon_path(path).into();
1489        let span = generators::request_span(&co).await;
1490        path.coerce_to_string(
1491            co,
1492            CoercionKind {
1493                strong: false,
1494                import_paths: false,
1495            },
1496            span,
1497        )
1498        .await
1499    }
1500
1501    #[builtin("tryEval")]
1502    async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1503        let res = match generators::request_try_force(&co, e).await {
1504            Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1505            value => [("value", value), ("success", true.into())],
1506        };
1507
1508        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1509    }
1510
1511    #[builtin("typeOf")]
1512    async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1513        if x.is_catchable() {
1514            return Ok(x);
1515        }
1516
1517        Ok(Value::from(x.type_of()))
1518    }
1519}
1520
1521/// Internal helper function for genericClosure, determining whether a
1522/// value has been seen before.
1523async fn bgc_insert_key(
1524    co: &GenCo,
1525    key: Value,
1526    done: &mut Vec<Value>,
1527) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1528    for existing in done.iter() {
1529        if try_cek!(
1530            generators::check_equality(
1531                co,
1532                existing.clone(),
1533                key.clone(),
1534                // TODO(tazjin): not actually sure which semantics apply here
1535                PointerEquality::ForbidAll,
1536            )
1537            .await?
1538        ) {
1539            return Ok(Ok(false));
1540        }
1541    }
1542
1543    done.push(key);
1544    Ok(Ok(true))
1545}
1546
1547/// The set of standard pure builtins in Nix, mostly concerned with
1548/// data structure manipulation (string, attrs, list, etc. functions).
1549pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1550    let mut result = pure_builtins::builtins(Rc::new(BuiltinState::default()));
1551
1552    // Pure-value builtins
1553    result.push(("nixVersion", Value::from("2.3.17-compat-snix-0.1")));
1554    result.push(("langVersion", Value::Integer(6)));
1555    result.push(("null", Value::Null));
1556    result.push(("true", Value::Bool(true)));
1557    result.push(("false", Value::Bool(false)));
1558
1559    result.push((
1560        "currentSystem",
1561        crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1562    ));
1563
1564    result.push((
1565        "__curPos",
1566        Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1567            // TODO: implement for nixpkgs compatibility
1568            Ok(Value::attrs(NixAttrs::from_iter([
1569                ("line", 42.into()),
1570                ("column", 42.into()),
1571                ("file", Value::String("/deep/thought".into())),
1572            ])))
1573        }))),
1574    ));
1575
1576    result
1577}
1578
1579#[builtins]
1580mod placeholder_builtins {
1581    use crate::NixContext;
1582
1583    use super::*;
1584
1585    #[builtin("unsafeDiscardStringContext")]
1586    async fn builtin_unsafe_discard_string_context(
1587        co: GenCo,
1588        s: Value,
1589    ) -> Result<Value, ErrorKind> {
1590        let span = generators::request_span(&co).await;
1591        let mut v = s
1592            .coerce_to_string(
1593                co,
1594                // It's weak because
1595                // lists, integers, floats and null are not
1596                // accepted as parameters.
1597                CoercionKind {
1598                    strong: false,
1599                    import_paths: true,
1600                },
1601                span,
1602            )
1603            .await?
1604            .to_contextful_str()?;
1605        v.clear_context();
1606        Ok(Value::from(v))
1607    }
1608
1609    #[builtin("unsafeDiscardOutputDependency")]
1610    async fn builtin_unsafe_discard_output_dependency(
1611        co: GenCo,
1612        s: Value,
1613    ) -> Result<Value, ErrorKind> {
1614        let span = generators::request_span(&co).await;
1615        let mut v = s
1616            .coerce_to_string(
1617                co,
1618                // It's weak because
1619                // lists, integers, floats and null are not
1620                // accepted as parameters.
1621                CoercionKind {
1622                    strong: false,
1623                    import_paths: true,
1624                },
1625                span,
1626            )
1627            .await?
1628            .to_contextful_str()?;
1629
1630        // If there's any context, we will swap any ... by a path one.
1631        if let Some(c) = v.take_context() {
1632            let mut context = NixContext::new();
1633            context.extend(c.into_iter().map(|elem| match elem {
1634                crate::NixContextElement::Derivation(drv_path) => {
1635                    crate::NixContextElement::Plain(drv_path.to_string())
1636                }
1637                elem => elem.clone(),
1638            }));
1639
1640            return Ok(Value::String(NixString::new_context_from(context, v)));
1641        }
1642        Ok(Value::from(v))
1643    }
1644
1645    #[builtin("addErrorContext")]
1646    async fn builtin_add_error_context(
1647        co: GenCo,
1648        #[lazy] _context: Value,
1649        #[lazy] val: Value,
1650    ) -> Result<Value, ErrorKind> {
1651        generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1652            .await;
1653        Ok(val)
1654    }
1655
1656    #[builtin("unsafeGetAttrPos")]
1657    async fn builtin_unsafe_get_attr_pos(
1658        co: GenCo,
1659        _name: Value,
1660        _attrset: Value,
1661    ) -> Result<Value, ErrorKind> {
1662        // TODO: implement for nixpkgs compatibility
1663        generators::emit_warning_kind(
1664            &co,
1665            WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1666        )
1667        .await;
1668        let res = [
1669            ("line", 42.into()),
1670            ("column", 42.into()),
1671            ("file", Value::String("/deep/thought".into())),
1672        ];
1673        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1674    }
1675}
1676
1677pub fn placeholders() -> Vec<(&'static str, Value)> {
1678    placeholder_builtins::builtins()
1679}