Size MAP to the longest argument array, not the shortest

MAP used the smallest dimensions across its argument arrays, so
when the arrays had different lengths the result was clipped to
the shorter one, dropping the elements only the longer array
reached.

Size the result to the largest argument array instead. A position
one array does not reach has no value to map, so it is left as a
not-available error, the way the OOXML format treats a length
mismatch.

Add a test that maps an addition over a three-element and a
two-element array and confirms the result has three elements, the
third a not-available error.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I11e756c74598de408da01cbc6844b674dda64ceb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5607
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit bc64a61d28bc9b1eeee0e5c08f67a0945a2781d1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207865
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-07-09 15:58:42 +02:00
committed by Xisco Fauli
parent cb9b0f4550
commit 45473674e8
2 changed files with 62 additions and 2 deletions
+43
View File
@@ -326,6 +326,49 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testMapPerElementErrorIsolation)
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testMapMismatchedArrayLengths)
{
// MAP over two arrays of different lengths produces a result as long as the
// longer one, with the position the shorter array cannot reach left not
// available, rather than clipping the result to the shorter array.
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
m_pDoc->InsertTab(0, u"Sheet1"_ustr);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
// Read MAP's result matrix directly. Entered over a single cell it is
// neither spilled nor padded, so the matrix is exactly what MAP produced.
// Linear element access keeps the check independent of the result shape.
m_pDoc->InsertMatrixFormula(0, 0, 0, 0, aMark,
u"=MAP({1;2;3}; {1;2}; LAMBDA(a; b; a + b))"_ustr);
ScFormulaCell* pCell = m_pDoc->GetFormulaCell(ScAddress(0, 0, 0));
CPPUNIT_ASSERT(pCell);
const ScMatrix* pMat = pCell->GetMatrix();
CPPUNIT_ASSERT(pMat);
// Three elements, the length of the longer array, not two.
SCSIZE nCols = 0;
SCSIZE nRows = 0;
pMat->GetDimensions(nCols, nRows);
CPPUNIT_ASSERT_EQUAL(SCSIZE(3), nCols * nRows);
// The two matched elements add. The third has no second operand.
CPPUNIT_ASSERT_EQUAL(2.0, pMat->GetDouble(0));
CPPUNIT_ASSERT_EQUAL(4.0, pMat->GetDouble(1));
CPPUNIT_ASSERT_EQUAL(sal_Int32(FormulaError::NotAvailable),
sal_Int32(GetDoubleErrorValue(pMat->GetDouble(2))));
// The same result reduced to a scalar, as a second check: IFERROR turns the
// not-available element into a sentinel, so a three-element result sums
// higher than one clipped to two elements.
m_pDoc->InsertMatrixFormula(
2, 0, 2, 0, aMark, u"=SUM(IFERROR(MAP({1;2;3}; {1;2}; LAMBDA(a; b; a + b)); 100))"_ustr);
CPPUNIT_ASSERT_EQUAL(106.0, m_pDoc->GetValue(ScAddress(2, 0, 0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testFuncCHOOSE)
{
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on.
+19 -2
View File
@@ -1590,9 +1590,11 @@ void ScInterpreter::ScMap()
{
SCSIZE nCols, nRows;
xMat->GetDimensions(nCols, nRows);
if (nC == 0 || nCols < nC)
// The result spans the largest argument array. A shorter array
// leaves its missing positions not available.
if (nCols > nC)
nC = nCols;
if (nR == 0 || nRows < nR)
if (nRows > nR)
nR = nRows;
aMatrices[i] = xMat;
}
@@ -1622,8 +1624,16 @@ void ScInterpreter::ScMap()
{
for (SCSIZE nRow = 0; nRow < nR; ++nRow)
{
bool bElementMissing = false;
for (sal_uInt8 nMat = 0; nMat < nArgCount; ++nMat)
{
SCSIZE nMatCols, nMatRows;
aMatrices[nMat]->GetDimensions(nMatCols, nMatRows);
if (nCol >= nMatCols || nRow >= nMatRows)
{
bElementMissing = true;
break;
}
if (aMatrices[nMat]->IsEmpty(nCol, nRow))
aArgs[nMat] = new formula::FormulaMissingToken();
else if (aMatrices[nMat]->IsValue(nCol, nRow))
@@ -1639,6 +1649,13 @@ void ScInterpreter::ScMap()
else // TODO: callable
aArgs[nMat] = new formula::FormulaErrorToken(FormulaError::IllegalParameter);
}
// One of the arrays is shorter and has no element here, so there is
// nothing to map at this position.
if (bElementMissing)
{
xResMat->PutError(FormulaError::NotAvailable, nCol, nRow);
continue;
}
ScCall(pCallable, aArgs);
PutMatrixValue(PopToken(), xResMat, nCol, nRow);
// The result of this cell is stored, so clear any error it produced