Skip to content

Commit 8793f30

Browse files
committed
Add ability to check if a variable is global in scope
1 parent 9d81092 commit 8793f30

8 files changed

+93
-8
lines changed

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public function create(
5656
bool $afterExtractCall = false,
5757
?Scope $parentScope = null,
5858
bool $nativeTypesPromoted = false,
59+
array $globalVariables = [],
5960
): MutatingScope
6061
{
6162
return new MutatingScope(
@@ -90,6 +91,7 @@ public function create(
9091
$afterExtractCall,
9192
$parentScope,
9293
$nativeTypesPromoted,
94+
$globalVariables,
9395
);
9496
}
9597

src/Analyser/InternalScopeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface InternalScopeFactory
1919
* @param array<string, true> $currentlyAssignedExpressions
2020
* @param array<string, true> $currentlyAllowedUndefinedExpressions
2121
* @param list<array{FunctionReflection|MethodReflection|null, ParameterReflection|null}> $inFunctionCallsStack
22+
* @param list<string> $globalVariables
2223
*/
2324
public function create(
2425
ScopeContext $context,
@@ -37,6 +38,7 @@ public function create(
3738
bool $afterExtractCall = false,
3839
?Scope $parentScope = null,
3940
bool $nativeTypesPromoted = false,
41+
array $globalVariables = [],
4042
): MutatingScope;
4143

4244
}

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function create(
4646
bool $afterExtractCall = false,
4747
?Scope $parentScope = null,
4848
bool $nativeTypesPromoted = false,
49+
array $globalVariables = [],
4950
): MutatingScope
5051
{
5152
return new MutatingScope(
@@ -80,6 +81,7 @@ public function create(
8081
$afterExtractCall,
8182
$parentScope,
8283
$nativeTypesPromoted,
84+
$globalVariables,
8385
);
8486
}
8587

src/Analyser/MutatingScope.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ final class MutatingScope implements Scope
202202
* @param array<string, true> $currentlyAllowedUndefinedExpressions
203203
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
204204
* @param list<array{MethodReflection|FunctionReflection|null, ParameterReflection|null}> $inFunctionCallsStack
205+
* @param list<string> $globalVariables
205206
*/
206207
public function __construct(
207208
private InternalScopeFactory $scopeFactory,
@@ -235,6 +236,7 @@ public function __construct(
235236
private bool $afterExtractCall = false,
236237
private ?Scope $parentScope = null,
237238
private bool $nativeTypesPromoted = false,
239+
private array $globalVariables = [],
238240
)
239241
{
240242
if ($namespace === '') {
@@ -363,6 +365,7 @@ public function rememberConstructorScope(): self
363365
$this->afterExtractCall,
364366
$this->parentScope,
365367
$this->nativeTypesPromoted,
368+
$this->globalVariables,
366369
);
367370
}
368371

@@ -441,6 +444,7 @@ public function afterExtractCall(): self
441444
true,
442445
$this->parentScope,
443446
$this->nativeTypesPromoted,
447+
$this->globalVariables,
444448
);
445449
}
446450

@@ -497,6 +501,7 @@ public function afterClearstatcacheCall(): self
497501
$this->afterExtractCall,
498502
$this->parentScope,
499503
$this->nativeTypesPromoted,
504+
$this->globalVariables,
500505
);
501506
}
502507

@@ -579,13 +584,14 @@ public function afterOpenSslCall(string $openSslFunctionName): self
579584
$this->afterExtractCall,
580585
$this->parentScope,
581586
$this->nativeTypesPromoted,
587+
$this->globalVariables,
582588
);
583589
}
584590

