Skip to content

Commit 6dcbf38

Browse files
authored
Improve expression type resolving of superglobals
1 parent 1070788 commit 6dcbf38

File tree

9 files changed

+132
-62
lines changed

9 files changed

+132
-62
lines changed

conf/config.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,8 @@ services:
607607

608608
-
609609
class: PHPStan\Analyser\ScopeFactory
610+
arguments:
611+
explicitMixedForGlobalVariables: %featureToggles.explicitMixedForGlobalVariables%
610612

611613
-
612614
class: PHPStan\Analyser\NodeScopeResolver

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,11 @@ public function __construct(
3434
private bool $treatPhpDocTypesAsCertain,
3535
private PhpVersion $phpVersion,
3636
private bool $explicitMixedInUnknownGenericNew,
37-
private bool $explicitMixedForGlobalVariables,
3837
private ConstantResolver $constantResolver,
3938
)
4039
{
4140
}
4241

43-
/**
44-
* @param array<string, ExpressionTypeHolder> $expressionTypes
45-
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
46-
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
47-
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
48-
* @param array<string, true> $currentlyAssignedExpressions
49-
* @param array<string, true> $currentlyAllowedUndefinedExpressions
50-
*/
5142
public function create(
5243
ScopeContext $context,
5344
bool $declareStrictTypes = false,
@@ -102,7 +93,6 @@ public function create(
10293
$parentScope,
10394
$nativeTypesPromoted,
10495
$this->explicitMixedInUnknownGenericNew,
105-
$this->explicitMixedForGlobalVariables,
10696
);
10797
}
10898

src/Analyser/InternalScopeFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface InternalScopeFactory
1515
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
1616
* @param array<string, true> $currentlyAssignedExpressions
1717
* @param array<string, true> $currentlyAllowedUndefinedExpressions
18-
* @param array<MethodReflection|FunctionReflection> $inFunctionCallsStack
18+
* @param array<FunctionReflection|MethodReflection> $inFunctionCallsStack
1919
*/
2020
public function create(
2121
ScopeContext $context,

src/Analyser/LazyInternalScopeFactory.php

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class LazyInternalScopeFactory implements InternalScopeFactory
2222

2323
private bool $explicitMixedInUnknownGenericNew;
2424

25-
private bool $explicitMixedForGlobalVariables;
26-
2725
/**
2826
* @param class-string $scopeClass
2927
*/
@@ -34,18 +32,8 @@ public function __construct(
3432
{
3533
$this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain');
3634
$this->explicitMixedInUnknownGenericNew = $this->container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'];
37-
$this->explicitMixedForGlobalVariables = $this->container->getParameter('featureToggles')['explicitMixedForGlobalVariables'];
3835
}
3936

40-
/**
41-
* @param array<string, ExpressionTypeHolder> $expressionTypes
42-
* @param array<string, ExpressionTypeHolder> $nativeExpressionTypes
43-
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
44-
* @param array<string, true> $currentlyAssignedExpressions
45-
* @param array<string, true> $currentlyAllowedUndefinedExpressions
46-
* @param array<(FunctionReflection|MethodReflection)> $inFunctionCallsStack
47-
*
48-
*/
4937
public function create(
5038
ScopeContext $context,
5139
bool $declareStrictTypes = false,
@@ -100,7 +88,6 @@ public function create(
10088
$parentScope,
10189
$nativeTypesPromoted,
10290
$this->explicitMixedInUnknownGenericNew,
103-
$this->explicitMixedForGlobalVariables,
10491
);
10592
}
10693

src/Analyser/MutatingScope.php

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ public function __construct(
188188
private ?Scope $parentScope = null,
189189
private bool $nativeTypesPromoted = false,
190190
private bool $explicitMixedInUnknownGenericNew = false,
191-
private bool $explicitMixedForGlobalVariables = false,
192191
)
193192
{
194193
if ($namespace === '') {
@@ -466,10 +465,6 @@ public function afterOpenSslCall(string $openSslFunctionName): self
466465
/** @api */
467466
public function hasVariableType(string $variableName): TrinaryLogic
468467
{
469-
if ($this->isGlobalVariable($variableName)) {
470-
return TrinaryLogic::createYes();
471-
}
472-
473468
$varExprString = '$' . $variableName;
474469
if (!isset($this->expressionTypes[$varExprString])) {
475470
if ($this->canAnyVariableExist()) {
@@ -500,10 +495,6 @@ public function getVariableType(string $variableName): Type
500495
}
501496
}
502497

503-
if ($this->isGlobalVariable($variableName)) {
504-
return new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables));
505-
}
506-
507498
if ($this->hasVariableType($variableName)->no()) {
508499
throw new UndefinedVariableException($this, $variableName);
509500
}
@@ -537,21 +528,6 @@ public function getDefinedVariables(): array
537528
return $variables;
538529
}
539530

540-
private function isGlobalVariable(string $variableName): bool
541-
{
542-
return in_array($variableName, [
543-
'GLOBALS',
544-
'_SERVER',
545-
'_GET',
546-
'_POST',
547-
'_FILES',
548-
'_COOKIE',
549-
'_SESSION',
550-
'_REQUEST',
551-
'_ENV',
552-
], true);
553-
}
554-
555531
/** @api */
556532
public function hasConstant(Name $name): bool
557533
{
@@ -2133,7 +2109,6 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope
21332109
$this->parentScope,
21342110
false,
21352111
$this->explicitMixedInUnknownGenericNew,
2136-
$this->explicitMixedForGlobalVariables,
21372112
);
21382113
}
21392114

@@ -2430,12 +2405,8 @@ public function enterClass(ClassReflection $classReflection): self
24302405
$this->isDeclareStrictTypes(),
24312406
null,
24322407
$this->getNamespace(),
2433-
array_merge($this->getConstantTypes(), [
2434-
'$this' => $thisHolder,
2435-
]),
2436-
array_merge($this->getNativeConstantTypes(), [
2437-
'$this' => $thisHolder,
2438-
]),
2408+
array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), ['$this' => $thisHolder]),
2409+
array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), ['$this' => $thisHolder]),
24392410
[],
24402411
null,
24412412
null,
@@ -2671,8 +2642,8 @@ private function enterFunctionLike(
26712642
$this->isDeclareStrictTypes(),
26722643
$functionReflection,
26732644
$this->getNamespace(),
2674-
array_merge($this->getConstantTypes(), $expressionTypes),
2675-
array_merge($this->getNativeConstantTypes(), $nativeExpressionTypes),
2645+
array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), $expressionTypes),
2646+
array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), $nativeExpressionTypes),
26762647
);
26772648
}
26782649

