diff --git a/crates/calc/src/eval.rs b/crates/calc/src/eval.rs index abef54ffd..d09da091c 100644 --- a/crates/calc/src/eval.rs +++ b/crates/calc/src/eval.rs @@ -13,6 +13,10 @@ use gridcraft_model::Workbook; /// Largest array a reference may expand to. pub const MAX_CELLS: u64 = 16_000_000; +/// References up to this many cells are read at their full size; larger ones (`A:A`, ranges +/// reserved for data to come such as `$A$2:$A$50000`) stop at the sheet's used range, so that +/// formulas over them stay fast. +const FULL_SIZE_CELLS: u64 = 16_384; const MAX_DEPTH: usize = 400; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -129,7 +133,10 @@ impl<'h> Evaluator<'h> { if a.range.is_single() { return self.host.cell_value(a.sheet, a.range.start); } - let range = self.trim(a); + // A reference keeps its size, so the array has its shape and its blanks count + // (`=A1:A10`, `SUMPRODUCT(--(A1:A10=""))`). + let cells = u64::from(a.range.height()) * u64::from(a.range.width()); + let range = if cells > FULL_SIZE_CELLS { self.trim(a) } else { a.range }; let (h, w) = (range.height() as usize, range.width() as usize); if (h as u64) * (w as u64) > MAX_CELLS { return Value::Error(CellError::Num); diff --git a/crates/calc/src/tests.rs b/crates/calc/src/tests.rs index 67db0830d..9ab26179e 100644 --- a/crates/calc/src/tests.rs +++ b/crates/calc/src/tests.rs @@ -158,6 +158,24 @@ fn spills() { assert_eq!(t.num("D2"), 40.0); } +#[test] +fn references_keep_their_size_past_the_used_range() { + let mut t = T::new(); + t.set("A1", "5"); + t.set("A2", "6"); + t.set("C1", "=ROWS(A1:A10*1)"); + t.set("C2", "=SUMPRODUCT(--(A1:A10=\"\"))"); + t.set("C3", "=COUNTBLANK(A1:A10)"); + t.set("C4", "=COUNTIF(A1:A10,\"\")"); + t.set("C5", "=COLUMNS(TRANSPOSE(A1:A10))"); + for (at, want) in [("C1", 10.0), ("C2", 8.0), ("C3", 8.0), ("C4", 8.0), ("C5", 10.0)] { + assert_eq!(t.num(at), want, "{at}"); + } + // Whole columns are still read up to the used range. + t.set("D1", "=ROWS(FILTER(A:A,A:A<>\"\"))"); + assert_eq!(t.num("D1"), 2.0); +} + #[test] fn let_lambda() { let mut t = T::new();