Skip to content

Commit db78deb

Browse files
committed
Use a dedicated virtual node
1 parent 1652ca4 commit db78deb

9 files changed

+150
-15
lines changed

src/Analyser/MutatingScope.php

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
3838
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
3939
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
40+
use PHPStan\Node\Expr\GlobalVariableExpr;
4041
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
4142
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
4243
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -177,8 +178,6 @@ final class MutatingScope implements Scope
177178

178179
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
179180

180-
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
181-
182181
/** @var Type[] */
183182
private array $resolvedTypes = [];
184183

@@ -593,12 +592,8 @@ public function isGlobalVariable(string $variableName): bool
593592
return true;
594593
}
595594

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;
595+
$globalVariableExprString = $this->getNodeKey(new GlobalVariableExpr(new Variable($variableName)));
596+
return array_key_exists($globalVariableExprString, $this->expressionTypes);
602597
}
603598

604599
/** @api */
@@ -793,6 +788,10 @@ public function getType(Expr $node): Type
793788
return $propertyReflection->getReadableType();
794789
}
795790

791+
if ($node instanceof GlobalVariableExpr) {
792+
return $this->getType($node->getVar());
793+
}
794+
796795
$key = $this->getNodeKey($node);
797796

798797
if (!array_key_exists($key, $this->resolvedTypes)) {
@@ -4185,13 +4184,9 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
41854184
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
41864185
}
41874186

4188-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
4187+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
41894188
{
41904189
$node = new Variable($variableName);
4191-
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4192-
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4193-
}
4194-
41954190
$scope = $this->assignExpression($node, $type, $nativeType);
41964191
if ($certainty->no()) {
41974192
throw new ShouldNotHappenException();
@@ -4294,6 +4289,17 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
42944289
}
42954290
}
42964291

4292+
if ($expr instanceof GlobalVariableExpr) {
4293+
foreach ($this->expressionTypeResolverExtensionRegistry->getExtensions() as $extension) {
4294+
$typeFromExtension = $extension->getType($expr, $this);
4295+
if ($typeFromExtension !== null) {
4296+
$type = $typeFromExtension;
4297+
break;
4298+
}
4299+
}
4300+
$scope = $scope->specifyExpressionType($expr->getVar(), $type, $nativeType, $certainty);
4301+
}
4302+
42974303
if ($certainty->no()) {
42984304
throw new ShouldNotHappenException();
42994305
}

src/Analyser/NodeScopeResolver.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
9090
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
9191
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
92+
use PHPStan\Node\Expr\GlobalVariableExpr;
9293
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
9394
use PHPStan\Node\Expr\PropertyInitializationExpr;
9495
use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr;
@@ -1967,7 +1968,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
19671968
continue;
19681969
}
19691970

1970-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
1971+
$scope = $scope->assignExpression(new GlobalVariableExpr($var), new MixedType(), new MixedType());
19711972
$vars[] = $var->name;
19721973
}
19731974
$scope = $this->processVarAnnotation($scope, $vars, $stmt);

src/Node/Expr/GlobalVariableExpr.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node\Expr;
4+
5+
use Override;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\Node\VirtualNode;
8+
9+
final class GlobalVariableExpr extends Expr implements VirtualNode
10+
{
11+
12+
public function __construct(private Expr $var)
13+
{
14+
parent::__construct([]);
15+
}
16+
17+
public function getVar(): Expr
18+
{
19+
return $this->var;
20+
}
21+
22+
#[Override]
23+
public function getType(): string
24+
{
25+
return 'PHPStan_Node_GlobalVariableExpr';
26+
}
27+
28+
/**
29+
* @return string[]
30+
*/
31+
#[Override]
32+
public function getSubNodeNames(): array
33+
{
34+
return [];
35+
}
36+
37+
}

src/Node/Printer/Printer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
1010
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
1111
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
12+
use PHPStan\Node\Expr\GlobalVariableExpr;
1213
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
1314
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
1415
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -92,4 +93,9 @@ protected function pPHPStan_Node_IssetExpr(IssetExpr $expr): string // phpcs:ign
9293
return sprintf('__phpstanIssetExpr(%s)', $this->p($expr->getExpr()));
9394
}
9495

