From 14d723ea36fe07f86d9109fb9cc14488897dfec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Thu, 25 Jun 2026 23:17:50 +0900 Subject: [PATCH] sc: let @ collapse arrays from range arithmetic, not just functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @ operator collapses an array operand to its upper-left value. A function operand like SEQUENCE pushes a matrix and @ collapses it. A range arithmetic operand like (B2:B3+0) does implicit intersection first and hands @ a scalar, so @ has nothing to collapse. Mark @'s operand as ForceArray in the parameter classifier and wrap UnaryLine in a CurrentFactor scope so the classification reaches the nested operators. Range arithmetic inside @ then materialises as a matrix and reaches @ for the upper-left pick. testSingleValueOperator covers @ on a range expression and a chained @ binary. Signed-off-by: Tomaž Vajngerl Change-Id: I7f59bfa1efbceeceb9a3d7f71a0243e9a521b24a Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5517 Tested-by: Tomaž Vajngerl Reviewed-by: Mike Kaganski (cherry picked from commit 4148cbecbdaa3164c53d3869252a3ca714ba5726) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207868 Tested-by: Xisco Fauli Reviewed-by: Xisco Fauli --- formula/source/core/api/FormulaCompiler.cxx | 9 ++++++++- sc/qa/unit/ucalc_formula2.cxx | 19 +++++++++++++++++++ sc/source/core/tool/parclass.cxx | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx index 03f8cc09c96e..4c4806c95507 100644 --- a/formula/source/core/api/FormulaCompiler.cxx +++ b/formula/source/core/api/FormulaCompiler.cxx @@ -2417,7 +2417,14 @@ void FormulaCompiler::UnaryLine() { FormulaTokenRef p = mpToken; NextToken(); - UnaryLine(); + // Hold the unary operator as the current factor so its + // parameter classification reaches the nested operators. + { + CurrentFactor pInnerFac(this); + pInnerFac = p; + CheckSetForceArrayParameter(mpToken, 0); + UnaryLine(); + } if (mbComputeII) { FormulaToken** pArg = mpCode - 1; diff --git a/sc/qa/unit/ucalc_formula2.cxx b/sc/qa/unit/ucalc_formula2.cxx index ea3e84bc6a5c..b4163a96c1ec 100644 --- a/sc/qa/unit/ucalc_formula2.cxx +++ b/sc/qa/unit/ucalc_formula2.cxx @@ -5955,6 +5955,25 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testSingleValueOperator) // No spill into the row below. CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(0, 1, 0))); + // The @ also collapses an array produced by range arithmetic. B2:B3+0 + // is a two-cell array whose upper-left is B2. The formula sits in a + // row that does not overlap B2:B3, so implicit intersection on the + // range would otherwise fail and the @ has to drive the collapse. + m_pDoc->SetValue(ScAddress(1, 1, 0), 7.0); + m_pDoc->SetValue(ScAddress(1, 2, 0), 9.0); + m_pDoc->SetFormula(ScAddress(0, 5, 0), u"=@(B2:B3+0)"_ustr, + formula::FormulaGrammar::GRAM_NATIVE); + CPPUNIT_ASSERT_EQUAL(7.0, m_pDoc->GetValue(ScAddress(0, 5, 0))); + CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(0, 6, 0))); + + // Two @-collapsed sub-expressions joined by a binary operator stay + // scalar. Without the collapse on each side the addition would see + // two arrays and produce one too, leaving the row below populated. + m_pDoc->SetFormula(ScAddress(0, 8, 0), u"=@((B2:B3+0)) + @((B2:B3+0))"_ustr, + formula::FormulaGrammar::GRAM_NATIVE); + CPPUNIT_ASSERT_EQUAL(14.0, m_pDoc->GetValue(ScAddress(0, 8, 0))); + CPPUNIT_ASSERT_EQUAL(CELLTYPE_NONE, m_pDoc->GetCellType(ScAddress(0, 9, 0))); + m_pDoc->DeleteTab(0); } diff --git a/sc/source/core/tool/parclass.cxx b/sc/source/core/tool/parclass.cxx index 580a3a071b78..30ba7efa2bb6 100644 --- a/sc/source/core/tool/parclass.cxx +++ b/sc/source/core/tool/parclass.cxx @@ -249,6 +249,7 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] = { ocScan, {{ Value, ForceArray, Value }, 0, ForceArrayReturn }}, { ocSheet, {{ Reference }, 0, Value }}, { ocSheets, {{ Reference }, 1, Value }}, + { ocSingleValue, {{ ForceArray }, 0, Value }}, { ocSkew, {{ Reference }, 1, Value }}, { ocSkewp, {{ Reference }, 1, Value }}, { ocSlope, {{ ForceArray, ForceArray }, 0, Value }},