Skip to content

Commit 30f69be

Browse files
committed
Use a dedicated virtual node
1 parent d995be1 commit 30f69be

9 files changed

+149
-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\NativeTypeExpr;
4142
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
4243
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
@@ -182,8 +183,6 @@ final class MutatingScope implements Scope, NodeCallbackInvoker
182183

183184
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
184185

185-
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
186-
187186
/** @var Type[] */
188187
private array $resolvedTypes = [];
189188

@@ -628,12 +627,8 @@ public function isGlobalVariable(string $variableName): bool
628627
return true;
629628
}
630629

631-
$varExprString = '$' . $variableName;
632-
if (!isset($this->expressionTypes[$varExprString])) {
633-
return false;
634-
}
635-
636-
return $this->expressionTypes[$varExprString]->getExpr()->getAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME) === true;
630+
$globalVariableExprString = $this->getNodeKey(new GlobalVariableExpr(new Variable($variableName)));
631+
return array_key_exists($globalVariableExprString, $this->expressionTypes);
637632
}
638633

639634
/** @api */
@@ -850,6 +845,10 @@ public function getType(Expr $node): Type
850845
return $propertyReflection->getReadableType();
851846
}
852847

848+
if ($node instanceof GlobalVariableExpr) {
849+
return $this->getType($node->getVar());
850+
}
851+
853852
$key = $this->getNodeKey($node);
854853

855854
if (!array_key_exists($key, $this->resolvedTypes)) {
@@ -4309,13 +4308,9 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
43094308
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
43104309
}
43114310

4312-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
4311+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
43134312
{
43144313
$node = new Variable($variableName);
4315-
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4316-
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4317-
}
4318-
43194314
$scope = $this->assignExpression($node, $type, $nativeType);
43204315
if ($certainty->no()) {
43214316
throw new ShouldNotHappenException();
@@ -4430,6 +4425,17 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
44304425
}
44314426
}
44324427

4428+
if ($expr instanceof GlobalVariableExpr) {
4429+
foreach ($this->expressionTypeResolverExtensionRegistry->getExtensions() as $extension) {
4430+
$typeFromExtension = $extension->getType($expr, $this);
4431+
if ($typeFromExtension !== null) {
4432+
$type = $typeFromExtension;
4433+
break;
4434+
}
4435+
}
4436+
$scope = $scope->specifyExpressionType($expr->getVar(), $type, $nativeType, $certainty);
4437+
}
4438+
44334439
if ($certainty->no()) {
44344440
throw new ShouldNotHappenException();
44354441
}

src/Analyser/NodeScopeResolver.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
9191
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
9292
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
93+
use PHPStan\Node\Expr\GlobalVariableExpr;
9394
use PHPStan\Node\Expr\NativeTypeExpr;
9495
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
9596
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -1978,7 +1979,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch
19781979
continue;
19791980
}
19801981

1981-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
1982+
$scope = $scope->assignExpression(new GlobalVariableExpr($var), new MixedType(), new MixedType());
19821983
$vars[] = $var->name;
19831984
}
19841985
$scope = $this->processVarAnnotation($scope, $vars, $stmt);
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
@@ -10,6 +10,7 @@
1010
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
1111
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
1212
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
13+
use PHPStan\Node\Expr\GlobalVariableExpr;
1314
use PHPStan\Node\Expr\NativeTypeExpr;
1415
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
1516
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
@@ -104,4 +105,9 @@ protected function pPHPStan_Node_IssetExpr(IssetExpr $expr): string // phpcs:ign
104105
return sprintf('__phpstanIssetExpr(%s)', $this->p($expr->getExpr()));
105106
}
106107

108+
protected function pPHPStan_Node_GlobalVariableExpr(GlobalVariableExpr $expr): string // phpcs:ignore
109+
{
110+
return sprintf('__phpstanGlobalVariable(%s)', $this->p($expr->getVar()));
111+
}
112+
107113
}

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: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace ExpressionTypeResolverExtension;
4+
5+
use PHPStan\Analyser\Scope;
6+
use PHPStan\Node\Expr\GlobalVariableExpr;
7+
use PHPStan\Type\ArrayType;
8+
use PHPStan\Type\BenevolentUnionType;
9+
use PHPStan\Type\BooleanType;
10+
use PHPStan\Type\ExpressionTypeResolverExtension;
11+
use PHPStan\Type\IntegerType;
12+
use PHPStan\Type\MixedType;
13+
use PHPStan\Type\StringType;
14+
use PHPStan\Type\Type;
15+
use PhpParser\Node\Expr;
16+
17+
class GlobalExpressionTypeResolverExtension implements ExpressionTypeResolverExtension {
18+
19+
public function getType(Expr $expr, Scope $scope): ?Type
20+
{
21+
22+
if (!$expr instanceof GlobalVariableExpr) {
23+
return null;
24+
}
25+
26+
$variableName = $expr->getVar()->name;
27+
28+
if ($variableName === 'MY_GLOBAL_BOOL') {
29+
return new BooleanType();
30+
}
31+
32+
if ($variableName === 'MY_GLOBAL_INT') {
33+
return new IntegerType();
34+
}
35+
36+
if ($variableName === 'MY_GLOBAL_STR') {
37+
return new StringType();
38+
}
39+
40+
if ($variableName === 'MY_GLOBAL_ARRAY') {
41+
return new ArrayType(new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType(true));
42+
}
43+
44+
return null;
45+
}
46+
47+
}
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+
};

tests/PHPStan/Analyser/data/expression-type-resolver-extension.php renamed to tests/PHPStan/Analyser/data/expression-type-resolver-extension-method-call-returns-bool.php

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)