Skip to content

Commit

Permalink
Scope - function call stack includes parameters too
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 10, 2024
1 parent 98a1037 commit b87e5c4
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/Analyser/DirectInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand Down Expand Up @@ -45,7 +46,7 @@ public function __construct(
* @param array<string, ExpressionTypeHolder> $expressionTypes
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
* @param list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> $inFunctionCallsStack
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, true> $currentlyAllowedUndefinedExpressions
*/
Expand Down
3 changes: 2 additions & 1 deletion src/Analyser/InternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptor;

interface InternalScopeFactory
Expand All @@ -16,7 +17,7 @@ interface InternalScopeFactory
* @param list<string> $inClosureBindScopeClasses
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, true> $currentlyAllowedUndefinedExpressions
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
* @param list<array{MethodReflection|FunctionReflection, ParameterReflection|null}> $inFunctionCallsStack
*/
public function create(
ScopeContext $context,
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/LazyInternalScopeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand Down Expand Up @@ -41,8 +42,7 @@ public function __construct(
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, true> $currentlyAllowedUndefinedExpressions
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
*
* @param list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> $inFunctionCallsStack
*/
public function create(
ScopeContext $context,
Expand Down
13 changes: 9 additions & 4 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class MutatingScope implements Scope
* @param array<string, true> $currentlyAssignedExpressions
* @param array<string, true> $currentlyAllowedUndefinedExpressions
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
* @param list<MethodReflection|FunctionReflection> $inFunctionCallsStack
* @param list<array{MethodReflection|FunctionReflection, ParameterReflection|null}> $inFunctionCallsStack
*/
public function __construct(
private InternalScopeFactory $scopeFactory,
Expand Down Expand Up @@ -2410,10 +2410,10 @@ public function hasExpressionType(Expr $node): TrinaryLogic
/**
* @param MethodReflection|FunctionReflection $reflection
*/
public function pushInFunctionCall($reflection): self
public function pushInFunctionCall($reflection, ?ParameterReflection $parameter): self
{
$stack = $this->inFunctionCallsStack;
$stack[] = $reflection;
$stack[] = [$reflection, $parameter];

$scope = $this->scopeFactory->create(
$this->context,
Expand Down Expand Up @@ -2473,7 +2473,7 @@ public function popInFunctionCall(): self
/** @api */
public function isInClassExists(string $className): bool
{
foreach ($this->inFunctionCallsStack as $inFunctionCall) {
foreach ($this->inFunctionCallsStack as [$inFunctionCall]) {
if (!$inFunctionCall instanceof FunctionReflection) {
continue;
}
Expand All @@ -2494,6 +2494,11 @@ public function isInClassExists(string $className): bool
}

public function getFunctionCallStack(): array
{
return array_map(static fn ($values) => $values[0], $this->inFunctionCallsStack);
}

public function getFunctionCallStackWithParameters(): array
{
return $this->inFunctionCallsStack;
}
Expand Down
21 changes: 12 additions & 9 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -3633,22 +3633,21 @@ private function processArgs(
}
}

if ($calleeReflection !== null) {
$scope = $scope->pushInFunctionCall($calleeReflection);
}

$hasYield = false;
$throwPoints = [];
foreach ($args as $i => $arg) {
$assignByReference = false;
$parameter = null;
if (isset($parameters) && $parametersAcceptor !== null) {
if (isset($parameters[$i])) {
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
$parameterType = $parameters[$i]->getType();
$parameter = $parameters[$i];
} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) {
$lastParameter = $parameters[count($parameters) - 1];
$assignByReference = $lastParameter->passedByReference()->createsNewVariable();
$parameterType = $lastParameter->getType();
$parameter = $lastParameter;
}
}

Expand All @@ -3658,6 +3657,10 @@ private function processArgs(
}
}

if ($calleeReflection !== null) {
$scope = $scope->pushInFunctionCall($calleeReflection, $parameter);
}

$originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg;
$nodeCallback($originalArg, $scope);

Expand All @@ -3682,6 +3685,11 @@ private function processArgs(
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $arg->value);
}
}

if ($calleeReflection !== null) {
$scope = $scope->popInFunctionCall();
}

$hasYield = $hasYield || $result->hasYield();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
if ($i !== 0 || $closureBindScope === null) {
Expand All @@ -3690,11 +3698,6 @@ private function processArgs(

$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope);
}

if ($calleeReflection !== null) {
$scope = $scope->popInFunctionCall();
}

foreach ($args as $i => $arg) {
if (!isset($parameters) || $parametersAcceptor === null) {
continue;
Expand Down
4 changes: 4 additions & 0 deletions src/Analyser/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\NamespaceAnswerer;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\TrinaryLogic;
Expand Down Expand Up @@ -108,6 +109,9 @@ public function isInClosureBind(): bool;
/** @return list<FunctionReflection|MethodReflection> */
public function getFunctionCallStack(): array;

/** @return list<array{FunctionReflection|MethodReflection, ParameterReflection|null}> */
public function getFunctionCallStackWithParameters(): array;

public function isParameterValueNullable(Param $parameter): bool;

/**
Expand Down
42 changes: 42 additions & 0 deletions tests/PHPStan/Rules/ScopeFunctionCallStackWithParametersRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules;

use PhpParser\Node;
use PhpParser\Node\Expr\Throw_;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\ShouldNotHappenException;
use function implode;
use function sprintf;

/** @implements Rule<Throw_> */
class ScopeFunctionCallStackWithParametersRule implements Rule
{

public function getNodeType(): string
{
return Throw_::class;
}

public function processNode(Node $node, Scope $scope): array
{
$messages = [];
foreach ($scope->getFunctionCallStackWithParameters() as [$reflection, $parameter]) {
if ($parameter === null) {
throw new ShouldNotHappenException();
}
if ($reflection instanceof FunctionReflection) {
$messages[] = sprintf('%s ($%s)', $reflection->getName(), $parameter->getName());
continue;
}

$messages[] = sprintf('%s::%s ($%s)', $reflection->getDeclaringClass()->getDisplayName(), $reflection->getName(), $parameter->getName());
}

return [
RuleErrorBuilder::message(implode("\n", $messages))->identifier('dummy')->build(),
];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules;

use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;

/**
* @extends RuleTestCase<ScopeFunctionCallStackWithParametersRule>
*/
class ScopeFunctionCallStackWithParametersRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new ScopeFunctionCallStackWithParametersRule();
}

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 (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
7,
],
[
"var_dump (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
10,
],
[
"var_dump (\$value)\nprint_r (\$value)\nsleep (\$seconds)",
13,
],
]);
}

}

0 comments on commit b87e5c4

Please sign in to comment.