Skip to content

Commit 72730d1

Browse files
committed
Use a dedicated virtual node
1 parent bedb4d1 commit 72730d1

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

180181
private const KEEP_VOID_ATTRIBUTE_NAME = 'keepVoid';
181182

182-
private const IS_GLOBAL_ATTRIBUTE_NAME = 'isGlobal';
183-
184183
/** @var Type[] */
185184
private array $resolvedTypes = [];
186185

@@ -618,12 +617,8 @@ public function isGlobalVariable(string $variableName): bool
618617
return true;
619618
}
620619

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;
620+
$globalVariableExprString = $this->getNodeKey(new GlobalVariableExpr(new Variable($variableName)));
621+
return array_key_exists($globalVariableExprString, $this->expressionTypes);
627622
}
628623

629624
/** @api */
@@ -824,6 +819,10 @@ public function getType(Expr $node): Type
824819
return $propertyReflection->getReadableType();
825820
}
826821

822+
if ($node instanceof GlobalVariableExpr) {
823+
return $this->getType($node->getVar());
824+
}
825+
827826
$key = $this->getNodeKey($node);
828827

829828
if (!array_key_exists($key, $this->resolvedTypes)) {
@@ -4221,13 +4220,9 @@ public function isUndefinedExpressionAllowed(Expr $expr): bool
42214220
return array_key_exists($exprString, $this->currentlyAllowedUndefinedExpressions);
42224221
}
42234222

4224-
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty, bool $isGlobal = false): self
4223+
public function assignVariable(string $variableName, Type $type, Type $nativeType, TrinaryLogic $certainty): self
42254224
{
42264225
$node = new Variable($variableName);
4227-
if ($isGlobal || $this->isGlobalVariable($variableName)) {
4228-
$node->setAttribute(self::IS_GLOBAL_ATTRIBUTE_NAME, true);
4229-
}
4230-
42314226
$scope = $this->assignExpression($node, $type, $nativeType);
42324227
if ($certainty->no()) {
42334228
throw new ShouldNotHappenException();
@@ -4330,6 +4325,17 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType,
43304325
}
43314326
}
43324327

4328+
if ($expr instanceof GlobalVariableExpr) {
4329+
foreach ($this->expressionTypeResolverExtensionRegistry->getExtensions() as $extension) {
4330+
$typeFromExtension = $extension->getType($expr, $this);
4331+
if ($typeFromExtension !== null) {
4332+
$type = $typeFromExtension;
4333+
break;
4334+
}
4335+
}
4336+
$scope = $scope->specifyExpressionType($expr->getVar(), $type, $nativeType, $certainty);
4337+
}
4338+
43334339
if ($certainty->no()) {
43344340
throw new ShouldNotHappenException();
43354341
}

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\NativeTypeExpr;
9394
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
9495
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -1976,7 +1977,7 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
19761977
continue;
19771978
}
19781979

1979-
$scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes(), true);
1980+
$scope = $scope->assignExpression(new GlobalVariableExpr($var), new MixedType(), new MixedType());
19801981
$vars[] = $var->name;
19811982
}
19821983
$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
@@ -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\NativeTypeExpr;
1314
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
1415
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
@@ -98,4 +99,9 @@ protected function pPHPStan_Node_IssetExpr(IssetExpr $expr): string // phpcs:ign
9899
return sprintf('__phpstanIssetExpr(%s)', $this->p($expr->getExpr()));
99100
}
100101

102+
protected function pPHPStan_Node_GlobalVariableExpr(GlobalVariableExpr $expr): string // phpcs:ignore
103+
{
104+
return sprintf('__phpstanGlobalVariable(%s)', $this->p($expr->getVar()));
105+
}
106+
101107
}

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+
};
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)