Skip to main content

sui_eval/
lazy.rs

1//! Lazy evaluation primitives — making accidental eagerness impossible.
2//!
3//! The core type `Lazy<T>` guarantees evaluation is deferred until `.demand()`.
4//! Unlike `Thunk` (which is a Value variant that callers must check for),
5//! `Lazy<T>` is a WRAPPER that enforces laziness by construction.
6//!
7//! # Architecture
8//!
9//! ```text
10//! Lazy<Value>       — a value that might not be computed yet
11//! LazyAttrs         — attrset where values are Lazy<Value>
12//! LazyList           — list where elements are Lazy<Value>
13//! ```
14//!
15//! The evaluator returns `Lazy<Value>` from all expression evaluation.
16//! Consumers that need concrete values call `.demand()` explicitly.
17//! Operations that DON'T need the value (attrset key checking, list
18//! length, typeOf on already-known types) work WITHOUT demanding.
19
20use std::cell::OnceCell;
21use std::rc::Rc;
22
23/// A lazy value: computed at most once, on first demand.
24///
25/// Unlike `Thunk`, this is not a `Value` variant — it's a WRAPPER.
26/// You can't accidentally pattern-match past it. You must call
27/// `.demand()` to get the inner value.
28///
29/// Size: 1 word (Rc pointer). The inner cell is shared across clones.
30#[derive(Clone)]
31pub struct Lazy<T: Clone> {
32    inner: Rc<LazyInner<T>>,
33}
34
35struct LazyInner<T: Clone> {
36    /// Cached result (set on first demand).
37    cache: OnceCell<T>,
38    /// Computation to produce the value. Consumed on first demand.
39    compute: std::cell::Cell<Option<Box<dyn FnOnce() -> T>>>,
40}
41
42impl<T: Clone> Lazy<T> {
43    /// Create a lazy value from a computation.
44    pub fn defer(f: impl FnOnce() -> T + 'static) -> Self {
45        Self {
46            inner: Rc::new(LazyInner {
47                cache: OnceCell::new(),
48                compute: std::cell::Cell::new(Some(Box::new(f))),
49            }),
50        }
51    }
52
53    /// Create an already-computed lazy value (no deferral).
54    pub fn ready(value: T) -> Self {
55        let cache = OnceCell::new();
56        let _ = cache.set(value);
57        Self {
58            inner: Rc::new(LazyInner {
59                cache,
60                compute: std::cell::Cell::new(None),
61            }),
62        }
63    }
64
65    /// Demand the value. Evaluates if not yet computed. Returns cached result.
66    pub fn demand(&self) -> &T {
67        self.inner.cache.get_or_init(|| {
68            let compute = self.inner.compute.take()
69                .expect("Lazy: cache empty but compute already consumed (bug)");
70            compute()
71        })
72    }
73
74    /// Check if already computed (without forcing).
75    pub fn is_ready(&self) -> bool {
76        self.inner.cache.get().is_some()
77    }
78}
79
80impl<T: Clone + std::fmt::Debug> std::fmt::Debug for Lazy<T> {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        if let Some(v) = self.inner.cache.get() {
83            write!(f, "Lazy({v:?})")
84        } else {
85            write!(f, "Lazy(<deferred>)")
86        }
87    }
88}
89
90// ── Fallible lazy values ─────────────────────────────────────
91
92/// A lazy value whose computation can fail.
93///
94/// Like `Lazy<T>` but the computation returns `Result<T, E>`.
95/// On first `.demand()`, if the computation fails, the error is
96/// returned and the computation can be retried on next `.demand()`.
97/// On success, the result is cached permanently.
98#[derive(Clone)]
99pub struct FallibleLazy<T: Clone, E: Clone> {
100    inner: Rc<FallibleLazyInner<T, E>>,
101}
102
103struct FallibleLazyInner<T: Clone, E: Clone> {
104    cache: OnceCell<T>,
105    compute: std::cell::Cell<Option<Box<dyn FnOnce() -> Result<T, E>>>>,
106}
107
108impl<T: Clone, E: Clone> FallibleLazy<T, E> {
109    /// Create a fallible lazy value from a computation.
110    pub fn defer(f: impl FnOnce() -> Result<T, E> + 'static) -> Self {
111        Self {
112            inner: Rc::new(FallibleLazyInner {
113                cache: OnceCell::new(),
114                compute: std::cell::Cell::new(Some(Box::new(f))),
115            }),
116        }
117    }
118
119    /// Create an already-computed fallible lazy value.
120    pub fn ready(value: T) -> Self {
121        let cache = OnceCell::new();
122        let _ = cache.set(value);
123        Self {
124            inner: Rc::new(FallibleLazyInner {
125                cache,
126                compute: std::cell::Cell::new(None),
127            }),
128        }
129    }
130
131    /// Demand the value. Evaluates if not yet computed.
132    /// Returns `Ok(&T)` if cached or freshly computed.
133    /// Returns `Err(E)` if computation fails.
134    pub fn demand(&self) -> Result<&T, E> {
135        if let Some(v) = self.inner.cache.get() {
136            return Ok(v);
137        }
138        if let Some(compute) = self.inner.compute.take() {
139            match compute() {
140                Ok(val) => {
141                    let _ = self.inner.cache.set(val);
142                    Ok(self.inner.cache.get().unwrap())
143                }
144                Err(e) => Err(e),
145            }
146        } else {
147            // Cache is empty and compute was already consumed (error case).
148            // This shouldn't happen in normal usage.
149            panic!("FallibleLazy: cache empty and compute consumed without storing result")
150        }
151    }
152
153    /// Check if already computed (without forcing).
154    pub fn is_ready(&self) -> bool {
155        self.inner.cache.get().is_some()
156    }
157}
158
159impl<T: Clone + std::fmt::Debug, E: Clone> std::fmt::Debug for FallibleLazy<T, E> {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        if let Some(v) = self.inner.cache.get() {
162            write!(f, "FallibleLazy({v:?})")
163        } else {
164            write!(f, "FallibleLazy(<deferred>)")
165        }
166    }
167}
168
169// ── Lazy attribute set ───────────────────────────────────────
170
171use crate::value::{Value, EvalError, NixAttrs, intern, resolve};
172use sui_intern::Symbol;
173
174/// A lazy Nix attribute value: the key is known, the value is deferred.
175///
176/// This is the building block for lazy attrsets. Unlike `Value::Thunk`,
177/// the laziness is part of the CONTAINER (the attrset), not the VALUE.
178/// The attrset knows its keys immediately but computes values on demand.
179pub type LazyValue = FallibleLazy<Value, EvalError>;
180
181/// A Nix attrset where values are computed on demand.
182///
183/// Keys are available immediately (for `builtins.attrNames`, `hasAttr`, `//` merge).
184/// Values are `LazyValue` — only computed when accessed via `.get()`.
185///
186/// This makes the attrset inherently lazy by construction:
187/// - `keys()` — no forcing, returns immediately
188/// - `contains_key()` — no forcing
189/// - `get()` — forces ONLY the requested value
190/// - `update()` — merges keys, values stay lazy
191#[derive(Clone, Debug)]
192pub struct LazyAttrs {
193    entries: im_rc::HashMap<Symbol, LazyValue, rustc_hash::FxBuildHasher>,
194}
195
196impl LazyAttrs {
197    /// Create an empty lazy attrset.
198    pub fn new() -> Self {
199        Self {
200            entries: im_rc::HashMap::default(),
201        }
202    }
203
204    /// Insert a lazy value.
205    pub fn insert(&mut self, key: Symbol, value: LazyValue) {
206        self.entries.insert(key, value);
207    }
208
209    /// Insert an already-computed value.
210    pub fn insert_ready(&mut self, key: Symbol, value: Value) {
211        self.entries.insert(key, LazyValue::ready(value));
212    }
213
214    /// Insert a deferred computation.
215    pub fn insert_deferred<F>(&mut self, key: Symbol, f: F)
216    where
217        F: FnOnce() -> Result<Value, EvalError> + 'static,
218    {
219        self.entries.insert(key, LazyValue::defer(f));
220    }
221
222    /// Look up a value by name — forces ONLY this value.
223    pub fn get(&self, key: &str) -> Option<Result<&Value, EvalError>> {
224        let sym = intern(key);
225        self.entries.get(&sym).map(|lv| lv.demand())
226    }
227
228    /// Look up by pre-interned symbol — forces ONLY this value.
229    pub fn get_sym(&self, sym: &Symbol) -> Option<Result<&Value, EvalError>> {
230        self.entries.get(sym).map(|lv| lv.demand())
231    }
232
233    /// Check if a key exists — NO forcing.
234    pub fn contains_key(&self, key: &str) -> bool {
235        self.entries.contains_key(&intern(key))
236    }
237
238    /// Get all key names — NO forcing of values.
239    pub fn keys(&self) -> impl Iterator<Item = String> + '_ {
240        self.entries.keys().map(|s| resolve(*s))
241    }
242
243    /// Number of entries — NO forcing.
244    pub fn len(&self) -> usize {
245        self.entries.len()
246    }
247
248    /// Whether empty — NO forcing.
249    pub fn is_empty(&self) -> bool {
250        self.entries.is_empty()
251    }
252
253    /// Merge two lazy attrsets (right overrides left) — NO forcing.
254    /// Values stay lazy. Only keys are merged.
255    pub fn update(&self, other: &LazyAttrs) -> LazyAttrs {
256        let mut result = self.entries.clone();
257        for (k, v) in other.entries.iter() {
258            result.insert(*k, v.clone());
259        }
260        LazyAttrs { entries: result }
261    }
262
263    /// Convert to a traditional NixAttrs by forcing ALL values.
264    /// Use sparingly — only when ALL values are needed.
265    pub fn force_all(&self) -> Result<NixAttrs, EvalError> {
266        let mut attrs = NixAttrs::new();
267        for (sym, lv) in self.entries.iter() {
268            let val = lv.demand()?;
269            attrs.insert(resolve(*sym), val.clone());
270        }
271        Ok(attrs)
272    }
273}
274
275impl Default for LazyAttrs {
276    fn default() -> Self {
277        Self::new()
278    }
279}
280
281// ── Lazy overlay chain ────────────────────────────────────────
282
283/// A lazy overlay: `left // right` without eagerly merging.
284///
285/// When an attribute is accessed, the chain is walked right-to-left
286/// (right overrides left). Keys are collected lazily on first `.keys()`.
287/// This eliminates the O(n) merge cost per overlay application —
288/// critical for nixpkgs with 20+ overlays × 80K+ attrs each.
289#[derive(Clone, Debug)]
290pub enum OverlayAttrs {
291    /// A concrete base attrset.
292    Base(Rc<NixAttrs>),
293    /// A lazy overlay: `left // right`.
294    /// Right overrides left. Neither is forced until accessed.
295    Overlay {
296        left: Rc<OverlayAttrs>,
297        right: Rc<NixAttrs>,
298    },
299}
300
301impl OverlayAttrs {
302    /// Create from a concrete attrset.
303    pub fn base(attrs: NixAttrs) -> Self {
304        OverlayAttrs::Base(Rc::new(attrs))
305    }
306
307    /// Apply an overlay: `self // right`.
308    /// O(1) — just creates a new node. No iteration.
309    pub fn overlay(self, right: NixAttrs) -> Self {
310        OverlayAttrs::Overlay {
311            left: Rc::new(self),
312            right: Rc::new(right),
313        }
314    }
315
316    /// Look up an attribute — walks the chain right-to-left.
317    /// O(depth) where depth = number of overlays.
318    /// For nixpkgs: O(20) per access instead of O(80K) per merge.
319    pub fn get(&self, key: &str) -> Option<&Value> {
320        match self {
321            OverlayAttrs::Base(attrs) => attrs.get(key),
322            OverlayAttrs::Overlay { left, right } => {
323                // Right overrides left
324                right.get(key).or_else(|| left.get(key))
325            }
326        }
327    }
328
329    /// Look up by pre-interned symbol.
330    pub fn get_sym(&self, sym: &Symbol) -> Option<&Value> {
331        match self {
332            OverlayAttrs::Base(attrs) => attrs.get_sym(sym),
333            OverlayAttrs::Overlay { left, right } => {
334                right.get_sym(sym).or_else(|| left.get_sym(sym))
335            }
336        }
337    }
338
339    /// Check if a key exists — walks chain, no value forcing.
340    pub fn contains_key(&self, key: &str) -> bool {
341        match self {
342            OverlayAttrs::Base(attrs) => attrs.contains_key(key),
343            OverlayAttrs::Overlay { left, right } => {
344                right.contains_key(key) || left.contains_key(key)
345            }
346        }
347    }
348
349    /// Collect all unique keys. O(total_keys) but only called when needed
350    /// (e.g., `builtins.attrNames`). NOT called during normal attribute access.
351    pub fn all_keys(&self) -> Vec<String> {
352        let mut seen = std::collections::HashSet::new();
353        let mut result = Vec::new();
354        self.collect_keys(&mut seen, &mut result);
355        result.sort();
356        result
357    }
358
359    fn collect_keys(&self, seen: &mut std::collections::HashSet<String>, result: &mut Vec<String>) {
360        match self {
361            OverlayAttrs::Base(attrs) => {
362                for (k, _) in attrs.iter_unsorted() {
363                    if seen.insert(k.clone()) {
364                        result.push(k);
365                    }
366                }
367            }
368            OverlayAttrs::Overlay { left, right } => {
369                // Right first (overrides left)
370                for (k, _) in right.iter_unsorted() {
371                    if seen.insert(k.clone()) {
372                        result.push(k);
373                    }
374                }
375                left.collect_keys(seen, result);
376            }
377        }
378    }
379
380    /// Flatten to a concrete NixAttrs. Use when the full attrset is needed.
381    pub fn flatten(&self) -> NixAttrs {
382        match self {
383            OverlayAttrs::Base(attrs) => (**attrs).clone(),
384            OverlayAttrs::Overlay { left, right } => {
385                let base = left.flatten();
386                base.update(right)
387            }
388        }
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use std::cell::Cell;
396
397    // ── Lazy<T> tests ────────────────────────────────────────
398
399    #[test]
400    fn deferred_not_evaluated_until_demand() {
401        let evaluated = Rc::new(Cell::new(false));
402        let e = evaluated.clone();
403        let lazy = Lazy::defer(move || {
404            e.set(true);
405            42
406        });
407        assert!(!evaluated.get());
408        assert_eq!(*lazy.demand(), 42);
409        assert!(evaluated.get());
410    }
411
412    #[test]
413    fn demand_memoizes() {
414        let count = Rc::new(Cell::new(0));
415        let c = count.clone();
416        let lazy = Lazy::defer(move || {
417            c.set(c.get() + 1);
418            "hello"
419        });
420        assert_eq!(*lazy.demand(), "hello");
421        assert_eq!(*lazy.demand(), "hello");
422        assert_eq!(count.get(), 1); // computed exactly once
423    }
424
425    #[test]
426    fn ready_is_immediate() {
427        let lazy = Lazy::ready(99);
428        assert!(lazy.is_ready());
429        assert_eq!(*lazy.demand(), 99);
430    }
431
432    #[test]
433    fn clone_shares_computation() {
434        let count = Rc::new(Cell::new(0));
435        let c = count.clone();
436        let lazy = Lazy::defer(move || {
437            c.set(c.get() + 1);
438            7
439        });
440        let clone = lazy.clone();
441        assert_eq!(*lazy.demand(), 7);
442        assert_eq!(*clone.demand(), 7); // same cache
443        assert_eq!(count.get(), 1); // computed once
444    }
445
446    // ── FallibleLazy<T, E> tests ─────────────────────────────
447
448    #[test]
449    fn fallible_lazy_success() {
450        let fl: FallibleLazy<i64, String> = FallibleLazy::defer(|| Ok(42));
451        assert!(!fl.is_ready());
452        assert_eq!(*fl.demand().unwrap(), 42);
453        assert!(fl.is_ready());
454        assert_eq!(*fl.demand().unwrap(), 42); // cached
455    }
456
457    #[test]
458    fn fallible_lazy_ready() {
459        let fl: FallibleLazy<i64, String> = FallibleLazy::ready(99);
460        assert!(fl.is_ready());
461        assert_eq!(*fl.demand().unwrap(), 99);
462    }
463
464    #[test]
465    fn fallible_lazy_clone_shares() {
466        let count = Rc::new(Cell::new(0));
467        let c = count.clone();
468        let fl: FallibleLazy<i64, String> = FallibleLazy::defer(move || {
469            c.set(c.get() + 1);
470            Ok(7)
471        });
472        let clone = fl.clone();
473        assert_eq!(*fl.demand().unwrap(), 7);
474        assert_eq!(*clone.demand().unwrap(), 7);
475        assert_eq!(count.get(), 1); // computed once
476    }
477
478    // ── LazyAttrs tests ──────────────────────────────────────
479
480    #[test]
481    fn lazy_attrs_keys_without_forcing() {
482        let evaluated = Rc::new(Cell::new(false));
483        let e = evaluated.clone();
484        let mut attrs = LazyAttrs::new();
485        attrs.insert_deferred(intern("expensive"), move || {
486            e.set(true);
487            Ok(Value::Int(42))
488        });
489        attrs.insert_ready(intern("cheap"), Value::Int(1));
490
491        // Keys available without forcing
492        assert_eq!(attrs.len(), 2);
493        assert!(attrs.contains_key("expensive"));
494        assert!(attrs.contains_key("cheap"));
495        assert!(!evaluated.get()); // expensive NOT computed
496
497        // Only force when accessed
498        let val = attrs.get("expensive").unwrap().unwrap();
499        assert_eq!(*val, Value::Int(42));
500        assert!(evaluated.get()); // NOW computed
501    }
502
503    #[test]
504    fn lazy_attrs_update_no_forcing() {
505        let evaluated = Rc::new(Cell::new(false));
506        let e = evaluated.clone();
507        let mut a = LazyAttrs::new();
508        a.insert_ready(intern("x"), Value::Int(1));
509        let mut b = LazyAttrs::new();
510        b.insert_deferred(intern("y"), move || {
511            e.set(true);
512            Ok(Value::Int(2))
513        });
514
515        // Merge without forcing
516        let merged = a.update(&b);
517        assert_eq!(merged.len(), 2);
518        assert!(!evaluated.get()); // y NOT computed during merge
519
520        // Access x (cheap) without touching y
521        assert_eq!(*merged.get("x").unwrap().unwrap(), Value::Int(1));
522        assert!(!evaluated.get()); // y STILL not computed
523    }
524
525    #[test]
526    fn lazy_attrs_force_all() {
527        let mut attrs = LazyAttrs::new();
528        attrs.insert_ready(intern("a"), Value::Int(1));
529        attrs.insert_deferred(intern("b"), || Ok(Value::Int(2)));
530        let nix_attrs = attrs.force_all().unwrap();
531        assert_eq!(nix_attrs.get("a"), Some(&Value::Int(1)));
532        assert_eq!(nix_attrs.get("b"), Some(&Value::Int(2)));
533    }
534
535    // ── OverlayAttrs tests ───────────────────────────────────
536
537    #[test]
538    fn overlay_get_right_overrides_left() {
539        let mut left = NixAttrs::new();
540        left.insert("x".to_string(), Value::Int(1));
541        left.insert("y".to_string(), Value::Int(2));
542
543        let mut right = NixAttrs::new();
544        right.insert("x".to_string(), Value::Int(10)); // overrides
545
546        let overlay = OverlayAttrs::base(left).overlay(right);
547        assert_eq!(overlay.get("x"), Some(&Value::Int(10))); // right wins
548        assert_eq!(overlay.get("y"), Some(&Value::Int(2)));   // from left
549        assert_eq!(overlay.get("z"), None);                    // not found
550    }
551
552    #[test]
553    fn overlay_chain_three_levels() {
554        let mut a = NixAttrs::new();
555        a.insert("x".to_string(), Value::Int(1));
556        let mut b = NixAttrs::new();
557        b.insert("y".to_string(), Value::Int(2));
558        let mut c = NixAttrs::new();
559        c.insert("x".to_string(), Value::Int(3)); // overrides a.x
560
561        let chain = OverlayAttrs::base(a).overlay(b).overlay(c);
562        assert_eq!(chain.get("x"), Some(&Value::Int(3)));  // c wins
563        assert_eq!(chain.get("y"), Some(&Value::Int(2)));  // b
564    }
565
566    #[test]
567    fn overlay_is_o1_construction() {
568        // Creating an overlay chain should be O(1) per overlay,
569        // NOT O(n) where n is the number of attributes.
570        let mut big = NixAttrs::new();
571        for i in 0..1000 {
572            big.insert(format!("attr_{i}"), Value::Int(i));
573        }
574        let mut small = NixAttrs::new();
575        small.insert("target".to_string(), Value::Int(42));
576
577        // This should be instant — no iteration over 1000 attrs
578        let overlay = OverlayAttrs::base(big).overlay(small);
579        assert_eq!(overlay.get("target"), Some(&Value::Int(42)));
580        assert_eq!(overlay.get("attr_0"), Some(&Value::Int(0)));
581    }
582
583    #[test]
584    fn overlay_all_keys() {
585        let mut a = NixAttrs::new();
586        a.insert("x".to_string(), Value::Int(1));
587        a.insert("y".to_string(), Value::Int(2));
588        let mut b = NixAttrs::new();
589        b.insert("y".to_string(), Value::Int(20));
590        b.insert("z".to_string(), Value::Int(30));
591
592        let overlay = OverlayAttrs::base(a).overlay(b);
593        let keys = overlay.all_keys();
594        assert_eq!(keys, vec!["x", "y", "z"]); // sorted, unique
595    }
596
597    #[test]
598    fn overlay_contains_key() {
599        let mut a = NixAttrs::new();
600        a.insert("x".to_string(), Value::Int(1));
601        let overlay = OverlayAttrs::base(a);
602        assert!(overlay.contains_key("x"));
603        assert!(!overlay.contains_key("y"));
604    }
605
606    #[test]
607    fn overlay_flatten() {
608        let mut a = NixAttrs::new();
609        a.insert("x".to_string(), Value::Int(1));
610        let mut b = NixAttrs::new();
611        b.insert("x".to_string(), Value::Int(2));
612        b.insert("y".to_string(), Value::Int(3));
613
614        let overlay = OverlayAttrs::base(a).overlay(b);
615        let flat = overlay.flatten();
616        assert_eq!(flat.get("x"), Some(&Value::Int(2)));
617        assert_eq!(flat.get("y"), Some(&Value::Int(3)));
618    }
619}