Skip to main content

snix_eval/value/
mod.rs

1//! This module implements the backing representation of runtime
2//! values in the Nix language.
3use bstr::{BString, ByteVec};
4use codemap::Span;
5use lexical_core::format::CXX_LITERAL;
6use serde::Deserialize;
7use std::cmp::Ordering;
8use std::fmt::Display;
9use std::io::Write;
10use std::num::{NonZeroI32, NonZeroUsize};
11use std::path::PathBuf;
12use std::rc::Rc;
13use std::sync::LazyLock;
14
15#[cfg(feature = "arbitrary")]
16mod arbitrary;
17mod attrs;
18mod builtin;
19mod function;
20mod json;
21mod list;
22mod path;
23mod string;
24mod thunk;
25
26use crate::AddContext;
27use crate::errors::{CatchableErrorKind, ErrorKind};
28use crate::opcode::StackIdx;
29use crate::vm::generators::{self, GenCo};
30pub use attrs::NixAttrs;
31pub use builtin::{Builtin, BuiltinResult};
32pub(crate) use function::Formals;
33pub use function::{Closure, Lambda};
34pub use list::NixList;
35pub use path::canon_path;
36pub use string::{NixContext, NixContextElement, NixString};
37pub use thunk::Thunk;
38
39pub use self::thunk::ThunkSet;
40
41#[warn(variant_size_differences)]
42#[derive(Clone, Debug, Deserialize)]
43#[serde(untagged)]
44pub enum Value {
45    Null,
46    Bool(bool),
47    Integer(i64),
48    Float(f64),
49    String(NixString),
50
51    #[serde(skip)]
52    Path(Box<PathBuf>),
53    Attrs(NixAttrs),
54    List(NixList),
55
56    #[serde(skip)]
57    Closure(Rc<Closure>), // must use Rc<Closure> here in order to get proper pointer equality
58
59    #[serde(skip)]
60    Builtin(Builtin),
61
62    // Internal values that, while they technically exist at runtime,
63    // are never returned to or created directly by users.
64    #[serde(skip_deserializing)]
65    Thunk(Thunk),
66
67    // See [`compiler::compile_select_or()`] for explanation
68    #[serde(skip)]
69    AttrNotFound,
70
71    // this can only occur in Chunk::Constants and nowhere else
72    #[serde(skip)]
73    Blueprint(Rc<Lambda>),
74
75    #[serde(skip)]
76    DeferredUpvalue(StackIdx),
77    #[serde(skip)]
78    UnresolvedPath(Box<PathBuf>),
79
80    #[serde(skip)]
81    FinaliseRequest(bool),
82
83    #[serde(skip)]
84    Catchable(Box<CatchableErrorKind>),
85}
86
87impl From<CatchableErrorKind> for Value {
88    #[inline]
89    fn from(c: CatchableErrorKind) -> Value {
90        Value::Catchable(Box::new(c))
91    }
92}
93
94impl<V> From<Result<V, CatchableErrorKind>> for Value
95where
96    Value: From<V>,
97{
98    #[inline]
99    fn from(v: Result<V, CatchableErrorKind>) -> Value {
100        match v {
101            Ok(v) => v.into(),
102            Err(e) => Value::Catchable(Box::new(e)),
103        }
104    }
105}
106
107static WRITE_FLOAT_OPTIONS: LazyLock<lexical_core::WriteFloatOptions> = LazyLock::new(|| {
108    lexical_core::WriteFloatOptionsBuilder::new()
109        .trim_floats(true)
110        .round_mode(lexical_core::write_float_options::RoundMode::Round)
111        .positive_exponent_break(Some(NonZeroI32::new(5).unwrap()))
112        .max_significant_digits(Some(NonZeroUsize::new(6).unwrap()))
113        .build()
114        .unwrap()
115});
116
117// Helper macros to generate the to_*/as_* macros while accounting for
118// thunks.
119
120/// Generate an `as_*` method returning a reference to the expected
121/// type, or a type error. This only works for types that implement
122/// `Copy`, as returning a reference to an inner thunk value is not
123/// possible.
124///
125/// Generate an `as_*/to_*` accessor method that returns either the
126/// expected type, or a type error.
127macro_rules! gen_cast {
128    ( $name:ident, $type:ty, $expected:expr, $variant:pat, $result:expr ) => {
129        pub fn $name(&self) -> Result<$type, ErrorKind> {
130            match self {
131                $variant => Ok($result),
132                Value::Thunk(thunk) => Self::$name(&thunk.value()),
133                other => Err(type_error($expected, &other)),
134            }
135        }
136    };
137}
138
139/// Generate an `as_*_mut/to_*_mut` accessor method that returns either the
140/// expected type, or a type error.
141macro_rules! gen_cast_mut {
142    ( $name:ident, $type:ty, $expected:expr, $variant:ident) => {
143        pub fn $name(&mut self) -> Result<&mut $type, ErrorKind> {
144            match self {
145                Value::$variant(x) => Ok(x),
146                other => Err(type_error($expected, &other)),
147            }
148        }
149    };
150}
151
152/// Generate an `is_*` type-checking method.
153macro_rules! gen_is {
154    ( $name:ident, $variant:pat ) => {
155        pub fn $name(&self) -> bool {
156            match self {
157                $variant => true,
158                Value::Thunk(thunk) => Self::$name(&thunk.value()),
159                _ => false,
160            }
161        }
162    };
163}
164
165/// Describes what input types are allowed when coercing a `Value` to a string
166#[derive(Clone, Copy, PartialEq, Eq, Debug)]
167pub struct CoercionKind {
168    /// If false only coerce already "stringly" types like strings and paths, but
169    /// also coerce sets that have a `__toString` attribute. In Snix, this is
170    /// usually called a weak coercion. Equivalent to passing `false` as the
171    /// `coerceMore` argument of `EvalState::coerceToString` in C++ Nix.
172    ///
173    /// If true coerce all value types included by a weak coercion, but also
174    /// coerce `null`, booleans, integers, floats and lists of coercible types.
175    /// Consequently, we call this a strong coercion. Equivalent to passing
176    /// `true` as `coerceMore` in C++ Nix.
177    pub strong: bool,
178
179    /// If `import_paths` is `true`, paths are imported into the store and their
180    /// store path is the result of the coercion (equivalent to the
181    /// `copyToStore` argument of `EvalState::coerceToString` in C++ Nix).
182    pub import_paths: bool,
183}
184
185impl From<CoercionKind> for u8 {
186    fn from(k: CoercionKind) -> u8 {
187        k.strong as u8 | ((k.import_paths as u8) << 1)
188    }
189}
190
191impl From<u8> for CoercionKind {
192    fn from(byte: u8) -> Self {
193        CoercionKind {
194            strong: byte & 0x01 != 0,
195            import_paths: byte & 0x02 != 0,
196        }
197    }
198}
199
200impl<T> From<T> for Value
201where
202    T: Into<NixString>,
203{
204    fn from(t: T) -> Self {
205        Self::String(t.into())
206    }
207}
208
209/// Controls what kind of by-pointer equality comparison is allowed.
210///
211/// See `//snix/docs/value-pointer-equality.md` for details.
212#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
213pub enum PointerEquality {
214    /// Pointer equality not allowed at all.
215    ForbidAll,
216
217    /// Pointer equality comparisons only allowed for nested values.
218    AllowNested,
219
220    /// Pointer equality comparisons are allowed in all contexts.
221    AllowAll,
222}
223
224impl Value {
225    /// Construct a [`Value::Attrs`] from a [`NixAttrs`].
226    pub fn attrs(attrs: NixAttrs) -> Self {
227        Self::Attrs(attrs)
228    }
229
230    /// Deeply forces a value, traversing e.g. lists and attribute sets and forcing
231    /// their contents, too.
232    ///
233    /// This is a generator function.
234    pub(super) async fn deep_force(self, co: GenCo, span: Span) -> Result<Value, ErrorKind> {
235        if let Some(v) = Self::deep_force_(self.clone(), co, span).await? {
236            Ok(v)
237        } else {
238            Ok(self)
239        }
240    }
241
242    /// Returns Some(v) or None to indicate the returned value is myself
243    async fn deep_force_(myself: Value, co: GenCo, span: Span) -> Result<Option<Value>, ErrorKind> {
244        // This is a stack of values which still remain to be forced.
245        let mut vals = vec![myself];
246
247        let mut thunk_set: ThunkSet = Default::default();
248
249        loop {
250            let v = if let Some(v) = vals.pop() {
251                v
252            } else {
253                return Ok(None);
254            };
255
256            // Get rid of any top-level thunks, and bail out of self-recursive
257            // thunks.
258            let value = if let Value::Thunk(t) = &v {
259                if !thunk_set.insert(t) {
260                    continue;
261                }
262                Thunk::force_(t.clone(), &co, span).await?
263            } else {
264                v
265            };
266
267            match value {
268                // Short-circuit on already evaluated values, or fail on internal values.
269                Value::Null
270                | Value::Bool(_)
271                | Value::Integer(_)
272                | Value::Float(_)
273                | Value::String(_)
274                | Value::Path(_)
275                | Value::Closure(_)
276                | Value::Builtin(_) => continue,
277
278                Value::List(list) => {
279                    for val in list.into_iter().rev() {
280                        vals.push(val);
281                    }
282                    continue;
283                }
284
285                Value::Attrs(attrs) => {
286                    for (_, val) in attrs.into_iter_sorted().rev() {
287                        vals.push(val);
288                    }
289                    continue;
290                }
291
292                Value::Thunk(_) => panic!("Snix bug: force_value() returned a thunk"),
293
294                Value::Catchable(_) => return Ok(Some(value)),
295
296                Value::AttrNotFound
297                | Value::Blueprint(_)
298                | Value::DeferredUpvalue(_)
299                | Value::UnresolvedPath(_)
300                | Value::FinaliseRequest(_) => panic!(
301                    "Snix bug: internal value left on stack: {}",
302                    value.type_of()
303                ),
304            }
305        }
306    }
307
308    pub async fn coerce_to_string(
309        self,
310        co: GenCo,
311        kind: CoercionKind,
312        span: Span,
313    ) -> Result<Value, ErrorKind> {
314        self.coerce_to_string_(&co, kind, span).await
315    }
316
317    /// Coerce a `Value` to a string. See `CoercionKind` for a rundown of what
318    /// input types are accepted under what circumstances.
319    pub async fn coerce_to_string_(
320        self,
321        co: &GenCo,
322        kind: CoercionKind,
323        span: Span,
324    ) -> Result<Value, ErrorKind> {
325        enum Item {
326            Value(Value),
327            WithSeparator(Value),
328            Space,
329        }
330
331        let mut out = BString::default();
332        let mut out_context: NixContext = NixContext::new();
333
334        let mut worklist = vec![Item::Value(self)];
335        while let Some(item) = worklist.pop() {
336            let value = match item {
337                Item::Space => {
338                    out.push(b' ');
339                    continue;
340                }
341                Item::Value(value) => value.force(co, span).await?,
342                Item::WithSeparator(value) => {
343                    let value = value.force(co, span).await?;
344
345                    // Separate non-final list elements with a space, except when the element is
346                    // an empty list. Queue the space so nested values are fully coerced before
347                    // the separator is emitted.
348                    if !matches!(&value, Value::List(list) if list.is_empty()) {
349                        worklist.push(Item::Space);
350                    }
351                    value
352                }
353            };
354
355            match (value, kind) {
356                // coercions that are always done
357                (Value::String(mut s), _) => {
358                    if let Some(ctx) = s.take_context() {
359                        out_context.extend(*ctx);
360                    }
361
362                    out.push_str(s);
363                }
364
365                // TODO(sterni): Think about proper encoding handling here. This needs
366                // general consideration anyways, since one current discrepancy between
367                // C++ Nix and Snix is that the former's strings are arbitrary byte
368                // sequences without NUL bytes, whereas Snix only allows valid
369                // Unicode. See also b/189.
370                (
371                    Value::Path(p),
372                    CoercionKind {
373                        import_paths: true, ..
374                    },
375                ) => {
376                    let imported = generators::request_path_import(co, *p).await;
377                    // When we import a path from the evaluator, we must attach
378                    // its original path as its context.
379                    out_context = out_context.append(NixContextElement::Plain(
380                        imported.to_string_lossy().to_string(),
381                    ));
382                    out.push_str(imported.as_os_str().as_encoded_bytes());
383                }
384                (
385                    Value::Path(p),
386                    CoercionKind {
387                        import_paths: false,
388                        ..
389                    },
390                ) => {
391                    out.push_str(p.as_os_str().as_encoded_bytes());
392                }
393
394                // Attribute sets can be converted to strings if they either have an
395                // `__toString` attribute which holds a function that receives the
396                // set itself or an `outPath` attribute which should be a string.
397                // `__toString` is preferred.
398                (Value::Attrs(attrs), kind) => {
399                    if let Some(to_string) = attrs.select("__toString") {
400                        let callable = to_string.clone().force(co, span).await?;
401
402                        // Leave the attribute set on the stack as an argument
403                        // to the function call.
404                        generators::request_stack_push(co, Value::Attrs(attrs.clone())).await;
405
406                        // Call the callable ...
407                        let result = generators::request_call(co, callable).await;
408
409                        // Recurse on the result, as attribute set coercion
410                        // actually works recursively, e.g. you can even return
411                        // /another/ set with a __toString attr.
412                        worklist.push(Item::Value(result));
413                    } else if let Some(out_path) = attrs.select("outPath") {
414                        worklist.push(Item::Value(out_path.clone()));
415                    } else {
416                        return Err(ErrorKind::NotCoercibleToString { from: "set", kind });
417                    }
418                }
419
420                // strong coercions
421                (Value::Null, CoercionKind { strong: true, .. })
422                | (Value::Bool(false), CoercionKind { strong: true, .. }) => {
423                    // empty string
424                }
425                (Value::Bool(true), CoercionKind { strong: true, .. }) => {
426                    out.push_str("1");
427                }
428                (Value::Integer(i), CoercionKind { strong: true, .. }) => {
429                    write!(&mut out, "{i}").unwrap();
430                }
431                (Value::Float(f), CoercionKind { strong: true, .. }) => {
432                    // contrary to normal Display, coercing a float to a string will
433                    // result in unconditional 6 decimal places
434                    write!(&mut out, "{f:.6}").unwrap();
435                }
436
437                // Lists are coerced by coercing their elements and interspersing spaces
438                (Value::List(list), CoercionKind { strong: true, .. }) => {
439                    let len = list.len();
440                    for (i, value) in list.into_iter().enumerate().rev() {
441                        if i < len - 1 {
442                            worklist.push(Item::WithSeparator(value))
443                        } else {
444                            worklist.push(Item::Value(value))
445                        }
446                    }
447                }
448
449                (Value::Thunk(_), _) => panic!("Snix bug: force returned unforced thunk"),
450
451                val @ (Value::Closure(_), _)
452                | val @ (Value::Builtin(_), _)
453                | val @ (Value::Null, _)
454                | val @ (Value::Bool(_), _)
455                | val @ (Value::Integer(_), _)
456                | val @ (Value::Float(_), _)
457                | val @ (Value::List(_), _) => {
458                    return Err(ErrorKind::NotCoercibleToString {
459                        from: val.0.type_of(),
460                        kind,
461                    });
462                }
463
464                (c @ Value::Catchable(_), _) => return Ok(c),
465
466                (Value::AttrNotFound, _)
467                | (Value::Blueprint(_), _)
468                | (Value::DeferredUpvalue(_), _)
469                | (Value::UnresolvedPath(_), _)
470                | (Value::FinaliseRequest(_), _) => {
471                    panic!("Snix bug: .coerce_to_string() called on internal value")
472                }
473            }
474        }
475        Ok(Value::String(NixString::new_context_from(out_context, out)))
476    }
477
478    pub(crate) async fn nix_eq_owned_genco(
479        self,
480        other: Value,
481        co: GenCo,
482        ptr_eq: PointerEquality,
483        span: Span,
484    ) -> Result<Value, ErrorKind> {
485        self.nix_eq(other, &co, ptr_eq, span).await
486    }
487
488    /// Compare two Nix values for equality, forcing nested parts of the structure
489    /// as needed.
490    ///
491    /// This comparison needs to be invoked for nested values (e.g. in lists and
492    /// attribute sets) as well, which is done by suspending and asking the VM to
493    /// perform the nested comparison.
494    ///
495    /// The `top_level` parameter controls whether this invocation is the top-level
496    /// comparison, or a nested value comparison. See
497    /// `//snix/docs/value-pointer-equality.md`
498    pub(crate) async fn nix_eq(
499        self,
500        other: Value,
501        co: &GenCo,
502        ptr_eq: PointerEquality,
503        span: Span,
504    ) -> Result<Value, ErrorKind> {
505        // this is a stack of ((v1,v2),peq) triples to be compared;
506        // after each triple is popped off of the stack, v1 is
507        // compared to v2 using peq-mode PointerEquality
508        let mut vals = vec![((self, other), ptr_eq)];
509
510        loop {
511            let ((a, b), ptr_eq) = if let Some(abp) = vals.pop() {
512                abp
513            } else {
514                // stack is empty, so comparison has succeeded
515                return Ok(Value::Bool(true));
516            };
517            let a = match a {
518                Value::Thunk(thunk) => {
519                    // If both values are thunks, and thunk comparisons are allowed by
520                    // pointer, do that and move on.
521                    if ptr_eq == PointerEquality::AllowAll
522                        && let Value::Thunk(t1) = &b
523                        && t1.ptr_eq(&thunk)
524                    {
525                        continue;
526                    };
527
528                    Thunk::force_(thunk, co, span).await?
529                }
530
531                _ => a,
532            };
533
534            let b = b.force(co, span).await?;
535
536            debug_assert!(!matches!(a, Value::Thunk(_)));
537            debug_assert!(!matches!(b, Value::Thunk(_)));
538
539            let result = match (a, b) {
540                // Trivial comparisons
541                (c @ Value::Catchable(_), _) => return Ok(c),
542                (_, c @ Value::Catchable(_)) => return Ok(c),
543                (Value::Null, Value::Null) => true,
544                (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
545                (Value::String(s1), Value::String(s2)) => s1 == s2,
546                (Value::Path(p1), Value::Path(p2)) => p1 == p2,
547
548                // Numerical comparisons (they work between float & int)
549                (Value::Integer(i1), Value::Integer(i2)) => i1 == i2,
550                (Value::Integer(i), Value::Float(f)) => i as f64 == f,
551                (Value::Float(f1), Value::Float(f2)) => f1 == f2,
552                (Value::Float(f), Value::Integer(i)) => i as f64 == f,
553
554                // List comparisons
555                (Value::List(l1), Value::List(l2)) => {
556                    if ptr_eq >= PointerEquality::AllowNested && l1.ptr_eq(&l2) {
557                        continue;
558                    }
559
560                    if l1.len() != l2.len() {
561                        return Ok(Value::Bool(false));
562                    }
563
564                    vals.extend(l1.into_iter().rev().zip(l2.into_iter().rev()).zip(
565                        std::iter::repeat(std::cmp::max(ptr_eq, PointerEquality::AllowNested)),
566                    ));
567                    continue;
568                }
569
570                (_, Value::List(_)) | (Value::List(_), _) => return Ok(Value::Bool(false)),
571
572                // Attribute set comparisons
573                (Value::Attrs(a1), Value::Attrs(a2)) => {
574                    if ptr_eq >= PointerEquality::AllowNested && a1.ptr_eq(&a2) {
575                        continue;
576                    }
577
578                    // Special-case for derivation comparisons: If both attribute sets
579                    // have `type = derivation`, compare them by `outPath`.
580                    #[allow(clippy::single_match)] // might need more match arms later
581                    match (a1.select("type"), a2.select("type")) {
582                        (Some(v1), Some(v2)) => {
583                            let s1 = v1.clone().force(co, span).await?;
584                            if s1.is_catchable() {
585                                return Ok(s1);
586                            }
587                            let s2 = v2.clone().force(co, span).await?;
588                            if s2.is_catchable() {
589                                return Ok(s2);
590                            }
591                            let s1 = s1.to_str();
592                            let s2 = s2.to_str();
593
594                            if let (Ok(s1), Ok(s2)) = (s1, s2)
595                                && s1 == "derivation"
596                                && s2 == "derivation"
597                            {
598                                // TODO(tazjin): are the outPaths really required,
599                                // or should it fall through?
600                                let out1 = a1
601                                    .select_required("outPath")
602                                    .context("comparing derivations")?
603                                    .clone();
604
605                                let out2 = a2
606                                    .select_required("outPath")
607                                    .context("comparing derivations")?
608                                    .clone();
609
610                                let out1 = out1.clone().force(co, span).await?;
611                                let out2 = out2.clone().force(co, span).await?;
612
613                                if out1.is_catchable() {
614                                    return Ok(out1);
615                                }
616
617                                if out2.is_catchable() {
618                                    return Ok(out2);
619                                }
620
621                                let result =
622                                    out1.to_contextful_str()? == out2.to_contextful_str()?;
623                                if !result {
624                                    return Ok(Value::Bool(false));
625                                } else {
626                                    continue;
627                                }
628                            }
629                        }
630                        _ => {}
631                    };
632
633                    if a1.len() != a2.len() {
634                        return Ok(Value::Bool(false));
635                    }
636
637                    // note that it is important to be careful here with the
638                    // order we push the keys and values in order to properly
639                    // compare attrsets containing `throw` elements.
640                    let iter1 = a1.into_iter_sorted().rev();
641                    let iter2 = a2.into_iter_sorted().rev();
642                    for ((k1, v1), (k2, v2)) in iter1.zip(iter2) {
643                        vals.push((
644                            (v1, v2),
645                            std::cmp::max(ptr_eq, PointerEquality::AllowNested),
646                        ));
647                        vals.push((
648                            (k1.into(), k2.into()),
649                            std::cmp::max(ptr_eq, PointerEquality::AllowNested),
650                        ));
651                    }
652                    continue;
653                }
654
655                (Value::Attrs(_), _) | (_, Value::Attrs(_)) => return Ok(Value::Bool(false)),
656
657                (Value::Closure(c1), Value::Closure(c2))
658                    if ptr_eq >= PointerEquality::AllowNested && Rc::ptr_eq(&c1, &c2) =>
659                {
660                    continue;
661                }
662
663                // Everything else is either incomparable (e.g. internal types) or
664                // false.
665                _ => return Ok(Value::Bool(false)),
666            };
667            if !result {
668                return Ok(Value::Bool(false));
669            }
670        }
671    }
672
673    pub fn type_of(&self) -> &'static str {
674        match self {
675            Value::Null => "null",
676            Value::Bool(_) => "bool",
677            Value::Integer(_) => "int",
678            Value::Float(_) => "float",
679            Value::String(_) => "string",
680            Value::Path(_) => "path",
681            Value::Attrs(_) => "set",
682            Value::List(_) => "list",
683            Value::Closure(_) | Value::Builtin(_) => "lambda",
684
685            // Internal types. Note: These are only elaborated here
686            // because it makes debugging easier. If a user ever sees
687            // any of these strings, it's a bug.
688            Value::Thunk(_) => "internal[thunk]",
689            Value::AttrNotFound => "internal[attr_not_found]",
690            Value::Blueprint(_) => "internal[blueprint]",
691            Value::DeferredUpvalue(_) => "internal[deferred_upvalue]",
692            Value::UnresolvedPath(_) => "internal[unresolved_path]",
693            Value::FinaliseRequest(_) => "internal[finaliser_sentinel]",
694            Value::Catchable(_) => "internal[catchable]",
695        }
696    }
697
698    gen_cast!(as_bool, bool, "bool", Value::Bool(b), *b);
699    gen_cast!(as_int, i64, "int", Value::Integer(x), *x);
700    gen_cast!(as_float, f64, "float", Value::Float(x), *x);
701
702    /// Cast the current value into a **context-less** string.
703    /// If you wanted to cast it into a potentially contextful string,
704    /// you have to explicitly use `to_contextful_str`.
705    /// Contextful strings are special, they should not be obtained
706    /// everytime you want a string.
707    pub fn to_str(&self) -> Result<NixString, ErrorKind> {
708        match self {
709            Value::String(s) if !s.has_context() => Ok((*s).clone()),
710            Value::Thunk(thunk) => Self::to_str(&thunk.value()),
711            other => Err(type_error("contextless strings", other)),
712        }
713    }
714
715    gen_cast!(
716        to_contextful_str,
717        NixString,
718        "contextful string",
719        Value::String(s),
720        (*s).clone()
721    );
722    gen_cast!(to_path, Box<PathBuf>, "path", Value::Path(p), p.clone());
723    gen_cast!(to_attrs, NixAttrs, "set", Value::Attrs(a), a.clone());
724    gen_cast!(to_list, NixList, "list", Value::List(l), l.clone());
725    gen_cast!(
726        as_closure,
727        Rc<Closure>,
728        "lambda",
729        Value::Closure(c),
730        c.clone()
731    );
732
733    gen_cast_mut!(as_list_mut, NixList, "list", List);
734
735    gen_is!(is_path, Value::Path(_));
736    gen_is!(is_number, Value::Integer(_) | Value::Float(_));
737    gen_is!(is_bool, Value::Bool(_));
738    gen_is!(is_attrs, Value::Attrs(_));
739    gen_is!(is_catchable, Value::Catchable(_));
740
741    /// Returns `true` if the value is a [`Thunk`].
742    ///
743    /// [`Thunk`]: Value::Thunk
744    pub fn is_thunk(&self) -> bool {
745        matches!(self, Self::Thunk(..))
746    }
747
748    /// Compare `self` against other using (fallible) Nix ordering semantics.
749    ///
750    /// The function is intended to be used from within other generator
751    /// functions or `gen!` blocks.
752    pub async fn nix_cmp_ordering(
753        self,
754        other: Self,
755        co: GenCo,
756        span: Span,
757    ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
758        Self::nix_cmp_ordering_(self, other, co, span).await
759    }
760
761    async fn nix_cmp_ordering_(
762        myself: Self,
763        other: Self,
764        co: GenCo,
765        span: Span,
766    ) -> Result<Result<Ordering, CatchableErrorKind>, ErrorKind> {
767        // this is a stack of ((v1,v2),peq) triples to be compared;
768        // after each triple is popped off of the stack, v1 is
769        // compared to v2 using peq-mode PointerEquality
770        let mut vals = vec![((myself, other), PointerEquality::ForbidAll)];
771
772        loop {
773            let ((mut a, mut b), ptr_eq) = if let Some(abp) = vals.pop() {
774                abp
775            } else {
776                // stack is empty, so they are equal
777                return Ok(Ok(Ordering::Equal));
778            };
779            if ptr_eq == PointerEquality::AllowAll {
780                if a.clone()
781                    .nix_eq(b.clone(), &co, PointerEquality::AllowAll, span)
782                    .await?
783                    .as_bool()?
784                {
785                    continue;
786                }
787                a = a.force(&co, span).await?;
788                b = b.force(&co, span).await?;
789            }
790            let result = match (a, b) {
791                (Value::Catchable(c), _) => return Ok(Err(*c)),
792                (_, Value::Catchable(c)) => return Ok(Err(*c)),
793                // same types
794                (Value::Integer(i1), Value::Integer(i2)) => i1.cmp(&i2),
795                (Value::Float(f1), Value::Float(f2)) => f1.total_cmp(&f2),
796                (Value::String(s1), Value::String(s2)) => s1.cmp(&s2),
797                (Value::List(l1), Value::List(l2)) => {
798                    let max = l1.len().max(l2.len());
799                    for j in 0..max {
800                        let i = max - 1 - j;
801                        if i >= l2.len() {
802                            vals.push(((1.into(), 0.into()), PointerEquality::ForbidAll));
803                        } else if i >= l1.len() {
804                            vals.push(((0.into(), 1.into()), PointerEquality::ForbidAll));
805                        } else {
806                            vals.push(((l1[i].clone(), l2[i].clone()), PointerEquality::AllowAll));
807                        }
808                    }
809                    continue;
810                }
811
812                // different types
813                (Value::Integer(i1), Value::Float(f2)) => (i1 as f64).total_cmp(&f2),
814                (Value::Float(f1), Value::Integer(i2)) => f1.total_cmp(&(i2 as f64)),
815
816                // unsupported types
817                (lhs, rhs) => {
818                    return Err(ErrorKind::Incomparable {
819                        lhs: lhs.type_of(),
820                        rhs: rhs.type_of(),
821                    });
822                }
823            };
824            if result != Ordering::Equal {
825                return Ok(Ok(result));
826            }
827        }
828    }
829
830    // TODO(amjoseph): de-asyncify this (when called directly by the VM)
831    pub async fn force(self, co: &GenCo, span: Span) -> Result<Value, ErrorKind> {
832        if let Value::Thunk(thunk) = self {
833            // TODO(amjoseph): use #[tailcall::mutual]
834            return Thunk::force_(thunk, co, span).await;
835        }
836        Ok(self)
837    }
838
839    // need two flavors, because async
840    pub async fn force_owned_genco(self, co: GenCo, span: Span) -> Result<Value, ErrorKind> {
841        if let Value::Thunk(thunk) = self {
842            // TODO(amjoseph): use #[tailcall::mutual]
843            return Thunk::force_(thunk, &co, span).await;
844        }
845        Ok(self)
846    }
847
848    /// Explain a value in a human-readable way, e.g. by presenting
849    /// the docstrings of functions if present.
850    pub fn explain(&self) -> String {
851        match self {
852            Value::Null => "the 'null' value".into(),
853            Value::Bool(b) => format!("the boolean value '{b}'"),
854            Value::Integer(i) => format!("the integer '{i}'"),
855            Value::Float(f) => format!("the float '{f}'"),
856            Value::String(s) if s.has_context() => format!("the contextful string '{s}'"),
857            Value::String(s) => format!("the contextless string '{s}'"),
858            Value::Path(p) => format!("the path '{}'", p.to_string_lossy()),
859            Value::Attrs(attrs) => format!("a {}-item attribute set", attrs.len()),
860            Value::List(list) => format!("a {}-item list", list.len()),
861
862            Value::Closure(f) => {
863                if let Some(name) = &f.lambda.name {
864                    format!("the user-defined Nix function '{name}'")
865                } else {
866                    "a user-defined Nix function".to_string()
867                }
868            }
869
870            Value::Builtin(b) => {
871                let mut out = format!("the builtin function '{}'", b.name());
872                if let Some(docs) = b.documentation() {
873                    out.push_str("\n\n");
874                    out.push_str(docs);
875                }
876                out
877            }
878
879            // TODO: handle suspended thunks with a different explanation instead of panicking
880            Value::Thunk(t) => t.value().explain(),
881
882            Value::Catchable(_) => "a catchable failure".into(),
883
884            Value::AttrNotFound
885            | Value::Blueprint(_)
886            | Value::DeferredUpvalue(_)
887            | Value::UnresolvedPath(_)
888            | Value::FinaliseRequest(_) => "an internal Snix evaluator value".into(),
889        }
890    }
891
892    /// Constructs a thunk that will be evaluated lazily at runtime. This lets
893    /// users of Snix implement their own lazy builtins and so on.
894    pub fn suspended_native_thunk(native: Box<dyn Fn() -> Result<Value, ErrorKind>>) -> Self {
895        Value::Thunk(Thunk::new_suspended_native(native))
896    }
897}
898
899trait TotalDisplay {
900    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result;
901}
902
903impl Display for Value {
904    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
905        self.total_fmt(f, &mut Default::default())
906    }
907}
908
909/// Emulates the C++-Nix style formatting of floats, which diverges
910/// significantly from Rust's native float formatting.
911fn total_fmt_float<F: std::fmt::Write>(num: f64, mut f: F) -> std::fmt::Result {
912    let mut buf = [b'0'; lexical_core::BUFFER_SIZE];
913    let mut s = lexical_core::write_with_options::<f64, { CXX_LITERAL }>(
914        num,
915        &mut buf,
916        &WRITE_FLOAT_OPTIONS,
917    );
918
919    // apply some postprocessing on the buffer. If scientific
920    // notation is used (we see an `e`), and the next character is
921    // a digit, add the missing `+` sign.)
922    let mut new_s = Vec::with_capacity(s.len());
923
924    if s.contains(&b'e') {
925        for (i, c) in s.iter().enumerate() {
926            // encountered `e`
927            if c == &b'e' {
928                // next character is a digit (so no negative exponent)
929                if s.len() > i && s[i + 1].is_ascii_digit() {
930                    // copy everything from the start up to (including) the e
931                    new_s.extend_from_slice(&s[0..=i]);
932                    // add the missing '+'
933                    new_s.push(b'+');
934                    // check for the remaining characters.
935                    // If it's only one, we need to prepend a trailing zero
936                    if s.len() == i + 2 {
937                        new_s.push(b'0');
938                    }
939                    new_s.extend_from_slice(&s[i + 1..]);
940                    break;
941                }
942            }
943        }
944
945        // if we modified the scientific notation, flip the reference
946        if !new_s.is_empty() {
947            s = &mut new_s
948        }
949    } else if s.contains(&b'.') {
950        // else, if this is not scientific notation, and there's a
951        // decimal point, make sure we really drop trailing zeroes.
952        // In some cases, lexical_core doesn't.
953        for (i, c) in s.iter().enumerate() {
954            // at `.``
955            if c == &b'.' {
956                // trim zeroes from the right side.
957                let frac = String::from_utf8_lossy(&s[i + 1..]);
958                let frac_no_trailing_zeroes = frac.trim_end_matches('0');
959
960                if frac.len() != frac_no_trailing_zeroes.len() {
961                    // we managed to strip something, construct new_s
962                    if frac_no_trailing_zeroes.is_empty() {
963                        // if frac_no_trailing_zeroes is empty, the fractional part was all zeroes, so we can drop the decimal point as well
964                        new_s.extend_from_slice(&s[0..=i - 1]);
965                    } else {
966                        // else, assemble the rest of the string
967                        new_s.extend_from_slice(&s[0..=i]);
968                        new_s.extend_from_slice(frac_no_trailing_zeroes.as_bytes());
969                    }
970
971                    // flip the reference
972                    s = &mut new_s;
973                    break;
974                }
975            }
976        }
977    }
978
979    write!(f, "{}", String::from_utf8_lossy(s))
980}
981
982impl TotalDisplay for Value {
983    fn total_fmt(&self, f: &mut std::fmt::Formatter<'_>, set: &mut ThunkSet) -> std::fmt::Result {
984        match self {
985            Value::Null => f.write_str("null"),
986            Value::Bool(true) => f.write_str("true"),
987            Value::Bool(false) => f.write_str("false"),
988            Value::Integer(num) => write!(f, "{num}"),
989            Value::String(s) => s.fmt(f),
990            Value::Path(p) => p.display().fmt(f),
991            Value::Attrs(attrs) => attrs.total_fmt(f, set),
992            Value::List(list) => list.total_fmt(f, set),
993            // TODO: fancy REPL display with position
994            Value::Closure(_) => f.write_str("<LAMBDA>"),
995            Value::Builtin(builtin) => builtin.fmt(f),
996
997            // Nix prints floats with a maximum precision of 5 digits
998            // only. Except when it decides to use scientific notation
999            // (with a + after the `e`, and zero-padded to 0 digits)
1000            Value::Float(num) => total_fmt_float(*num, f),
1001
1002            // internal types
1003            Value::AttrNotFound => f.write_str("internal[not found]"),
1004            Value::Blueprint(_) => f.write_str("internal[blueprint]"),
1005            Value::DeferredUpvalue(_) => f.write_str("internal[deferred_upvalue]"),
1006            Value::UnresolvedPath(_) => f.write_str("internal[unresolved_path]"),
1007            Value::FinaliseRequest(_) => f.write_str("internal[finaliser_sentinel]"),
1008
1009            // Delegate thunk display to the type, as it must handle
1010            // the case of already evaluated or cyclic thunks.
1011            Value::Thunk(t) => t.total_fmt(f, set),
1012            Value::Catchable(_) => panic!("total_fmt() called on a CatchableErrorKind"),
1013        }
1014    }
1015}
1016
1017impl From<bool> for Value {
1018    fn from(b: bool) -> Self {
1019        Value::Bool(b)
1020    }
1021}
1022
1023impl From<i64> for Value {
1024    fn from(i: i64) -> Self {
1025        Self::Integer(i)
1026    }
1027}
1028
1029impl From<f64> for Value {
1030    fn from(i: f64) -> Self {
1031        Self::Float(i)
1032    }
1033}
1034
1035impl From<PathBuf> for Value {
1036    fn from(path: PathBuf) -> Self {
1037        Self::Path(Box::new(path))
1038    }
1039}
1040
1041fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
1042    ErrorKind::TypeError {
1043        expected,
1044        actual: actual.type_of(),
1045    }
1046}
1047
1048#[cfg(test)]
1049mod tests {
1050    use super::*;
1051    use std::mem::size_of;
1052
1053    #[test]
1054    fn size() {
1055        assert_eq!(size_of::<Value>(), 16);
1056    }
1057
1058    mod floats {
1059        use crate::value::total_fmt_float;
1060
1061        #[test]
1062        fn format_float() {
1063            let ff = [
1064                (0f64, "0"),
1065                (1.0f64, "1"),
1066                (-0.01, "-0.01"),
1067                (5e+22, "5e+22"),
1068                (1e6, "1e+06"),
1069                (-2E-2, "-0.02"),
1070                (6.626e-34, "6.626e-34"),
1071                (9_224_617.445_991_227, "9.22462e+06"),
1072            ];
1073            for (n, expected) in ff.iter() {
1074                let mut buf = String::new();
1075                let res = total_fmt_float(*n, &mut buf);
1076                assert!(res.is_ok());
1077                assert_eq!(
1078                    expected, &buf,
1079                    "{} should be formatted as {}, but got {}",
1080                    n, expected, buf
1081                );
1082            }
1083        }
1084    }
1085}