Skip to content

Commit

Permalink
Specify array type via in_array
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen committed Jun 16, 2022
1 parent 6696f45 commit efee1cd
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 36 deletions.
84 changes: 50 additions & 34 deletions src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Expand Up @@ -11,6 +11,7 @@
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
Expand All @@ -27,7 +28,6 @@
use function implode;
use function in_array;
use function is_string;
use function reset;
use function sprintf;
use function strtolower;

Expand Down Expand Up @@ -189,50 +189,66 @@ public function findSpecifiedType(
return null;
}

if (count($sureTypes) === 1 && count($sureNotTypes) === 0) {
$sureType = reset($sureTypes);
if (self::isSpecified($scope, $node, $sureType[0])) {
return null;
}
if (count($sureTypes) > 0 && count($sureNotTypes) === 0) {
$results = [];

if ($this->treatPhpDocTypesAsCertain) {
$argumentType = $scope->getType($sureType[0]);
} else {
$argumentType = $scope->getNativeType($sureType[0]);
}
foreach ($sureTypes as $sureType) {
if (self::isSpecified($scope, $node, $sureType[0])) {
return null;
}

/** @var Type $resultType */
$resultType = $sureType[1];
if ($this->treatPhpDocTypesAsCertain) {
$argumentType = $scope->getType($sureType[0]);
} else {
$argumentType = $scope->getNativeType($sureType[0]);
}

$isSuperType = $resultType->isSuperTypeOf($argumentType);
if ($isSuperType->yes()) {
return true;
} elseif ($isSuperType->no()) {
return false;
/** @var Type $resultType */
$resultType = $sureType[1];

$isSuperType = $resultType->isSuperTypeOf($argumentType);
if ($isSuperType->maybe()) {
continue;
}

$results[] = $isSuperType;
}

return null;
} elseif (count($sureNotTypes) === 1 && count($sureTypes) === 0) {
$sureNotType = reset($sureNotTypes);
if (self::isSpecified($scope, $node, $sureNotType[0])) {
if (count($results) === 0) {
return null;
}

if ($this->treatPhpDocTypesAsCertain) {
$argumentType = $scope->getType($sureNotType[0]);
} else {
$argumentType = $scope->getNativeType($sureNotType[0]);
}
return TrinaryLogic::createYes()->and(...$results)->yes();
} elseif (count($sureNotTypes) > 0 && count($sureTypes) === 0) {
$results = [];

/** @var Type $resultType */
$resultType = $sureNotType[1];
foreach ($sureNotTypes as $sureNotType) {
if (self::isSpecified($scope, $node, $sureNotType[0])) {
return null;
}

if ($this->treatPhpDocTypesAsCertain) {
$argumentType = $scope->getType($sureNotType[0]);
} else {
$argumentType = $scope->getNativeType($sureNotType[0]);
}

/** @var Type $resultType */
$resultType = $sureNotType[1];

$isSuperType = $resultType->isSuperTypeOf($argumentType);
if ($isSuperType->yes()) {
return false;
} elseif ($isSuperType->no()) {
return true;
$isSuperType = $resultType->isSuperTypeOf($argumentType);
if ($isSuperType->maybe()) {
continue;
}

$results[] = $isSuperType;
}

if (count($results) === 0) {
return null;
}

return TrinaryLogic::createYes()->and(...$results)->no();
}

return null;
Expand Down
30 changes: 28 additions & 2 deletions src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php
Expand Up @@ -9,8 +9,11 @@
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function count;
use function strtolower;
Expand Down Expand Up @@ -41,23 +44,46 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
return new SpecifiedTypes([], []);
}

$needleType = $scope->getType($node->getArgs()[0]->value);
$arrayValueType = $scope->getType($node->getArgs()[1]->value)->getIterableValueType();

if (
$context->truthy()
|| count(TypeUtils::getConstantScalars($arrayValueType)) > 0
|| count(TypeUtils::getEnumCaseObjects($arrayValueType)) > 0
) {
return $this->typeSpecifier->create(
$specifiedTypes = $this->typeSpecifier->create(
$node->getArgs()[0]->value,
$arrayValueType,
$context,
false,
$scope,
);
} else {
$specifiedTypes = new SpecifiedTypes([], []);
}

return new SpecifiedTypes([], []);
if (
$context->truthy()
|| count(TypeUtils::getConstantScalars($needleType)) > 0
|| count(TypeUtils::getEnumCaseObjects($needleType)) > 0
) {
if ($context->truthy()) {
$arrayValueType = TypeCombinator::union($arrayValueType, $needleType);
} else {
$arrayValueType = TypeCombinator::remove($arrayValueType, $needleType);
}

$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
$node->getArgs()[1]->value,
new ArrayType(new MixedType(), $arrayValueType),
TypeSpecifierContext::createTrue(),
false,
$scope,
));
}

return $specifiedTypes;
}

}
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Expand Up @@ -847,6 +847,18 @@ public function testBug7381(): void
$this->assertNoErrors($errors);
}

public function testBug7153(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7153.php');
$this->assertNoErrors($errors);
}

public function testBug7275(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-7275.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -913,6 +913,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7353.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7031.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-intersect.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7153.php');
}

/**
Expand Down
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7153.php
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace Bug7153;

use Exception;
use function PHPStan\Testing\assertType;

function blah(): string
{
return 'blah';
}

function bleh(): ?string
{
return random_int(0, 1) > 0 ? 'bleh' : null;
}

function blih(string $blah, string $bleh): void
{
}

function () {
$data = [blah(), bleh()];

assertType('array{string, string|null}', $data);

if (in_array(null, $data, true)) {
assertType('array{string, string|null}', $data);
throw new Exception();
}

assertType('array{string, string}', $data);

blih($data[0], $data[1]);
};
28 changes: 28 additions & 0 deletions tests/PHPStan/Analyser/data/bug-7275.php
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace Bug7275;

class HelloWorld
{
/**
* @param array<HelloWorld|null> $collectionWithPotentialNulls
*
* @return mixed[]
*/
public function doSomething(array $collectionWithPotentialNulls): array
{
return !in_array(null, $collectionWithPotentialNulls, true)
? $this->doSomethingElse($collectionWithPotentialNulls)
: [];
}

/**
* @param array<HelloWorld> $collectionWithoutNulls
*
* @return mixed[]
*/
public function doSomethingElse(array $collectionWithoutNulls): array
{
return [];
}
}

0 comments on commit efee1cd

Please sign in to comment.