Skip to content

Commit f5cc717

Browse files
committed
Indicates whether a variable is global
1 parent 5a39902 commit f5cc717

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

178178
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
179179

180+
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
181+
180182
/** @var Type[] */
181183
private array $resolvedTypes = [];
182184

@@ -584,10 +586,25 @@ public function afterOpenSslCall(string $openSslFunctionName): self
584586
);
585587
}
586588

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

@@ -628,7 +645,7 @@ public function getVariableType(string $variableName): Type
628645

629646
$varExprString = '$' . $variableName;
630647
if (!array_key_exists($varExprString, $this->expressionTypes)) {
631-
if ($this->isGlobalVariable($variableName)) {
648+
if ($this->isSuperglobalVariable($variableName)) {
632649
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
633650
}
634651
return new MixedType();
@@ -679,7 +696,7 @@ public function getMaybeDefinedVariables(): array
679696
return $variables;
680697
}
681698

682-
private function isGlobalVariable(string $variableName): bool
699+
private function isSuperglobalVariable(string $variableName): bool
683700
{
684701
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
685702
}
@@ -4168,9 +4185,13 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
41684185
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
41694186
}
41704187

4171-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
4188+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
41724189
{
41734190
$node = new Variable($variableName);
4191+
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4192+
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4193+
}
4194+
41744195
$scope = $this->assignExpression($node, $type, $nativeType);
41754196
if ($certainty->no()) {
41764197
throw new ShouldNotHappenException();
@@ -4945,7 +4966,7 @@ private function createConditionalExpressions(
49454966
private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
49464967
{
49474968
$intersectedVariableTypeHolders = [];
4948-
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
4969+
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isSuperglobalVariable($node->name);
49494970
$nodeFinder = new NodeFinder();
49504971
foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
49514972
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)