Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify array type via in_array #1430

Merged
merged 1 commit into from Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 18 additions & 21 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,10 +189,12 @@ public function findSpecifiedType(
return null;
}

if (count($sureTypes) === 1 && count($sureNotTypes) === 0) {
$sureType = reset($sureTypes);
$results = [];

foreach ($sureTypes as $sureType) {
if (self::isSpecified($scope, $node, $sureType[0])) {
return null;
$results[] = TrinaryLogic::createMaybe();
continue;
}

if ($this->treatPhpDocTypesAsCertain) {
Expand All @@ -204,18 +206,13 @@ public function findSpecifiedType(
/** @var Type $resultType */
$resultType = $sureType[1];

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

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

if ($this->treatPhpDocTypesAsCertain) {
Expand All @@ -227,15 +224,15 @@ public function findSpecifiedType(
/** @var Type $resultType */
$resultType = $sureNotType[1];

$isSuperType = $resultType->isSuperTypeOf($argumentType);
if ($isSuperType->yes()) {
return false;
} elseif ($isSuperType->no()) {
return true;
}
$results[] = $resultType->isSuperTypeOf($argumentType)->negate();
}

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

return null;
$result = TrinaryLogic::createYes()->and(...$results);
return $result->maybe() ? null : $result->yes();
}

private static function isSpecified(Scope $scope, Expr $node, Expr $expr): bool
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 [];
}
}