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::{BString, ByteSlice, B};
105    use itertools::Itertools;
106    use os_str_bytes::OsStringBytes;
107    use rustc_hash::{FxHashMap, FxHashSet};
108
109    use crate::{value::PointerEquality, AddContext, NixContext, NixContextElement};
110
111    use super::*;
112
113    macro_rules! try_value {
114        ($value:expr) => {{
115            let val = $value;
116            if val.is_catchable() {
117                return Ok(val);
118            }
119            val
120        }};
121    }
122
123    #[builtin("abort")]
124    async fn builtin_abort(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
125        // 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() {
359                    B("/")
360                } else {
361                    x
362                }
363            })
364            .unwrap_or(b".");
365        if is_path {
366            Ok(Value::Path(Box::new(PathBuf::from(
367                OsString::assert_from_raw_vec(result.to_owned()),
368            ))))
369        } else {
370            Ok(Value::from(NixString::new_inherit_context_from(
371                &str, result,
372            )))
373        }
374    }
375
376    #[builtin("elem")]
377    async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
378        for val in xs.to_list()? {
379            match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
380            {
381                Ok(true) => return Ok(true.into()),
382                Ok(false) => continue,
383                Err(cek) => return Ok(Value::from(cek)),
384            }
385        }
386        Ok(false.into())
387    }
388
389    #[builtin("elemAt")]
390    async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
391        let xs = xs.to_list()?;
392        let i = i.as_int()?;
393        if i < 0 {
394            Err(ErrorKind::IndexOutOfBounds { index: i })
395        } else {
396            match xs.get(i as usize) {
397                Some(x) => Ok(x.clone()),
398                None => Err(ErrorKind::IndexOutOfBounds { index: i }),
399            }
400        }
401    }
402
403    #[builtin("filter")]
404    async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
405        let list: NixList = list.to_list()?;
406        let mut out = Vec::new();
407
408        for value in list {
409            let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
410            let verdict = try_value!(generators::request_force(&co, result).await);
411            if verdict.as_bool()? {
412                out.push(value);
413            }
414        }
415
416        Ok(Value::List(out.into()))
417    }
418
419    #[builtin("floor")]
420    async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
421        Ok(Value::Integer(double.as_float()?.floor() as i64))
422    }
423
424    #[builtin("foldl'")]
425    async fn builtin_foldl(
426        co: GenCo,
427        op: Value,
428        #[lazy] nul: Value,
429        list: Value,
430    ) -> Result<Value, ErrorKind> {
431        let mut nul = nul;
432        let list = list.to_list()?;
433        for val in list {
434            // Every call of `op` is forced immediately, but `nul` is not, see
435            // https://github.com/NixOS/nix/blob/940e9eb8/src/libexpr/primops.cc#L3069-L3070C36
436            // and our tests for foldl'.
437            nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
438            nul = generators::request_force(&co, nul).await;
439            if let c @ Value::Catchable(_) = nul {
440                return Ok(c);
441            }
442        }
443
444        Ok(nul)
445    }
446
447    #[builtin("functionArgs")]
448    async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
449        let lambda = &f.as_closure()?.lambda();
450        let formals = if let Some(formals) = &lambda.formals {
451            formals
452        } else {
453            return Ok(Value::attrs(NixAttrs::empty()));
454        };
455        Ok(Value::attrs(NixAttrs::from_iter(
456            formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
457        )))
458    }
459
460    #[builtin("fromJSON")]
461    async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
462        let json_str = json.to_str()?;
463        serde_json::from_slice(&json_str).map_err(|err| err.into())
464    }
465
466    #[builtin("toJSON")]
467    async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
468        match val.into_contextful_json(&co).await {
469            Err(ErrorKind::CatchableError(catchable)) => Ok(Value::Catchable(Box::new(catchable))),
470            Err(err) => Err(err),
471            Ok((json, context)) => {
472                let json_str = serde_json::to_string(&json)
473                    .map_err(|err| ErrorKind::JsonError(err.to_string()))?;
474                Ok(Value::String(NixString::new_context_from(
475                    context, json_str,
476                )))
477            }
478        }
479    }
480
481    #[builtin("fromTOML")]
482    async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
483        let toml_str = toml.to_str()?;
484
485        toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
486    }
487
488    #[builtin("genericClosure")]
489    async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
490        let attrs = input.to_attrs()?;
491
492        // The work set is maintained as a VecDeque because new items
493        // are popped from the front.
494        let mut work_set: VecDeque<Value> =
495            generators::request_force(&co, attrs.select_required("startSet")?.clone())
496                .await
497                .to_list()?
498                .into_iter()
499                .collect();
500
501        let operator = attrs.select_required("operator")?;
502
503        let mut res = Vec::new();
504        let mut done_keys: Vec<Value> = vec![];
505
506        while let Some(val) = work_set.pop_front() {
507            let val = generators::request_force(&co, val).await;
508            let attrs = val.to_attrs()?;
509            let key = attrs.select_required("key")?;
510
511            let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?;
512
513            if let Err(cek) = value_missing {
514                return Ok(Value::Catchable(Box::new(cek)));
515            }
516
517            if let Ok(false) = value_missing {
518                continue;
519            }
520
521            res.push(val.clone());
522
523            let op_result = generators::request_force(
524                &co,
525                generators::request_call_with(&co, operator.clone(), [val]).await,
526            )
527            .await;
528
529            work_set.extend(op_result.to_list()?.into_iter());
530        }
531
532        Ok(Value::List(NixList::from(res)))
533    }
534
535    #[builtin("genList")]
536    async fn builtin_gen_list(
537        co: GenCo,
538        // Nix 2.3 doesn't propagate failures here
539        #[lazy] generator: Value,
540        length: Value,
541    ) -> Result<Value, ErrorKind> {
542        let len = length.as_int()?;
543        let mut out = Vec::with_capacity(
544            len.try_into()
545                .map_err(|_| ErrorKind::Abort(format!("can not create list of size {len}")))?,
546        );
547
548        // the best span we can get…
549        let span = generators::request_span(&co).await;
550
551        for i in 0..len {
552            let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
553            out.push(val);
554        }
555
556        Ok(Value::List(out.into()))
557    }
558
559    #[builtin("getAttr")]
560    async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
561        let k = key.to_str()?;
562        let xs = set.to_attrs()?;
563
564        match xs.select(&k) {
565            Some(x) => Ok(x.clone()),
566            None => Err(ErrorKind::AttributeNotFound {
567                name: k.to_string(),
568            }),
569        }
570    }
571
572    #[builtin("groupBy")]
573    async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
574        let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
575        for val in list.to_list()? {
576            let key = try_value!(
577                generators::request_force(
578                    &co,
579                    generators::request_call_with(&co, f.clone(), [val.clone()]).await,
580                )
581                .await
582            )
583            .to_str()?;
584
585            res.entry(key).or_default().push(val);
586        }
587        Ok(Value::attrs(NixAttrs::from_iter(
588            res.into_iter()
589                .map(|(k, v)| (k, Value::List(NixList::from(v)))),
590        )))
591    }
592
593    #[builtin("hasAttr")]
594    async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
595        let k = key.to_str()?;
596        let xs = set.to_attrs()?;
597
598        Ok(Value::Bool(xs.contains(&k)))
599    }
600
601    #[builtin("hasContext")]
602    #[allow(non_snake_case)]
603    async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
604        if e.is_catchable() {
605            return Ok(e);
606        }
607
608        let v = e.to_contextful_str()?;
609        Ok(Value::Bool(v.has_context()))
610    }
611
612    #[builtin("getContext")]
613    #[allow(non_snake_case)]
614    async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
615        if e.is_catchable() {
616            return Ok(e);
617        }
618
619        // also forces the value
620        let span = generators::request_span(&co).await;
621        let v = e
622            .coerce_to_string(
623                co,
624                CoercionKind {
625                    strong: true,
626                    import_paths: true,
627                },
628                span,
629            )
630            .await?;
631        let s = v.to_contextful_str()?;
632
633        let groups = s
634            .iter_context()
635            .flat_map(|context| context.iter())
636            // Do not think `group_by` works here.
637            // `group_by` works on consecutive elements of the iterator.
638            // Due to how `HashSet` works (ordering is not guaranteed),
639            // this can become a source of non-determinism if you `group_by` naively.
640            // I know I did.
641            .into_grouping_map_by(|ctx_element| match ctx_element {
642                NixContextElement::Plain(spath) => spath,
643                NixContextElement::Single { derivation, .. } => derivation,
644                NixContextElement::Derivation(drv_path) => drv_path,
645            })
646            .collect::<Vec<_>>();
647
648        let elements = groups
649            .into_iter()
650            .map(|(key, group)| {
651                let mut outputs: Vec<NixString> = Vec::new();
652                let mut is_path = false;
653                let mut all_outputs = false;
654
655                for ctx_element in group {
656                    match ctx_element {
657                        NixContextElement::Plain(spath) => {
658                            debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {spath:?}");
659                            is_path = true;
660                        }
661
662                        NixContextElement::Single { name, derivation } => {
663                            debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {derivation:?}");
664                            outputs.push(name.clone().into());
665                        }
666
667                        NixContextElement::Derivation(drv_path) => {
668                            debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {key:?}, encountered {drv_path:?}");
669                            all_outputs = true;
670                        }
671                    }
672                }
673
674                // FIXME(raitobezarius): is there a better way to construct an attribute set
675                // conditionally?
676                let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
677
678                if is_path {
679                    vec_attrs.push(("path", true.into()));
680                }
681
682                if all_outputs {
683                    vec_attrs.push(("allOutputs", true.into()));
684                }
685
686                if !outputs.is_empty() {
687                    outputs.sort();
688                    vec_attrs.push(("outputs", Value::List(outputs
689                                .into_iter()
690                                .map(|s| s.into())
691                                .collect::<Vec<Value>>()
692                                .into()
693                    )));
694                }
695
696                (key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
697            });
698
699        Ok(Value::attrs(NixAttrs::from_iter(elements)))
700    }
701
702    #[builtin("appendContext")]
703    #[allow(non_snake_case)]
704    async fn builtin_appendContext(
705        co: GenCo,
706        origin: Value,
707        added_context: Value,
708    ) -> Result<Value, ErrorKind> {
709        // `appendContext` is a "grow" context function.
710        // It cannot remove a context element, neither replace a piece of its contents.
711        //
712        // Growing context is always a safe operation, there's no loss of dependency tracking
713        // information.
714        //
715        // This is why this operation is not prefixed by `unsafe` and is deemed *safe*.
716        // Nonetheless, it is possible to craft nonsensical context elements referring
717        // to inexistent derivations, output paths or output names.
718        //
719        // In Nix, those nonsensical context elements are partially mitigated by checking
720        // that various parameters are indeed syntatically valid store paths in the context, i.e.
721        // starting with the same prefix as `builtins.storeDir`, or ending with `.drv`.
722        // In addition, if writing to the store is possible (evaluator not in read-only mode), Nix
723        // will realize some paths and ensures they are present in the store.
724        //
725        // In this implementation, we do none of that, no syntax checks, no realization.
726        // The next `TODO` are the checks that Nix implements.
727        let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
728        let span = generators::request_span(&co).await;
729        let origin = origin
730            .coerce_to_string(
731                co,
732                CoercionKind {
733                    strong: true,
734                    import_paths: true,
735                },
736                span,
737            )
738            .await?;
739        let mut origin = origin.to_contextful_str()?;
740
741        let added_context = added_context.to_attrs()?;
742        for (context_key, context_element) in added_context.into_iter() {
743            // Invariant checks:
744            // - TODO: context_key must be a syntactically valid store path.
745            // - Perform a deep force `context_element`.
746            let context_element = context_element.to_attrs()?;
747            if let Some(path) = context_element.select_str("path") {
748                if path.as_bool()? {
749                    ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
750                }
751            }
752            if let Some(all_outputs) = context_element.select_str("allOutputs") {
753                if all_outputs.as_bool()? {
754                    // TODO: check if `context_key` is a derivation path.
755                    // This may require realization.
756                    ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
757                }
758            }
759            if let Some(some_outputs) = context_element.select_str("outputs") {
760                let some_outputs = some_outputs.to_list()?;
761                // TODO: check if `context_key` is a derivation path.
762                // This may require realization.
763                for output in some_outputs.into_iter() {
764                    let output = output.to_str()?;
765                    ctx_elements.insert(NixContextElement::Single {
766                        derivation: context_key.to_string(),
767                        name: output.to_string(),
768                    });
769                }
770            }
771        }
772
773        if let Some(origin_ctx) = origin.context_mut() {
774            origin_ctx.extend(ctx_elements)
775            // TODO: didn't we forget cases where origin had no context?
776        }
777
778        Ok(origin.into())
779    }
780
781    #[builtin("hashString")]
782    async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
783        hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
784    }
785
786    #[builtin("head")]
787    async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
788        if list.is_catchable() {
789            return Ok(list);
790        }
791
792        match list.to_list()?.get(0) {
793            Some(x) => Ok(x.clone()),
794            None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
795        }
796    }
797
798    #[builtin("intersectAttrs")]
799    async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
800        if x.is_catchable() {
801            return Ok(x);
802        }
803        if y.is_catchable() {
804            return Ok(y);
805        }
806        let left_set = x.to_attrs()?;
807        if left_set.is_empty() {
808            return Ok(Value::attrs(NixAttrs::empty()));
809        }
810
811        let right_set = y.to_attrs()?;
812
813        if right_set.is_empty() {
814            return Ok(Value::attrs(NixAttrs::empty()));
815        }
816
817        Ok(Value::attrs(left_set.intersect(&right_set)))
818    }
819
820    #[builtin("isAttrs")]
821    async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
822        // TODO(edef): make this beautiful
823        if value.is_catchable() {
824            return Ok(value);
825        }
826
827        Ok(Value::Bool(matches!(value, Value::Attrs(_))))
828    }
829
830    #[builtin("isBool")]
831    async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
832        if value.is_catchable() {
833            return Ok(value);
834        }
835
836        Ok(Value::Bool(matches!(value, Value::Bool(_))))
837    }
838
839    #[builtin("isFloat")]
840    async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
841        if value.is_catchable() {
842            return Ok(value);
843        }
844
845        Ok(Value::Bool(matches!(value, Value::Float(_))))
846    }
847
848    #[builtin("isFunction")]
849    async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
850        if value.is_catchable() {
851            return Ok(value);
852        }
853
854        Ok(Value::Bool(matches!(
855            value,
856            Value::Closure(_) | Value::Builtin(_)
857        )))
858    }
859
860    #[builtin("isInt")]
861    async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
862        if value.is_catchable() {
863            return Ok(value);
864        }
865
866        Ok(Value::Bool(matches!(value, Value::Integer(_))))
867    }
868
869    #[builtin("isList")]
870    async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
871        if value.is_catchable() {
872            return Ok(value);
873        }
874
875        Ok(Value::Bool(matches!(value, Value::List(_))))
876    }
877
878    #[builtin("isNull")]
879    async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
880        if value.is_catchable() {
881            return Ok(value);
882        }
883
884        Ok(Value::Bool(matches!(value, Value::Null)))
885    }
886
887    #[builtin("isPath")]
888    async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
889        if value.is_catchable() {
890            return Ok(value);
891        }
892
893        Ok(Value::Bool(matches!(value, Value::Path(_))))
894    }
895
896    #[builtin("isString")]
897    async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
898        if value.is_catchable() {
899            return Ok(value);
900        }
901
902        Ok(Value::Bool(matches!(value, Value::String(_))))
903    }
904
905    #[builtin("length")]
906    async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
907        if list.is_catchable() {
908            return Ok(list);
909        }
910        Ok(Value::Integer(list.to_list()?.len() as i64))
911    }
912
913    #[builtin("lessThan")]
914    async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
915        let span = generators::request_span(&co).await;
916        match x.nix_cmp_ordering(y, co, span).await? {
917            Err(cek) => Ok(Value::from(cek)),
918            Ok(Ordering::Less) => Ok(Value::Bool(true)),
919            Ok(_) => Ok(Value::Bool(false)),
920        }
921    }
922
923    #[builtin("listToAttrs")]
924    async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
925        let list = list.to_list()?;
926        let mut map = FxHashMap::default();
927        for val in list {
928            let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
929            let name = try_value!(
930                generators::request_force(&co, attrs.select_required("name")?.clone()).await
931            )
932            .to_str()?;
933            let value = attrs.select_required("value")?.clone();
934            // Map entries earlier in the list take precedence over entries later in the list
935            map.entry(name).or_insert(value);
936        }
937        Ok(Value::attrs(NixAttrs::from(map)))
938    }
939
940    #[builtin("map")]
941    async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
942        let list = list_val.to_list()?;
943        let mut out = Vec::with_capacity(list.len());
944
945        // the best span we can get…
946        let span = generators::request_span(&co).await;
947
948        for val in list {
949            let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
950            out.push(result)
951        }
952
953        Ok(Value::List(out.into()))
954    }
955
956    #[builtin("mapAttrs")]
957    async fn builtin_map_attrs(
958        co: GenCo,
959        #[lazy] f: Value,
960        attrs: Value,
961    ) -> Result<Value, ErrorKind> {
962        let attrs = attrs.to_attrs()?;
963        let mut out = FxHashMap::default();
964
965        // the best span we can get…
966        let span = generators::request_span(&co).await;
967
968        for (key, value) in attrs.into_iter() {
969            let result = Value::Thunk(Thunk::new_suspended_call(
970                f.clone(),
971                key.clone().into(),
972                span,
973            ));
974            let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
975
976            out.insert(key, result);
977        }
978
979        Ok(Value::attrs(out.into()))
980    }
981
982    #[builtin("match")]
983    async fn builtin_match(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
984        let s = str;
985        if s.is_catchable() {
986            return Ok(s);
987        }
988        let s = s.to_contextful_str()?;
989        let re = regex;
990        if re.is_catchable() {
991            return Ok(re);
992        }
993        let re = re.to_str()?;
994        let re: Regex =
995            cached_regex(&format!("^{}$", re.to_str()?)).expect("TODO(tazjin): propagate error");
996
997        match re.captures(s.to_str()?) {
998            Some(caps) => Ok(Value::List(
999                caps.iter()
1000                    .skip(1)
1001                    .map(|grp| {
1002                        // Surprisingly, Nix does not propagate
1003                        // the original context here.
1004                        // Though, it accepts contextful strings as an argument.
1005                        // An example of such behaviors in nixpkgs
1006                        // can be observed in make-initrd.nix when it comes
1007                        // to compressors which are matched over their full command
1008                        // and then a compressor name will be extracted from that.
1009                        grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
1010                    })
1011                    .collect::<Vec<Value>>()
1012                    .into(),
1013            )),
1014            None => Ok(Value::Null),
1015        }
1016    }
1017
1018    #[builtin("mul")]
1019    async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1020        arithmetic_op!(&x, &y, *)
1021    }
1022
1023    #[builtin("parseDrvName")]
1024    async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1025        if s.is_catchable() {
1026            return Ok(s);
1027        }
1028
1029        // This replicates cppnix's (mis?)handling of codepoints
1030        // above U+007f following 0x2d ('-')
1031        let s = s.to_str()?;
1032        let slice: &[u8] = s.as_ref();
1033        let (name, dash_and_version) = slice.split_at(
1034            slice
1035                .windows(2)
1036                .enumerate()
1037                .find_map(|x| match x {
1038                    (idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
1039                    _ => None,
1040                })
1041                .unwrap_or(slice.len()),
1042        );
1043        let version = dash_and_version
1044            .split_first()
1045            .map(|x| core::str::from_utf8(x.1))
1046            .unwrap_or(Ok(""))?;
1047        Ok(Value::attrs(NixAttrs::from_iter(
1048            [("name", core::str::from_utf8(name)?), ("version", version)].into_iter(),
1049        )))
1050    }
1051
1052    #[builtin("partition")]
1053    async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
1054        let mut right: Vec<Value> = Default::default();
1055        let mut wrong: Vec<Value> = Default::default();
1056
1057        let list: NixList = list.to_list()?;
1058        for elem in list {
1059            let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
1060
1061            if try_value!(generators::request_force(&co, result).await).as_bool()? {
1062                right.push(elem);
1063            } else {
1064                wrong.push(elem);
1065            };
1066        }
1067
1068        let res = [
1069            ("right", Value::List(NixList::from(right))),
1070            ("wrong", Value::List(NixList::from(wrong))),
1071        ];
1072
1073        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1074    }
1075
1076    #[builtin("removeAttrs")]
1077    async fn builtin_remove_attrs(
1078        co: GenCo,
1079        attrs: Value,
1080        keys: Value,
1081    ) -> Result<Value, ErrorKind> {
1082        let attrs = attrs.to_attrs()?;
1083        let keys = keys
1084            .to_list()?
1085            .into_iter()
1086            .map(|v| v.to_str())
1087            .collect::<Result<FxHashSet<_>, _>>()?;
1088        let res = attrs.iter().filter_map(|(k, v)| {
1089            if !keys.contains(k) {
1090                Some((k.clone(), v.clone()))
1091            } else {
1092                None
1093            }
1094        });
1095        Ok(Value::attrs(NixAttrs::from_iter(res)))
1096    }
1097
1098    #[builtin("replaceStrings")]
1099    async fn builtin_replace_strings(
1100        co: GenCo,
1101        from: Value,
1102        to: Value,
1103        s: Value,
1104    ) -> Result<Value, ErrorKind> {
1105        let from = from.to_list()?;
1106        for val in &from {
1107            try_value!(generators::request_force(&co, val.clone()).await);
1108        }
1109
1110        let to = to.to_list()?;
1111        for val in &to {
1112            try_value!(generators::request_force(&co, val.clone()).await);
1113        }
1114
1115        let mut string = s.to_contextful_str()?;
1116
1117        let mut res = BString::default();
1118
1119        let mut i: usize = 0;
1120        let mut empty_string_replace = false;
1121        let mut context = NixContext::new();
1122
1123        if let Some(string_context) = string.take_context() {
1124            context.extend(string_context.into_iter());
1125        }
1126
1127        // This can't be implemented using Rust's string.replace() as
1128        // well as a map because we need to handle errors with results
1129        // as well as "reset" the iterator to zero for the replacement
1130        // everytime there's a successful match.
1131        // Also, Rust's string.replace allocates a new string
1132        // on every call which is not preferable.
1133        'outer: while i < string.len() {
1134            // Try a match in all the from strings
1135            for elem in std::iter::zip(from.iter(), to.iter()) {
1136                let from = elem.0.to_contextful_str()?;
1137                let mut to = elem.1.to_contextful_str()?;
1138
1139                if i + from.len() > string.len() {
1140                    continue;
1141                }
1142
1143                // We already applied a from->to with an empty from
1144                // transformation.
1145                // Let's skip it so that we don't loop infinitely
1146                if empty_string_replace && from.is_empty() {
1147                    continue;
1148                }
1149
1150                // if we match the `from` string, let's replace
1151                if string[i..i + from.len()] == *from {
1152                    res.push_str(&to);
1153                    i += from.len();
1154                    if let Some(to_ctx) = to.take_context() {
1155                        context.extend(to_ctx.into_iter());
1156                    }
1157
1158                    // remember if we applied the empty from->to
1159                    empty_string_replace = from.is_empty();
1160
1161                    continue 'outer;
1162                }
1163            }
1164
1165            // If we don't match any `from`, we simply add a character
1166            res.push_str(&string[i..i + 1]);
1167            i += 1;
1168
1169            // Since we didn't apply anything transformation,
1170            // we reset the empty string replacement
1171            empty_string_replace = false;
1172        }
1173
1174        // Special case when the string is empty or at the string's end
1175        // and one of the from is also empty
1176        for elem in std::iter::zip(from.iter(), to.iter()) {
1177            let from = elem.0.to_contextful_str()?;
1178            // We mutate `to` by consuming its context
1179            // if we perform a successful replacement.
1180            // Therefore, it's fine if `to` was mutate and we reuse it here.
1181            // We don't need to merge again the context, it's already in the right state.
1182            let mut to = elem.1.to_contextful_str()?;
1183
1184            if from.is_empty() {
1185                res.push_str(&to);
1186                if let Some(to_ctx) = to.take_context() {
1187                    context.extend(to_ctx.into_iter());
1188                }
1189                break;
1190            }
1191        }
1192
1193        Ok(Value::from(NixString::new_context_from(context, res)))
1194    }
1195
1196    #[builtin("seq")]
1197    async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
1198        // The builtin calling infra has already forced both args for us, so
1199        // we just return the second and ignore the first
1200        Ok(y)
1201    }
1202
1203    #[builtin("split")]
1204    async fn builtin_split(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
1205        if str.is_catchable() {
1206            return Ok(str);
1207        }
1208
1209        if regex.is_catchable() {
1210            return Ok(regex);
1211        }
1212
1213        let s = str.to_contextful_str()?;
1214        let text = s.to_str()?;
1215        let re = regex.to_str()?;
1216        let re = cached_regex(re.to_str()?).unwrap();
1217        let mut capture_locations = re.capture_locations();
1218        let num_captures = capture_locations.len();
1219        let mut ret = Vec::new();
1220        let mut pos = 0;
1221        let mut prev_match_end = 0;
1222
1223        while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
1224            // push the unmatched characters preceding the match
1225            ret.push(Value::from(NixString::new_inherit_context_from(
1226                &s,
1227                &text[prev_match_end..thematch.start()],
1228            )));
1229
1230            // Push a list with one element for each capture
1231            // group in the regex, containing the characters
1232            // matched by that capture group, or null if no match.
1233            // We skip capture 0; it represents the whole match.
1234            let v: Vec<Value> = (1..num_captures)
1235                .map(|i| capture_locations.get(i))
1236                .map(|o| {
1237                    o.map(|(start, end)| {
1238                        // Here, a surprising thing happens: we silently discard the original
1239                        // context. This is as intended, Nix does the same.
1240                        Value::from(&text[start..end])
1241                    })
1242                    .unwrap_or(Value::Null)
1243                })
1244                .collect();
1245            ret.push(Value::List(NixList::from(v)));
1246            if pos == text.len() {
1247                break;
1248            }
1249
1250            // if the regex matches the empty string, we need to advance, but also
1251            // correctly track the span between matches
1252            prev_match_end = thematch.end();
1253            pos = std::cmp::max(pos + 1, prev_match_end);
1254        }
1255
1256        // push the unmatched characters following the last match
1257        // Here, a surprising thing happens: we silently discard the original
1258        // context. This is as intended, Nix does the same.
1259        ret.push(Value::from(&text[pos..]));
1260
1261        Ok(Value::List(NixList::from(ret)))
1262    }
1263
1264    #[builtin("sort")]
1265    async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
1266        let list = list.to_list()?;
1267        let mut len = list.len();
1268        let mut data = list.into_inner();
1269
1270        // Asynchronous sorting algorithm in which the comparator can make use of
1271        // VM requests (required as `builtins.sort` uses comparators written in
1272        // Nix).
1273        //
1274        // This is a simple, optimised bubble sort implementation. The choice of
1275        // algorithm is constrained by the comparator in Nix not being able to
1276        // yield equality, and us being unable to use the standard library
1277        // implementation of sorting (which is a lot longer, but a lot more
1278        // efficient) here.
1279        // TODO(amjoseph): Investigate potential impl in Nix code, or Tvix bytecode.
1280        loop {
1281            let mut new_len = 0;
1282            for i in 1..len {
1283                if try_value!(
1284                    generators::request_force(
1285                        &co,
1286                        generators::request_call_with(
1287                            &co,
1288                            comparator.clone(),
1289                            [data[i].clone(), data[i - 1].clone()],
1290                        )
1291                        .await,
1292                    )
1293                    .await
1294                )
1295                .as_bool()
1296                .context("evaluating comparator in `builtins.sort`")?
1297                {
1298                    data.swap(i, i - 1);
1299                    new_len = i;
1300                }
1301            }
1302
1303            if new_len == 0 {
1304                break;
1305            }
1306
1307            len = new_len;
1308        }
1309
1310        Ok(Value::List(data.into()))
1311    }
1312
1313    #[builtin("splitVersion")]
1314    async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1315        if s.is_catchable() {
1316            return Ok(s);
1317        }
1318        let s = s.to_str()?;
1319        let s = VersionPartsIter::new((&s).into());
1320
1321        let parts = s
1322            .map(|s| {
1323                Value::from(match s {
1324                    VersionPart::Number(n) => n,
1325                    VersionPart::Word(w) => w,
1326                })
1327            })
1328            .collect::<Vec<Value>>();
1329        Ok(Value::List(NixList::construct(parts.len(), parts)))
1330    }
1331
1332    #[builtin("stringLength")]
1333    async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
1334        // also forces the value
1335        let span = generators::request_span(&co).await;
1336        let s = s
1337            .coerce_to_string(
1338                co,
1339                CoercionKind {
1340                    strong: false,
1341                    import_paths: true,
1342                },
1343                span,
1344            )
1345            .await?;
1346
1347        if s.is_catchable() {
1348            return Ok(s);
1349        }
1350
1351        Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
1352    }
1353
1354    #[builtin("sub")]
1355    async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
1356        arithmetic_op!(&x, &y, -)
1357    }
1358
1359    #[builtin("substring")]
1360    async fn builtin_substring(
1361        co: GenCo,
1362        start: Value,
1363        len: Value,
1364        s: Value,
1365    ) -> Result<Value, ErrorKind> {
1366        let beg = start.as_int()?;
1367        let len = len.as_int()?;
1368        let span = generators::request_span(&co).await;
1369        let x = s
1370            .coerce_to_string(
1371                co,
1372                CoercionKind {
1373                    strong: false,
1374                    import_paths: true,
1375                },
1376                span,
1377            )
1378            .await?;
1379        if x.is_catchable() {
1380            return Ok(x);
1381        }
1382        let x = x.to_contextful_str()?;
1383
1384        if beg < 0 {
1385            return Err(ErrorKind::IndexOutOfBounds { index: beg });
1386        }
1387        let beg = beg as usize;
1388
1389        // Nix doesn't assert that the length argument is
1390        // non-negative when the starting index is GTE the
1391        // string's length.
1392        if beg >= x.len() {
1393            return Ok(Value::from(NixString::new_inherit_context_from(
1394                &x,
1395                BString::default(),
1396            )));
1397        }
1398
1399        let end = if len < 0 {
1400            x.len()
1401        } else {
1402            cmp::min(beg + (len as usize), x.len())
1403        };
1404
1405        Ok(Value::from(NixString::new_inherit_context_from(
1406            &x,
1407            &x[beg..end],
1408        )))
1409    }
1410
1411    #[builtin("tail")]
1412    async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
1413        if list.is_catchable() {
1414            return Ok(list);
1415        }
1416
1417        let xs = list.to_list()?;
1418
1419        if xs.is_empty() {
1420            Err(ErrorKind::TailEmptyList)
1421        } else {
1422            let output = xs.into_iter().skip(1).collect::<Vec<_>>();
1423            Ok(Value::List(NixList::construct(output.len(), output)))
1424        }
1425    }
1426
1427    #[builtin("throw")]
1428    async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
1429        // If it's already some error, let's propagate it immediately.
1430        if message.is_catchable() {
1431            return Ok(message);
1432        }
1433
1434        // TODO(sterni): coerce to string
1435        // We do not care about the context here explicitly.
1436        Ok(Value::from(CatchableErrorKind::Throw(message.to_str()?)))
1437    }
1438
1439    #[builtin("toString")]
1440    async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
1441        // TODO(edef): please fix me w.r.t. to catchability.
1442        // coerce_to_string forces for us
1443        // FIXME: should `coerce_to_string` preserve context?
1444        // it does for now.
1445        let span = generators::request_span(&co).await;
1446        x.coerce_to_string(
1447            co,
1448            CoercionKind {
1449                strong: true,
1450                import_paths: false,
1451            },
1452            span,
1453        )
1454        .await
1455    }
1456
1457    #[builtin("toXML")]
1458    async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
1459        let value = generators::request_deep_force(&co, value).await;
1460        if value.is_catchable() {
1461            return Ok(value);
1462        }
1463
1464        let mut buf: Vec<u8> = vec![];
1465        let context = to_xml::value_to_xml(&mut buf, &value)?;
1466
1467        Ok(NixString::new_context_from(context, buf).into())
1468    }
1469
1470    #[builtin("trace")]
1471    async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
1472        // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on
1473        // the VM
1474        eprintln!("trace: {} :: {}", message, message.type_of());
1475        Ok(value)
1476    }
1477
1478    #[builtin("toPath")]
1479    async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
1480        if s.is_catchable() {
1481            return Ok(s);
1482        }
1483
1484        match coerce_value_to_path(&co, s).await? {
1485            Err(cek) => Ok(Value::from(cek)),
1486            Ok(path) => {
1487                let path: Value = crate::value::canon_path(path).into();
1488                let span = generators::request_span(&co).await;
1489                Ok(path
1490                    .coerce_to_string(
1491                        co,
1492                        CoercionKind {
1493                            strong: false,
1494                            import_paths: false,
1495                        },
1496                        span,
1497                    )
1498                    .await?)
1499            }
1500        }
1501    }
1502
1503    #[builtin("tryEval")]
1504    async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
1505        let res = match generators::request_try_force(&co, e).await {
1506            Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
1507            value => [("value", value), ("success", true.into())],
1508        };
1509
1510        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1511    }
1512
1513    #[builtin("typeOf")]
1514    async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
1515        if x.is_catchable() {
1516            return Ok(x);
1517        }
1518
1519        Ok(Value::from(x.type_of()))
1520    }
1521}
1522
1523/// Internal helper function for genericClosure, determining whether a
1524/// value has been seen before.
1525async fn bgc_insert_key(
1526    co: &GenCo,
1527    key: Value,
1528    done: &mut Vec<Value>,
1529) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
1530    for existing in done.iter() {
1531        match generators::check_equality(
1532            co,
1533            existing.clone(),
1534            key.clone(),
1535            // TODO(tazjin): not actually sure which semantics apply here
1536            PointerEquality::ForbidAll,
1537        )
1538        .await?
1539        {
1540            Ok(true) => return Ok(Ok(false)),
1541            Ok(false) => (),
1542            Err(cek) => return Ok(Err(cek)),
1543        }
1544    }
1545
1546    done.push(key);
1547    Ok(Ok(true))
1548}
1549
1550/// The set of standard pure builtins in Nix, mostly concerned with
1551/// data structure manipulation (string, attrs, list, etc. functions).
1552pub fn pure_builtins() -> Vec<(&'static str, Value)> {
1553    let mut result = pure_builtins::builtins();
1554
1555    // Pure-value builtins
1556    result.push(("nixVersion", Value::from("2.18.3-compat-tvix-0.1")));
1557    result.push(("langVersion", Value::Integer(6)));
1558    result.push(("null", Value::Null));
1559    result.push(("true", Value::Bool(true)));
1560    result.push(("false", Value::Bool(false)));
1561
1562    result.push((
1563        "currentSystem",
1564        crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
1565    ));
1566
1567    result.push((
1568        "__curPos",
1569        Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
1570            // TODO: implement for nixpkgs compatibility
1571            Ok(Value::attrs(NixAttrs::from_iter([
1572                ("line", 42.into()),
1573                ("column", 42.into()),
1574                ("file", Value::String("/deep/thought".into())),
1575            ])))
1576        }))),
1577    ));
1578
1579    result
1580}
1581
1582#[builtins]
1583mod placeholder_builtins {
1584    use crate::NixContext;
1585
1586    use super::*;
1587
1588    #[builtin("unsafeDiscardStringContext")]
1589    async fn builtin_unsafe_discard_string_context(
1590        co: GenCo,
1591        s: Value,
1592    ) -> Result<Value, ErrorKind> {
1593        let span = generators::request_span(&co).await;
1594        let mut v = s
1595            .coerce_to_string(
1596                co,
1597                // It's weak because
1598                // lists, integers, floats and null are not
1599                // accepted as parameters.
1600                CoercionKind {
1601                    strong: false,
1602                    import_paths: true,
1603                },
1604                span,
1605            )
1606            .await?
1607            .to_contextful_str()?;
1608        v.clear_context();
1609        Ok(Value::from(v))
1610    }
1611
1612    #[builtin("unsafeDiscardOutputDependency")]
1613    async fn builtin_unsafe_discard_output_dependency(
1614        co: GenCo,
1615        s: Value,
1616    ) -> Result<Value, ErrorKind> {
1617        let span = generators::request_span(&co).await;
1618        let mut v = s
1619            .coerce_to_string(
1620                co,
1621                // It's weak because
1622                // lists, integers, floats and null are not
1623                // accepted as parameters.
1624                CoercionKind {
1625                    strong: false,
1626                    import_paths: true,
1627                },
1628                span,
1629            )
1630            .await?
1631            .to_contextful_str()?;
1632
1633        // If there's any context, we will swap any ... by a path one.
1634        if let Some(c) = v.take_context() {
1635            let mut context = NixContext::new();
1636            context.extend(c.into_iter().map(|elem| match elem {
1637                crate::NixContextElement::Derivation(drv_path) => {
1638                    crate::NixContextElement::Plain(drv_path.to_string())
1639                }
1640                elem => elem.clone(),
1641            }));
1642
1643            return Ok(Value::String(NixString::new_context_from(context, v)));
1644        }
1645        Ok(Value::from(v))
1646    }
1647
1648    #[builtin("addErrorContext")]
1649    async fn builtin_add_error_context(
1650        co: GenCo,
1651        #[lazy] _context: Value,
1652        #[lazy] val: Value,
1653    ) -> Result<Value, ErrorKind> {
1654        generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
1655            .await;
1656        Ok(val)
1657    }
1658
1659    #[builtin("unsafeGetAttrPos")]
1660    async fn builtin_unsafe_get_attr_pos(
1661        co: GenCo,
1662        _name: Value,
1663        _attrset: Value,
1664    ) -> Result<Value, ErrorKind> {
1665        // TODO: implement for nixpkgs compatibility
1666        generators::emit_warning_kind(
1667            &co,
1668            WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
1669        )
1670        .await;
1671        let res = [
1672            ("line", 42.into()),
1673            ("column", 42.into()),
1674            ("file", Value::String("/deep/thought".into())),
1675        ];
1676        Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
1677    }
1678}
1679
1680pub fn placeholders() -> Vec<(&'static str, Value)> {
1681    placeholder_builtins::builtins()
1682}