diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 1cacff0192..694ba46891 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2213,7 +2213,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); } elseif ($expr instanceof Expr\NullsafeMethodCall) { $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); - $exprResult = $this->processExprNode(new MethodCall($expr->var, $expr->name, $expr->args, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $exprResult = $this->processExprNode(new MethodCall($expr->var, $expr->name, $expr->args, array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); return new ExpressionResult( @@ -2368,7 +2368,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression } } elseif ($expr instanceof Expr\NullsafePropertyFetch) { $nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var); - $exprResult = $this->processExprNode(new PropertyFetch($expr->var, $expr->name, $expr->getAttributes()), $nonNullabilityResult->getScope(), $nodeCallback, $context); + $exprResult = $this->processExprNode(new PropertyFetch($expr->var, $expr->name, array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true])), $nonNullabilityResult->getScope(), $nodeCallback, $context); $scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions()); return new ExpressionResult( diff --git a/tests/PHPStan/Rules/Methods/VirtualNullsafeMethodCallTest.php b/tests/PHPStan/Rules/Methods/VirtualNullsafeMethodCallTest.php new file mode 100644 index 0000000000..ae4499167e --- /dev/null +++ b/tests/PHPStan/Rules/Methods/VirtualNullsafeMethodCallTest.php @@ -0,0 +1,56 @@ + + */ +class VirtualNullsafeMethodCallTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new /** @implements Rule */ class implements Rule { + + public function getNodeType(): string + { + return MethodCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getAttribute('virtualNullsafeMethodCall') === true) { + return [RuleErrorBuilder::message('Nullable method call detected')->identifier('')->build()]; + } + + return [RuleErrorBuilder::message('Regular method call detected')->identifier('')->build()]; + } + + }; + } + + public function testAttribute(): void + { + $this->analyse([ __DIR__ . '/data/virtual-nullsafe-method-call.php'], [ + [ + 'Regular method call detected', + 3, + ], + [ + 'Nullable method call detected', + 4, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Methods/data/virtual-nullsafe-method-call.php b/tests/PHPStan/Rules/Methods/data/virtual-nullsafe-method-call.php new file mode 100644 index 0000000000..e5cd99d7a4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/virtual-nullsafe-method-call.php @@ -0,0 +1,4 @@ += 8.0 + +$foo->regularCall(); +$foo?->nullsafeCall(); diff --git a/tests/PHPStan/Rules/Properties/VirtualNullsafePropertyFetchTest.php b/tests/PHPStan/Rules/Properties/VirtualNullsafePropertyFetchTest.php new file mode 100644 index 0000000000..477eafc4d5 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/VirtualNullsafePropertyFetchTest.php @@ -0,0 +1,56 @@ + + */ +class VirtualNullsafePropertyFetchTest extends RuleTestCase +{ + + /** + * @return Rule + */ + protected function getRule(): Rule + { + return new /** @implements Rule */ class implements Rule { + + public function getNodeType(): string + { + return PropertyFetch::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ($node->getAttribute('virtualNullsafePropertyFetch') === true) { + return [RuleErrorBuilder::message('Nullable property fetch detected')->identifier('')->build()]; + } + + return [RuleErrorBuilder::message('Regular property fetch detected')->identifier('')->build()]; + } + + }; + } + + public function testAttribute(): void + { + $this->analyse([ __DIR__ . '/data/virtual-nullsafe-property-fetch.php'], [ + [ + 'Regular property fetch detected', + 3, + ], + [ + 'Nullable property fetch detected', + 4, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Properties/data/virtual-nullsafe-property-fetch.php b/tests/PHPStan/Rules/Properties/data/virtual-nullsafe-property-fetch.php new file mode 100644 index 0000000000..a06b89d8b1 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/virtual-nullsafe-property-fetch.php @@ -0,0 +1,4 @@ += 8.0 + +$foo->regularFetch; +$foo?->nullsafeFetch;