diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 8c6aaa03ce3..3caf7cf44cd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -2085,6 +2085,18 @@ private static function getNullInequalityAssertions( $source, ); + + if ($base_conditional instanceof PhpParser\Node\Expr\NullsafeMethodCall) { + $vvar_name = ExpressionIdentifier::getExtendedVarId( + $base_conditional->var, + $this_class_name, + $source, + ); + if ($vvar_name !== null) { + $if_types[$vvar_name] = [[new IsNotType(new TNull())]]; + } + } + if ($var_name) { if ($base_conditional instanceof PhpParser\Node\Expr\Assign) { $var_name = '=' . $var_name; @@ -2801,6 +2813,17 @@ private static function getNullEqualityAssertions( throw new UnexpectedValueException('$null_position value'); } + if ($base_conditional instanceof PhpParser\Node\Expr\NullsafeMethodCall) { + $vvar_name = ExpressionIdentifier::getExtendedVarId( + $base_conditional->var, + $this_class_name, + $source, + ); + if ($vvar_name !== null) { + $if_types[$vvar_name] = [[new IsNotType(new TNull())]]; + } + } + $var_name = ExpressionIdentifier::getExtendedVarId( $base_conditional, $this_class_name, diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index fa56564e790..1d888370947 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -196,6 +196,60 @@ function printInt(int $int): void { public function providerValidCodeParse(): iterable { return [ + 'nullSafeCallNotNullMakesVarNotNull' => [ + 'code' => 'check() === null) { + /** @psalm-check-type-exact $foo = null */ + } + + /** @var ?Foo */ + $foo = null; + + if ($foo?->check() !== true) { + /** @psalm-check-type-exact $foo = null */ + } + + /** @var ?Foo */ + $foo = null; + + if ($foo?->check() !== false) { + /** @psalm-check-type-exact $foo = null */ + } + + /** @var ?Foo */ + $foo = null; + + if ($foo?->check() !== null) { + /** @psalm-check-type-exact $foo = Foo */ + } + + /** @var ?Foo */ + $foo = null; + + if ($foo?->check() === false) { + /** @psalm-check-type-exact $foo = Foo */ + } + + /** @var ?Foo */ + $foo = null; + + if ($foo?->check() === true) { + /** @psalm-check-type-exact $foo = Foo */ + }', + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.1' + ], 'notInCallMapTest' => [ 'code' => '