diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 48eea97d6f..8195c0d815 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -147,7 +147,7 @@ public function findSpecifiedType( foreach ($haystackArrayTypes as $haystackArrayType) { if ($haystackArrayType instanceof ConstantArrayType) { foreach ($haystackArrayType->getValueTypes() as $i => $haystackArrayValueType) { - if ($haystackArrayType->isOptionalKey($i)) { + if ($haystackArrayValueType instanceof UnionType || $haystackArrayType->isOptionalKey($i)) { continue; } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 021f8cd455..999889f963 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -971,4 +971,18 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } + public function testBug12755(): void + { + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.'; + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12755.php'], [ + [ + 'Call to function in_array() with arguments null, array{key1: bool|null, key2: null} and true will always evaluate to true.', + 51, + $tipText, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12755.php b/tests/PHPStan/Rules/Comparison/data/bug-12755.php new file mode 100644 index 0000000000..7b65f96d6f --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12755.php @@ -0,0 +1,90 @@ +<?php declare(strict_types = 1); + +namespace Bug12755; + +class MyEnum +{ + public const ONE = 'one'; + public const TWO = 'two'; + public const THREE = 'three'; +} + +class HelloWorld +{ + /** + * @param array{ + * key1: ?int, + * key2: ?string, + * } $myArray + */ + public function testOther(array $myArray): ?\stdClass + { + if (\in_array(null, $myArray, true)) { + return null; + } + + return (object) $myArray; + } + + /** + * @param array{ + * key1: ?bool, + * } $myArray + */ + public function testBool(array $myArray): ?\stdClass + { + if (\in_array(null, $myArray, true)) { + return null; + } + + return (object) $myArray; + } + + /** + * @param array{ + * key1: ?bool, + * key2: null, + * } $myArray + */ + public function testNull(array $myArray): ?\stdClass + { + if (\in_array(null, $myArray, true)) { + return null; + } + + return (object) $myArray; + } + + /** + * @param list<MyEnum::*> $stack + */ + public function testEnum(array $stack): bool + { + return count($stack) === 1 && in_array(MyEnum::ONE, $stack, true); + } + + /** + * @param array{1|2|3} $stack + * @param array{1|2|3, 1|2|3} $stack2 + * @param array{1|2|3, 2|3} $stack3 + * @param array{a?: 1, b: 2|3} $stack4 + * @param array{a?: 1} $stack5 + */ + public function sayHello(array $stack, array $stack2, array $stack3, array $stack4, array $stack5): void + { + if (in_array(1, $stack, true)) { + } + + if (in_array(1, $stack2, true)) { + } + + if (in_array(1, $stack3, true)) { + } + + if (in_array(1, $stack4, true)) { + } + + if (in_array(1, $stack5, true)) { + } + } +}