585591
/** @api */
586592
public function hasVariableType(string $variableName): TrinaryLogic
587593
{
588-
if ($this->isGlobalVariable($variableName)) {
594+
if ($this->isSuperGlobalVariable($variableName)) {
589595
return TrinaryLogic::createYes();
590596
}
591597

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

627633
$varExprString = '$' . $variableName;
628634
if (!array_key_exists($varExprString, $this->expressionTypes)) {
629-
if ($this->isGlobalVariable($variableName)) {
635+
if ($this->isSuperGlobalVariable($variableName)) {
630636
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
631637
}
632638
return new MixedType();
@@ -635,6 +641,18 @@ public function getVariableType(string $variableName): Type
635641
return TypeUtils::resolveLateResolvableTypes($this->expressionTypes[$varExprString]->getType());
636642
}
637643

644+
public function setVariableAsGlobal(string $variableName): self
645+
{
646+
$this->globalVariables[] = $variableName;
647+
648+
return $this;
649+
}
650+
651+
public function isGlobalVariable(string $variableName): bool
652+
{
653+
return in_array($variableName, $this->globalVariables, true);
654+
}
655+
638656
/**
639657
* @api
640658
* @return list<string>
@@ -677,7 +695,7 @@ public function getMaybeDefinedVariables(): array
677695
return $variables;
678696
}
679697

680-
private function isGlobalVariable(string $variableName): bool
698+
private function isSuperGlobalVariable(string $variableName): bool
681699
{
682700
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
683701
}
@@ -2762,6 +2780,7 @@ private function promoteNativeTypes(): self
27622780
$this->afterExtractCall,
27632781
$this->parentScope,
27642782
true,
2783+
$this->globalVariables,
27652784
);
27662785
}
27672786

@@ -2956,6 +2975,7 @@ public function pushInFunctionCall($reflection, ?ParameterReflection $parameter)
29562975
$this->afterExtractCall,
29572976
$this->parentScope,
29582977
$this->nativeTypesPromoted,
2978+
$this->globalVariables,
29592979
);
29602980
}
29612981

@@ -2981,6 +3001,7 @@ public function popInFunctionCall(): self
29813001
$this->afterExtractCall,
29823002
$this->parentScope,
29833003
$this->nativeTypesPromoted,
3004+
$this->globalVariables,
29843005
);
29853006
}
29863007

@@ -3572,6 +3593,7 @@ public function restoreThis(self $restoreThisScope): self
35723593
$this->afterExtractCall,
35733594
$this->parentScope,
35743595
$this->nativeTypesPromoted,
3596+
$this->globalVariables,
35753597
);
35763598
}
35773599

@@ -3635,6 +3657,7 @@ public function enterAnonymousFunction(
36353657
false,
36363658
$this,
36373659
$this->nativeTypesPromoted,
3660+
[],
36383661
);
36393662
}
36403663

@@ -3743,6 +3766,7 @@ private function enterAnonymousFunctionWithoutReflection(
37433766
false,
37443767
$this,
37453768
$this->nativeTypesPromoted,
3769+
[],
37463770
);
37473771
}
37483772

@@ -3811,6 +3835,7 @@ public function enterArrowFunction(Expr\ArrowFunction $arrowFunction, ?array $ca
38113835
$scope->afterExtractCall,
38123836
$scope->parentScope,
38133837
$this->nativeTypesPromoted,
3838+
[],
38143839
);
38153840
}
38163841

@@ -3870,6 +3895,7 @@ private function enterArrowFunctionWithoutReflection(Expr\ArrowFunction $arrowFu
38703895
$arrowFunctionScope->afterExtractCall,
38713896
$arrowFunctionScope->parentScope,
38723897
$this->nativeTypesPromoted,
3898+
[],
38733899
);
38743900
}
38753901

@@ -4033,6 +4059,7 @@ public function enterExpressionAssign(Expr $expr): self
40334059
$this->afterExtractCall,
40344060
$this->parentScope,
40354061
$this->nativeTypesPromoted,
4062+
$this->globalVariables,
40364063
);
40374064
$scope->resolvedTypes = $this->resolvedTypes;
40384065
$scope->truthyScopes = $this->truthyScopes;
@@ -4064,6 +4091,7 @@ public function exitExpressionAssign(Expr $expr): self
40644091
$this->afterExtractCall,
40654092
$this->parentScope,
40664093
$this->nativeTypesPromoted,
4094+
$this->globalVariables,
40674095
);
40684096
$scope->resolvedTypes = $this->resolvedTypes;
40694097
$scope->truthyScopes = $this->truthyScopes;
@@ -4106,6 +4134,7 @@ public function setAllowedUndefinedExpression(Expr $expr): self
41064134
$this->afterExtractCall,
41074135
$this->parentScope,
41084136
$this->nativeTypesPromoted,
4137+
$this->globalVariables,
41094138
);
41104139
$scope->resolvedTypes = $this->resolvedTypes;
41114140
$scope->truthyScopes = $this->truthyScopes;
@@ -4137,6 +4166,7 @@ public function unsetAllowedUndefinedExpression(Expr $expr): self
41374166
$this->afterExtractCall,
41384167
$this->parentScope,
41394168
$this->nativeTypesPromoted,
4169+
$this->globalVariables,
41404170
);
41414171
$scope->resolvedTypes = $this->resolvedTypes;
41424172
$scope->truthyScopes = $this->truthyScopes;
@@ -4284,6 +4314,7 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
42844314
$this->afterExtractCall,
42854315
$this->parentScope,
42864316
$this->nativeTypesPromoted,
4317+
$this->globalVariables,
42874318
);
42884319

42894320
if ($expr instanceof AlwaysRememberedExpr) {
@@ -4393,6 +4424,7 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
43934424
$this->afterExtractCall,
43944425
$this->parentScope,
43954426
$this->nativeTypesPromoted,
4427+
$this->globalVariables,
43964428
);
43974429
}
43984430

@@ -4493,6 +4525,7 @@ private function invalidateMethodsOnExpression(Expr $expressionToInvalidate): se
44934525
$this->afterExtractCall,
44944526
$this->parentScope,
44954527
$this->nativeTypesPromoted,
4528+
$this->globalVariables,
44964529
);
44974530
}
44984531

@@ -4705,6 +4738,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
47054738
$scope->afterExtractCall,
47064739
$scope->parentScope,
47074740
$scope->nativeTypesPromoted,
4741+
$this->globalVariables,
47084742
);
47094743
}
47104744

@@ -4732,6 +4766,7 @@ public function addConditionalExpressions(string $exprString, array $conditional
47324766
$this->afterExtractCall,
47334767
$this->parentScope,
47344768
$this->nativeTypesPromoted,
4769+
$this->globalVariables,
47354770
);
47364771
}
47374772

@@ -4762,6 +4797,7 @@ public function exitFirstLevelStatements(): self
47624797
$this->afterExtractCall,
47634798
$this->parentScope,
47644799
$this->nativeTypesPromoted,
4800+
$this->globalVariables,
47654801
);
47664802
$scope->resolvedTypes = $this->resolvedTypes;
47674803
$scope->truthyScopes = $this->truthyScopes;
@@ -4799,6 +4835,9 @@ public function mergeWith(?self $otherScope): self
47994835
$ourExpressionTypes,
48004836
$mergedExpressionTypes,
48014837
);
4838+
4839+
$mergedGlobalVariables = array_merge($this->globalVariables, $otherScope->globalVariables);
4840+
48024841
return $this->scopeFactory->create(
48034842
$this->context,
48044843
$this->isDeclareStrictTypes(),
@@ -4816,6 +4855,7 @@ public function mergeWith(?self $otherScope): self
48164855
$this->afterExtractCall && $otherScope->afterExtractCall,
48174856
$this->parentScope,
48184857
$this->nativeTypesPromoted,
4858+
$mergedGlobalVariables,
48194859
);
48204860
}
48214861

@@ -4929,7 +4969,7 @@ private function createConditionalExpressions(
49294969
private function mergeVariableHolders(array $ourVariableTypeHolders, array $theirVariableTypeHolders): array
49304970
{
49314971
$intersectedVariableTypeHolders = [];
4932-
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name);
4972+
$globalVariableCallback = fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isSuperGlobalVariable($node->name);
49334973
$nodeFinder = new NodeFinder();
49344974
foreach ($ourVariableTypeHolders as $exprString => $variableTypeHolder) {
49354975
if (isset($theirVariableTypeHolders[$exprString])) {
@@ -5020,6 +5060,7 @@ public function processFinallyScope(self $finallyScope, self $originalFinallySco
50205060
$this->afterExtractCall,
50215061
$this->parentScope,
50225062
$this->nativeTypesPromoted,
5063+
$this->globalVariables,
50235064
);
50245065
}
50255066

@@ -5115,6 +5156,7 @@ public function processClosureScope(
51155156
$this->afterExtractCall,
51165157
$this->parentScope,
51175158
$this->nativeTypesPromoted,
5159+
$this->globalVariables,
51185160
);
51195161
}
51205162

@@ -5164,6 +5206,7 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
51645206
$this->afterExtractCall,
51655207
$this->parentScope,
51665208
$this->nativeTypesPromoted,
5209+
$this->globalVariables,
51675210
);
51685211
}
51695212

@@ -5195,6 +5238,7 @@ public function generalizeWith(self $otherScope): self
51955238
$this->afterExtractCall,
51965239
$this->parentScope,
51975240
$this->nativeTypesPromoted,
5241+
$this->globalVariables,
51985242
);
51995243
}
52005244

src/Analyser/NodeScopeResolver.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,8 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
19661966
}
19671967

19681968
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
1969+
$scope->setVariableAsGlobal($var->name);
1970+
19691971
$vars[] = $var->name;
19701972
}
19711973
$scope = $this->processVarAnnotation($scope, $vars, $stmt);

src/Analyser/Scope.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public function hasVariableType(string $variableName): TrinaryLogic;
6161

6262
public function getVariableType(string $variableName): Type;
6363

64+
public function isGlobalVariable(string $variableName): bool;
65+
6466
public function canAnyVariableExist(): bool;
6567

6668
/**

tests/PHPStan/Analyser/data/GlobalExpressionTypeResolverExtension.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,34 @@
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Type\BooleanType;
99
use PHPStan\Type\ExpressionTypeResolverExtension;
10+
use PHPStan\Type\IntegerType;
11+
use PHPStan\Type\StringType;
1012
use PHPStan\Type\Type;
1113

1214
class GlobalExpressionTypeResolverExtension implements ExpressionTypeResolverExtension {
1315

1416
public function getType(Expr $expr, Scope $scope): ?Type
1517
{
16-
if (!$expr instanceof Variable) {
18+
if (
19+
!$expr instanceof Variable
20+
|| !\is_string($expr->name)
21+
|| !$scope->isGlobalVariable($expr->name)
22+
) {
1723
return null;
1824
}
1925

20-
if ($expr->name === 'MY_FRAMEWORK_GLOBAL') {
26+
if ($expr->name === 'MY_GLOBAL_BOOL') {
2127
return new BooleanType();
2228
}
2329

30+
if ($expr->name === 'MY_GLOBAL_INT') {
31+
return new IntegerType();
32+
}
33+
34+
if ($expr->name === 'MY_GLOBAL_STR') {
35+
return new StringType();
36+
}
37+
2438
return null;
2539
}
2640

0 commit comments

Comments
 (0)