diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index d485dc6e73..9bd7c0ec6a 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -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; @@ -27,7 +28,6 @@ use function implode; use function in_array; use function is_string; -use function reset; use function sprintf; use function strtolower; @@ -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) { @@ -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) { @@ -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 diff --git a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php index 6988702c57..65b38f234c 100644 --- a/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php @@ -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; @@ -41,6 +44,7 @@ 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 ( @@ -48,16 +52,38 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n || 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; } } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index e59509170b..0b4b314dbf 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -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[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index b77b86240d..ae77180c18 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-7153.php b/tests/PHPStan/Analyser/data/bug-7153.php new file mode 100644 index 0000000000..973beecf24 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7153.php @@ -0,0 +1,35 @@ + 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]); +}; diff --git a/tests/PHPStan/Analyser/data/bug-7275.php b/tests/PHPStan/Analyser/data/bug-7275.php new file mode 100644 index 0000000000..4f4224e313 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7275.php @@ -0,0 +1,28 @@ + $collectionWithPotentialNulls + * + * @return mixed[] + */ + public function doSomething(array $collectionWithPotentialNulls): array + { + return !in_array(null, $collectionWithPotentialNulls, true) + ? $this->doSomethingElse($collectionWithPotentialNulls) + : []; + } + + /** + * @param array $collectionWithoutNulls + * + * @return mixed[] + */ + public function doSomethingElse(array $collectionWithoutNulls): array + { + return []; + } +}