mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-09 20:09:57 -06:00
Engine: a deleted reference drops a Python cell's kept result; spills read past the scan budget stay values
A structural edit that deleted the data an xl() reference names rewrote the reference to #REF! but kept the cell's cached result, so the cell went on showing a result for data that is gone. rewrite_python_refs now makes such a cell new code (#BLOCKED!, no kept result), and the recalculation removes its spill. A rename or a move, after which the reference names the same data, still keeps the result. When the reader's range-scan budget ran out part way through a Python spill, the rest of its cells were read as plain cells, and the result rebuilt on open was then blocked by them (#SPILL!). On such a sheet every Python spill now keeps its values as plain values, with no live spill. Tests: deleting_the_data_python_code_reads_drops_its_kept_result and python_spills_read_past_the_scan_budget_keep_their_values_as_plain_values; both fail without the fix.
This commit is contained in:
@@ -397,7 +397,10 @@ pub(crate) fn rewrite_all_formulas(wb: &mut gridcraft_model::Workbook, target: &
|
||||
/// Rewrites the `xl("…")` literals in every Python cell with `f`, which maps a reference held on
|
||||
/// sheet `host` to its new form. Every reference-moving edit (insert and delete of lines or cells,
|
||||
/// cut-move, sheet rename and delete) runs it, as it adjusts formulas. A literal is only replaced
|
||||
/// when the reference it names changed, and text that doesn't parse is kept. A rewrite that would
|
||||
/// when the reference it names changed, and text that doesn't parse is kept. A cell's cached
|
||||
/// result is kept while its references still name the same data (a rename, a move); a reference
|
||||
/// the edit deleted (`#REF!`) leaves the result stale, so the cell is new code (`#BLOCKED!`) and
|
||||
/// the recalculation removes its spill. A rewrite that would
|
||||
/// make a cell's code longer than one cell holds (a longer sheet name, `Z1` becoming `AA1`) refuses
|
||||
/// the whole edit, which then changes nothing: see [`check_python_fits`].
|
||||
pub(crate) fn rewrite_python_refs(
|
||||
@@ -407,29 +410,36 @@ pub(crate) fn rewrite_python_refs(
|
||||
for si in 0..wb.sheets.len() {
|
||||
let Some(sh) = wb.sheets.get(si) else { continue };
|
||||
let host = sh.name.clone();
|
||||
let mut new_code: Vec<(CellRef, String)> = Vec::new();
|
||||
let mut new_code: Vec<(CellRef, String, bool)> = Vec::new();
|
||||
for (c, cell) in sh.cells.iter() {
|
||||
let Some(py) = &cell.py else { continue };
|
||||
let mut lost = false;
|
||||
let changed = gridcraft_model::python::rewrite_xl(&py.code, |reference| {
|
||||
let expr = gridcraft_formula::parse(reference).ok()?;
|
||||
let before = gridcraft_formula::print(&expr);
|
||||
let after = gridcraft_formula::print(&f(&host, expr));
|
||||
lost |= after.contains("#REF!") && !before.contains("#REF!");
|
||||
(after != before).then_some(after)
|
||||
});
|
||||
if let Some(code) = changed {
|
||||
check_python_fits(&gridcraft_model::PyCode { code: code.clone(), object: py.object, ..Default::default() }, &host, c)?;
|
||||
new_code.push((c, code));
|
||||
new_code.push((c, code, lost));
|
||||
}
|
||||
}
|
||||
if new_code.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some(sh) = wb.sheet_mut(si) else { continue };
|
||||
for (c, code) in new_code {
|
||||
for (c, code, lost) in new_code {
|
||||
if let Some(cell) = sh.cells.get_mut(c)
|
||||
&& let Some(py) = cell.py.as_mut()
|
||||
{
|
||||
std::sync::Arc::make_mut(py).code = code;
|
||||
let py = std::sync::Arc::make_mut(py);
|
||||
py.code = code;
|
||||
if lost {
|
||||
py.output = None;
|
||||
cell.value = Value::Error(gridcraft_core::CellError::Blocked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user