Skip to content

Commit

Permalink
feat(type): support $preserve_keys argument for iterator_to_array()
Browse files Browse the repository at this point in the history
  • Loading branch information
Lctrs authored and ondrejmirtes committed Nov 3, 2021
1 parent 9beb27e commit bb49b3b
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 4 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,11 @@ services:
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\IteratorToArrayFunctionReturnTypeExtension
tags:
- phpstan.broker.dynamicFunctionReturnTypeExtension

-
class: PHPStan\Type\Php\IsObjectFunctionTypeSpecifyingExtension
tags:
Expand Down
1 change: 0 additions & 1 deletion src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class ArgumentBasedFunctionReturnTypeExtension implements \PHPStan\Type\DynamicF
'array_uintersect_assoc' => 0,
'array_uintersect_uassoc' => 0,
'array_uintersect' => 0,
'iterator_to_array' => 0,
];

public function isFunctionSupported(FunctionReflection $functionReflection): bool
Expand Down
49 changes: 49 additions & 0 deletions src/Type/Php/IteratorToArrayFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\Type;
use function strtolower;

final class IteratorToArrayFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
return strtolower($functionReflection->getName()) === 'iterator_to_array';
}

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
{
$arguments = $functionCall->getArgs();

if ($arguments === []) {
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
}

$traversableType = $scope->getType($arguments[0]->value);
$arrayKeyType = $traversableType->getIterableKeyType();

if (isset($arguments[1])) {
$preserveKeysType = $scope->getType($arguments[1]->value);

if ($preserveKeysType instanceof ConstantBooleanType && !$preserveKeysType->getValue()) {
$arrayKeyType = new IntegerType();
}
}

return new ArrayType(
$arrayKeyType,
$traversableType->getIterableValueType()
);
}

}
22 changes: 19 additions & 3 deletions tests/PHPStan/Analyser/data/iterator_to_array.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@
class Foo
{
/**
* @param Traversable<string, int> $ints
* @param Traversable<string, int> $foo
*/
public function doFoo(Traversable $ints)
public function testDefaultBehavior(Traversable $foo)
{
assertType('array<string, int>', iterator_to_array($ints));
assertType('array<string, int>', iterator_to_array($foo));
}

/**
* @param Traversable<string, string> $foo
*/
public function testExplicitlyPreserveKeys(Traversable $foo)
{
assertType('array<string, string>', iterator_to_array($foo, true));
}

/**
* @param Traversable<string, string> $foo
*/
public function testNotPreservingKeys(Traversable $foo)
{
assertType('array<int, string>', iterator_to_array($foo, false));
}
}

0 comments on commit bb49b3b

Please sign in to comment.