Skip to content

Commit 0731c47

Browse files
committed
Indicates whether a variable is global
1 parent 33359be commit 0731c47

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

180180
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
181181

182+
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
183+
182184
/** @var Type[] */
183185
private array $resolvedTypes = [];
184186

@@ -609,10 +611,25 @@ public function afterOpenSslCall(string $openSslFunctionName): self
609611
);
610612
}
611613

614+
/** @api */
615+
public function isGlobalVariable(string $variableName): bool
616+
{
617+
if ($this->isSuperglobalVariable($variableName)) {
618+
return true;
619+
}
620+
621+
$varExprString = '$' . $variableName;
622+
if (!isset($this->expressionTypes[$varExprString])) {
623+
return false;
624+
}
625+
626+
return $this->expressionTypes[$varExprString]->getExpr()->getAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME) === true;
627+
}
628+
612629
/** @api */
613630
public function hasVariableType(string $variableName): TrinaryLogic
614631
{
615-
if ($this->isGlobalVariable($variableName)) {
632+
if ($this->isSuperglobalVariable($variableName)) {
616633
return TrinaryLogic::createYes();
617634
}
618635

@@ -653,7 +670,7 @@ public function getVariableType(string $variableName): Type
653670

654671
$varExprString = '$' . $variableName;
655672
if (!array_key_exists($varExprString, $this->expressionTypes)) {
656-
if ($this->isGlobalVariable($variableName)) {
673+
if ($this->isSuperglobalVariable($variableName)) {
657674
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
658675
}
659676
return new MixedType();
@@ -704,7 +721,7 @@ public function getMaybeDefinedVariables(): array
704721
return $variables;
705722
}
706723

707-
private function isGlobalVariable(string $variableName): bool
724+
private function isSuperglobalVariable(string $variableName): bool
708725
{
709726
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
710727
}
@@ -4204,9 +4221,13 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
42044221
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
42054222
}
42064223

4207-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
4224+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
42084225
{
42094226
$node = new Variable($variableName);
4227+
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4228+
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4229+
}
4230+
42104231
$scope = $this->assignExpression($node, $type, $nativeType);
42114232
if ($certainty->no()) {
42124233
throw new ShouldNotHappenException();
@@ -4979,7 +5000,7 @@ private function createConditionalExpressions(
49795000
private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
49805001
{
49815002
$intersectedVariableTypeHolders = [];
4982-
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
5003+
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isSuperglobalVariable($node->name);
49835004
$nodeFinder = new NodeFinder();
49845005
foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
49855006
if (isset($theirVariableTypeHolders[$exprString])) {

src/Analyser/NodeScopeResolver.php

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

1979-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
1979+
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
19801980
$vars[] = $var->name;
19811981
}
19821982
$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)