diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 603035e2d1..f4bf060987 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -4305,7 +4305,16 @@ private function getFunctionThrowPoint( $throwType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope); if ($throwType === null) { - return null; + if ($this->treatPhpDocTypesAsCertain) { + return null; + } + + $nativeThrowType = $extension->getThrowTypeFromFunctionCall($functionReflection, $normalizedFuncCall, $scope->doNotTreatPhpDocTypesAsCertain()); + if ($nativeThrowType === null) { + return null; + } + + return ThrowPoint::createImplicit($scope, $funcCall); } return ThrowPoint::createExplicit($scope, $throwType, $funcCall, false); @@ -4363,7 +4372,16 @@ private function getMethodThrowPoint(MethodReflection $methodReflection, Paramet $throwType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope); if ($throwType === null) { - return null; + if ($this->treatPhpDocTypesAsCertain) { + return null; + } + + $nativeThrowType = $extension->getThrowTypeFromMethodCall($methodReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain()); + if ($nativeThrowType === null) { + return null; + } + + return ThrowPoint::createImplicit($scope, $methodCall); } return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); @@ -4407,7 +4425,16 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio $throwType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope); if ($throwType === null) { - return null; + if ($this->treatPhpDocTypesAsCertain) { + return null; + } + + $nativeThrowType = $extension->getThrowTypeFromStaticMethodCall($constructorReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain()); + if ($nativeThrowType === null) { + return null; + } + + return ThrowPoint::createImplicit($scope, $new); } return ThrowPoint::createExplicit($scope, $throwType, $new, false); @@ -4439,7 +4466,16 @@ private function getStaticMethodThrowPoint(MethodReflection $methodReflection, P $throwType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope); if ($throwType === null) { - return null; + if ($this->treatPhpDocTypesAsCertain) { + return null; + } + + $nativeThrowType = $extension->getThrowTypeFromStaticMethodCall($methodReflection, $normalizedMethodCall, $scope->doNotTreatPhpDocTypesAsCertain()); + if ($nativeThrowType === null) { + return null; + } + + return ThrowPoint::createImplicit($scope, $methodCall); } return ThrowPoint::createExplicit($scope, $throwType, $methodCall, false); diff --git a/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchPhpDocNotCertainTest.php b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchPhpDocNotCertainTest.php new file mode 100644 index 0000000000..b214d48231 --- /dev/null +++ b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchPhpDocNotCertainTest.php @@ -0,0 +1,47 @@ + + */ +class DynamicMethodThrowTypeExtensionDeadCatchPhpDocNotCertainTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return self::getContainer()->getByType(CatchWithUnthrownExceptionRule::class); + } + + public function testDynamicMethodThrowTypeExtension(): void + { + $this->analyse([__DIR__ . '/data/dynamic-method-throw-type-extension.php'], [ + [ + 'Dead catch - Exception is never thrown in the try block.', + 102, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 124, + ], + ]); + } + + protected function shouldTreatPhpDocTypesAsCertain(): bool + { + return false; + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-throw-type-extension.neon', + __DIR__ . '/do-not-treat-php-doc-type-as-certain.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchTest.php b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchTest.php new file mode 100644 index 0000000000..85050a7178 --- /dev/null +++ b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionDeadCatchTest.php @@ -0,0 +1,49 @@ + + */ +class DynamicMethodThrowTypeExtensionDeadCatchTest extends RuleTestCase +{ + + protected function getRule(): TRule + { + return self::getContainer()->getByType(CatchWithUnthrownExceptionRule::class); + } + + public function testMixedUnknownType(): void + { + $this->analyse([__DIR__ . '/data/dynamic-method-throw-type-extension.php'], [ + [ + 'Dead catch - Exception is never thrown in the try block.', + 102, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 124, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 148, + ], + [ + 'Dead catch - Exception is never thrown in the try block.', + 172, + ], + ]); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-throw-type-extension.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionPhpDocNotCertainTest.php b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionPhpDocNotCertainTest.php new file mode 100644 index 0000000000..d51df82934 --- /dev/null +++ b/tests/PHPStan/Analyser/DynamicMethodThrowTypeExtensionPhpDocNotCertainTest.php @@ -0,0 +1,43 @@ +assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/dynamic-throw-type-extension.neon', + __DIR__ . '/do-not-treat-php-doc-type-as-certain.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php b/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php index 9d4aaf48a3..8adde97b48 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-throw-type-extension.php @@ -28,11 +28,11 @@ public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, M } $argType = $scope->getType($methodCall->args[0]->value); - if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { - return $methodReflection->getThrowType(); + if ((new ConstantBooleanType(false))->isSuperTypeOf($argType)->yes()) { + return null; } - return null; + return $methodReflection->getThrowType(); } } @@ -52,11 +52,11 @@ public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflect } $argType = $scope->getType($methodCall->args[0]->value); - if ((new ConstantBooleanType(true))->isSuperTypeOf($argType)->yes()) { - return $methodReflection->getThrowType(); + if ((new ConstantBooleanType(false))->isSuperTypeOf($argType)->yes()) { + return null; } - return null; + return $methodReflection->getThrowType(); } } @@ -88,6 +88,8 @@ public function doFoo1() { try { $result = $this->throwOrNot(true); + } catch (\Exception) { + } finally { assertVariableCertainty(TrinaryLogic::createMaybe(), $result); } @@ -97,6 +99,8 @@ public function doFoo2() { try { $result = $this->throwOrNot(false); + } catch (\Exception) { // DeadCatch + } finally { assertVariableCertainty(TrinaryLogic::createYes(), $result); } @@ -106,6 +110,8 @@ public function doFoo3() { try { $result = self::staticThrowOrNot(true); + } catch (\Exception) { + } finally { assertVariableCertainty(TrinaryLogic::createMaybe(), $result); } @@ -115,6 +121,56 @@ public function doFoo4() { try { $result = self::staticThrowOrNot(false); + } catch (\Exception) { // DeadCatch + + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $result); + } + } + + /** @param true $value */ + public function doFooPhpdoc1(bool $value) + { + try { + $result = $this->throwOrNot($value); + } catch (\Exception) { + + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result); + } + } + + /** @param false $value */ + public function doFooPhpdoc2(bool $value) + { + try { + $result = $this->throwOrNot($value); + } catch (\Exception) { // DeadCatch if phpdoc is trusted + + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $result); + } + } + + /** @param true $value */ + public function doFooPhpdoc3(bool $value) + { + try { + $result = self::staticThrowOrNot($value); + } catch (\Exception) { + + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result); + } + } + + /** @param false $value */ + public function doFooPhpdoc4(bool $value) + { + try { + $result = self::staticThrowOrNot($value); + } catch (\Exception) { // DeadCatch if phpdoc is trusted + } finally { assertVariableCertainty(TrinaryLogic::createYes(), $result); } diff --git a/tests/PHPStan/Analyser/do-not-treat-php-doc-type-as-certain.neon b/tests/PHPStan/Analyser/do-not-treat-php-doc-type-as-certain.neon new file mode 100644 index 0000000000..c551b84f1f --- /dev/null +++ b/tests/PHPStan/Analyser/do-not-treat-php-doc-type-as-certain.neon @@ -0,0 +1,2 @@ +parameters: + treatPhpDocTypesAsCertain: false