diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9b1e118976..084368fb4e 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -869,13 +869,40 @@ public function create( Expr $expr, Type $type, TypeSpecifierContext $context, - bool $overwrite = false + bool $overwrite = false, + ?Scope $scope = null ): SpecifiedTypes { if ($expr instanceof New_ || $expr instanceof Instanceof_) { return new SpecifiedTypes(); } + if ( + $expr instanceof FuncCall + && $expr->name instanceof Name + && $this->reflectionProvider->hasFunction($expr->name, $scope) + ) { + $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); + if ($functionReflection->hasSideEffects()->yes()) { + return new SpecifiedTypes(); + } + } + + if ( + $expr instanceof MethodCall + && $expr->name instanceof Node\Identifier + && $scope !== null + ) { + $methodName = $expr->name->toString(); + $calledOnType = $scope->getType($expr->var); + if ($calledOnType->hasMethod($methodName)->yes()) { + $methodReflection = $calledOnType->getMethod($methodName, $scope); + if ($methodReflection->hasSideEffects()->yes()) { + return new SpecifiedTypes(); + } + } + } + $sureTypes = []; $sureNotTypes = []; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 42781da8f6..6f5cd925d3 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -179,6 +179,10 @@ public function findSpecifiedType( return null; } + if ($sureType[0] === $node) { + return null; + } + if ($this->treatPhpDocTypesAsCertain) { $argumentType = $scope->getType($sureType[0]); } else { @@ -202,6 +206,10 @@ public function findSpecifiedType( return null; } + if ($sureNotType[0] === $node) { + return null; + } + if ($this->treatPhpDocTypesAsCertain) { $argumentType = $scope->getType($sureNotType[0]); } else { diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 8928b5ee79..a93dcb2c81 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5760,6 +5760,11 @@ public function dataBug560(): array return $this->gatherAssertTypes(__DIR__ . '/data/bug-560.php'); } + public function dataDoNotRememberImpureFunctions(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/do-not-remember-impure-functions.php'); + } + /** * @dataProvider dataArrayFunctions * @param string $description @@ -11395,6 +11400,7 @@ private function gatherAssertTypes(string $file): array * @dataProvider dataBug3190 * @dataProvider dataTernarySpecifiedTypes * @dataProvider dataBug560 + * @dataProvider dataDoNotRememberImpureFunctions * @param string $assertType * @param string $file * @param mixed ...$args diff --git a/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php new file mode 100644 index 0000000000..2ec0309152 --- /dev/null +++ b/tests/PHPStan/Analyser/data/do-not-remember-impure-functions.php @@ -0,0 +1,72 @@ +doBar() === true) { + assertType('true', $this->doBar()); + } + + if ($this->doBaz() === true) { + assertType('true', $this->doBaz()); + } + + if ($this->doLorem() === true) { + assertType('bool', $this->doLorem()); + } + } + + public function doDolor() + { + if ($this->doBar()) { + assertType('true', $this->doBar()); + } + + if ($this->doBaz()) { + assertType('true', $this->doBaz()); + } + + if ($this->doLorem()) { + assertType('bool', $this->doLorem()); + } + } + +} diff --git a/tests/PHPStan/Analyser/data/isset.php b/tests/PHPStan/Analyser/data/isset.php index 1497e0c56c..f78ad029b2 100644 --- a/tests/PHPStan/Analyser/data/isset.php +++ b/tests/PHPStan/Analyser/data/isset.php @@ -15,11 +15,11 @@ public function doFoo(array $integers, string $string, $mixedIsset, $mixedArrayK 'a' => 1, 'b' => 2, ]; - } elseif (rand(0, 11) === 0) { + } elseif (rand(0, 10) === 0) { $array = [ 'a' => 2, ]; - } elseif (rand(0, 12) === 0) { + } elseif (rand(0, 10) === 0) { $array = [ 'a' => 3, 'b' => 3, diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index f998a0a306..b38a6ac5ce 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -183,14 +183,6 @@ public function testReturnTypeRule(): void 'Method ReturnTypes\TrickyVoid::returnVoidOrInt() should return int|void but returns string.', 656, ], - [ - 'Method ReturnTypes\TernaryWithJsonEncode::toJsonOrNull() should return string|null but returns string|false|null.', - 671, - ], - [ - 'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.', - 684, - ], [ 'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.', 687, @@ -235,10 +227,6 @@ public function testReturnTypeRule(): void 'Method ReturnTypes\WrongMagicMethods::__clone() with return type void returns int but should not return anything.', 757, ], - [ - 'Method ReturnTypes\ReturnSpecifiedMethodCall::doFoo() should return string but returns string|false.', - 776, - ], [ 'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array> but returns array>.', 815,