From 490eb8a1c3f47a0eed898cc8cd89746aef20ad5b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 30 Nov 2025 08:20:05 +0100 Subject: [PATCH] Implement SpaceshipHandler --- .../ExprHandler/SpaceshipHandler.php | 53 +++++++++++++++++++ .../InitializerExprTypeResolver.php | 13 +++-- .../PHPStan/Analyser/Generator/data/gnsr.php | 16 ++++++ 3 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/Analyser/Generator/ExprHandler/SpaceshipHandler.php diff --git a/src/Analyser/Generator/ExprHandler/SpaceshipHandler.php b/src/Analyser/Generator/ExprHandler/SpaceshipHandler.php new file mode 100644 index 0000000000..d14b384ca2 --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/SpaceshipHandler.php @@ -0,0 +1,53 @@ + + */ +#[AutowiredService] +final class SpaceshipHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof Expr\BinaryOp\Spaceship; + } + + public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, ExpressionContext $context, ?callable $alternativeNodeCallback): Generator + { + $leftResult = yield new ExprAnalysisRequest($stmt, $expr->left, $scope, $context, $alternativeNodeCallback); + $rightResult = yield new ExprAnalysisRequest($stmt, $expr->right, $leftResult->scope, $context, $alternativeNodeCallback); + + return new ExprAnalysisResult( + $this->initializerExprTypeResolver->getSpaceshipTypeFromTypes($leftResult->type, $rightResult->type), + $this->initializerExprTypeResolver->getSpaceshipTypeFromTypes($leftResult->nativeType, $rightResult->nativeType), + $rightResult->scope, + hasYield: $leftResult->hasYield || $rightResult->hasYield, + isAlwaysTerminating: $leftResult->isAlwaysTerminating || $rightResult->isAlwaysTerminating, + throwPoints: array_merge($leftResult->throwPoints, $rightResult->throwPoints), + impurePoints: array_merge($leftResult->impurePoints, $rightResult->impurePoints), + specifiedTruthyTypes: new SpecifiedTypes(), + specifiedFalseyTypes: new SpecifiedTypes(), + specifiedNullTypes: new SpecifiedTypes(), + ); + } + +} diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 3cc45424d1..f3284672cc 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1156,12 +1156,17 @@ public function getSpaceshipType(Expr $left, Expr $right, callable $getTypeCallb $callbackLeftType = $getTypeCallback($left); $callbackRightType = $getTypeCallback($right); - if ($callbackLeftType instanceof NeverType || $callbackRightType instanceof NeverType) { - return $this->getNeverType($callbackLeftType, $callbackRightType); + return $this->getSpaceshipTypeFromTypes($callbackLeftType, $callbackRightType); + } + + public function getSpaceshipTypeFromTypes(Type $leftTypes, Type $rightTypes): Type + { + if ($leftTypes instanceof NeverType || $rightTypes instanceof NeverType) { + return $this->getNeverType($leftTypes, $rightTypes); } - $leftTypes = $callbackLeftType->getConstantScalarTypes(); - $rightTypes = $callbackRightType->getConstantScalarTypes(); + $leftTypes = $leftTypes->getConstantScalarTypes(); + $rightTypes = $rightTypes->getConstantScalarTypes(); $leftTypesCount = count($leftTypes); $rightTypesCount = count($rightTypes); diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index 61c0152f10..aadd6edb21 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -220,6 +220,22 @@ public function doShiftRight($a, $b, int $c, int $d): void assertType('int', $c >> $d); assertNativeType('int', $c >> $d); } + + /** + * @param string $a + * @param string $b + * @return void + */ + public function doSpaceship($a, $b, string $c, string $d): void + { + assertType('int<-1, 1>', $a <=> $b); + assertNativeType('int<-1, 1>', $a <=> $b); + assertType('-1', '1' <=> 'a'); + assertNativeType('-1', '1' <=> 'a'); + assertType('int<-1, 1>', $c <=> $d); + assertNativeType('int<-1, 1>', $c <=> $d); + } + } function (): void {