refactor: replace CellValue::Empty with Option<CellValue>

Previously CellValue had three variants: Number, Text, and Empty.
The Empty variant acted as a null sentinel, but the compiler could not
distinguish between "this is a real value" and "this might be empty".
Code that received a CellValue could use it without checking for Empty,
because there was no type-level enforcement.

Now CellValue has only Number and Text. The absence of a value is
represented as None at every API boundary:

  DataStore::get()    → Option<&CellValue>  (was &CellValue / Empty)
  Model::get_cell()   → Option<&CellValue>  (was &CellValue / Empty)
  Model::evaluate()   → Option<CellValue>   (was CellValue::Empty)
  eval_formula()      → Option<CellValue>   (was CellValue::Empty)

Model gains clear_cell() for explicit key removal; ClearCell dispatch
calls it instead of set_cell(key, CellValue::Empty).

The compiler now forces every caller of evaluate/get_cell to handle
the None case explicitly — accidental use of an empty value as if it
were real is caught at compile time rather than silently computing
wrong results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ed L
2026-03-24 08:06:51 -07:00
parent e680b098ec
commit a2e519efcc
8 changed files with 95 additions and 109 deletions

View File

@ -370,8 +370,8 @@ mod tests {
("Measure".to_string(), "revenue".to_string()),
("region".to_string(), "West".to_string()),
]);
assert_eq!(model.get_cell(&k_east).as_f64(), Some(100.0));
assert_eq!(model.get_cell(&k_west).as_f64(), Some(200.0));
assert_eq!(model.get_cell(&k_east).and_then(|v| v.as_f64()), Some(100.0));
assert_eq!(model.get_cell(&k_west).and_then(|v| v.as_f64()), Some(200.0));
}
#[test]