Skip to content

Commit

Permalink
Dynamic return type extension for understanding compact function
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 1, 2021
1 parent e92a5ab commit 2a75a25
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 1 deletion.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,11 @@ services:
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension

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

-
class: PHPStan\Type\Php\CountFunctionReturnTypeExtension
tags:
Expand Down
2 changes: 1 addition & 1 deletion resources/functionMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@
'com_message_pump' => ['bool', 'timeoutms='=>'int'],
'com_print_typeinfo' => ['bool', 'comobject_or_typelib'=>'object', 'dispinterface='=>'string', 'wantsink='=>'bool'],
'com_release' => [''],
'compact' => ['array', '...var_names='=>'string|array'],
'compact' => ['array<string, mixed>', '...var_names='=>'string|array'],
'COMPersistHelper::__construct' => ['void', 'com_object'=>'object'],
'COMPersistHelper::GetCurFile' => ['string'],
'COMPersistHelper::GetMaxStreamSize' => ['int'],
Expand Down
85 changes: 85 additions & 0 deletions src/Type/Php/CompactFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?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\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\Type;

class CompactFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
{

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

public function getTypeFromFunctionCall(
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope
): Type
{
$defaultReturnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
if (count($functionCall->args) === 0) {
return $defaultReturnType;
}

if ($scope->canAnyVariableExist()) {
return $defaultReturnType;
}

$array = ConstantArrayTypeBuilder::createEmpty();
foreach ($functionCall->args as $arg) {
$type = $scope->getType($arg->value);
$constantStrings = $this->findConstantStrings($type);
if ($constantStrings === null) {
return $defaultReturnType;
}
foreach ($constantStrings as $constantString) {
$has = $scope->hasVariableType($constantString->getValue());
if ($has->no()) {
continue;
}

$array->setOffsetValueType($constantString, $scope->getVariableType($constantString->getValue()), $has->maybe());
}
}

return $array->getArray();
}

/**
* @param Type $type
* @return array<int, ConstantStringType>|null
*/
private function findConstantStrings(Type $type): ?array
{
if ($type instanceof ConstantStringType) {
return [$type];
}

if ($type instanceof ConstantArrayType) {
$result = [];
foreach ($type->getValueTypes() as $valueType) {
$constantStrings = $this->findConstantStrings($valueType);
if ($constantStrings === null) {
return null;
}

$result = array_merge($result, $constantStrings);
}

return $result;
}

return null;
}

}
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10743,6 +10743,11 @@ public function dataBug4415(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4415.php');
}

public function dataCompact(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/compact.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10955,6 +10960,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataClosureReturnType
* @dataProvider dataBug4398
* @dataProvider dataBug4415
* @dataProvider dataCompact
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
22 changes: 22 additions & 0 deletions tests/PHPStan/Analyser/data/compact.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace CompactExtension;

use function PHPStan\Analyser\assertType;

assertType('array<string, mixed>', compact(['foo' => 'bar']));

function (string $dolor): void {
$foo = 'bar';
$bar = 'baz';
if (rand(0, 1)) {
$lorem = 'ipsum';
}
assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\')', compact('foo', ['bar']));
assertType('array(\'foo\' => \'bar\', \'bar\' => \'baz\', ?\'lorem\' => \'ipsum\')', compact([['foo']], 'bar', 'lorem'));

assertType('array<string, mixed>', compact($dolor));
assertType('array<string, mixed>', compact([$dolor]));

assertType('array()', compact([]));
};

0 comments on commit 2a75a25

Please sign in to comment.