Skip to content

Commit b87e5c4

Browse files
committed
Scope - function call stack includes parameters too
1 parent 98a1037 commit b87e5c4

8 files changed

+114
-17
lines changed

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Reflection\FunctionReflection;
1111
use PHPStan\Reflection\InitializerExprTypeResolver;
1212
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ParameterReflection;
1314
use PHPStan\Reflection\ParametersAcceptor;
1415
use PHPStan\Reflection\ReflectionProvider;
1516
use PHPStan\Rules\Properties\PropertyReflectionFinder;
@@ -45,7 +46,7 @@ public function __construct(
4546
* @param array<string, ExpressionTypeHolder> $expressionTypes
4647
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
4748
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
48-
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
49+
* @param list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> $inFunctionCallsStack
4950
* @param array<string, true> $currentlyAssignedExpressions
5051
* @param array<string, true> $currentlyAllowedUndefinedExpressions
5152
*/

src/Analyser/InternalScopeFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Reflection\FunctionReflection;
66
use PHPStan\Reflection\MethodReflection;
7+
use PHPStan\Reflection\ParameterReflection;
78
use PHPStan\Reflection\ParametersAcceptor;
89

910
interface InternalScopeFactory
@@ -16,7 +17,7 @@ interface InternalScopeFactory
1617
* @param list<string> $inClosureBindScopeClasses
1718
* @param array<string, true> $currentlyAssignedExpressions
1819
* @param array<string, true> $currentlyAllowedUndefinedExpressions
19-
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
20+
* @param list<array{MethodReflection|FunctionReflection, ParameterReflection|null}> $inFunctionCallsStack
2021
*/
2122
public function create(
2223
ScopeContext $context,

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Reflection\FunctionReflection;
1111
use PHPStan\Reflection\InitializerExprTypeResolver;
1212
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ParameterReflection;
1314
use PHPStan\Reflection\ParametersAcceptor;
1415
use PHPStan\Reflection\ReflectionProvider;
1516
use PHPStan\Rules\Properties\PropertyReflectionFinder;
@@ -41,8 +42,7 @@ public function __construct(
4142
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
4243
* @param array<string, true> $currentlyAssignedExpressions
4344
* @param array<string, true> $currentlyAllowedUndefinedExpressions
44-
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
45-
*
45+
* @param list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> $inFunctionCallsStack
4646
*/
4747
public function create(
4848
ScopeContext $context,

src/Analyser/MutatingScope.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class MutatingScope implements Scope
174174
* @param array<string, true> $currentlyAssignedExpressions
175175
* @param array<string, true> $currentlyAllowedUndefinedExpressions
176176
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
177-
* @param list<MethodReflection|FunctionReflection> $inFunctionCallsStack
177+
* @param list<array{MethodReflection|FunctionReflection, ParameterReflection|null}> $inFunctionCallsStack
178178
*/
179179
public function __construct(
180180
private InternalScopeFactory $scopeFactory,
@@ -2410,10 +2410,10 @@ public function hasExpressionType(Expr $node): TrinaryLogic
24102410
/**
24112411
* @param MethodReflection|FunctionReflection $reflection
24122412
*/
2413-
public function pushInFunctionCall($reflection): self
2413+
public function pushInFunctionCall($reflection, ?ParameterReflection $parameter): self
24142414
{
24152415
$stack = $this->inFunctionCallsStack;
2416-
$stack[] = $reflection;
2416+
$stack[] = [$reflection, $parameter];
24172417

24182418
$scope = $this->scopeFactory->create(
24192419
$this->context,
@@ -2473,7 +2473,7 @@ public function popInFunctionCall(): self
24732473
/** @api */
24742474
public function isInClassExists(string $className): bool
24752475
{
2476-
foreach ($this->inFunctionCallsStack as $inFunctionCall) {
2476+
foreach ($this->inFunctionCallsStack as [$inFunctionCall]) {
24772477
if (!$inFunctionCall instanceof FunctionReflection) {
24782478
continue;
24792479
}
@@ -2494,6 +2494,11 @@ public function isInClassExists(string $className): bool
24942494
}
24952495

24962496
public function getFunctionCallStack(): array
2497+
{
2498+
return array_map(static fn ($values) => $values[0], $this->inFunctionCallsStack);
2499+
}
2500+
2501+
public function getFunctionCallStackWithParameters(): array
24972502
{
24982503
return $this->inFunctionCallsStack;
24992504
}

src/Analyser/NodeScopeResolver.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3633,22 +3633,21 @@ private function processArgs(
36333633
}
36343634
}
36353635

3636-
if ($calleeReflection !== null) {
3637-
$scope = $scope->pushInFunctionCall($calleeReflection);
3638-
}
3639-
36403636
$hasYield = false;
36413637
$throwPoints = [];
36423638
foreach ($args as $i => $arg) {
36433639
$assignByReference = false;
3640+
$parameter = null;
36443641
if (isset($parameters) && $parametersAcceptor !== null) {
36453642
if (isset($parameters[$i])) {
36463643
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
36473644
$parameterType = $parameters[$i]->getType();
3645+
$parameter = $parameters[$i];
36483646
} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) {
36493647
$lastParameter = $parameters[count($parameters) - 1];
36503648
$assignByReference = $lastParameter->passedByReference()->createsNewVariable();
36513649
$parameterType = $lastParameter->getType();
3650+
$parameter = $lastParameter;
36523651
}
36533652
}
36543653

@@ -3658,6 +3657,10 @@ private function processArgs(
36583657
}
36593658
}
36603659

3660+
if ($calleeReflection !== null) {
3661+
$scope = $scope->pushInFunctionCall($calleeReflection, $parameter);
3662+
}
3663+
36613664
$originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg;
36623665
$nodeCallback($originalArg, $scope);
36633666

@@ -3682,6 +3685,11 @@ private function processArgs(
36823685
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $arg->value);
36833686
}
36843687
}
3688+
3689+
if ($calleeReflection !== null) {
3690+
$scope = $scope->popInFunctionCall();
3691+
}
3692+
36853693
$hasYield = $hasYield || $result->hasYield();
36863694
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
36873695
if ($i !== 0 || $closureBindScope === null) {
@@ -3690,11 +3698,6 @@ private function processArgs(
36903698

36913699
$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope);
36923700
}
3693-
3694-
if ($calleeReflection !== null) {
3695-
$scope = $scope->popInFunctionCall();
3696-
}
3697-
36983701
foreach ($args as $i => $arg) {
36993702
if (!isset($parameters) || $parametersAcceptor === null) {
37003703
continue;

src/Analyser/Scope.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Reflection\FunctionReflection;
1414
use PHPStan\Reflection\MethodReflection;
1515
use PHPStan\Reflection\NamespaceAnswerer;
16+
use PHPStan\Reflection\ParameterReflection;
1617
use PHPStan\Reflection\ParametersAcceptor;
1718
use PHPStan\Reflection\PropertyReflection;
1819
use PHPStan\TrinaryLogic;
@@ -108,6 +109,9 @@ public function isInClosureBind(): bool;
108109
/** @return list<FunctionReflection|MethodReflection> */
109110
public function getFunctionCallStack(): array;
110111

112+
/** @return list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> */
113+
public function getFunctionCallStackWithParameters(): array;
114+
111115
public function isParameterValueNullable(Param $parameter): bool;
112116

113117
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\Throw_;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\ShouldNotHappenException;
10+
use function implode;
11+
use function sprintf;
12+
13+
/** @implements Rule<Throw_> */
14+
class ScopeFunctionCallStackWithParametersRule implements Rule
15+
{
16+
17+
public function getNodeType(): string
18+
{
19+
return Throw_::class;
20+
}
21+
22+
public function processNode(Node $node, Scope $scope): array
23+
{
24+
$messages = [];
25+
foreach ($scope->getFunctionCallStackWithParameters() as [$reflection, $parameter]) {
26+
if ($parameter === null) {
27+
throw new ShouldNotHappenException();
28+
}
29+
if ($reflection instanceof FunctionReflection) {
30+
$messages[] = sprintf('%s ($%s)', $reflection->getName(), $parameter->getName());
31+
continue;
32+
}
33+
34+
$messages[] = sprintf('%s::%s ($%s)', $reflection->getDeclaringClass()->getDisplayName(), $reflection->getName(), $parameter->getName());
35+
}
36+
37+
return [
38+
RuleErrorBuilder::message(implode("\n", $messages))->identifier('dummy')->build(),
39+
];
40+
}
41+
42+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules;
4+
5+
use PHPStan\Testing\RuleTestCase;
6+
use const PHP_VERSION_ID;
7+
8+
/**
9+
* @extends RuleTestCase<ScopeFunctionCallStackWithParametersRule>
10+
*/
11+
class ScopeFunctionCallStackWithParametersRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new ScopeFunctionCallStackWithParametersRule();
17+
}
18+
19+
public function testRule(): void
20+
{
21+
if (PHP_VERSION_ID < 80000) {
22+
$this->markTestSkipped('Test requires PHP 8.0.');
23+
}
24+
25+
$this->analyse([__DIR__ . '/data/scope-function-call-stack.php'], [
26+
[
27+
"var_dump (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
28+
7,
29+
],
30+
[
31+
"var_dump (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
32+
10,
33+
],
34+
[
35+
"var_dump (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
36+
13,
37+
],
38+
]);
39+
}
40+
41+
}

0 commit comments

Comments
 (0)