From d1c5f09d15b552aa94cfed5e92c32b5d47879555 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 1 Dec 2025 07:22:17 +0100 Subject: [PATCH 1/3] Implement UnaryMinusHandler --- .../ExprHandler/UnaryMinusHandler.php | 51 +++++++++++++++++++ .../InitializerExprTypeResolver.php | 9 +++- .../PHPStan/Analyser/Generator/data/gnsr.php | 12 ++++- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php diff --git a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php new file mode 100644 index 0000000000..add1f07af0 --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php @@ -0,0 +1,51 @@ + + */ +#[AutowiredService] +final class UnaryMinusHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof Expr\UnaryMinus; + } + + public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, ExpressionContext $context, ?callable $alternativeNodeCallback): Generator + { + $result = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context->enterDeep(), $alternativeNodeCallback); + + return new ExprAnalysisResult( + $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->type), + $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->nativeType), + $result->scope, + hasYield: $result->hasYield, + isAlwaysTerminating: $result->isAlwaysTerminating, + throwPoints: $result->throwPoints, + impurePoints: $result->impurePoints, + specifiedTruthyTypes: new SpecifiedTypes(), + specifiedFalseyTypes: new SpecifiedTypes(), + specifiedNullTypes: new SpecifiedTypes(), + ); + } + +} diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 4a4fcdadae..920713a83b 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2522,7 +2522,14 @@ public function getClassConstFetchType(Name|Expr $class, string $constantName, ? */ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type { - $type = $getTypeCallback($expr)->toNumber(); + $type = $getTypeCallback($expr); + + return $this->getUnaryMinusTypeFromType($expr, $type); + } + + public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type + { + $type = $type->toNumber(); $scalarValues = $type->getConstantScalarValues(); if (count($scalarValues) > 0) { diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index d25f728f0b..948255f0aa 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -191,7 +191,8 @@ public function doConcat($a, $b, string $c, string $d): void assertNativeType('string', $c . $d); } - function doUnaryPlus(int $i) { + function doUnaryPlus(int $i) + { $a = '1'; assertType('1', +$a); @@ -200,6 +201,15 @@ function doUnaryPlus(int $i) { assertNativeType('int', +$i); } + function doUnaryMinus(int $i) { + $a = '1'; + + assertType('-1', -$a); + assertNativeType('-1', -$a); + assertType('int', -$i); + assertNativeType('int', -$i); + } + /** * @param int $a * @param int $b From ed9dfbd527c9f153f94e2e7c92a291913384833d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 1 Dec 2025 07:34:40 +0100 Subject: [PATCH 2/3] fix --- .../Generator/ExprHandler/UnaryMinusHandler.php | 16 ++++++++++++++-- src/Reflection/InitializerExprTypeResolver.php | 11 ++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php index add1f07af0..ad4018ac23 100644 --- a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php @@ -4,15 +4,19 @@ use Generator; use PhpParser\Node\Expr; +use PhpParser\Node\Scalar\Int_; use PhpParser\Node\Stmt; use PHPStan\Analyser\ExpressionContext; use PHPStan\Analyser\Generator\ExprAnalysisRequest; use PHPStan\Analyser\Generator\ExprAnalysisResult; use PHPStan\Analyser\Generator\ExprHandler; use PHPStan\Analyser\Generator\GeneratorScope; +use PHPStan\Analyser\Generator\NoopNodeCallback; use PHPStan\Analyser\SpecifiedTypes; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Type\IntegerRangeType; +use function PHPStan\dumpType; /** * @implements ExprHandler @@ -34,9 +38,17 @@ public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, Expre { $result = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context->enterDeep(), $alternativeNodeCallback); + $type = $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->type); + $nativeType = $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->nativeType); + if ($type instanceof IntegerRangeType) { + $mulResult = yield new ExprAnalysisRequest($stmt, new Expr\BinaryOp\Mul($expr, new Int_(-1)), $scope, $context->enterDeep(), new NoopNodeCallback()); + $type = $mulResult->result; + $nativeType = $mulResult->nativeType; + } + return new ExprAnalysisResult( - $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->type), - $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->nativeType), + $type, + $nativeType, $result->scope, hasYield: $result->hasYield, isAlwaysTerminating: $result->isAlwaysTerminating, diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 920713a83b..d0e3d42031 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2524,7 +2524,12 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type { $type = $getTypeCallback($expr); - return $this->getUnaryMinusTypeFromType($expr, $type); + $type = $this->getUnaryMinusTypeFromType($expr, $type); + if ($type instanceof IntegerRangeType) { + return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); + } + + return $type; } public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type @@ -2550,10 +2555,6 @@ public function getUnaryMinusTypeFromType(Expr $expr, Type $type): Type return TypeCombinator::union(...$newTypes); } - if ($type instanceof IntegerRangeType) { - return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); - } - return $type; } From 9d59c3b88cdd394a23142b1b238b980f34597830 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 1 Dec 2025 07:35:13 +0100 Subject: [PATCH 3/3] Update UnaryMinusHandler.php --- src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php | 3 +-- tests/PHPStan/Analyser/Generator/data/gnsr.php | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php index ad4018ac23..bd7a00a193 100644 --- a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php +++ b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php @@ -16,7 +16,6 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Reflection\InitializerExprTypeResolver; use PHPStan\Type\IntegerRangeType; -use function PHPStan\dumpType; /** * @implements ExprHandler @@ -42,7 +41,7 @@ public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, Expre $nativeType = $this->initializerExprTypeResolver->getUnaryMinusTypeFromType($expr->expr, $result->nativeType); if ($type instanceof IntegerRangeType) { $mulResult = yield new ExprAnalysisRequest($stmt, new Expr\BinaryOp\Mul($expr, new Int_(-1)), $scope, $context->enterDeep(), new NoopNodeCallback()); - $type = $mulResult->result; + $type = $mulResult->type; $nativeType = $mulResult->nativeType; } diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index 948255f0aa..9dfb927cad 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -191,7 +191,10 @@ public function doConcat($a, $b, string $c, string $d): void assertNativeType('string', $c . $d); } - function doUnaryPlus(int $i) + /** + * @param int $ii + */ + function doUnaryPlus(int $i, $ii) { $a = '1'; @@ -199,6 +202,8 @@ function doUnaryPlus(int $i) assertNativeType('1', +$a); assertType('int', +$i); assertNativeType('int', +$i); + assertType('int', +$ii); + assertNativeType('float|int', +$ii); } function doUnaryMinus(int $i) {