mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 21:26:48 -06:00
Calc: a dynamic array of one value still anchors a spill range (#1491)
`C1#` gave `#REF!` while the dynamic-array formula in C1 happened to return a
single value -- a FILTER matching one row, a SORT or UNIQUE over one item -- so
`ROWS(C1#)` and `SUM(C1#)` failed and `COUNT(C1#)` gave 0. Every formula that
counts or sums a filtered list therefore broke the moment the list had exactly
one entry. In Excel the formula is the anchor of a 1 x 1 range and the three
give 1, the value, and 1.
A pass recorded the spill only for an array larger than one cell:
if a.rows > 1 || a.cols > 1 {
self.spills.insert(k, a.clone());
}
so a 1 x 1 result left no range for `spill_range` to answer with, and `eval`
turned the missing range into `#REF!`. It is recorded unconditionally now.
The report suggested keying this on the cell carrying `cm`, which turned out not
to be expressible: nothing in the model says "dynamic array" positively -- the
reader tells one from a legacy CSE formula only by leaving `Formula::array`
unset. The arm itself is the better test, because a formula whose result is a
scalar never reaches it: `=1+1` does not become an anchor, which a test pins.
It also matches Excel 365, where `=A1:A1` is a 1 x 1 anchor of its own.
This commit is contained in:
@@ -517,9 +517,11 @@ impl PassHost<'_> {
|
||||
let data = a.data.iter().map(|x| if matches!(x, Value::Empty) { Value::Number(0.0) } else { x.clone() }).collect();
|
||||
Array::new(a.rows, a.cols, data).map(std::sync::Arc::new).unwrap_or(a)
|
||||
};
|
||||
if a.rows > 1 || a.cols > 1 {
|
||||
self.spills.insert(k, a.clone());
|
||||
}
|
||||
// A dynamic-array formula anchors a spill range even while its result is one
|
||||
// value -- a FILTER matching one row, a SORT or UNIQUE over one item -- so `C1#`
|
||||
// is the 1 x 1 range and `ROWS(C1#)` is 1, as in Excel (#1491). A formula that
|
||||
// returns a scalar never reaches this arm, so it does not become an anchor.
|
||||
self.spills.insert(k, a.clone());
|
||||
a.data.first().cloned().unwrap_or_default()
|
||||
}
|
||||
Value::Empty => Value::Number(0.0),
|
||||
|
||||
@@ -1294,3 +1294,41 @@ fn median_large_values_recalculate_dependents() {
|
||||
assert!((t.num(cell) + 1.3).abs() < 1e-14);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dynamic_array_of_one_value_still_anchors_a_spill() {
|
||||
// In Excel a dynamic-array formula anchors a spill range even while its result is a single
|
||||
// value, so `C1#` is the 1 x 1 range and ROWS/SUM/COUNT of it work. GridCraft gave #REF!
|
||||
// (and COUNT 0), so every formula counting a filtered list broke the moment the list had
|
||||
// exactly one entry (#1491). Values and cells follow the reported fixture.
|
||||
let mut t = T::new();
|
||||
for i in 1..=5 {
|
||||
t.set(&format!("A{i}"), &i.to_string());
|
||||
t.set(&format!("B{i}"), "7");
|
||||
}
|
||||
t.set("C1", "=FILTER(A1:A5,A1:A5>4)");
|
||||
t.set("C3", "=SORT(FILTER(A1:A5,A1:A5=3))");
|
||||
t.set("C5", "=UNIQUE(B1:B5)");
|
||||
// Control: the same FILTER matching two rows, which worked before.
|
||||
t.set("C9", "=FILTER(A1:A5,A1:A5>3)");
|
||||
|
||||
t.set("E1", "=ROWS(C1#)");
|
||||
t.set("E2", "=SUM(C1#)");
|
||||
t.set("E3", "=ROWS(C3#)");
|
||||
t.set("E5", "=COUNT(C5#)");
|
||||
t.set("E9", "=ROWS(C9#)");
|
||||
assert_eq!(t.get("E1"), Value::from(1.0));
|
||||
assert_eq!(t.get("E2"), Value::from(5.0));
|
||||
assert_eq!(t.get("E3"), Value::from(1.0));
|
||||
assert_eq!(t.get("E5"), Value::from(1.0));
|
||||
assert_eq!(t.get("E9"), Value::from(2.0), "a spilling array is unchanged");
|
||||
|
||||
// The anchors still show their first value, as they did.
|
||||
assert_eq!(t.get("C1"), Value::from(5.0));
|
||||
assert_eq!(t.get("C5"), Value::from(7.0));
|
||||
|
||||
// A formula whose result is a scalar never becomes an anchor: `#` on it is still #REF!.
|
||||
t.set("D1", "=1+1");
|
||||
t.set("E11", "=ROWS(D1#)");
|
||||
assert_eq!(t.get("E11"), Value::Error(CellError::Ref));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user