@@ -2684,6 +2655,8 @@ public function enterNamespace(string $namespaceName): self
26842655
$this->isDeclareStrictTypes(),
26852656
null,
26862657
$namespaceName,
2658+
$this->getSuperglobalTypes(),
2659+
$this->getNativeSuperglobalTypes(),
26872660
);
26882661
}
26892662

@@ -2907,8 +2880,8 @@ private function enterAnonymousFunctionWithoutReflection(
29072880
$this->isDeclareStrictTypes(),
29082881
$this->getFunction(),
29092882
$this->getNamespace(),
2910-
array_merge($this->getConstantTypes(), $expressionTypes),
2911-
array_merge($this->getNativeConstantTypes(), $nativeTypes),
2883+
array_merge($this->getSuperglobalTypes(), $this->getConstantTypes(), $expressionTypes),
2884+
array_merge($this->getNativeSuperglobalTypes(), $this->getNativeConstantTypes(), $nativeTypes),
29122885
[],
29132886
$this->inClosureBindScopeClass,
29142887
new TrivialParametersAcceptor(),
@@ -4990,4 +4963,34 @@ private function getNativeConstantTypes(): array
49904963
return $constantTypes;
49914964
}
49924965

4966+
/** @return array<string, ExpressionTypeHolder> */
4967+
private function getSuperglobalTypes(): array
4968+
{
4969+
$superglobalTypes = [];
4970+
$exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV'];
4971+
foreach ($this->expressionTypes as $exprString => $typeHolder) {
4972+
if (!in_array($exprString, $exprStrings, true)) {
4973+
continue;
4974+
}
4975+
4976+
$superglobalTypes[$exprString] = $typeHolder;
4977+
}
4978+
return $superglobalTypes;
4979+
}
4980+
4981+
/** @return array<string, ExpressionTypeHolder> */
4982+
private function getNativeSuperglobalTypes(): array
4983+
{
4984+
$superglobalTypes = [];
4985+
$exprStrings = ['$GLOBALS', '$_SERVER', '$_GET', '$_POST', '$_FILES', '$_COOKIE', '$_SESSION', '$_REQUEST', '$_ENV'];
4986+
foreach ($this->nativeExpressionTypes as $exprString => $typeHolder) {
4987+
if (!in_array($exprString, $exprStrings, true)) {
4988+
continue;
4989+
}
4990+
4991+
$superglobalTypes[$exprString] = $typeHolder;
4992+
}
4993+
return $superglobalTypes;
4994+
}
4995+
49934996
}

src/Analyser/ScopeFactory.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,45 @@
22

33
namespace PHPStan\Analyser;
44

