From fda35f803baff711a60354bc89d775106c65cfa5 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Tue, 15 Apr 2025 21:23:27 +0200 Subject: [PATCH] Fix specifying types on nullsafe true/false comparison --- src/Analyser/TypeSpecifier.php | 4 +- tests/PHPStan/Analyser/nsrt/bug-12866.php | 65 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12866.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f7d01f2da6..ff08ce4503 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1134,7 +1134,7 @@ private function specifyTypesForConstantBinaryExpression( { if (!$context->null() && $constantType->isFalse()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { + if (!$context->true() && ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch)) { return $types; } @@ -1148,7 +1148,7 @@ private function specifyTypesForConstantBinaryExpression( if (!$context->null() && $constantType->isTrue()->yes()) { $types = $this->create($exprNode, $constantType, $context, false, $scope, $rootExpr); - if ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch) { + if (!$context->true() && ($exprNode instanceof Expr\NullsafeMethodCall || $exprNode instanceof Expr\NullsafePropertyFetch)) { return $types; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12866.php b/tests/PHPStan/Analyser/nsrt/bug-12866.php new file mode 100644 index 0000000000..4360d8bdd1 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12866.php @@ -0,0 +1,65 @@ += 8.0 + +namespace Bug12866; + +use function PHPStan\Testing\assertType; + +interface I +{ + /** + * @phpstan-assert-if-true A $this + */ + public function isA(): bool; +} + +class A implements I +{ + public function isA(): bool + { + return true; + } +} + +class B implements I +{ + public function isA(): bool + { + return false; + } +} + +function takesI(I $i): void +{ + if (!$i->isA()) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesIStrictComparison(I $i): void +{ + if ($i->isA() !== true) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesNullableI(?I $i): void +{ + if (!$i?->isA()) { + return; + } + + assertType('Bug12866\\A', $i); +} + +function takesNullableIStrictComparison(?I $i): void +{ + if ($i?->isA() !== true) { + return; + } + + assertType('Bug12866\\A', $i); +}