snix_eval/builtins/
mod.rs

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