From e135333e3dad43f6a98b2849e0acc11a7a82d5e7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 14 Aug 2024 00:36:54 +0200 Subject: [PATCH 1/2] Improve int div throw type extension --- src/Type/Php/IntdivThrowTypeExtension.php | 39 ++++------------ .../CatchWithUnthrownExceptionRuleTest.php | 4 ++ .../Exceptions/data/unthrown-exception.php | 46 +++++++++++++++++++ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/Type/Php/IntdivThrowTypeExtension.php b/src/Type/Php/IntdivThrowTypeExtension.php index aab1448733..68326534f3 100644 --- a/src/Type/Php/IntdivThrowTypeExtension.php +++ b/src/Type/Php/IntdivThrowTypeExtension.php @@ -7,11 +7,10 @@ use PhpParser\Node\Expr\FuncCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicFunctionThrowTypeExtension; -use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use PHPStan\Type\TypeCombinator; use function count; use const PHP_INT_MIN; @@ -29,39 +28,19 @@ public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflect return $functionReflection->getThrowType(); } - $containsMin = false; - $valueType = $scope->getType($funcCall->getArgs()[0]->value); - foreach ($valueType->getConstantScalarTypes() as $constantScalarType) { - if ($constantScalarType->getValue() === PHP_INT_MIN) { - $containsMin = true; - } - - $valueType = TypeCombinator::remove($valueType, $constantScalarType); - } - - if (!$valueType instanceof NeverType) { - $containsMin = true; - } + $valueType = $scope->getType($funcCall->getArgs()[0]->value)->toInteger(); + $containsMin = $valueType->acceptsWithReason(new ConstantIntegerType(PHP_INT_MIN), true); - $divisionByZero = false; - $divisorType = $scope->getType($funcCall->getArgs()[1]->value); - foreach ($divisorType->getConstantScalarTypes() as $constantScalarType) { - if ($containsMin && $constantScalarType->getValue() === -1) { + $divisorType = $scope->getType($funcCall->getArgs()[1]->value)->toInteger(); + if (!$containsMin->no()) { + $divisionByMinusOne = $divisorType->acceptsWithReason(new ConstantIntegerType(-1), true); + if (!$divisionByMinusOne->no()) { return new ObjectType(ArithmeticError::class); } - - if ($constantScalarType->getValue() === 0) { - $divisionByZero = true; - } - - $divisorType = TypeCombinator::remove($divisorType, $constantScalarType); - } - - if (!$divisorType instanceof NeverType) { - return new ObjectType($containsMin ? ArithmeticError::class : DivisionByZeroError::class); } - if ($divisionByZero) { + $divisionByZero = $divisorType->acceptsWithReason(new ConstantIntegerType(0), false); + if (!$divisionByZero->no()) { return new ObjectType(DivisionByZeroError::class); } diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 83943ccbb2..0958befd2f 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -137,6 +137,10 @@ public function testRule(): void 'Dead catch - InvalidArgumentException is never thrown in the try block.', 741, ], + [ + 'Dead catch - ArithmeticError is never thrown in the try block.', + 762, + ], ]); } diff --git a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php index d501e1cffc..0327fcb086 100644 --- a/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php +++ b/tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php @@ -744,3 +744,49 @@ public function passCallableToMethod2(): void } } + +class TestIntdivWithRange +{ + /** + * @param int $int + * @param int $negativeInt + * @param int<1, max> $positiveInt + */ + public function doFoo(int $int, int $negativeInt, int $positiveInt): void + { + try { + intdiv($int, $positiveInt); + intdiv($positiveInt, $negativeInt); + intdiv($negativeInt, $positiveInt); + intdiv($positiveInt, $positiveInt); + } catch (\ArithmeticError $e) { + + } + try { + intdiv($int, $negativeInt); + } catch (\ArithmeticError $e) { + + } + try { + intdiv($negativeInt, $negativeInt); + } catch (\ArithmeticError $e) { + + } + try { + intdiv($positiveInt, $int); + } catch (\ArithmeticError $e) { + + } + try { + intdiv($negativeInt, $int); + } catch (\ArithmeticError $e) { + + } + try { + intdiv($int, '-1,5'); + } catch (\ArithmeticError $e) { + + } + } + +} From cba6496c3dec5a77afdc56e50d26807861e83493 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 14 Aug 2024 21:57:15 +0200 Subject: [PATCH 2/2] Use isSuperType --- src/Type/Php/IntdivThrowTypeExtension.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/IntdivThrowTypeExtension.php b/src/Type/Php/IntdivThrowTypeExtension.php index 68326534f3..ef8ac1ef54 100644 --- a/src/Type/Php/IntdivThrowTypeExtension.php +++ b/src/Type/Php/IntdivThrowTypeExtension.php @@ -29,17 +29,17 @@ public function getThrowTypeFromFunctionCall(FunctionReflection $functionReflect } $valueType = $scope->getType($funcCall->getArgs()[0]->value)->toInteger(); - $containsMin = $valueType->acceptsWithReason(new ConstantIntegerType(PHP_INT_MIN), true); + $containsMin = $valueType->isSuperTypeOf(new ConstantIntegerType(PHP_INT_MIN)); $divisorType = $scope->getType($funcCall->getArgs()[1]->value)->toInteger(); if (!$containsMin->no()) { - $divisionByMinusOne = $divisorType->acceptsWithReason(new ConstantIntegerType(-1), true); + $divisionByMinusOne = $divisorType->isSuperTypeOf(new ConstantIntegerType(-1)); if (!$divisionByMinusOne->no()) { return new ObjectType(ArithmeticError::class); } } - $divisionByZero = $divisorType->acceptsWithReason(new ConstantIntegerType(0), false); + $divisionByZero = $divisorType->isSuperTypeOf(new ConstantIntegerType(0)); if (!$divisionByZero->no()) { return new ObjectType(DivisionByZeroError::class); }