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.into_iter());
270        }
271
272        Ok(Value::List(out.into()))
273    }
274
275    #[builtin("concatMap")]
276    async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
277        let list = list.to_list()?;
278        let mut res = Vec::new();
279        for val in list {
280            let out = generators::request_call_with(&co, f.clone(), [val]).await;
281            let out = try_value!(generators::request_force(&co, out).await);
282            res.extend(out.to_list()?);
283        }
284        Ok(Value::List(res.into()))
285    }
286
287    #[builtin("concatStringsSep")]
288    async fn builtin_concat_strings_sep(
289        co: GenCo,
290        separator: Value,
291        list: Value,
292    ) -> Result<Value, ErrorKind> {
293        let mut separator = separator.to_contextful_str()?;
294
295        let mut context = NixContext::new();
296        if let Some(sep_context) = separator.take_context() {
297            context.extend(sep_context.into_iter())
298        }
299        let list = list.to_list()?;
300        let mut res = BString::default();
301        for (i, val) in list.into_iter().enumerate() {
302            if i != 0 {
303                res.push_str(&separator);
304            }
305            match generators::request_string_coerce(
306                &co,
307                val,
308                CoercionKind {
309                    strong: false,
310                    import_paths: true,
311                },
312            )
313            .await
314            {
315                Ok(mut s) => {
316                    res.push_str(&s);
317                    if let Some(other_context) = s.take_context() {
318                        context.extend(other_context.into_iter());
319                    }
320                }
321                Err(c) => return Ok(Value::Catchable(Box::new(c))),
322            }
323        }
324        // 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()?.into_iter());
526        }
527
528        Ok(Value::List(NixList::from(res)))
529    }
530
531    #[builtin("genList")]
532    async fn builtin_gen_list(
533        co: GenCo,
534        // 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.into_iter())))
693            });
694
695        Ok(Value::attrs(NixAttrs::from_iter(elements)))
696    }
697
698    #[builtin("appendContext")]
699    #[allow(non_snake_case)]
700    async fn builtin_appendContext(
701        co: GenCo,
702        origin: Value,
703        added_context: Value,
704    ) -> Result<Value, ErrorKind> {
705        // `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)?), ("version", version)].into_iter(),
1045        )))
1046    }
1047
1048    #[builtin("partition")]
1049    async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
1050        let mut right: Vec<Value> = Default::default();
1051        let mut wrong: Vec<Value> = Default::default();
1052
1053        let list: NixList = list.to_list()?;
1054        for elem in list {
1055            let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
1056
1057            if try_value!(generators::request_force(&co, result).await).as_bool()? {
1058                right.push(elem);
1059            } else {
1060                wrong.push(elem);
1061            };
1062        }
1063
1064        let res = [
1065            ("right", Value::List(NixList::from(right))),
1066            ("wrong", Value::List(NixList::from(wrong))),
1067        ];
1068
1069        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1070    }
1071
1072    #[builtin("removeAttrs")]
1073    async fn builtin_remove_attrs(
1074        co: GenCo,
1075        attrs: Value,
1076        keys: Value,
1077    ) -> Result<Value, ErrorKind> {
1078        let attrs = attrs.to_attrs()?;
1079        let keys = keys
1080            .to_list()?
1081            .into_iter()
1082            .map(|v| v.to_str())
1083            .collect::<Result<FxHashSet<_>, _>>()?;
1084        let res = attrs.iter().filter_map(|(k, v)| {
1085            if !keys.contains(k) {
1086                Some((k.clone(), v.clone()))
1087            } else {
1088                None
1089            }
1090        });
1091        Ok(Value::attrs(NixAttrs::from_iter(res)))
1092    }
1093
1094    #[builtin("replaceStrings")]
1095    async fn builtin_replace_strings(
1096        co: GenCo,
1097        from: Value,
1098        to: Value,
1099        s: Value,
1100    ) -> Result<Value, ErrorKind> {
1101        let from = from.to_list()?;
1102        for val in &from {
1103            try_value!(generators::request_force(&co, val.clone()).await);
1104        }
1105
1106        let to = to.to_list()?;
1107        for val in &to {
1108            try_value!(generators::request_force(&co, val.clone()).await);
1109        }
1110
1111        let mut string = s.to_contextful_str()?;
1112
1113        let mut res = BString::default();
1114
1115        let mut i: usize = 0;
1116        let mut empty_string_replace = false;
1117        let mut context = NixContext::new();
1118
1119        if let Some(string_context) = string.take_context() {
1120            context.extend(string_context.into_iter());
1121        }
1122
1123        // This can't be implemented using Rust's string.replace() as
1124        // well as a map because we need to handle errors with results
1125        // as well as "reset" the iterator to zero for the replacement
1126        // everytime there's a successful match.
1127        // Also, Rust's string.replace allocates a new string
1128        // on every call which is not preferable.
1129        'outer: while i < string.len() {
1130            // Try a match in all the from strings
1131            for elem in std::iter::zip(from.iter(), to.iter()) {
1132                let from = elem.0.to_contextful_str()?;
1133                let mut to = elem.1.to_contextful_str()?;
1134
1135                if i + from.len() > string.len() {
1136                    continue;
1137                }
1138
1139                // We already applied a from->to with an empty from
1140                // transformation.
1141                // Let's skip it so that we don't loop infinitely
1142                if empty_string_replace && from.is_empty() {
1143                    continue;
1144                }
1145
1146                // if we match the `from` string, let's replace
1147                if string[i..i + from.len()] == *from {
1148                    res.push_str(&to);
1149                    i += from.len();
1150                    if let Some(to_ctx) = to.take_context() {
1151                        context.extend(to_ctx.into_iter());
1152                    }
1153
1154                    // remember if we applied the empty from->to
1155                    empty_string_replace = from.is_empty();
1156
1157                    continue 'outer;
1158                }
1159            }
1160
1161            // If we don't match any `from`, we simply add a character
1162            res.push_str(&string[i..i + 1]);
1163            i += 1;
1164
1165            // Since we didn't apply anything transformation,
1166            // we reset the empty string replacement
1167            empty_string_replace = false;
1168        }
1169
1170        // Special case when the string is empty or at the string's end
1171        // and one of the from is also empty
1172        for elem in std::iter::zip(from.iter(), to.iter()) {
1173            let from = elem.0.to_contextful_str()?;
1174            // We mutate `to` by consuming its context
1175            // if we perform a successful replacement.
1176            // Therefore, it's fine if `to` was mutate and we reuse it here.
1177            // We don't need to merge again the context, it's already in the right state.
1178            let mut to = elem.1.to_contextful_str()?;
1179
1180            if from.is_empty() {
1181                res.push_str(&to);
1182                if let Some(to_ctx) = to.take_context() {
1183                    context.extend(to_ctx.into_iter());
1184                }
1185                break;
1186            }
1187        }
1188
1189        Ok(Value::from(NixString::new_context_from(context, res)))
1190    }
1191
1192    #[builtin("seq")]
1193    async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
1194        // The builtin calling infra has already forced both args for us, so
1195        // we just return the second and ignore the first
1196        Ok(y)
1197    }
1198
1199    #[builtin("split")]
1200    async fn builtin_split(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
1201        if str.is_catchable() {
1202            return Ok(str);
1203        }
1204
1205        if regex.is_catchable() {
1206            return Ok(regex);
1207        }
1208
1209        let s = str.to_contextful_str()?;
1210        let text = s.to_str()?;
1211        let re = regex.to_str()?;
1212        let re = cached_regex(re.to_str()?).unwrap();
1213        let mut capture_locations = re.capture_locations();
1214        let num_captures = capture_locations.len();
1215        let mut ret = Vec::new();
1216        let mut pos = 0;
1217        let mut prev_match_end = 0;
1218
1219        while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1220            // push the unmatched characters preceding the match
1221            ret.push(Value::from(NixString::new_inherit_context_from(
1222                &s,
1223                &text[prev_match_end..thematch.start()],
1224            )));
1225
1226            // Push a list with one element for each capture
1227            // group in the regex, containing the characters
1228            // matched by that capture group, or null if no match.
1229            // We skip capture 0; it represents the whole match.
1230            let v: Vec<Value> = (1..num_captures)
1231                .map(|i| capture_locations.get(i))
1232                .map(|o| {
1233                    o.map(|(start, end)| {
1234                        // Here, a surprising thing happens: we silently discard the original
1235                        // context. This is as intended, Nix does the same.
1236                        Value::from(&text[start..end])
1237                    })
1238                    .unwrap_or(Value::Null)
1239                })
1240                .collect();
1241            ret.push(Value::List(NixList::from(v)));
1242            if pos == text.len() {
1243                break;
1244            }
1245
1246            // if the regex matches the empty string, we need to advance, but also
1247            // correctly track the span between matches
1248            prev_match_end = thematch.end();
1249            pos = std::cmp::max(pos + 1, prev_match_end);
1250        }
1251
1252        // push the unmatched characters following the last match
1253        // Here, a surprising thing happens: we silently discard the original
1254        // context. This is as intended, Nix does the same.
1255        ret.push(Value::from(&text[pos..]));
1256
1257        Ok(Value::List(NixList::from(ret)))
1258    }
1259
1260    #[builtin("sort")]
1261    async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1262        let list = list.to_list()?;
1263        let mut len = list.len();
1264        let mut data = list.into_inner();
1265
1266        // Asynchronous sorting algorithm in which the comparator can make use of
1267        // VM requests (required as `builtins.sort` uses comparators written in
1268        // Nix).
1269        //
1270        // This is a simple, optimised bubble sort implementation. The choice of
1271        // algorithm is constrained by the comparator in Nix not being able to
1272        // yield equality, and us being unable to use the standard library
1273        // implementation of sorting (which is a lot longer, but a lot more
1274        // efficient) here.
1275        // TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
1276        loop {
1277            let mut new_len = 0;
1278            for i in 1..len {
1279                if try_value!(
1280                    generators::request_force(
1281                        &co,
1282                        generators::request_call_with(
1283                            &co,
1284                            comparator.clone(),
1285                            [data[i].clone(), data[i - 1].clone()],
1286                        )
1287                        .await,
1288                    )
1289                    .await
1290                )
1291                .as_bool()
1292                .context("evaluating comparator in `builtins.sort`")?
1293                {
1294                    data.swap(i, i - 1);
1295                    new_len = i;
1296                }
1297            }
1298
1299            if new_len == 0 {
1300                break;
1301            }
1302
1303            len = new_len;
1304        }
1305
1306        Ok(Value::List(data.into()))
1307    }
1308
1309    #[builtin("splitVersion")]
1310    async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1311        if s.is_catchable() {
1312            return Ok(s);
1313        }
1314        let s = s.to_str()?;
1315        let s = VersionPartsIter::new((&s).into());
1316
1317        let parts = s
1318            .map(|s| {
1319                Value::from(match s {
1320                    VersionPart::Number(n) => n,
1321                    VersionPart::Word(w) => w,
1322                })
1323            })
1324            .collect::<Vec<Value>>();
1325        Ok(Value::List(NixList::construct(parts.len(), parts)))
1326    }
1327
1328    #[builtin("stringLength")]
1329    async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1330        // also forces the value
1331        let span = generators::request_span(&co).await;
1332        let s = s
1333            .coerce_to_string(
1334                co,
1335                CoercionKind {
1336                    strong: false,
1337                    import_paths: true,
1338                },
1339                span,
1340            )
1341            .await?;
1342
1343        if s.is_catchable() {
1344            return Ok(s);
1345        }
1346
1347        Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1348    }
1349
1350    #[builtin("sub")]
1351    async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1352        arithmetic_op!(&x, &y, -)
1353    }
1354
1355    #[builtin("substring")]
1356    async fn builtin_substring(
1357        co: GenCo,
1358        start: Value,
1359        len: Value,
1360        s: Value,
1361    ) -> Result<Value, ErrorKind> {
1362        let beg = start.as_int()?;
1363        let len = len.as_int()?;
1364        let span = generators::request_span(&co).await;
1365        let x = s
1366            .coerce_to_string(
1367                co,
1368                CoercionKind {
1369                    strong: false,
1370                    import_paths: true,
1371                },
1372                span,
1373            )
1374            .await?;
1375        if x.is_catchable() {
1376            return Ok(x);
1377        }
1378        let x = x.to_contextful_str()?;
1379
1380        if beg < 0 {
1381            return Err(ErrorKind::IndexOutOfBounds { index: beg });
1382        }
1383        let beg = beg as usize;
1384
1385        // Nix doesn't assert that the length argument is
1386        // non-negative when the starting index is GTE the
1387        // string's length.
1388        if beg >= x.len() {
1389            return Ok(Value::from(NixString::new_inherit_context_from(
1390                &x,
1391                BString::default(),
1392            )));
1393        }
1394
1395        let end = if len < 0 {
1396            x.len()
1397        } else {
1398            cmp::min(beg + (len as usize), x.len())
1399        };
1400
1401        Ok(Value::from(NixString::new_inherit_context_from(
1402            &x,
1403            &x[beg..end],
1404        )))
1405    }
1406
1407    #[builtin("tail")]
1408    async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1409        if list.is_catchable() {
1410            return Ok(list);
1411        }
1412
1413        let xs = list.to_list()?;
1414
1415        if xs.is_empty() {
1416            Err(ErrorKind::TailEmptyList)
1417        } else {
1418            let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1419            Ok(Value::List(NixList::construct(output.len(), output)))
1420        }
1421    }
1422
1423    #[builtin("throw")]
1424    async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1425        // If it's already some error, let's propagate it immediately.
1426        if message.is_catchable() {
1427            return Ok(message);
1428        }
1429
1430        // TODO(sterni): coerce to string
1431        // We do not care about the context here explicitly.
1432        Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1433    }
1434
1435    #[builtin("toString")]
1436    async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1437        // TODO(edef): please fix me w.r.t. to catchability.
1438        // coerce_to_string forces for us
1439        // FIXME: should `coerce_to_string` preserve context?
1440        // it does for now.
1441        let span = generators::request_span(&co).await;
1442        x.coerce_to_string(
1443            co,
1444            CoercionKind {
1445                strong: true,
1446                import_paths: false,
1447            },
1448            span,
1449        )
1450        .await
1451    }
1452
1453    #[builtin("toXML")]
1454    async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1455        let value = generators::request_deep_force(&co, value).await;
1456        if value.is_catchable() {
1457            return Ok(value);
1458        }
1459
1460        let mut buf: Vec<u8> = vec![];
1461        let context = to_xml::value_to_xml(&mut buf, &value)?;
1462
1463        Ok(NixString::new_context_from(context, buf).into())
1464    }
1465
1466    #[builtin("trace")]
1467    async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1468        // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on
1469        // the VM
1470        eprintln!("trace: {} :: {}", message, message.type_of());
1471        Ok(value)
1472    }
1473
1474    #[builtin("toPath")]
1475    async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1476        if s.is_catchable() {
1477            return Ok(s);
1478        }
1479
1480        match coerce_value_to_path(&co, s).await? {
1481            Err(cek) => Ok(Value::from(cek)),
1482            Ok(path) => {
1483                let path: Value = crate::value::canon_path(path).into();
1484                let span = generators::request_span(&co).await;
1485                Ok(path
1486                    .coerce_to_string(
1487                        co,
1488                        CoercionKind {
1489                            strong: false,
1490                            import_paths: false,
1491                        },
1492                        span,
1493                    )
1494                    .await?)
1495            }
1496        }
1497    }
1498
1499    #[builtin("tryEval")]
1500    async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1501        let res = match generators::request_try_force(&co, e).await {
1502            Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1503            value => [("value", value), ("success", true.into())],
1504        };
1505
1506        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1507    }
1508
1509    #[builtin("typeOf")]
1510    async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1511        if x.is_catchable() {
1512            return Ok(x);
1513        }
1514
1515        Ok(Value::from(x.type_of()))
1516    }
1517}
1518
1519/// Internal helper function for genericClosure, determining whether a
1520/// value has been seen before.
1521async fn bgc_insert_key(
1522    co: &GenCo,
1523    key: Value,
1524    done: &mut Vec<Value>,
1525) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1526    for existing in done.iter() {
1527        match generators::check_equality(
1528            co,
1529            existing.clone(),
1530            key.clone(),
1531            // TODO(tazjin): not actually sure which semantics apply here
1532            PointerEquality::ForbidAll,
1533        )
1534        .await?
1535        {
1536            Ok(true) => return Ok(Ok(false)),
1537            Ok(false) => (),
1538            Err(cek) => return Ok(Err(cek)),
1539        }
1540    }
1541
1542    done.push(key);
1543    Ok(Ok(true))
1544}
1545
1546/// The set of standard pure builtins in Nix, mostly concerned with
1547/// data structure manipulation (string, attrs, list, etc. functions).
1548pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1549    let mut result = pure_builtins::builtins();
1550
1551    // Pure-value builtins
1552    result.push(("nixVersion", Value::from("2.18.3-compat-tvix-0.1")));
1553    result.push(("langVersion", Value::Integer(6)));
1554    result.push(("null", Value::Null));
1555    result.push(("true", Value::Bool(true)));
1556    result.push(("false", Value::Bool(false)));
1557
1558    result.push((
1559        "currentSystem",
1560        crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1561    ));
1562
1563    result.push((
1564        "__curPos",
1565        Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1566            // TODO: implement for nixpkgs compatibility
1567            Ok(Value::attrs(NixAttrs::from_iter([
1568                ("line", 42.into()),
1569                ("column", 42.into()),
1570                ("file", Value::String("/deep/thought".into())),
1571            ])))
1572        }))),
1573    ));
1574
1575    result
1576}
1577
1578#[builtins]
1579mod placeholder_builtins {
1580    use crate::NixContext;
1581
1582    use super::*;
1583
1584    #[builtin("unsafeDiscardStringContext")]
1585    async fn builtin_unsafe_discard_string_context(
1586        co: GenCo,
1587        s: Value,
1588    ) -> Result<Value, ErrorKind> {
1589        let span = generators::request_span(&co).await;
1590        let mut v = s
1591            .coerce_to_string(
1592                co,
1593                // It's weak because
1594                // lists, integers, floats and null are not
1595                // accepted as parameters.
1596                CoercionKind {
1597                    strong: false,
1598                    import_paths: true,
1599                },
1600                span,
1601            )
1602            .await?
1603            .to_contextful_str()?;
1604        v.clear_context();
1605        Ok(Value::from(v))
1606    }
1607
1608    #[builtin("unsafeDiscardOutputDependency")]
1609    async fn builtin_unsafe_discard_output_dependency(
1610        co: GenCo,
1611        s: Value,
1612    ) -> Result<Value, ErrorKind> {
1613        let span = generators::request_span(&co).await;
1614        let mut v = s
1615            .coerce_to_string(
1616                co,
1617                // It's weak because
1618                // lists, integers, floats and null are not
1619                // accepted as parameters.
1620                CoercionKind {
1621                    strong: false,
1622                    import_paths: true,
1623                },
1624                span,
1625            )
1626            .await?
1627            .to_contextful_str()?;
1628
1629        // If there's any context, we will swap any ... by a path one.
1630        if let Some(c) = v.take_context() {
1631            let mut context = NixContext::new();
1632            context.extend(c.into_iter().map(|elem| match elem {
1633                crate::NixContextElement::Derivation(drv_path) => {
1634                    crate::NixContextElement::Plain(drv_path.to_string())
1635                }
1636                elem => elem.clone(),
1637            }));
1638
1639            return Ok(Value::String(NixString::new_context_from(context, v)));
1640        }
1641        Ok(Value::from(v))
1642    }
1643
1644    #[builtin("addErrorContext")]
1645    async fn builtin_add_error_context(
1646        co: GenCo,
1647        #[lazy] _context: Value,
1648        #[lazy] val: Value,
1649    ) -> Result<Value, ErrorKind> {
1650        generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1651            .await;
1652        Ok(val)
1653    }
1654
1655    #[builtin("unsafeGetAttrPos")]
1656    async fn builtin_unsafe_get_attr_pos(
1657        co: GenCo,
1658        _name: Value,
1659        _attrset: Value,
1660    ) -> Result<Value, ErrorKind> {
1661        // TODO: implement for nixpkgs compatibility
1662        generators::emit_warning_kind(
1663            &co,
1664            WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1665        )
1666        .await;
1667        let res = [
1668            ("line", 42.into()),
1669            ("column", 42.into()),
1670            ("file", Value::String("/deep/thought".into())),
1671        ];
1672        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1673    }
1674}
1675
1676pub fn placeholders() -> Vec<(&'static str, Value)> {
1677    placeholder_builtins::builtins()
1678}