Skip to content

Commit

Permalink
Fix type inference of array_sum()
Browse files Browse the repository at this point in the history
  • Loading branch information
zer0-star authored Aug 31, 2023
1 parent 9f93828 commit d44ae55
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 17 deletions.
43 changes: 27 additions & 16 deletions src/Type/Php/ArraySumFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\BinaryOp\Mul;
use PhpParser\Node\Expr\BinaryOp\Plus;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Scalar\LNumber;
use PHPStan\Analyser\Scope;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;
use function count;

final class ArraySumFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{
Expand All @@ -28,28 +31,36 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

$arrayType = $scope->getType($functionCall->getArgs()[0]->value);
$itemType = $arrayType->getIterableValueType();
$argType = $scope->getType($functionCall->getArgs()[0]->value);
$resultTypes = [];

if ($arrayType->isIterableAtLeastOnce()->no()) {
return new ConstantIntegerType(0);
}
if (count($argType->getConstantArrays()) > 0) {
foreach ($argType->getConstantArrays() as $constantArray) {
$node = new LNumber(0);

$intUnionFloat = new UnionType([new IntegerType(), new FloatType()]);
foreach ($constantArray->getValueTypes() as $i => $type) {
if ($constantArray->isOptionalKey($i)) {
$node = new Plus($node, new TypeExpr(TypeCombinator::union($type, new ConstantIntegerType(0))));
} else {
$node = new Plus($node, new TypeExpr($type));
}
}

if ($arrayType->isIterableAtLeastOnce()->yes()) {
if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) {
return $itemType;
$resultTypes[] = $scope->getType($node);
}
} else {
$itemType = $argType->getIterableValueType();

$mulNode = new Mul(new TypeExpr($itemType), new TypeExpr(IntegerRangeType::fromInterval(0, null)));

return $intUnionFloat;
$resultTypes[] = $scope->getType(new Plus(new TypeExpr($itemType), $mulNode));
}

if ($intUnionFloat->isSuperTypeOf($itemType)->yes()) {
return TypeCombinator::union(new ConstantIntegerType(0), $itemType);
if (!$argType->isIterableAtLeastOnce()->yes()) {
$resultTypes[] = new ConstantIntegerType(0);
}

return TypeCombinator::union(new ConstantIntegerType(0), $intUnionFloat);
return TypeCombinator::union(...$resultTypes)->toNumber();
}

}
218 changes: 217 additions & 1 deletion tests/PHPStan/Analyser/data/array-sum.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function foo3($floatList)
function foo4($list)
{
$sum = array_sum($list);
assertType('float|int', $sum);
assertType('(float|int)', $sum);
}

/**
Expand All @@ -48,3 +48,219 @@ function foo5($list)
$sum = array_sum($list);
assertType('float|int', $sum);
}

/**
* @param list<0> $list
*/
function foo6($list)
{
assertType('0', array_sum($list));
}
/**
* @param list<1> $list
*/
function foo7($list)
{
assertType('int<0, max>', array_sum($list));
}

/**
* @param non-empty-list<1> $list
*/
function foo8($list)
{
assertType('int<1, max>', array_sum($list));
}

/**
* @param list<-1> $list
*/
function foo9($list)
{
assertType('int<min, 0>', array_sum($list));
}

/**
* @param list<1|2|3> $list
*/
function foo10($list)
{
assertType('int<0, max>', array_sum($list));
}

/**
* @param non-empty-list<1|2|3> $list
*/
function foo11($list)
{
assertType('int<1, max>', array_sum($list));
}

/**
* @param list<1|-1> $list
*/
function foo12($list)
{
assertType('int', array_sum($list));
}
/**
* @param non-empty-list<1|-1> $list
*/
function foo13($list)
{
assertType('int', array_sum($list));
}

/**
* @param array{0} $list
*/
function foo14($list)
{
assertType('0', array_sum($list));
}
/**
* @param array{1} $list
*/
function foo15($list)
{
assertType('1', array_sum($list));
}

/**
* @param array{1, 2, 3} $list
*/
function foo16($list)
{
assertType('6', array_sum($list));
}

/**
* @param array{1, int} $list
*/
function foo17($list)
{
assertType('int', array_sum($list));
}

/**
* @param array{1, float} $list
*/
function foo18($list)
{
assertType('float', array_sum($list));
}

/**
* @param array{} $list
*/
function foo19($list)
{
assertType('0', array_sum($list));
}


/**
* @param list<1|float> $list
*/
function foo20($list)
{
assertType('float|int<0, max>', array_sum($list));
}

/**
* @param array{1, int|float} $list
*/
function foo21($list)
{
assertType('float|int', array_sum($list));
}

/**
* @param array{1, string} $list
*/
function foo22($list)
{
assertType('float|int', array_sum($list));
}


/**
* @param array{1, 3.2} $list
*/
function foo23($list)
{
assertType('4.2', array_sum($list));
}

/**
* @param array{1, float|4} $list
*/
function foo24($list)
{
assertType('5|float', array_sum($list));
}

/**
* @param array{1, 2|3.4} $list
*/
function foo25($list)
{
assertType('3|4.4', array_sum($list));
}

/**
* @param array{1, 2.4|3.4} $list
*/
function foo26($list)
{
assertType('3.4|4.4', array_sum($list));
}


/**
* @param array{1}|array{2, 3} $list
*/
function foo27($list)
{
assertType('1|5', array_sum($list));
}

/**
* @param array{1}|list<1>|array{float} $list
*/
function foo28($list)
{
assertType('float|int<0, max>', array_sum($list));
}

/**
* @param non-empty-list<int<min, -1>|int<1, max>> $list
*/
function foo29($list)
{
assertType('int', array_sum($list));
}

/**
* @param array{'133', 3} $list
*/
function foo30($list)
{
assertType('136', array_sum($list));
}

/**
* @param array{0: 1, 1?: 2, 2?: 3} $list
*/
function foo31($list)
{
assertType('1|3|4|6', array_sum($list));
}

/**
* @param mixed $list
*/
function foo32($list)
{
assertType('(float|int)', array_sum($list));
}

0 comments on commit d44ae55

Please sign in to comment.