From e726b27acf571817108c515fd9b30b5eb2fa528d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 30 Nov 2025 07:31:56 +0100 Subject: [PATCH 1/2] Implement BitwiseNotHandler --- .../ExprHandler/BitwiseNotHandler.php | 51 +++++++++++++++++++ .../InitializerExprTypeResolver.php | 6 +++ .../PHPStan/Analyser/Generator/data/gnsr.php | 14 +++++ 3 files changed, 71 insertions(+) create mode 100644 src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php diff --git a/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php b/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php new file mode 100644 index 0000000000..9e1b41d8fe --- /dev/null +++ b/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php @@ -0,0 +1,51 @@ + + */ +#[AutowiredService] +final class BitwiseNotHandler implements ExprHandler +{ + + public function __construct(private InitializerExprTypeResolver $initializerExprTypeResolver) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof Expr\BitwiseNot; + } + + public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, ExpressionContext $context, ?callable $alternativeNodeCallback): Generator + { + $result = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context, $alternativeNodeCallback); + + return new ExprAnalysisResult( + $this->initializerExprTypeResolver->getBitwiseNotTypeFromType($result->type), + $this->initializerExprTypeResolver->getBitwiseNotTypeFromType($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 3bd59c0b10..eec0aac329 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2532,6 +2532,12 @@ public function getUnaryMinusType(Expr $expr, callable $getTypeCallback): Type public function getBitwiseNotType(Expr $expr, callable $getTypeCallback): Type { $exprType = $getTypeCallback($expr); + + return $this->getBitwiseNotTypeFromType($exprType); + } + + public function getBitwiseNotTypeFromType(Type $exprType): Type + { return TypeTraverser::map($exprType, static function (Type $type, callable $traverse): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); diff --git a/tests/PHPStan/Analyser/Generator/data/gnsr.php b/tests/PHPStan/Analyser/Generator/data/gnsr.php index 60e78e87ca..03002b9f85 100644 --- a/tests/PHPStan/Analyser/Generator/data/gnsr.php +++ b/tests/PHPStan/Analyser/Generator/data/gnsr.php @@ -87,6 +87,20 @@ public function doMinus($a, $b, int $c, int $d): void assertNativeType('int', $c - $d); } + /** + * @param int $a + * @return void + */ + public function doBitwiseNot($a, int $b): void + { + assertType('int', ~$a); + assertNativeType('int', ~$b); + assertType('int', ~1); + assertNativeType('int', ~1); + assertType('int', ~$b); + assertNativeType('int', ~$b); + } + /** * @param int $a * @param int $b From 1b10ef94818b982412da3220154e51adeedd60f3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 30 Nov 2025 07:35:29 +0100 Subject: [PATCH 2/2] enterDeep --- src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php b/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php index 9e1b41d8fe..7ddc90221b 100644 --- a/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php +++ b/src/Analyser/Generator/ExprHandler/BitwiseNotHandler.php @@ -32,7 +32,7 @@ public function supports(Expr $expr): bool public function analyseExpr(Stmt $stmt, Expr $expr, GeneratorScope $scope, ExpressionContext $context, ?callable $alternativeNodeCallback): Generator { - $result = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context, $alternativeNodeCallback); + $result = yield new ExprAnalysisRequest($stmt, $expr->expr, $scope, $context->enterDeep(), $alternativeNodeCallback); return new ExprAnalysisResult( $this->initializerExprTypeResolver->getBitwiseNotTypeFromType($result->type),