Skip to content

Commit d995be1

Browse files
committed
Indicates whether a variable is global
1 parent 7cb0ec7 commit d995be1

File tree

7 files changed

+117
-6
lines changed

7 files changed

+117
-6
lines changed

src/Analyser/MutatingScope.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ final class MutatingScope implements Scope, NodeCallbackInvoker
182182

183183
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
184184

185+
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
186+
185187
/** @var Type[] */
186188
private array $resolvedTypes = [];
187189

@@ -619,10 +621,25 @@ public function afterOpenSslCall(string $openSslFunctionName): self
619621
);
620622
}
621623

624+
/** @api */
625+
public function isGlobalVariable(string $variableName): bool
626+
{
627+
if ($this->isSuperglobalVariable($variableName)) {
628+
return true;
629+
}
630+
631+
$varExprString = '$' . $variableName;
632+
if (!isset($this->expressionTypes[$varExprString])) {
633+
return false;
634+
}
635+
636+
return $this->expressionTypes[$varExprString]->getExpr()->getAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME) === true;
637+
}
638+
622639
/** @api */
623640
public function hasVariableType(string $variableName): TrinaryLogic
624641
{
625-
if ($this->isGlobalVariable($variableName)) {
642+
if ($this->isSuperglobalVariable($variableName)) {
626643
return TrinaryLogic::createYes();
627644
}
628645

@@ -663,7 +680,7 @@ public function getVariableType(string $variableName): Type
663680

664681
$varExprString = '$' . $variableName;
665682
if (!array_key_exists($varExprString, $this->expressionTypes)) {
666-
if ($this->isGlobalVariable($variableName)) {
683+
if ($this->isSuperglobalVariable($variableName)) {
667684
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
668685
}
669686
return new MixedType();
@@ -714,7 +731,7 @@ public function getMaybeDefinedVariables(): array
714731
return $variables;
715732
}
716733

717-
private function isGlobalVariable(string $variableName): bool
734+
private function isSuperglobalVariable(string $variableName): bool
718735
{
719736
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
720737
}
@@ -4292,9 +4309,13 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
42924309
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
42934310
}
42944311

4295-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
4312+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
42964313
{
42974314
$node = new Variable($variableName);
4315+
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4316+
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4317+
}
4318+
42984319
$scope = $this->assignExpression($node, $type, $nativeType);
42994320
if ($certainty->no()) {
43004321
throw new ShouldNotHappenException();
@@ -5079,7 +5100,7 @@ private function createConditionalExpressions(
50795100
private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
50805101
{
50815102
$intersectedVariableTypeHolders = [];
5082-
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
5103+
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isSuperglobalVariable($node->name);
50835104
$nodeFinder = new NodeFinder();
50845105
foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
50855106
if (isset($theirVariableTypeHolders[$exprString])) {

src/Analyser/NodeScopeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1978,7 +1978,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch
19781978
continue;
19791979
}
19801980

1981-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
1981+
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
19821982
$vars[] = $var->name;
19831983
}
19841984
$scope = $this->processVarAnnotation($scope, $vars, $stmt);

src/Analyser/Scope.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public function getFunctionName(): ?string;
5757

5858
public function getParentScope(): ?self;
5959

60+
public function isGlobalVariable(string $variableName): bool;
61+
6062
public function hasVariableType(string $variableName): TrinaryLogic;
6163

6264
public function getVariableType(string $variableName): Type;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\Return_;
7+
use PHPStan\Testing\TypeInferenceTestCase;
8+
9+
class GlobalVariableTest extends TypeInferenceTestCase
10+
{
11+
12+
public function testGlobalVariableInScript(): void
13+
{
14+
self::processFile(__DIR__ . '/data/global-in-script.php', function (Node $node, Scope $scope): void {
15+
if (!($node instanceof Return_)) {
16+
return;
17+
}
18+
19+
$this->assertTrue($scope->isGlobalVariable('FOO'));
20+
$this->assertFalse($scope->isGlobalVariable('whatever'));
21+
});
22+
}
23+
24+
public function testGlobalVariableInFunction(): void
25+
{
26+
self::processFile(__DIR__ . '/data/global-in-function.php', function (Node $node, Scope $scope): void {
27+
if (!($node instanceof Return_)) {
28+
return;
29+
}
30+
31+
$this->assertFalse($scope->isGlobalVariable('BAR'));
32+
$this->assertTrue($scope->isGlobalVariable('CONFIG'));
33+
$this->assertFalse($scope->isGlobalVariable('localVar'));
34+
});
35+
}
36+
37+
public function testGlobalVariableInClassMethod(): void
38+
{
39+
self::processFile(__DIR__ . '/data/global-in-class-method.php', function (Node $node, Scope $scope): void {
40+
if (!($node instanceof Return_)) {
41+
return;
42+
}
43+
44+
$this->assertFalse($scope->isGlobalVariable('count'));
45+
$this->assertTrue($scope->isGlobalVariable('GLB_A'));
46+
$this->assertTrue($scope->isGlobalVariable('GLB_B'));
47+
$this->assertFalse($scope->isGlobalVariable('key'));
48+
$this->assertFalse($scope->isGlobalVariable('step'));
49+
});
50+
}
51+
52+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
class ClassForGlobalTest
4+
{
5+
6+
public function doSomething(int $count = 3): bool
7+
{
8+
global $GLB_A, $GLB_B;
9+
10+
foreach ([1, 2, 3] as $key => $step) {
11+
break;
12+
}
13+
14+
return false;
15+
}
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
global $BAR;
4+
5+
function globalTest(string $BAR): void
6+
{
7+
global $CONFIG;
8+
9+
$localVar = true;
10+
11+
return;
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
global $FOO;
4+
5+
$FOO = "bar";
6+
$whatever = 15;
7+
8+
return;

0 commit comments

Comments
 (0)