@@ -18,6 +18,66 @@ pub enum Value {
1818 Id(Rc<String>),
1919 Blob(Rc<BlobVal>),
2020 Shape(Rc<ShapeVal>),
21+ /// a value computed on first use (§2): currently used for a commit's
22+ /// `files` list, so loading a repo does not materialize every commit's
23+ /// file list. Forced transparently by `Value::field` and `value_eq`.
24+ Thunk(Rc<ThunkVal>),
25+}
26+
27+pub struct ThunkVal {
28+ state: std::cell::RefCell<ThunkState>,
29+}
30+
31+enum ThunkState {
32+ Pending(Box<dyn FnOnce() -> Result<Value, Crash>>),
33+ Ready(Value),
34+}
35+
36+impl std::fmt::Debug for ThunkVal {
37+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38+ match &*self.state.borrow() {
39+ ThunkState::Pending(_) => write!(f, "Thunk(pending)"),
40+ ThunkState::Ready(v) => write!(f, "Thunk({:?})", v),
41+ }
42+ }
43+}
44+
45+impl ThunkVal {
46+ pub fn new(f: impl FnOnce() -> Result<Value, Crash> + 'static) -> Self {
47+ ThunkVal {
48+ state: std::cell::RefCell::new(ThunkState::Pending(Box::new(f))),
49+ }
50+ }
51+
52+ /// the value, computing it on first call and memoizing
53+ pub fn force(&self) -> Result<Value, Crash> {
54+ if let ThunkState::Ready(v) = &*self.state.borrow() {
55+ return Ok(v.clone());
56+ }
57+ let thunk = {
58+ let mut st = self.state.borrow_mut();
59+ match std::mem::replace(&mut *st, ThunkState::Ready(Value::Bool(false))) {
60+ ThunkState::Pending(t) => t,
61+ ThunkState::Ready(v) => {
62+ *st = ThunkState::Ready(v.clone());
63+ return Ok(v);
64+ }
65+ }
66+ };
67+ let v = thunk()?;
68+ *self.state.borrow_mut() = ThunkState::Ready(v.clone());
69+ Ok(v)
70+ }
71+}
72+
73+impl Value {
74+ /// force a thunk; every other value is returned unchanged
75+ pub fn forced(&self) -> Result<Value, Crash> {
76+ match self {
77+ Value::Thunk(t) => t.force(),
78+ _ => Ok(self.clone()),
79+ }
80+ }
2181}
2282
2383#[derive(Clone, Debug, PartialEq, Eq)]
@@ -266,6 +326,9 @@ impl Value {
266326 Value::Id(_) => "Id",
267327 Value::Blob(_) => "Blob",
268328 Value::Shape(_) => "Shape",
329+ // a thunk stands in for its eventual value (currently a list of
330+ // file entries); kind_name is used in error messages
331+ Value::Thunk(_) => "list",
269332 }
270333 }
271334
@@ -282,7 +345,8 @@ impl Value {
282345 Value::Record(m) => m
283346 .get(name)
284347 .cloned()
285− .ok_or_else(|| Crash::new(format!("record has no field `{}`", name))),
348+ .ok_or_else(|| Crash::new(format!("record has no field `{}`", name)))
349+ .and_then(|v| v.forced()),
286350 _ => Err(Crash::new(format!(
287351 "cannot select field `{}` from a {}",
288352 name,
@@ -533,6 +597,13 @@ impl Env {
533597// ----------------------------------------------------------------------
534598
535599pub fn value_eq(a: &Value, b: &Value) -> Result<bool, Crash> {
600+ // force thunks at the boundary so callers never see them
601+ if let Value::Thunk(_) = a {
602+ return value_eq(&a.forced()?, b);
603+ }
604+ if let Value::Thunk(_) = b {
605+ return value_eq(a, &b.forced()?);
606+ }
536607 Ok(match (a, b) {
537608 (Value::Int(x), Value::Int(y)) => x == y,
538609 (Value::Text(x), Value::Text(y)) => x == y,
@@ -596,6 +667,7 @@ impl std::fmt::Debug for Value {
596667 Value::Fun(_) => write!(f, "Fun(..)"),
597668 Value::Blob(b) => write!(f, "Blob({:?})", b),
598669 Value::Shape(s) => write!(f, "Shape({})", s.name),
670+ Value::Thunk(t) => write!(f, "{:?}", t),
599671 }
600672 }
601673}