diff --git a/src/Analyser/Generator/ExprHandler/BinaryShiftLeftHandler.php b/src/Analyser/Generator/ExprHandler/BinaryShiftLeftHandler.php new file mode 100644 index 0000000000..dd7a683d4b --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/BinaryShiftLeftHandler.php @@ -0,0 +1,53 @@ + + */ +#[AutowiredService] +final class BinaryShiftLeftHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof Expr\BinaryOp\ShiftLeft; + } + + 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->getShiftLeftTypeFromTypes($expr->left, $expr->right, $leftResult->type, $rightResult->type), + $this->initializerExprTypeResolver->getShiftLeftTypeFromTypes($expr->left, $expr->right, $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/Analyser/Generator/ExprHandler/BinaryShiftRightHandler.php b/src/Analyser/Generator/ExprHandler/BinaryShiftRightHandler.php new file mode 100644 index 0000000000..9ce9489978 --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/BinaryShiftRightHandler.php @@ -0,0 +1,53 @@ + + */ +#[AutowiredService] +final class BinaryShiftRightHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof Expr\BinaryOp\ShiftRight; + } + + 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->getShiftRightTypeFromTypes($expr->left, $expr->right, $leftResult->type, $rightResult->type), + $this->initializerExprTypeResolver->getShiftRightTypeFromTypes($expr->left, $expr->right, $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 a8515bb506..3cc45424d1 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -1690,6 +1690,11 @@ public function getShiftLeftType(Expr $left, Expr $right, callable $getTypeCallb $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + return $this->getShiftLeftTypeFromTypes($left, $right, $leftType, $rightType); + } + + public function getShiftLeftTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type + { if ($leftType instanceof NeverType || $rightType instanceof NeverType) { return $this->getNeverType($leftType, $rightType); } @@ -1749,6 +1754,11 @@ public function getShiftRightType(Expr $left, Expr $right, callable $getTypeCall $leftType = $getTypeCallback($left); $rightType = $getTypeCallback($right); + return $this->getShiftRightTypeFromTypes($left, $right, $leftType, $rightType); + } + + public function getShiftRightTypeFromTypes(Expr $left, Expr $right, Type $leftType, Type $rightType): Type + { if ($leftType instanceof NeverType || $rightType instanceof NeverType) { return $this->getNeverType($leftType, $rightType); } diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index 7d3d2c3470..61c0152f10 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -191,6 +191,35 @@ public function doConcat($a, $b, string $c, string $d): void assertNativeType('string', $c . $d); } + /** + * @param int $a + * @param int $b + * @return void + */ + public function doShiftLeft($a, $b, int $c, int $d): void + { + assertType('int', $a << $b); + assertNativeType('(float|int)', $a << $b); + assertType('8', 1 << 3); + assertNativeType('8', 1 << 3); + assertType('int', $c << $d); + assertNativeType('int', $c << $d); + } + + /** + * @param int $a + * @param int $b + * @return void + */ + public function doShiftRight($a, $b, int $c, int $d): void + { + assertType('int', $a >> $b); + assertNativeType('(float|int)', $a >> $b); + assertType('0', 1 >> 3); + assertNativeType('0', 1 >> 3); + assertType('int', $c >> $d); + assertNativeType('int', $c >> $d); + } } function (): void {