5+
use PhpParser\Node\Expr\Variable;
6+
use PHPStan\Type\ArrayType;
7+
use PHPStan\Type\MixedType;
8+
use PHPStan\Type\StringType;
9+
510
/** @api */
611
class ScopeFactory
712
{
813

9-
public function __construct(private InternalScopeFactory $internalScopeFactory)
14+
public function __construct(
15+
private InternalScopeFactory $internalScopeFactory,
16+
private bool $explicitMixedForGlobalVariables,
17+
)
1018
{
1119
}
1220

1321
public function create(ScopeContext $context): MutatingScope
1422
{
15-
return $this->internalScopeFactory->create($context);
23+
$superglobalType = new ArrayType(new StringType(), new MixedType($this->explicitMixedForGlobalVariables));
24+
$expressionTypes = [
25+
'$GLOBALS' => ExpressionTypeHolder::createYes(new Variable('GLOBALS'), $superglobalType),
26+
'$_SERVER' => ExpressionTypeHolder::createYes(new Variable('_SERVER'), $superglobalType),
27+
'$_GET' => ExpressionTypeHolder::createYes(new Variable('_GET'), $superglobalType),
28+
'$_POST' => ExpressionTypeHolder::createYes(new Variable('_POST'), $superglobalType),
29+
'$_FILES' => ExpressionTypeHolder::createYes(new Variable('_FILES'), $superglobalType),
30+
'$_COOKIE' => ExpressionTypeHolder::createYes(new Variable('_COOKIE'), $superglobalType),
31+
'$_SESSION' => ExpressionTypeHolder::createYes(new Variable('_SESSION'), $superglobalType),
32+
'$_REQUEST' => ExpressionTypeHolder::createYes(new Variable('_REQUEST'), $superglobalType),
33+
'$_ENV' => ExpressionTypeHolder::createYes(new Variable('_ENV'), $superglobalType),
34+
];
35+
36+
return $this->internalScopeFactory->create(
37+
$context,
38+
false,
39+
null,
40+
null,
41+
$expressionTypes,
42+
$expressionTypes,
43+
);
1644
}
1745

1846
}

src/Testing/PHPStanTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS
179179
$this->shouldTreatPhpDocTypesAsCertain(),
180180
$container->getByType(PhpVersion::class),
181181
$container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'],
182-
$container->getParameter('featureToggles')['explicitMixedForGlobalVariables'],
183182
$constantResolver,
184183
),
184+
$container->getParameter('featureToggles')['explicitMixedForGlobalVariables'],
185185
);
186186
}
187187

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ public function dataFileAsserts(): iterable
463463
yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-types.php');
464464
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5219.php');
465465
yield from $this->gatherAssertTypes(__DIR__ . '/data/strval.php');
466+
yield from $this->gatherAssertTypes(__DIR__ . '/data/superglobals.php');
466467
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-next.php');
467468

468469
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string.php');
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Superglobals;
4+
5+
use function PHPStan\Testing\assertNativeType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class Superglobals
9+
{
10+
11+
public function originalTypes(): void
12+
{
13+
assertType('array<string, mixed>', $GLOBALS);
14+
assertType('array<string, mixed>', $_SERVER);
15+
assertType('array<string, mixed>', $_GET);
16+
assertType('array<string, mixed>', $_POST);
17+
assertType('array<string, mixed>', $_FILES);
18+
assertType('array<string, mixed>', $_COOKIE);
19+
assertType('array<string, mixed>', $_SESSION);
20+
assertType('array<string, mixed>', $_REQUEST);
21+
assertType('array<string, mixed>', $_ENV);
22+
}
23+
24+
public function canBeOverwritten(): void
25+
{
26+
$GLOBALS = [];
27+
assertType('array{}', $GLOBALS);
28+
assertNativeType('array{}', $GLOBALS);
29+
}
30+
31+
public function canBePartlyOverwritten(): void
32+
{
33+
$GLOBALS['foo'] = 'foo';
34+
assertType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
35+
assertNativeType("non-empty-array<string, mixed>&hasOffsetValue('foo', 'foo')", $GLOBALS);
36+
}
37+
38+
public function canBeNarrowed(): void
39+
{
40+
if (isset($GLOBALS['foo'])) {
41+
assertType("array<string, mixed>&hasOffsetValue('foo', mixed~null)", $GLOBALS);
42+
assertNativeType("array<string, mixed>&hasOffset('foo')", $GLOBALS); // https://github.com/phpstan/phpstan/issues/8395
43+
} else {
44+
assertType('array<string, mixed>', $GLOBALS);
45+
assertNativeType('array<string, mixed>', $GLOBALS);
46+
}
47+
assertType('array<string, mixed>', $GLOBALS);
48+
assertNativeType('array<string, mixed>', $GLOBALS);
49+
}
50+
51+
}
52+
53+
function functionScope() {
54+
assertType('array<string, mixed>', $GLOBALS);
55+
assertNativeType('array<string, mixed>', $GLOBALS);
56+
}
57+
58+
assertType('array<string, mixed>', $GLOBALS);
59+
assertNativeType('array<string, mixed>', $GLOBALS);

0 commit comments

Comments
 (0)