diff --git a/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php new file mode 100644 index 0000000000..bd7a00a193 --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/UnaryMinusHandler.php @@ -0,0 +1,62 @@ + + */ +#[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); + + $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->type; + $nativeType = $mulResult->nativeType; + } + + return new ExprAnalysisResult( + $type, + $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..d0e3d42031 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2522,7 +2522,19 @@ public function getClassConstFetchType(Name|Expr $class, string $constantName, ? */ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type { - $type = $getTypeCallback($expr)->toNumber(); + $type = $getTypeCallback($expr); + + $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 + { + $type = $type->toNumber(); $scalarValues = $type->getConstantScalarValues(); if (count($scalarValues) > 0) { @@ -2543,10 +2555,6 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type return TypeCombinator::union(...$newTypes); } - if ($type instanceof IntegerRangeType) { - return $getTypeCallback(new Expr\BinaryOp\Mul($expr, new Int_(-1))); - } - return $type; } diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index d25f728f0b..9dfb927cad 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -191,13 +191,28 @@ 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'; assertType('1', +$a); assertNativeType('1', +$a); assertType('int', +$i); assertNativeType('int', +$i); + assertType('int', +$ii); + assertNativeType('float|int', +$ii); + } + + function doUnaryMinus(int $i) { + $a = '1'; + + assertType('-1', -$a); + assertNativeType('-1', -$a); + assertType('int', -$i); + assertNativeType('int', -$i); } /**