Skip to content

Commit

Permalink
Ensure full support for vector/matrix combinations and for asymetric …
Browse files Browse the repository at this point in the history
…matrix/matrix arguments

Re-baseline phpstan
  • Loading branch information
MarkBaker committed Feb 8, 2022
1 parent db502ce commit 30bfd9e
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 87 deletions.
91 changes: 48 additions & 43 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Calculation.php

-
message: "#^Cannot access offset int(<0, max>)? on mixed\\.$#"
count: 16
message: "#^Cannot access offset int on mixed\\.$#"
count: 10
path: src/PhpSpreadsheet/Calculation/Calculation.php

-
message: "#^Cannot access offset int\\<0, max\\> on mixed\\.$#"
count: 6
path: src/PhpSpreadsheet/Calculation/Calculation.php

-
Expand Down Expand Up @@ -1461,7 +1466,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php

-
message: "#^Parameter \\#1 \\$array_arg of function uasort expects array(<T>)?, mixed given\\.$#"
message: "#^Parameter \\#1 \\$array_arg of function uasort expects array\\<T\\>, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php

Expand All @@ -1471,54 +1476,34 @@ parameters:
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php

-
message: "#^Parameter \\#2 \\$callback of function uasort expects callable\\((mixed|T), (mixed|T)\\)\\: int, array\\{'self', 'vlookupSort'\\} given\\.$#"
message: "#^Parameter \\#2 \\$callback of function uasort expects callable\\(T, T\\)\\: int, array\\{'self', 'vlookupSort'\\} given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Cosine\\:\\:acos\\(\\) expects float, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Cosine\\:\\:acosh\\(\\) expects float, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Sine\\:\\:asin\\(\\) expects float, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Sine\\:\\:asinh\\(\\) expects float, mixed given\\.$#"
message: "#^Cannot cast mixed to string\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php
path: src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Tangent\\:\\:atan\\(\\) expects float, mixed given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php
message: "#^Binary operation \"/\" between array\\|float\\|int\\|string and array\\|float\\|int\\|string results in an error\\.$#"
count: 2
path: src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php

-
message: "#^Parameter \\#1 \\$number of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Trig\\\\Tangent\\:\\:atanh\\(\\) expects float, mixed given\\.$#"
message: "#^Parameter \\#1 \\$factVal of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Factorial\\:\\:fact\\(\\) expects array\\|float, int\\<min, \\-2\\>\\|int\\<0, max\\> given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig.php
path: src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php

-
message: "#^Cannot cast mixed to string\\.$#"
message: "#^Binary operation \"/\" between array\\|float\\|int\\|string and float\\|int results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php

-
message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int\\|string results in an error\\.$#"
count: 2
path: src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php
path: src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php

-
message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int results in an error\\.$#"
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\IntClass\\:\\:evaluate\\(\\) should return array\\|string but returns int\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php
path: src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php

-
message: "#^Cannot call method getWorksheet\\(\\) on mixed\\.$#"
Expand Down Expand Up @@ -1810,6 +1795,11 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php

-
message: "#^Parameter \\#1 \\$factVal of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Factorial\\:\\:fact\\(\\) expects array\\|float, int\\<0, max\\> given\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php

-
message: "#^Binary operation \"\\-\" between float\\|string and float\\|int\\|numeric\\-string results in an error\\.$#"
count: 1
Expand Down Expand Up @@ -1841,7 +1831,7 @@ parameters:
path: src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php

-
message: "#^Binary operation \"/\" between float\\|int\\|string and float\\|int\\|string results in an error\\.$#"
message: "#^Binary operation \"/\" between array\\|float\\|int\\|string and array\\|float\\|int\\|string results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/Statistical/Permutations.php

Expand Down Expand Up @@ -6046,8 +6036,13 @@ parameters:
path: src/PhpSpreadsheet/Worksheet/Worksheet.php

-
message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int(<1, max>)? given\\.$#"
count: 2
message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int given\\.$#"
count: 1
path: src/PhpSpreadsheet/Worksheet/Worksheet.php

-
message: "#^Parameter \\#1 \\$row of method PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\:\\:removeRow\\(\\) expects string, int\\<1, max\\> given\\.$#"
count: 1
path: src/PhpSpreadsheet/Worksheet/Worksheet.php

-
Expand Down Expand Up @@ -6996,7 +6991,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx.php

-
message: "#^Cannot access offset int(<0, max>)? on mixed\\.$#"
message: "#^Cannot access offset int\\<0, max\\> on mixed\\.$#"
count: 2
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php

Expand Down Expand Up @@ -7161,7 +7156,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx/DocProps.php

-
message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int(<0, max>)? given\\.$#"
message: "#^Parameter \\#1 \\$index of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getChartByIndex\\(\\) expects string, int\\<0, max\\> given\\.$#"
count: 1
path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php

Expand Down Expand Up @@ -7401,7 +7396,7 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

-
message: "#^Offset int(<1, max>)? on array\\<string, non\\-empty\\-array\\<int, string\\>\\> in isset\\(\\) does not exist\\.$#"
message: "#^Offset int\\<1, max\\> on array\\<string, non\\-empty\\-array\\<int, string\\>\\> in isset\\(\\) does not exist\\.$#"
count: 2
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Expand Down Expand Up @@ -7436,8 +7431,18 @@ parameters:
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, (int|int\\<(0|1), max\\>) given\\.$#"
count: 27
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
count: 15
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<0, max\\> given\\.$#"
count: 3
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<1, max\\> given\\.$#"
count: 9
path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

