diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 0551a2b2d1..ab412e21ec 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -68,11 +68,12 @@ public function findSpecifiedType( if ($node->isFirstClassCallable()) { return null; } - $argsCount = count($node->getArgs()); + $args = $node->getArgs(); + $argsCount = count($args); if ($node->name instanceof Node\Name) { $functionName = strtolower((string) $node->name); if ($functionName === 'assert' && $argsCount >= 1) { - $arg = $node->getArgs()[0]->value; + $arg = $args[0]->value; $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); $assertValueIsTrue = $assertValue->isTrue()->yes(); if (! $assertValueIsTrue && ! $assertValue->isFalse()->yes()) { @@ -96,7 +97,7 @@ public function findSpecifiedType( } elseif ($functionName === 'array_search') { return null; } elseif ($functionName === 'in_array' && $argsCount >= 2) { - $haystackArg = $node->getArgs()[1]->value; + $haystackArg = $args[1]->value; $haystackType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg)); if ($haystackType instanceof MixedType) { return null; @@ -106,12 +107,12 @@ public function findSpecifiedType( return null; } - $needleArg = $node->getArgs()[0]->value; + $needleArg = $args[0]->value; $needleType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($needleArg) : $scope->getNativeType($needleArg)); $isStrictComparison = false; if ($argsCount >= 3) { - $strictNodeType = $scope->getType($node->getArgs()[2]->value); + $strictNodeType = $scope->getType($args[2]->value); $isStrictComparison = $strictNodeType->isTrue()->yes(); } @@ -192,7 +193,7 @@ public function findSpecifiedType( } } } elseif ($functionName === 'method_exists' && $argsCount >= 2) { - $objectArg = $node->getArgs()[0]->value; + $objectArg = $args[0]->value; $objectType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($objectArg) : $scope->getNativeType($objectArg)); if ($objectType instanceof ConstantStringType @@ -201,7 +202,7 @@ public function findSpecifiedType( return false; } - $methodArg = $node->getArgs()[1]->value; + $methodArg = $args[1]->value; $methodType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($methodArg) : $scope->getNativeType($methodArg)); if ($methodType instanceof ConstantStringType) { @@ -278,6 +279,16 @@ public function findSpecifiedType( $results = []; + $assignedInCallVars = []; + if ($node instanceof Expr\CallLike) { + foreach ($node->getArgs() as $arg) { + if (!$arg->value instanceof Expr\Assign) { + continue; + } + + $assignedInCallVars[] = $arg->value; + } + } foreach ($sureTypes as $sureType) { if (self::isSpecified($typeSpecifierScope, $node, $sureType[0])) { $results[] = TrinaryLogic::createMaybe(); @@ -293,6 +304,14 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureType[1]; + foreach ($assignedInCallVars as $assignedInCallVar) { + if ($sureType[0] !== $assignedInCallVar->var) { + continue; + } + + $argumentType = $scope->getType($assignedInCallVar->expr); + } + $results[] = $resultType->isSuperTypeOf($argumentType)->result; } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 132ec6bfa2..2c0c7fbbfe 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1032,4 +1032,53 @@ public function testBug13291(): void $this->analyse([__DIR__ . '/data/bug-13291.php'], []); } + #[RequiresPhp('>= 8.0')] + public function testBug13268(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-13268.php'], []); + } + + #[RequiresPhp('>= 8.1')] + public function testBug12087(): void + { + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12087.php'], [ + [ + 'Call to function is_null() with null will always evaluate to true.', + 14, + $tipText, + ], + ]); + } + + public function testBug9666(): void + { + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-9666.php'], [ + [ + 'Call to function is_bool() with bool will always evaluate to true.', + 20, + $tipText, + ], + ]); + } + + #[RequiresPhp('>= 8.0')] + public function testBug9445(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-9445.php'], []); + } + + public function testBug7773(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-7773.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php index 3fd625a4b1..fb64511aff 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeMethodCallRuleTest.php @@ -275,6 +275,21 @@ public function testBug12473(): void ]); } + #[RequiresPhp('>= 8.1')] + public function testBug12087b(): void + { + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12087b.php'], [ + [ + 'Call to method Bug12087b\MyAssert::is_null() with null will always evaluate to true.', + 37, + $tipText, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 1b43cdea9d..8a68085379 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhp; /** * @extends RuleTestCase @@ -145,6 +146,21 @@ public function testReportAlwaysTrueInLastCondition(bool $reportAlwaysTrueInLast $this->analyse([__DIR__ . '/data/impossible-static-method-report-always-true-last-condition.php'], $expectedErrors); } + #[RequiresPhp('>= 8.1')] + public function testBug12087b(): void + { + $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; + + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-12087b.php'], [ + [ + 'Call to static method Bug12087b\MyAssert::static_is_null() with null will always evaluate to true.', + 31, + $tipText, + ], + ]); + } + public static function getAdditionalConfigFiles(): array { return [ diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12087.php b/tests/PHPStan/Rules/Comparison/data/bug-12087.php new file mode 100644 index 0000000000..31b18128b1 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12087.php @@ -0,0 +1,15 @@ += 8.1 + +namespace Bug12087; + +enum Button: int +{ + case On = 1; + + case Off = 0; +} + +$value = 10; + +is_null($value = Button::tryFrom($value)); + diff --git a/tests/PHPStan/Rules/Comparison/data/bug-12087b.php b/tests/PHPStan/Rules/Comparison/data/bug-12087b.php new file mode 100644 index 0000000000..3c26c567e9 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-12087b.php @@ -0,0 +1,38 @@ += 8.1 + +namespace Bug12087b; + +enum Button: int +{ + case On = 1; + + case Off = 0; +} + +class MyAssert { + /** + * @return ($value is null ? true : false) + */ + static public function static_is_null(mixed $value): bool { + return $value === null; + } + + /** + * @return ($value is null ? true : false) + */ + public function is_null(mixed $value): bool { + return $value === null; + } +} + +function doFoo(): void { + $value = 10; + + MyAssert::static_is_null($value = Button::tryFrom($value)); +} + +function doBar(MyAssert $assert): void { + $value = 10; + + $assert->is_null($value = Button::tryFrom($value)); +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-13268.php b/tests/PHPStan/Rules/Comparison/data/bug-13268.php new file mode 100644 index 0000000000..0bdbd130eb --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-13268.php @@ -0,0 +1,28 @@ + $data json array + * @return string json string + * @throws JSONEncodingException + */ + public static function JSONEncode(array $data): string + { + if (!is_string($data = json_encode($data))) + throw new JSONEncodingException(); + return $data; + } + + /** + * Decodes the JSON data as an array + * @param string $data json string + * @return array json array + * @throws JSONDecodingException + */ + public static function JSONDecode(string $data): array + { + if (!is_array($data = json_decode($data, true))) + throw new JSONDecodingException(); + return $data; + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-9445.php b/tests/PHPStan/Rules/Comparison/data/bug-9445.php new file mode 100644 index 0000000000..8fd828d0cc --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-9445.php @@ -0,0 +1,22 @@ += 8.0 + +declare(strict_types=1); + +namespace Bug9445; + +class Foo +{ + public int $id; + public null|self $parent; + + public function contains(self $foo): bool + { + do { + if ($this->id === $foo->id) { + return true; + } + } while (!is_null($foo = $foo->parent)); + + return false; + } +} diff --git a/tests/PHPStan/Rules/Comparison/data/bug-9666.php b/tests/PHPStan/Rules/Comparison/data/bug-9666.php new file mode 100644 index 0000000000..028601f5c2 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-9666.php @@ -0,0 +1,24 @@ + + */ + function b() + { + return []; + } +} + +function doFoo() { + $a = new A(); + $b = $a->b(); + $c = null; + if ($b && is_bool($c = reset($b))) { + // + } +} +