Skip to main content

tvix_eval/builtins/
mod.rs

1//! This module implements the builtins exposed in the Nix language.
2//!
3//! See //tvix/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::cmp::{self, Ordering};
12use std::collections::BTreeMap;
13use std::collections::VecDeque;
14use std::path::PathBuf;
15use std::sync::{Mutex, OnceLock};
16
17use crate::arithmetic_op;
18use crate::value::PointerEquality;
19use crate::vm::generators::{self, GenCo};
20use crate::warnings::WarningKind;
21use crate::{
22    self as tvix_eval,
23    builtins::hash::hash_nix_string,
24    errors::{CatchableErrorKind, ErrorKind},
25    value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
26};
27
28use self::versions::{VersionPart, VersionPartsIter};
29
30mod hash;
31mod to_xml;
32mod versions;
33
34#[cfg(test)]
35pub use to_xml::value_to_xml;
36
37#[cfg(feature = "impure")]
38mod impure;
39
40#[cfg(feature = "impure")]
41pub use impure::impure_builtins;
42
43// we set TVIX_CURRENT_SYSTEM in build.rs
44pub const CURRENT_PLATFORM: &str = env!("TVIX_CURRENT_SYSTEM");
45
46/// Coerce a Nix Value to a plain path, e.g. in order to access the
47/// file it points to via either `builtins.toPath` or an impure
48/// builtin. This coercion can _never_ be performed in a Nix program
49/// without using builtins (i.e. the trick `path: /. + path` to
50/// convert from a string to a path wouldn't hit this code).
51///
52/// This operation doesn't import a Nix path value into the store.
53pub async fn coerce_value_to_path(
54    co: &GenCo,
55    v: Value,
56) -> Result<Result<PathBuf, CatchableErrorKind>, ErrorKind> {
57    let value = generators::request_force(co, v).await;
58    if let Value::Path(p) = value {
59        return Ok(Ok(*p));
60    }
61
62    match generators::request_string_coerce(
63        co,
64        value,
65        CoercionKind {
66            strong: false,
67            import_paths: false,
68        },
69    )
70    .await
71    {
72        Ok(vs) => {
73            let path = vs.to_path()?.to_owned();
74            if path.is_absolute() {
75                Ok(Ok(path))
76            } else {
77                Err(ErrorKind::NotAnAbsolutePath(path))
78            }
79        }
80        Err(cek) => Ok(Err(cek)),
81    }
82}
83
84static REGEX_CACHE: OnceLock<Mutex<FxHashMap<String, Regex>>> = OnceLock::new();
85
86fn cached_regex(pattern: &str) -> Result<Regex, regex::Error> {
87    let cache = REGEX_CACHE.get_or_init(|| Mutex::new(Default::default()));
88    let mut map = cache.lock().unwrap();
89
90    match map.get(pattern) {
91        Some(regex) => Ok(regex.clone()),
92        None => {
93            let regex = Regex::new(pattern)?;
94            map.insert(pattern.to_string(), regex.clone());
95            Ok(regex)
96        }
97    }
98}
99
100#[builtins]
101mod pure_builtins {
102    use std::ffi::OsString;
103
104    use bstr::{B, BString, ByteSlice};
105    use itertools::Itertools;
106    use os_str_bytes::OsStringBytes;
107    use rustc_hash::{FxHashMap, FxHashSet};
108
109    use crate::{AddContext, NixContext, NixContextElement, value::PointerEquality};
110
111    use super::*;
112
113    macro_rules! try_value {
114        ($value:expr) => {{
115            let val = $value;
116            if val.is_catchable() {
117                return Ok(val);
118            }
119            val
120        }};
121    }
122
123    #[builtin("abort")]
124    async fn builtin_abort(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
125        // TODO(sterni): coerces to string
126        // Although `abort` does not make use of any context,
127        // we must still accept contextful strings as parameters.
128        // If `to_str` was used, this would err out with an unexpected type error.
129        // Therefore, we explicitly accept contextful strings and ignore their contexts.
130        Err(ErrorKind::Abort(message.to_contextful_str()?.to_string()))
131    }
132
133    #[builtin("add")]
134    async fn builtin_add(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
135        arithmetic_op!(&x, &y, +)
136    }
137
138    #[builtin("all")]
139    async fn builtin_all(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
140        for value in list.to_list()?.into_iter() {
141            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
142            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
143
144            if !pred_result.as_bool()? {
145                return Ok(Value::Bool(false));
146            }
147        }
148
149        Ok(Value::Bool(true))
150    }
151
152    #[builtin("any")]
153    async fn builtin_any(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
154        for value in list.to_list()?.into_iter() {
155            let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
156            let pred_result = try_value!(generators::request_force(&co, pred_result).await);
157
158            if pred_result.as_bool()? {
159                return Ok(Value::Bool(true));
160            }
161        }
162
163        Ok(Value::Bool(false))
164    }
165
166    #[builtin("attrNames")]
167    async fn builtin_attr_names(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
168        let xs = set.to_attrs()?;
169        let mut output = Vec::with_capacity(xs.len());
170
171        for (key, _val) in xs.iter_sorted() {
172            output.push(Value::from(key.clone()));
173        }
174
175        Ok(Value::List(NixList::construct(output.len(), output)))
176    }
177
178    #[builtin("attrValues")]
179    async fn builtin_attr_values(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
180        let xs = set.to_attrs()?;
181        let mut output = Vec::with_capacity(xs.len());
182
183        for (_key, val) in xs.iter_sorted() {
184            output.push(val.clone());
185        }
186
187        Ok(Value::List(NixList::construct(output.len(), output)))
188    }
189
190    #[builtin("baseNameOf")]
191    async fn builtin_base_name_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
192        let span = generators::request_span(&co).await;
193        let s = s
194            .coerce_to_string(
195                co,
196                CoercionKind {
197                    strong: false,
198                    import_paths: false,
199                },
200                span,
201            )
202            .await?
203            .to_contextful_str()?;
204
205        let mut bs = (**s).to_owned();
206        if let Some(last_slash) = bs.rfind_char('/') {
207            bs = bs[(last_slash + 1)..].into();
208        }
209        Ok(NixString::new_inherit_context_from(&s, bs).into())
210    }
211
212    #[builtin("bitAnd")]
213    async fn builtin_bit_and(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
214        Ok(Value::Integer(x.as_int()? & y.as_int()?))
215    }
216
217    #[builtin("bitOr")]
218    async fn builtin_bit_or(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
219        Ok(Value::Integer(x.as_int()? | y.as_int()?))
220    }
221
222    #[builtin("bitXor")]
223    async fn builtin_bit_xor(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
224        Ok(Value::Integer(x.as_int()? ^ y.as_int()?))
225    }
226
227    #[builtin("catAttrs")]
228    async fn builtin_cat_attrs(co: GenCo, key: Value, list: Value) -> Result<Value, ErrorKind> {
229        let key = key.to_str()?;
230        let list = list.to_list()?;
231        let mut output = vec![];
232
233        for item in list.into_iter() {
234            let set = generators::request_force(&co, item).await.to_attrs()?;
235
236            if let Some(value) = set.select(&key) {
237                output.push(value.clone());
238            }
239        }
240
241        Ok(Value::List(NixList::construct(output.len(), output)))
242    }
243
244    #[builtin("ceil")]
245    async fn builtin_ceil(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
246        Ok(Value::Integer(double.as_float()?.ceil() as i64))
247    }
248
249    #[builtin("compareVersions")]
250    async fn builtin_compare_versions(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
251        let s1 = x.to_str()?;
252        let s1 = VersionPartsIter::new_for_cmp((&s1).into());
253        let s2 = y.to_str()?;
254        let s2 = VersionPartsIter::new_for_cmp((&s2).into());
255
256        match s1.cmp(s2) {
257            std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
258            std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
259            std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
260        }
261    }
262
263    #[builtin("concatLists")]
264    async fn builtin_concat_lists(co: GenCo, lists: Value) -> Result<Value, ErrorKind> {
265        let mut out = Vec::new();
266
267        for value in lists.to_list()? {
268            let list = try_value!(generators::request_force(&co, value).await).to_list()?;
269            out.extend(list);
270        }
271
272        Ok(Value::List(out.into()))
273    }
274
275    #[builtin("concatMap")]
276    async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
277        let list = list.to_list()?;
278        let mut res = Vec::new();
279        for val in list {
280            let out = generators::request_call_with(&co, f.clone(), [val]).await;
281            let out = try_value!(generators::request_force(&co, out).await);
282            res.extend(out.to_list()?);
283        }
284        Ok(Value::List(res.into()))
285    }
286
287    #[builtin("concatStringsSep")]
288    async fn builtin_concat_strings_sep(
289        co: GenCo,
290        separator: Value,
291        list: Value,
292    ) -> Result<Value, ErrorKind> {
293        let mut separator = separator.to_contextful_str()?;
294
295        let mut context = NixContext::new();
296        if let Some(sep_context) = separator.take_context() {
297            context.extend(*sep_context)
298        }
299        let list = list.to_list()?;
300        let mut res = BString::default();
301        for (i, val) in list.into_iter().enumerate() {
302            if i != 0 {
303                res.push_str(&separator);
304            }
305            match generators::request_string_coerce(
306                &co,
307                val,
308                CoercionKind {
309                    strong: false,
310                    import_paths: true,
311                },
312            )
313            .await
314            {
315                Ok(mut s) => {
316                    res.push_str(&s);
317                    if let Some(other_context) = s.take_context() {
318                        context.extend(*other_context);
319                    }
320                }
321                Err(c) => return Ok(Value::Catchable(Box::new(c))),
322            }
323        }
324        // FIXME: pass immediately the string res.
325        Ok(NixString::new_context_from(context, res).into())
326    }
327
328    #[builtin("deepSeq")]
329    async fn builtin_deep_seq(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
330        generators::request_deep_force(&co, x).await;
331        Ok(y)
332    }
333
334    #[builtin("div")]
335    async fn builtin_div(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
336        arithmetic_op!(&x, &y, /)
337    }
338
339    #[builtin("dirOf")]
340    async fn builtin_dir_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
341        let is_path = s.is_path();
342        let span = generators::request_span(&co).await;
343        let str = s
344            .coerce_to_string(
345                co,
346                CoercionKind {
347                    strong: false,
348                    import_paths: false,
349                },
350                span,
351            )
352            .await?
353            .to_contextful_str()?;
354        let result = str
355            .rfind_char('/')
356            .map(|last_slash| {
357                let x = &str[..last_slash];
358                if x.is_empty() { B("/") } else { x }
359            })
360            .unwrap_or(b".");
361        if is_path {
362            Ok(Value::Path(Box::new(PathBuf::from(
363                OsString::assert_from_raw_vec(result.to_owned()),
364            ))))
365        } else {
366            Ok(Value::from(NixString::new_inherit_context_from(
367                &str, result,
368            )))
369        }
370    }
371
372    #[builtin("elem")]
373    async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
374        for val in xs.to_list()? {
375            match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
376            {
377                Ok(true) => return Ok(true.into()),
378                Ok(false) => continue,
379                Err(cek) => return Ok(Value::from(cek)),
380            }
381        }
382        Ok(false.into())
383    }
384
385    #[builtin("elemAt")]
386    async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
387        let xs = xs.to_list()?;
388        let i = i.as_int()?;
389        if i < 0 {
390            Err(ErrorKind::IndexOutOfBounds { index: i })
391        } else {
392            match xs.get(i as usize) {
393                Some(x) => Ok(x.clone()),
394                None => Err(ErrorKind::IndexOutOfBounds { index: i }),
395            }
396        }
397    }
398
399    #[builtin("filter")]
400    async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
401        let list: NixList = list.to_list()?;
402        let mut out = Vec::new();
403
404        for value in list {
405            let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
406            let verdict = try_value!(generators::request_force(&co, result).await);
407            if verdict.as_bool()? {
408                out.push(value);
409            }
410        }
411
412        Ok(Value::List(out.into()))
413    }
414
415    #[builtin("floor")]
416    async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
417        Ok(Value::Integer(double.as_float()?.floor() as i64))
418    }
419
420    #[builtin("foldl'")]
421    async fn builtin_foldl(
422        co: GenCo,
423        op: Value,
424        #[lazy] nul: Value,
425        list: Value,
426    ) -> Result<Value, ErrorKind> {
427        let mut nul = nul;
428        let list = list.to_list()?;
429        for val in list {
430            // Every call of `op` is forced immediately, but `nul` is not, see
431            // https://github.com/NixOS/nix/blob/940e9eb8/src/libexpr/primops.cc#L3069-L3070C36
432            // and our tests for foldl'.
433            nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
434            nul = generators::request_force(&co, nul).await;
435            if let c @ Value::Catchable(_) = nul {
436                return Ok(c);
437            }
438        }
439
440        Ok(nul)
441    }
442
443    #[builtin("functionArgs")]
444    async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
445        let lambda = &f.as_closure()?.lambda();
446        let formals = if let Some(formals) = &lambda.formals {
447            formals
448        } else {
449            return Ok(Value::attrs(NixAttrs::empty()));
450        };
451        Ok(Value::attrs(NixAttrs::from_iter(
452            formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
453        )))
454    }
455
456    #[builtin("fromJSON")]
457    async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
458        let json_str = json.to_str()?;
459        serde_json::from_slice(&json_str).map_err(|err| err.into())
460    }
461
462    #[builtin("toJSON")]
463    async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
464        match val.into_contextful_json(&co).await {
465            Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
466            Err(err) => Err(err),
467            Ok((json, context)) => {
468                let json_str = serde_json::to_string(&json)
469                    .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
470                Ok(Value::String(NixString::new_context_from(
471                    context, json_str,
472                )))
473            }
474        }
475    }
476
477    #[builtin("fromTOML")]
478    async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
479        let toml_str = toml.to_str()?;
480
481        toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
482    }
483
484    #[builtin("genericClosure")]
485    async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
486        let attrs = input.to_attrs()?;
487
488        // The work set is maintained as a VecDeque because new items
489        // are popped from the front.
490        let mut work_set: VecDeque<Value> =
491            generators::request_force(&co, attrs.select_required("startSet")?.clone())
492                .await
493                .to_list()?
494                .into_iter()
495                .collect();
496
497        let operator = attrs.select_required("operator")?;
498
499        let mut res = Vec::new();
500        let mut done_keys: Vec<Value> = vec![];
501
502        while let Some(val) = work_set.pop_front() {
503            let val = generators::request_force(&co, val).await;
504            let attrs = val.to_attrs()?;
505            let key = attrs.select_required("key")?;
506
507            let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?;
508
509            if let Err(cek) = value_missing {
510                return Ok(Value::Catchable(Box::new(cek)));
511            }
512
513            if let Ok(false) = value_missing {
514                continue;
515            }
516
517            res.push(val.clone());
518
519            let op_result = generators::request_force(
520                &co,
521                generators::request_call_with(&co, operator.clone(), [val]).await,
522            )
523            .await;
524
525            work_set.extend(op_result.to_list()?);
526        }
527
528        Ok(Value::List(NixList::from(res)))
529    }
530
531    #[builtin("genList")]
532    async fn builtin_gen_list(
533        co: GenCo,
534        // Nix 2.3 doesn't propagate failures here
535        #[lazy] generator: Value,
536        length: Value,
537    ) -> Result<Value, ErrorKind> {
538        let len = length.as_int()?;
539        let mut out = Vec::with_capacity(
540            len.try_into()
541                .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
542        );
543
544        // the best span we can get…
545        let span = generators::request_span(&co).await;
546
547        for i in 0..len {
548            let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
549            out.push(val);
550        }
551
552        Ok(Value::List(out.into()))
553    }
554
555    #[builtin("getAttr")]
556    async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
557        let k = key.to_str()?;
558        let xs = set.to_attrs()?;
559
560        match xs.select(&k) {
561            Some(x) => Ok(x.clone()),
562            None => Err(ErrorKind::AttributeNotFound {
563                name: k.to_string(),
564            }),
565        }
566    }
567
568    #[builtin("groupBy")]
569    async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
570        let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
571        for val in list.to_list()? {
572            let key = try_value!(
573                generators::request_force(
574                    &co,
575                    generators::request_call_with(&co, f.clone(), [val.clone()]).await,
576                )
577                .await
578            )
579            .to_str()?;
580
581            res.entry(key).or_default().push(val);
582        }
583        Ok(Value::attrs(NixAttrs::from_iter(
584            res.into_iter()
585                .map(|(k, v)| (k, Value::List(NixList::from(v)))),
586        )))
587    }
588
589    #[builtin("hasAttr")]
590    async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
591        let k = key.to_str()?;
592        let xs = set.to_attrs()?;
593
594        Ok(Value::Bool(xs.contains(&k)))
595    }
596
597    #[builtin("hasContext")]
598    #[allow(non_snake_case)]
599    async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
600        if e.is_catchable() {
601            return Ok(e);
602        }
603
604        let v = e.to_contextful_str()?;
605        Ok(Value::Bool(v.has_context()))
606    }
607
608    #[builtin("getContext")]
609    #[allow(non_snake_case)]
610    async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
611        if e.is_catchable() {
612            return Ok(e);
613        }
614
615        // also forces the value
616        let span = generators::request_span(&co).await;
617        let v = e
618            .coerce_to_string(
619                co,
620                CoercionKind {
621                    strong: true,
622                    import_paths: true,
623                },
624                span,
625            )
626            .await?;
627        let s = v.to_contextful_str()?;
628
629        let groups = s
630            .iter_context()
631            .flat_map(|context| context.iter())
632            // Do not think `group_by` works here.
633            // `group_by` works on consecutive elements of the iterator.
634            // Due to how `HashSet` works (ordering is not guaranteed),
635            // this can become a source of non-determinism if you `group_by` naively.
636            // I know I did.
637            .into_grouping_map_by(|ctx_element| match ctx_element {
638                NixContextElement::Plain(spath) => spath,
639                NixContextElement::Single { derivation, .. } => derivation,
640                NixContextElement::Derivation(drv_path) => drv_path,
641            })
642            .collect::<Vec<_>>();
643
644        let elements = groups
645            .into_iter()
646            .map(|(key, group)| {
647                let mut outputs: Vec<NixString> = Vec::new();
648                let mut is_path = false;
649                let mut all_outputs = false;
650
651                for ctx_element in group {
652                    match ctx_element {
653                        NixContextElement::Plain(spath) => {
654                            debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
655                            is_path = true;
656                        }
657
658                        NixContextElement::Single { name, derivation } => {
659                            debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
660                            outputs.push(name.clone().into());
661                        }
662
663                        NixContextElement::Derivation(drv_path) => {
664                            debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
665                            all_outputs = true;
666                        }
667                    }
668                }
669
670                // FIXME(raitobezarius): is there a better way to construct an attribute set
671                // conditionally?
672                let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
673
674                if is_path {
675                    vec_attrs.push(("path", true.into()));
676                }
677
678                if all_outputs {
679                    vec_attrs.push(("allOutputs", true.into()));
680                }
681
682                if !outputs.is_empty() {
683                    outputs.sort();
684                    vec_attrs.push(("outputs", Value::List(outputs
685                                .into_iter()
686                                .map(|s| s.into())
687                                .collect::<Vec<Value>>()
688                                .into()
689                    )));
690                }
691
692                (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs)))
693            });
694
695        Ok(Value::attrs(NixAttrs::from_iter(elements)))
696    }
697
698    #[builtin("appendContext")]
699    #[allow(non_snake_case)]
700    async fn builtin_appendContext(
701        co: GenCo,
702        origin: Value,
703        added_context: Value,
704    ) -> Result<Value, ErrorKind> {
705        // `appendContext` is a "grow" context function.
706        // It cannot remove a context element, neither replace a piece of its contents.
707        //
708        // Growing context is always a safe operation, there's no loss of dependency tracking
709        // information.
710        //
711        // This is why this operation is not prefixed by `unsafe` and is deemed *safe*.
712        // Nonetheless, it is possible to craft nonsensical context elements referring
713        // to inexistent derivations, output paths or output names.
714        //
715        // In Nix, those nonsensical context elements are partially mitigated by checking
716        // that various parameters are indeed syntatically valid store paths in the context, i.e.
717        // starting with the same prefix as `builtins.storeDir`, or ending with `.drv`.
718        // In addition, if writing to the store is possible (evaluator not in read-only mode), Nix
719        // will realize some paths and ensures they are present in the store.
720        //
721        // In this implementation, we do none of that, no syntax checks, no realization.
722        // The next `TODO` are the checks that Nix implements.
723        let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
724        let span = generators::request_span(&co).await;
725        let origin = origin
726            .coerce_to_string(
727                co,
728                CoercionKind {
729                    strong: true,
730                    import_paths: true,
731                },
732                span,
733            )
734            .await?;
735        let mut origin = origin.to_contextful_str()?;
736
737        let added_context = added_context.to_attrs()?;
738        for (context_key, context_element) in added_context.into_iter() {
739            // Invariant checks:
740            // - TODO: context_key must be a syntactically valid store path.
741            // - Perform a deep force `context_element`.
742            let context_element = context_element.to_attrs()?;
743            if let Some(path) = context_element.select_str("path") {
744                if path.as_bool()? {
745                    ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
746                }
747            }
748            if let Some(all_outputs) = context_element.select_str("allOutputs") {
749                if all_outputs.as_bool()? {
750                    // TODO: check if `context_key` is a derivation path.
751                    // This may require realization.
752                    ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
753                }
754            }
755            if let Some(some_outputs) = context_element.select_str("outputs") {
756                let some_outputs = some_outputs.to_list()?;
757                // TODO: check if `context_key` is a derivation path.
758                // This may require realization.
759                for output in some_outputs.into_iter() {
760                    let output = output.to_str()?;
761                    ctx_elements.insert(NixContextElement::Single {
762                        derivation: context_key.to_string(),
763                        name: output.to_string(),
764                    });
765                }
766            }
767        }
768
769        if let Some(origin_ctx) = origin.context_mut() {
770            origin_ctx.extend(ctx_elements)
771            // TODO: didn't we forget cases where origin had no context?
772        }
773
774        Ok(origin.into())
775    }
776
777    #[builtin("hashString")]
778    async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
779        hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
780    }
781
782    #[builtin("head")]
783    async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
784        if list.is_catchable() {
785            return Ok(list);
786        }
787
788        match list.to_list()?.get(0) {
789            Some(x) => Ok(x.clone()),
790            None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
791        }
792    }
793
794    #[builtin("intersectAttrs")]
795    async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
796        if x.is_catchable() {
797            return Ok(x);
798        }
799        if y.is_catchable() {
800            return Ok(y);
801        }
802        let left_set = x.to_attrs()?;
803        if left_set.is_empty() {
804            return Ok(Value::attrs(NixAttrs::empty()));
805        }
806
807        let right_set = y.to_attrs()?;
808
809        if right_set.is_empty() {
810            return Ok(Value::attrs(NixAttrs::empty()));
811        }
812
813        Ok(Value::attrs(left_set.intersect(&right_set)))
814    }
815
816    #[builtin("isAttrs")]
817    async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
818        // TODO(edef): make this beautiful
819        if value.is_catchable() {
820            return Ok(value);
821        }
822
823        Ok(Value::Bool(matches!(value, Value::Attrs(_))))
824    }
825
826    #[builtin("isBool")]
827    async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
828        if value.is_catchable() {
829            return Ok(value);
830        }
831
832        Ok(Value::Bool(matches!(value, Value::Bool(_))))
833    }
834
835    #[builtin("isFloat")]
836    async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
837        if value.is_catchable() {
838            return Ok(value);
839        }
840
841        Ok(Value::Bool(matches!(value, Value::Float(_))))
842    }
843
844    #[builtin("isFunction")]
845    async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
846        if value.is_catchable() {
847            return Ok(value);
848        }
849
850        Ok(Value::Bool(matches!(
851            value,
852            Value::Closure(_) | Value::Builtin(_)
853        )))
854    }
855
856    #[builtin("isInt")]
857    async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
858        if value.is_catchable() {
859            return Ok(value);
860        }
861
862        Ok(Value::Bool(matches!(value, Value::Integer(_))))
863    }
864
865    #[builtin("isList")]
866    async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
867        if value.is_catchable() {
868            return Ok(value);
869        }
870
871        Ok(Value::Bool(matches!(value, Value::List(_))))
872    }
873
874    #[builtin("isNull")]
875    async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
876        if value.is_catchable() {
877            return Ok(value);
878        }
879
880        Ok(Value::Bool(matches!(value, Value::Null)))
881    }
882
883    #[builtin("isPath")]
884    async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
885        if value.is_catchable() {
886            return Ok(value);
887        }
888
889        Ok(Value::Bool(matches!(value, Value::Path(_))))
890    }
891
892    #[builtin("isString")]
893    async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
894        if value.is_catchable() {
895            return Ok(value);
896        }
897
898        Ok(Value::Bool(matches!(value, Value::String(_))))
899    }
900
901    #[builtin("length")]
902    async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
903        if list.is_catchable() {
904            return Ok(list);
905        }
906        Ok(Value::Integer(list.to_list()?.len() as i64))
907    }
908
909    #[builtin("lessThan")]
910    async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
911        let span = generators::request_span(&co).await;
912        match x.nix_cmp_ordering(y, co, span).await? {
913            Err(cek) => Ok(Value::from(cek)),
914            Ok(Ordering::Less) => Ok(Value::Bool(true)),
915            Ok(_) => Ok(Value::Bool(false)),
916        }
917    }
918
919    #[builtin("listToAttrs")]
920    async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
921        let list = list.to_list()?;
922        let mut map = FxHashMap::default();
923        for val in list {
924            let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
925            let name = try_value!(
926                generators::request_force(&co, attrs.select_required("name")?.clone()).await
927            )
928            .to_str()?;
929            let value = attrs.select_required("value")?.clone();
930            // Map entries earlier in the list take precedence over entries later in the list
931            map.entry(name).or_insert(value);
932        }
933        Ok(Value::attrs(NixAttrs::from(map)))
934    }
935
936    #[builtin("map")]
937    async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
938        let list = list_val.to_list()?;
939        let mut out = Vec::with_capacity(list.len());
940
941        // the best span we can get…
942        let span = generators::request_span(&co).await;
943
944        for val in list {
945            let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
946            out.push(result)
947        }
948
949        Ok(Value::List(out.into()))
950    }
951
952    #[builtin("mapAttrs")]
953    async fn builtin_map_attrs(
954        co: GenCo,
955        #[lazy] f: Value,
956        attrs: Value,
957    ) -> Result<Value, ErrorKind> {
958        let attrs = attrs.to_attrs()?;
959        let mut out = FxHashMap::default();
960
961        // the best span we can get…
962        let span = generators::request_span(&co).await;
963
964        for (key, value) in attrs.into_iter() {
965            let result = Value::Thunk(Thunk::new_suspended_call(
966                f.clone(),
967                key.clone().into(),
968                span,
969            ));
970            let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
971
972            out.insert(key, result);
973        }
974
975        Ok(Value::attrs(out.into()))
976    }
977
978    #[builtin("match")]
979    async fn builtin_match(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
980        let s = str;
981        if s.is_catchable() {
982            return Ok(s);
983        }
984        let s = s.to_contextful_str()?;
985        let re = regex;
986        if re.is_catchable() {
987            return Ok(re);
988        }
989        let re = re.to_str()?;
990        let re: Regex =
991            cached_regex(&format!("^{}$", re.to_str()?)).expect("TODO(tazjin): propagate error");
992
993        match re.captures(s.to_str()?) {
994            Some(caps) => Ok(Value::List(
995                caps.iter()
996                    .skip(1)
997                    .map(|grp| {
998                        // Surprisingly, Nix does not propagate
999                        // the original context here.
1000                        // Though, it accepts contextful strings as an argument.
1001                        // An example of such behaviors in nixpkgs
1002                        // can be observed in make-initrd.nix when it comes
1003                        // to compressors which are matched over their full command
1004                        // and then a compressor name will be extracted from that.
1005                        grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
1006                    })
1007                    .collect::<Vec<Value>>()
1008                    .into(),
1009            )),
1010            None => Ok(Value::Null),
1011        }
1012    }
1013
1014    #[builtin("mul")]
1015    async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1016        arithmetic_op!(&x, &y, *)
1017    }
1018
1019    #[builtin("parseDrvName")]
1020    async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1021        if s.is_catchable() {
1022            return Ok(s);
1023        }
1024
1025        // This replicates cppnix's (mis?)handling of codepoints
1026        // above U+007f following 0x2d ('-')
1027        let s = s.to_str()?;
1028        let slice: &[u8] = s.as_ref();
1029        let (name, dash_and_version) = slice.split_at(
1030            slice
1031                .windows(2)
1032                .enumerate()
1033                .find_map(|x| match x {
1034                    (idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
1035                    _ => None,
1036                })
1037                .unwrap_or(slice.len()),
1038        );
1039        let version = dash_and_version
1040            .split_first()
1041            .map(|x| core::str::from_utf8(x.1))
1042            .unwrap_or(Ok(""))?;
1043        Ok(Value::attrs(NixAttrs::from_iter([
1044            ("name", core::str::from_utf8(name)?),
1045            ("version", version),
1046        ])))
1047    }
1048
1049    #[builtin("partition")]
1050    async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
1051        let mut right: Vec<Value> = Default::default();
1052        let mut wrong: Vec<Value> = Default::default();
1053
1054        let list: NixList = list.to_list()?;
1055        for elem in list {
1056            let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
1057
1058            if try_value!(generators::request_force(&co, result).await).as_bool()? {
1059                right.push(elem);
1060            } else {
1061                wrong.push(elem);
1062            };
1063        }
1064
1065        let res = [
1066            ("right", Value::List(NixList::from(right))),
1067            ("wrong", Value::List(NixList::from(wrong))),
1068        ];
1069
1070        Ok(Value::attrs(NixAttrs::from_iter(res)))
1071    }
1072
1073    #[builtin("removeAttrs")]
1074    async fn builtin_remove_attrs(
1075        co: GenCo,
1076        attrs: Value,
1077        keys: Value,
1078    ) -> Result<Value, ErrorKind> {
1079        let attrs = attrs.to_attrs()?;
1080        let keys = keys
1081            .to_list()?
1082            .into_iter()
1083            .map(|v| v.to_str())
1084            .collect::<Result<FxHashSet<_>, _>>()?;
1085        let res = attrs.iter().filter_map(|(k, v)| {
1086            if !keys.contains(k) {
1087                Some((k.clone(), v.clone()))
1088            } else {
1089                None
1090            }
1091        });
1092        Ok(Value::attrs(NixAttrs::from_iter(res)))
1093    }
1094
1095    #[builtin("replaceStrings")]
1096    async fn builtin_replace_strings(
1097        co: GenCo,
1098        from: Value,
1099        to: Value,
1100        s: Value,
1101    ) -> Result<Value, ErrorKind> {
1102        let from = from.to_list()?;
1103        for val in &from {
1104            try_value!(generators::request_force(&co, val.clone()).await);
1105        }
1106
1107        let to = to.to_list()?;
1108        for val in &to {
1109            try_value!(generators::request_force(&co, val.clone()).await);
1110        }
1111
1112        let mut string = s.to_contextful_str()?;
1113
1114        let mut res = BString::default();
1115
1116        let mut i: usize = 0;
1117        let mut empty_string_replace = false;
1118        let mut context = NixContext::new();
1119
1120        if let Some(string_context) = string.take_context() {
1121            context.extend(*string_context);
1122        }
1123
1124        // This can't be implemented using Rust's string.replace() as
1125        // well as a map because we need to handle errors with results
1126        // as well as "reset" the iterator to zero for the replacement
1127        // everytime there's a successful match.
1128        // Also, Rust's string.replace allocates a new string
1129        // on every call which is not preferable.
1130        'outer: while i < string.len() {
1131            // Try a match in all the from strings
1132            for elem in std::iter::zip(from.iter(), to.iter()) {
1133                let from = elem.0.to_contextful_str()?;
1134                let mut to = elem.1.to_contextful_str()?;
1135
1136                if i + from.len() > string.len() {
1137                    continue;
1138                }
1139
1140                // We already applied a from->to with an empty from
1141                // transformation.
1142                // Let's skip it so that we don't loop infinitely
1143                if empty_string_replace && from.is_empty() {
1144                    continue;
1145                }
1146
1147                // if we match the `from` string, let's replace
1148                if string[i..i + from.len()] == *from {
1149                    res.push_str(&to);
1150                    i += from.len();
1151                    if let Some(to_ctx) = to.take_context() {
1152                        context.extend(*to_ctx);
1153                    }
1154
1155                    // remember if we applied the empty from->to
1156                    empty_string_replace = from.is_empty();
1157
1158                    continue 'outer;
1159                }
1160            }
1161
1162            // If we don't match any `from`, we simply add a character
1163            res.push_str(&string[i..i + 1]);
1164            i += 1;
1165
1166            // Since we didn't apply anything transformation,
1167            // we reset the empty string replacement
1168            empty_string_replace = false;
1169        }
1170
1171        // Special case when the string is empty or at the string's end
1172        // and one of the from is also empty
1173        for elem in std::iter::zip(from.iter(), to.iter()) {
1174            let from = elem.0.to_contextful_str()?;
1175            // We mutate `to` by consuming its context
1176            // if we perform a successful replacement.
1177            // Therefore, it's fine if `to` was mutate and we reuse it here.
1178            // We don't need to merge again the context, it's already in the right state.
1179            let mut to = elem.1.to_contextful_str()?;
1180
1181            if from.is_empty() {
1182                res.push_str(&to);
1183                if let Some(to_ctx) = to.take_context() {
1184                    context.extend(*to_ctx);
1185                }
1186                break;
1187            }
1188        }
1189
1190        Ok(Value::from(NixString::new_context_from(context, res)))
1191    }
1192
1193    #[builtin("seq")]
1194    async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
1195        // The builtin calling infra has already forced both args for us, so
1196        // we just return the second and ignore the first
1197        Ok(y)
1198    }
1199
1200    #[builtin("split")]
1201    async fn builtin_split(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
1202        if str.is_catchable() {
1203            return Ok(str);
1204        }
1205
1206        if regex.is_catchable() {
1207            return Ok(regex);
1208        }
1209
1210        let s = str.to_contextful_str()?;
1211        let text = s.to_str()?;
1212        let re = regex.to_str()?;
1213        let re = cached_regex(re.to_str()?).unwrap();
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        let mut prev_match_end = 0;
1219
1220        while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1221            // push the unmatched characters preceding the match
1222            ret.push(Value::from(NixString::new_inherit_context_from(
1223                &s,
1224                &text[prev_match_end..thematch.start()],
1225            )));
1226
1227            // Push a list with one element for each capture
1228            // group in the regex, containing the characters
1229            // matched by that capture group, or null if no match.
1230            // We skip capture 0; it represents the whole match.
1231            let v: Vec<Value> = (1..num_captures)
1232                .map(|i| capture_locations.get(i))
1233                .map(|o| {
1234                    o.map(|(start, end)| {
1235                        // Here, a surprising thing happens: we silently discard the original
1236                        // context. This is as intended, Nix does the same.
1237                        Value::from(&text[start..end])
1238                    })
1239                    .unwrap_or(Value::Null)
1240                })
1241                .collect();
1242            ret.push(Value::List(NixList::from(v)));
1243            if pos == text.len() {
1244                break;
1245            }
1246
1247            // if the regex matches the empty string, we need to advance, but also
1248            // correctly track the span between matches
1249            prev_match_end = thematch.end();
1250            pos = std::cmp::max(pos + 1, prev_match_end);
1251        }
1252
1253        // push the unmatched characters following the last match
1254        // Here, a surprising thing happens: we silently discard the original
1255        // context. This is as intended, Nix does the same.
1256        ret.push(Value::from(&text[pos..]));
1257
1258        Ok(Value::List(NixList::from(ret)))
1259    }
1260
1261    #[builtin("sort")]
1262    async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1263        let list = list.to_list()?;
1264        let mut len = list.len();
1265        let mut data = list.into_inner();
1266
1267        // Asynchronous sorting algorithm in which the comparator can make use of
1268        // VM requests (required as `builtins.sort` uses comparators written in
1269        // Nix).
1270        //
1271        // This is a simple, optimised bubble sort implementation. The choice of
1272        // algorithm is constrained by the comparator in Nix not being able to
1273        // yield equality, and us being unable to use the standard library
1274        // implementation of sorting (which is a lot longer, but a lot more
1275        // efficient) here.
1276        // TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
1277        loop {
1278            let mut new_len = 0;
1279            for i in 1..len {
1280                if try_value!(
1281                    generators::request_force(
1282                        &co,
1283                        generators::request_call_with(
1284                            &co,
1285                            comparator.clone(),
1286                            [data[i].clone(), data[i - 1].clone()],
1287                        )
1288                        .await,
1289                    )
1290                    .await
1291                )
1292                .as_bool()
1293                .context("evaluating comparator in `builtins.sort`")?
1294                {
1295                    data.swap(i, i - 1);
1296                    new_len = i;
1297                }
1298            }
1299
1300            if new_len == 0 {
1301                break;
1302            }
1303
1304            len = new_len;
1305        }
1306
1307        Ok(Value::List(data.into()))
1308    }
1309
1310    #[builtin("splitVersion")]
1311    async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1312        if s.is_catchable() {
1313            return Ok(s);
1314        }
1315        let s = s.to_str()?;
1316        let s = VersionPartsIter::new((&s).into());
1317
1318        let parts = s
1319            .map(|s| {
1320                Value::from(match s {
1321                    VersionPart::Number(n) => n,
1322                    VersionPart::Word(w) => w,
1323                })
1324            })
1325            .collect::<Vec<Value>>();
1326        Ok(Value::List(NixList::construct(parts.len(), parts)))
1327    }
1328
1329    #[builtin("stringLength")]
1330    async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1331        // also forces the value
1332        let span = generators::request_span(&co).await;
1333        let s = s
1334            .coerce_to_string(
1335                co,
1336                CoercionKind {
1337                    strong: false,
1338                    import_paths: true,
1339                },
1340                span,
1341            )
1342            .await?;
1343
1344        if s.is_catchable() {
1345            return Ok(s);
1346        }
1347
1348        Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1349    }
1350
1351    #[builtin("sub")]
1352    async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1353        arithmetic_op!(&x, &y, -)
1354    }
1355
1356    #[builtin("substring")]
1357    async fn builtin_substring(
1358        co: GenCo,
1359        start: Value,
1360        len: Value,
1361        s: Value,
1362    ) -> Result<Value, ErrorKind> {
1363        let beg = start.as_int()?;
1364        let len = len.as_int()?;
1365        let span = generators::request_span(&co).await;
1366        let x = s
1367            .coerce_to_string(
1368                co,
1369                CoercionKind {
1370                    strong: false,
1371                    import_paths: true,
1372                },
1373                span,
1374            )
1375            .await?;
1376        if x.is_catchable() {
1377            return Ok(x);
1378        }
1379        let x = x.to_contextful_str()?;
1380
1381        if beg < 0 {
1382            return Err(ErrorKind::IndexOutOfBounds { index: beg });
1383        }
1384        let beg = beg as usize;
1385
1386        // Nix doesn't assert that the length argument is
1387        // non-negative when the starting index is GTE the
1388        // string's length.
1389        if beg >= x.len() {
1390            return Ok(Value::from(NixString::new_inherit_context_from(
1391                &x,
1392                BString::default(),
1393            )));
1394        }
1395
1396        let end = if len < 0 {
1397            x.len()
1398        } else {
1399            cmp::min(beg + (len as usize), x.len())
1400        };
1401
1402        Ok(Value::from(NixString::new_inherit_context_from(
1403            &x,
1404            &x[beg..end],
1405        )))
1406    }
1407
1408    #[builtin("tail")]
1409    async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1410        if list.is_catchable() {
1411            return Ok(list);
1412        }
1413
1414        let xs = list.to_list()?;
1415
1416        if xs.is_empty() {
1417            Err(ErrorKind::TailEmptyList)
1418        } else {
1419            let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1420            Ok(Value::List(NixList::construct(output.len(), output)))
1421        }
1422    }
1423
1424    #[builtin("throw")]
1425    async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1426        // If it's already some error, let's propagate it immediately.
1427        if message.is_catchable() {
1428            return Ok(message);
1429        }
1430
1431        // TODO(sterni): coerce to string
1432        // We do not care about the context here explicitly.
1433        Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1434    }
1435
1436    #[builtin("toString")]
1437    async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1438        // TODO(edef): please fix me w.r.t. to catchability.
1439        // coerce_to_string forces for us
1440        // FIXME: should `coerce_to_string` preserve context?
1441        // it does for now.
1442        let span = generators::request_span(&co).await;
1443        x.coerce_to_string(
1444            co,
1445            CoercionKind {
1446                strong: true,
1447                import_paths: false,
1448            },
1449            span,
1450        )
1451        .await
1452    }
1453
1454    #[builtin("toXML")]
1455    async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1456        let value = generators::request_deep_force(&co, value).await;
1457        if value.is_catchable() {
1458            return Ok(value);
1459        }
1460
1461        let mut buf: Vec<u8> = vec![];
1462        let context = to_xml::value_to_xml(&mut buf, &value)?;
1463
1464        Ok(NixString::new_context_from(context, buf).into())
1465    }
1466
1467    #[builtin("trace")]
1468    async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1469        // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on
1470        // the VM
1471        eprintln!("trace: {} :: {}", message, message.type_of());
1472        Ok(value)
1473    }
1474
1475    #[builtin("toPath")]
1476    async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1477        if s.is_catchable() {
1478            return Ok(s);
1479        }
1480
1481        match coerce_value_to_path(&co, s).await? {
1482            Err(cek) => Ok(Value::from(cek)),
1483            Ok(path) => {
1484                let path: Value = crate::value::canon_path(path).into();
1485                let span = generators::request_span(&co).await;
1486                Ok(path
1487                    .coerce_to_string(
1488                        co,
1489                        CoercionKind {
1490                            strong: false,
1491                            import_paths: false,
1492                        },
1493                        span,
1494                    )
1495                    .await?)
1496            }
1497        }
1498    }
1499
1500    #[builtin("tryEval")]
1501    async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1502        let res = match generators::request_try_force(&co, e).await {
1503            Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1504            value => [("value", value), ("success", true.into())],
1505        };
1506
1507        Ok(Value::attrs(NixAttrs::from_iter(res)))
1508    }
1509
1510    #[builtin("typeOf")]
1511    async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1512        if x.is_catchable() {
1513            return Ok(x);
1514        }
1515
1516        Ok(Value::from(x.type_of()))
1517    }
1518}
1519
1520/// Internal helper function for genericClosure, determining whether a
1521/// value has been seen before.
1522async fn bgc_insert_key(
1523    co: &GenCo,
1524    key: Value,
1525    done: &mut Vec<Value>,
1526) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1527    for existing in done.iter() {
1528        match generators::check_equality(
1529            co,
1530            existing.clone(),
1531            key.clone(),
1532            // TODO(tazjin): not actually sure which semantics apply here
1533            PointerEquality::ForbidAll,
1534        )
1535        .await?
1536        {
1537            Ok(true) => return Ok(Ok(false)),
1538            Ok(false) => (),
1539            Err(cek) => return Ok(Err(cek)),
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();
1551
1552    // Pure-value builtins
1553    result.push(("nixVersion", Value::from("2.18.3-compat-tvix-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)))
1674    }
1675}
1676
1677pub fn placeholders() -> Vec<(&'static str, Value)> {
1678    placeholder_builtins::builtins()
1679}