-
Expand Down
98 changes: 63 additions & 35 deletions src/PhpSpreadsheet/Calculation/ArrayEnabled.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,63 +54,91 @@ protected static function evaluateArrayArguments(callable $method, ...$arguments
return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
}

$rowVectorIndexes = self::$arrayArgumentHelper->getRowVectors();
$columnVectorIndexes = self::$arrayArgumentHelper->getColumnVectors();

// Logic for a two row vectors or two column vectors
if (count($rowVectorIndexes) === 2 && count($columnVectorIndexes) === 0) {
return self::evaluateRowVectorPair($method, $rowVectorIndexes, ...$arguments);
} elseif (count($rowVectorIndexes) === 0 && count($columnVectorIndexes) === 2) {
return self::evaluateColumnVectorPair($method, $columnVectorIndexes, ...$arguments);
$matrixPair = self::$arrayArgumentHelper->getMatrixPair();
if ($matrixPair !== []) {
if (
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
(self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
) {
// Logic for a matrix and a vector (row or column)
return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
}
// Logic for matrix/matrix, column vector/column vector or row vector/row vector
return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
}

// If we have multiple arrays, and they don't match a row vector/column vector pattern,
// or two row vectors and two column vectors,
// then we drop through to an error return for the moment
// Still need to work out the logic for multiple matrices as array arguments,
// or when we have more than two arrays
// Still need to work out the logic for more than two array arguments,
return ['#VALUE!'];
}

/**
* @param mixed ...$arguments
*/
private static function evaluateRowVectorPair(callable $method, array $vectorIndexes, ...$arguments): array
private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$vector2 = array_pop($vectorIndexes);
$vectorValues2 = Functions::flattenArray($arguments[$vector2]);
$vector1 = array_pop($vectorIndexes);
$vectorValues1 = Functions::flattenArray($arguments[$vector1]);
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];

$rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));

if ($rows === 1) {
$rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
}
if ($columns === 1) {
$columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
}

$result = [];
foreach ($vectorValues1 as $index => $value1) {
$value2 = $vectorValues2[$index];
$arguments[$vector1] = $value1;
$arguments[$vector2] = $value2;

$result[] = $method(...$arguments);
for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
$rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
$columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
$value1 = $matrixValues1[$rowIndex1][$columnIndex1];
$rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
$columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
$value2 = $matrixValues2[$rowIndex2][$columnIndex2];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;

$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}

return [$result];
return $result;
}

/**
* @param mixed ...$arguments
*/
private static function evaluateColumnVectorPair(callable $method, array $vectorIndexes, ...$arguments): array
private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
{
$vector2 = array_pop($vectorIndexes);
$vectorValues2 = Functions::flattenArray($arguments[$vector2]);
$vector1 = array_pop($vectorIndexes);
$vectorValues1 = Functions::flattenArray($arguments[$vector1]);
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];

$result = [];
foreach ($vectorValues1 as $index => $value1) {
$value2 = $vectorValues2[$index];
$arguments[$vector1] = $value1;
$arguments[$vector2] = $value2;
foreach ($matrixValues1 as $rowIndex => $row) {
foreach ($row as $columnIndex => $value1) {
if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
continue;
}

$result[] = [$method(...$arguments)];
$value2 = $matrixValues2[$rowIndex][$columnIndex];
$arguments[$matrix1] = $value1;
$arguments[$matrix2] = $value2;

$result[$rowIndex][$columnIndex] = $method(...$arguments);
}
}

return $result;
Expand Down
54 changes: 46 additions & 8 deletions src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ public function getFirstArrayArgumentNumber(): int
return 0;
}

public function getRowVectors(): array
public function getSingleRowVector(): ?int
{
$rowVectors = $this->getRowVectors();

return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
}

private function getRowVectors(): array
{
$rowVectors = [];
for ($index = 0; $index < $this->argumentCount; ++$index) {
Expand All @@ -87,14 +94,14 @@ public function getRowVectors(): array
return $rowVectors;
}

public function getSingleRowVector(): ?int
public function getSingleColumnVector(): ?int
{
$rowVectors = $this->getRowVectors();
$columnVectors = $this->getColumnVectors();

return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
}

public function getColumnVectors(): array
private function getColumnVectors(): array
{
$columnVectors = [];
for ($index = 0; $index < $this->argumentCount; ++$index) {
Expand All @@ -106,11 +113,42 @@ public function getColumnVectors(): array
return $columnVectors;
}

public function getSingleColumnVector(): ?int
public function getMatrixPair(): array
{
$columnVectors = $this->getColumnVectors();
for ($i = 0; $i < ($this->argumentCount - 1); ++$i) {
for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
if (isset($this->rows[$i], $this->rows[$j])) {
return [$i, $j];
}
}
}

return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
return [];
}

public function isVector(int $argument): bool
{
return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
}

public function isRowVector(int $argument): bool
{
return $this->rows[$argument] === 1;
}

public function isColumnVector(int $argument): bool
{
return $this->columns[$argument] === 1;
}

public function rowCount(int $argument): int
{
return $this->rows[$argument];
}

public function columnCount(int $argument): int
{
return $this->columns[$argument];
}

private function rows(array $arguments): array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public function providerAbsArray(): array
return [
'row vector' => [[[1, 0, 1]], '{-1, 0, 1}'],
'column vector' => [[[1], [0], [1]], '{-1; 0; 1}'],
'matrix' => [[[1, 0], [1, 1]], '{-1, 0; 1, -1}'],
'matrix' => [[[1, 0], [1, 1.4]], '{-1, 0; 1, -1.4}'],
];
}
}
Loading

0 comments on commit 30bfd9e

Please sign in to comment.