96+
protected function pPHPStan_Node_GlobalVariableExpr(GlobalVariableExpr $expr): string // phpcs:ignore
97+
{
98+
return sprintf('__phpstanGlobalVariable(%s)', $this->p($expr->getVar()));
99+
}
100+
95101
}

tests/PHPStan/Analyser/ExpressionTypeResolverExtensionTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ class ExpressionTypeResolverExtensionTest extends TypeInferenceTestCase
1010

1111
public static function dataFileAsserts(): iterable
1212
{
13-
yield from self::gatherAssertTypes(__DIR__ . '/data/expression-type-resolver-extension.php');
13+
yield from self::gatherAssertTypes(__DIR__ . '/data/expression-type-resolver-extension-method-call-returns-bool.php');
14+
yield from self::gatherAssertTypes(__DIR__ . '/data/expression-type-resolver-extension-global-statement.php');
1415
}
1516

1617
/**
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace ExpressionTypeResolverExtension;
4+
5+
use PHPStan\Analyser\Scope;
6+
use PHPStan\Type\ArrayType;
7+
use PHPStan\Type\BenevolentUnionType;
8+
use PHPStan\Type\BooleanType;
9+
use PHPStan\Type\ExpressionTypeResolverExtension;
10+
use PHPStan\Type\IntegerType;
11+
use PHPStan\Type\MixedType;
12+
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
14+
use PhpParser\Node\Expr;
15+
use PhpParser\Node\Expr\Variable;
16+
use PHPStan\Node\Expr\GlobalVariableExpr;
17+
18+
class GlobalExpressionTypeResolverExtension implements ExpressionTypeResolverExtension {
19+
20+
public function getType(Expr $expr, Scope $scope): ?Type
21+
{
22+
23+
if (!$expr instanceof GlobalVariableExpr) {
24+
return null;
25+
}
26+
27+
$variableName = $expr->getVar()->name;
28+
29+
if ($variableName === 'MY_GLOBAL_BOOL') {
30+
return new BooleanType();
31+
}
32+
33+
if ($variableName === 'MY_GLOBAL_INT') {
34+
return new IntegerType();
35+
}
36+
37+
if ($variableName === 'MY_GLOBAL_STR') {
38+
return new StringType();
39+
}
40+
41+
if ($variableName === 'MY_GLOBAL_ARRAY') {
42+
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
43+
}
44+
45+
return null;
46+
}
47+
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
// test file for ExpressionTypeResolverExtensionTest
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
global $MY_GLOBAL_BOOL, $ANOTHER_GLOBAL;
8+
9+
assertType('bool', $MY_GLOBAL_BOOL);
10+
assertType('mixed', $MY_GLOBAL_INT); // not declared in the global statement = no type assigned
11+
assertType('mixed', $ANOTHER_GLOBAL);
12+
13+
$testFct = function ($MY_GLOBAL_BOOL) {
14+
/** @var float $MY_GLOBAL_STR */
15+
global $MY_GLOBAL_INT, $MY_GLOBAL_STR, $MY_GLOBAL_ARRAY;
16+
17+
$MY_GLOBAL_ARRAY = new ArrayIterator([1, 2, 3]);
18+
19+
assertType('mixed', $MY_GLOBAL_BOOL); // not declared in the global statement = no type assigned
20+
assertType('float', $MY_GLOBAL_STR); // overriden by PHPDoc
21+
assertType('ArrayIterator<int, int>', $MY_GLOBAL_ARRAY); // overriden by value assign expression
22+
assertType('int', $MY_GLOBAL_INT);
23+
};
24+
25+
$testClass = new class () {
26+
public function foo($MY_GLOBAL_INT) {
27+
global $MY_GLOBAL_STR;
28+
29+
assertType('string', $MY_GLOBAL_STR);
30+
assertType('mixed', $MY_GLOBAL_INT);
31+
}
32+
};
File renamed without changes.

tests/PHPStan/Analyser/expression-type-resolver-extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# config for ExpressionTypeResolverExtensionTest
22
services:
3+
-
4+
class: ExpressionTypeResolverExtension\GlobalExpressionTypeResolverExtension
5+
tags:
6+
- phpstan.broker.expressionTypeResolverExtension
37
-
48
class: ExpressionTypeResolverExtension\MethodCallReturnsBoolExpressionTypeResolverExtension
59
tags:

0 commit comments

Comments
 (0)