Skip to main content

snix_eval/builtins/
mod.rs

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