From 74d1c9a1ca1cd6199a8771444e9bc4d6b7737f46 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 9 May 2023 15:03:38 +0200 Subject: [PATCH] Report `?->` call on always-null --- src/Analyser/NodeScopeResolver.php | 4 ++++ tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php | 8 ++++++++ tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php | 7 +++++++ .../PHPStan/Rules/Properties/AccessPropertiesRuleTest.php | 8 ++++++++ tests/PHPStan/Rules/Properties/data/bug-4559.php | 4 ++-- .../Rules/Properties/data/nullsafe-property-fetch.php | 7 +++++++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c64670a657..1d577ead2e 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1647,6 +1647,10 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo private function ensureShallowNonNullability(MutatingScope $scope, Scope $originalScope, Expr $exprToSpecify): EnsuredNonNullabilityResult { $exprType = $scope->getType($exprToSpecify); + $isNull = $exprType->isNull(); + if ($isNull->yes()) { + return new EnsuredNonNullabilityResult($scope, []); + } $exprTypeWithoutNull = TypeCombinator::removeNull($exprType); if ($exprType->equals($exprTypeWithoutNull)) { $originalExprType = $originalScope->getType($exprToSpecify); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index b81d2bd964..d90457661f 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -1742,6 +1742,14 @@ public function testNullSafe(): void 'Parameter #1 $passedByRef of method NullsafeMethodCall\Foo::doBaz() is passed by reference, so it expects variables only.', 27, ], + [ + 'Cannot call method foo() on null.', + 33, + ], + [ + 'Cannot call method foo() on null.', + 34, + ], ]); } diff --git a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php index dbe6ef395d..0eb89b8ae8 100644 --- a/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php +++ b/tests/PHPStan/Rules/Methods/data/nullsafe-method-call.php @@ -27,4 +27,11 @@ public function doLorem(?self $selfOrNull): void $this->doBaz($selfOrNull?->test->test); } + public function doNull(): void + { + $null = null; + $null->foo(); + $null?->foo(); + } + } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 28d38a1bcf..d2bfbbfbc2 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -487,6 +487,14 @@ public function testNullSafe(): void 'Cannot access property $bar on string.', 22, ], + [ + 'Cannot access property $foo on null.', + 28, + ], + [ + 'Cannot access property $foo on null.', + 29, + ], ]); } diff --git a/tests/PHPStan/Rules/Properties/data/bug-4559.php b/tests/PHPStan/Rules/Properties/data/bug-4559.php index 30136d761b..e3c0b6952f 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-4559.php +++ b/tests/PHPStan/Rules/Properties/data/bug-4559.php @@ -4,9 +4,9 @@ class HelloWorld { - public function doBar() + public function doBar(string $s) { - $response = json_decode(''); + $response = json_decode($s); if (isset($response->error->code)) { echo $response->error->message ?? ''; } diff --git a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php index 9c6bbb66c4..d2a693fe64 100644 --- a/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php +++ b/tests/PHPStan/Rules/Properties/data/nullsafe-property-fetch.php @@ -22,4 +22,11 @@ public function doBar(string $string, ?string $nullableString): void echo $nullableString?->bar ?? 4; } + public function doNull(): void + { + $null = null; + $null->foo; + $null?->foo; + } + }