From 82f79638487d4bae41e7e0c61ddb34341fbb31cb Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 4 Oct 2025 17:22:54 +0200 Subject: [PATCH] Add VersionCompareFunctionDynamicThrowTypeExtension --- ...pareFunctionDynamicReturnTypeExtension.php | 2 +- ...mpareFunctionDynamicThrowTypeExtension.php | 60 +++++++++++++++++++ ...FunctionWithExplicitThrowPointRuleTest.php | 7 +++ .../Rules/Exceptions/data/bug-13515.php | 18 ++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/Type/Php/VersionCompareFunctionDynamicThrowTypeExtension.php create mode 100644 tests/PHPStan/Rules/Exceptions/data/bug-13515.php diff --git a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php index d177092349..2f17375c94 100644 --- a/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/VersionCompareFunctionDynamicReturnTypeExtension.php @@ -29,7 +29,7 @@ final class VersionCompareFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension { - private const VALID_OPERATORS = [ + public const VALID_OPERATORS = [ '<', 'lt', '<=', diff --git a/src/Type/Php/VersionCompareFunctionDynamicThrowTypeExtension.php b/src/Type/Php/VersionCompareFunctionDynamicThrowTypeExtension.php new file mode 100644 index 0000000000..e4c4f92b34 --- /dev/null +++ b/src/Type/Php/VersionCompareFunctionDynamicThrowTypeExtension.php @@ -0,0 +1,60 @@ +getName() === 'version_compare'; + } + + public function getThrowTypeFromFunctionCall( + FunctionReflection $functionReflection, + FuncCall $funcCall, + Scope $scope, + ): ?Type + { + if (!$this->phpVersion->throwsValueErrorForInternalFunctions()) { + return null; + } + + $args = $funcCall->getArgs(); + if (!isset($args[2])) { + return null; + } + + $operatorStrings = $scope->getType($args[2]->value)->getConstantStrings(); + if (count($operatorStrings) === 0) { + return $functionReflection->getThrowType(); + } + + foreach ($operatorStrings as $operatorString) { + $operatorValue = $operatorString->getValue(); + if (!in_array($operatorValue, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { + return $functionReflection->getThrowType(); + } + } + + return null; + } + +} diff --git a/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php index af4f7bc372..207b05bb76 100644 --- a/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.php @@ -97,4 +97,11 @@ public function testRule(bool $missingCheckedExceptionInThrows, array $checkedEx $this->analyse([__DIR__ . '/data/throws-void-function.php'], $errors); } + public function testBug13515(): void + { + $this->missingCheckedExceptionInThrows = false; + $this->checkedExceptionClasses = []; + $this->analyse([__DIR__ . '/data/bug-13515.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-13515.php b/tests/PHPStan/Rules/Exceptions/data/bug-13515.php new file mode 100644 index 0000000000..9cda263f8c --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-13515.php @@ -0,0 +1,18 @@ +=' + ) + ) { + $name = 'macOS'; + $marketingName = 'macOS'; + } +}