From 0151f43af84d50515da76a7322661a48090b2890 Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:11:59 -0500 Subject: [PATCH] Engine: python.set keeps a cell's result only for the same code and return type python.set kept the stored result whenever the code was unchanged, also when the call switched the return type between an Excel value and a Python object. The cell then showed a value and preview of the other type, and a save wrote the new type with that old cached value. The result is now kept only when both the code and the return type match; otherwise the cell is new code, which shows #BLOCKED! until it runs, and its old spill goes with its readers recalculated. --- crates/engine/src/cmd/python.rs | 6 ++++-- crates/engine/src/tests/python_cells.rs | 2 +- crates/engine/src/tests/python_files.rs | 24 ++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/python.rs b/crates/engine/src/cmd/python.rs index dc27e2f82..8e7007ea1 100644 --- a/crates/engine/src/cmd/python.rs +++ b/crates/engine/src/cmd/python.rs @@ -68,8 +68,10 @@ fn set(s: &mut Session, p: &Json) -> Result { if header { return Err(EngineError::Other(format!("{} is a table header; Python cells can't go in a table's header row", at.a1()))); } - // Unchanged code keeps its result (and its value); new code has none until it runs. - let keep = old.filter(|o| o.code == py.code); + // Unchanged code with the same return type keeps its result (and its value); new code, or a + // new return type, has none until it runs: a result of the other type would show a value and + // preview that don't belong to the cell. + let keep = old.filter(|o| o.code == py.code && o.object == py.object); let value = match &keep { Some(o) => { py.output = o.output.clone(); diff --git a/crates/engine/src/tests/python_cells.rs b/crates/engine/src/tests/python_cells.rs index d0976b7a0..92efdad29 100644 --- a/crates/engine/src/tests/python_cells.rs +++ b/crates/engine/src/tests/python_cells.rs @@ -373,7 +373,7 @@ fn save_model_asks_before_it_writes_over_a_python_cell_that_shows_nothing() { } /// A1 is a Python cell whose kept result `[5, 6]` spills over A1:A2, and B1 reads A2. -fn spill_with_a_reader() -> crate::Session { +pub(super) fn spill_with_a_reader() -> crate::Session { let mut s = s(); s.execute("python.set", json!({"cell": "A1", "code": "[5, 6]"})).unwrap(); { diff --git a/crates/engine/src/tests/python_files.rs b/crates/engine/src/tests/python_files.rs index 4317bbb6b..e04d6a7da 100644 --- a/crates/engine/src/tests/python_files.rs +++ b/crates/engine/src/tests/python_files.rs @@ -622,3 +622,27 @@ fn a_formula_entered_in_a_python_spill_takes_the_area_as_a_formula_would() { assert_eq!(wb.active().unwrap().value(c), fresh.active().unwrap().value(c), "{c}"); } } + +#[test] +fn a_new_return_type_drops_the_kept_result() { + // Same code, the other return type: the result belongs to the old type, so it goes. + let mut s = super::python_cells::spill_with_a_reader(); + s.execute("python.set", json!({"cell": "A1", "code": "[5, 6]", "output": "object"})).unwrap(); + let a1 = code(&s, "A1").unwrap(); + assert!(a1.object && a1.output.is_none(), "no result of the other type is kept"); + assert_eq!(v(&s, "A1"), Value::Error(CellError::Blocked)); + assert_eq!(v(&s, "A2"), Value::Empty, "no spill"); + assert!(s.doc().unwrap().wb.active().unwrap().spill_ranges.is_empty()); + assert_eq!(v(&s, "B1"), Value::Number(0.0), "the reader of the old spill is recalculated"); + let xml = sheet_xml(&save(&mut s, "xlsx")); + assert!(xml.contains("_xlfn._xlws.PY(0,1)#VALUE!") && !xml.contains("r=\"A2\""), "no old cached value is written: {xml}"); + // Undo brings the result back. + s.execute("edit.undo", json!({})).unwrap(); + let a1 = code(&s, "A1").unwrap(); + assert!(!a1.object && a1.output.is_some()); + assert_eq!((v(&s, "A1"), v(&s, "A2"), v(&s, "B1")), (Value::Number(5.0), Value::Number(6.0), Value::Number(6.0))); + // Same code, same return type: the result is kept as before. + s.execute("python.set", json!({"cell": "A1", "code": "[5, 6]", "output": "value"})).unwrap(); + assert!(code(&s, "A1").unwrap().output.is_some()); + assert_eq!((v(&s, "A1"), v(&s, "A2"), v(&s, "B1")), (Value::Number(5.0), Value::Number(6.0), Value::Number(6.0))); +}