Skip to content

Commit 4717d70

Browse files
committed
Indicates whether a variable is global
1 parent dba6c24 commit 4717d70

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
@@ -175,6 +175,8 @@ final class MutatingScope implements Scope
175175

176176
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
177177

178+
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
179+
178180
/** @var Type[] */
179181
private array $resolvedTypes = [];
180182

@@ -582,10 +584,25 @@ public function afterOpenSslCall(string $openSslFunctionName): self
582584
);
583585
}
584586

587+
/** @api */
588+
public function isGlobalVariable(string $variableName): bool
589+
{
590+
if ($this->isSuperglobalVariable($variableName)) {
591+
return true;
592+
}
593+
594+
$varExprString = '$' . $variableName;
595+
if (!isset($this->expressionTypes[$varExprString])) {
596+
return false;
597+
}
598+
599+
return $this->expressionTypes[$varExprString]->getExpr()->getAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME) === true;
600+
}
601+
585602
/** @api */
586603
public function hasVariableType(string $variableName): TrinaryLogic
587604
{
588-
if ($this->isGlobalVariable($variableName)) {
605+
if ($this->isSuperglobalVariable($variableName)) {
589606
return TrinaryLogic::createYes();
590607
}
591608

@@ -626,7 +643,7 @@ public function getVariableType(string $variableName): Type
626643

627644
$varExprString = '$' . $variableName;
628645
if (!array_key_exists($varExprString, $this->expressionTypes)) {
629-
if ($this->isGlobalVariable($variableName)) {
646+
if ($this->isSuperglobalVariable($variableName)) {
630647
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
631648
}
632649
return new MixedType();
@@ -677,7 +694,7 @@ public function getMaybeDefinedVariables(): array
677694
return $variables;
678695
}
679696

680-
private function isGlobalVariable(string $variableName): bool
697+
private function isSuperglobalVariable(string $variableName): bool
681698
{
682699
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
683700
}
@@ -4152,9 +4169,13 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
41524169
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
41534170
}
41544171

4155-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
4172+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
41564173
{
41574174
$node = new Variable($variableName);
4175+
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4176+
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4177+
}
4178+
41584179
$scope = $this->assignExpression($node, $type, $nativeType);
41594180
if ($certainty->no()) {
41604181
throw new ShouldNotHappenException();
@@ -4929,7 +4950,7 @@ private function createConditionalExpressions(
49294950
private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
49304951
{
49314952
$intersectedVariableTypeHolders = [];
4932-
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
4953+
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isSuperglobalVariable($node->name);
49334954
$nodeFinder = new NodeFinder();
49344955
foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
49354956
if (isset($theirVariableTypeHolders[$exprString])) {

src/Analyser/NodeScopeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1965,7 +1965,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
19651965
continue;
19661966
}
19671967

1968-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
1968+
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
19691969
$vars[] = $var->name;
19701970
}
19711971
$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)