diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 99402f6a6f..bb7f76168a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -174,7 +174,7 @@ class MutatingScope implements Scope * @param array $currentlyAssignedExpressions * @param array $currentlyAllowedUndefinedExpressions * @param array $nativeExpressionTypes - * @param array $inFunctionCallsStack + * @param list $inFunctionCallsStack */ public function __construct( private InternalScopeFactory $scopeFactory, @@ -2493,6 +2493,11 @@ public function isInClassExists(string $className): bool return $this->getType($expr)->isTrue()->yes(); } + public function getFunctionCallStack(): array + { + return $this->inFunctionCallsStack; + } + /** @api */ public function isInFunctionExists(string $functionName): bool { diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index dc800b6833..d941d5c6a9 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -11,6 +11,7 @@ use PHPStan\Reflection\ConstantReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\FunctionReflection; +use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\NamespaceAnswerer; use PHPStan\Reflection\ParametersAcceptor; use PHPStan\Reflection\PropertyReflection; @@ -104,6 +105,9 @@ public function isInFunctionExists(string $functionName): bool; public function isInClosureBind(): bool; + /** @return list */ + public function getFunctionCallStack(): array; + public function isParameterValueNullable(Param $parameter): bool; /** diff --git a/tests/PHPStan/Rules/ScopeFunctionCallStackRule.php b/tests/PHPStan/Rules/ScopeFunctionCallStackRule.php new file mode 100644 index 0000000000..573602c8dd --- /dev/null +++ b/tests/PHPStan/Rules/ScopeFunctionCallStackRule.php @@ -0,0 +1,38 @@ + */ +class ScopeFunctionCallStackRule implements Rule +{ + + public function getNodeType(): string + { + return Throw_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $messages = []; + foreach ($scope->getFunctionCallStack() as $reflection) { + if ($reflection instanceof FunctionReflection) { + $messages[] = $reflection->getName(); + continue; + } + + $messages[] = sprintf('%s::%s', $reflection->getDeclaringClass()->getDisplayName(), $reflection->getName()); + } + + return [ + RuleErrorBuilder::message(implode("\n", $messages))->identifier('dummy')->build(), + ]; + } + +} diff --git a/tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php b/tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php new file mode 100644 index 0000000000..b7a1a53c23 --- /dev/null +++ b/tests/PHPStan/Rules/ScopeFunctionCallStackRuleTest.php @@ -0,0 +1,33 @@ + + */ +class ScopeFunctionCallStackRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ScopeFunctionCallStackRule(); + } + + public function testRule(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->analyse([__DIR__ . '/data/scope-function-call-stack.php'], [ + [ + "var_dump\nprint_r\nsleep", + 7, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/data/scope-function-call-stack.php b/tests/PHPStan/Rules/data/scope-function-call-stack.php new file mode 100644 index 0000000000..1b5d729ca9 --- /dev/null +++ b/tests/PHPStan/Rules/data/scope-function-call-stack.php @@ -0,0 +1,8 @@ += 8.0 + +namespace ScopeFunctionCallStack; + +function (): void +{ + var_dump(print_r(sleep(throw new \Exception()))); +};