Skip to content

Commit

Permalink
Support @var above global keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 7, 2021
1 parent e501091 commit fa5f8ec
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ private function processStmtNode(
} elseif (
!$stmt instanceof Static_
&& !$stmt instanceof Foreach_
&& !$stmt instanceof Node\Stmt\Global_
) {
$scope = $this->processStmtVarAnnotation($scope, $stmt, null);
}
Expand Down Expand Up @@ -1152,6 +1153,7 @@ private function processStmtNode(
}
} elseif ($stmt instanceof Node\Stmt\Global_) {
$hasYield = false;
$vars = [];
foreach ($stmt->vars as $var) {
if (!$var instanceof Variable) {
throw new \PHPStan\ShouldNotHappenException();
Expand All @@ -1165,6 +1167,11 @@ private function processStmtNode(
}

$scope = $scope->assignVariable($var->name, new MixedType());
$vars[] = $var->name;
}
$comment = CommentHelper::getDocComment($stmt);
if ($comment !== null) {
$scope = $this->processVarAnnotation($scope, $vars, $comment);
}
} elseif ($stmt instanceof Static_) {
$hasYield = false;
Expand Down
52 changes: 52 additions & 0 deletions src/Rules/PhpDoc/WrongVariableNameInVarTagRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public function processNode(Node $node, Scope $scope): array
return $this->processStmt($scope, $varTags, $node->expr);
}

if ($node instanceof Node\Stmt\Global_) {
return $this->processGlobal($scope, $node, $varTags);
}

return $this->processStmt($scope, $varTags, null);
}

Expand Down Expand Up @@ -285,4 +289,52 @@ private function processStmt(Scope $scope, array $varTags, ?Expr $defaultExpr):
return $errors;
}

/**
* @param \PHPStan\Analyser\Scope $scope
* @param \PHPStan\PhpDoc\Tag\VarTag[] $varTags
* @return \PHPStan\Rules\RuleError[]
*/
private function processGlobal(Scope $scope, Node\Stmt\Global_ $node, array $varTags): array
{
$variableNames = [];
foreach ($node->vars as $var) {
if (!$var instanceof Expr\Variable) {
continue;
}
if (!is_string($var->name)) {
continue;
}

$variableNames[$var->name] = true;
}

$errors = [];
foreach (array_keys($varTags) as $name) {
if (is_int($name)) {
if (count($variableNames) === 1) {
continue;
}

$errors[] = RuleErrorBuilder::message(
'PHPDoc tag @var above multiple global variables does not specify variable name.'
)->build();
continue;
}

if (isset($variableNames[$name])) {
continue;
}

$errors[] = RuleErrorBuilder::message(sprintf(
'Variable $%s in PHPDoc tag @var does not match any global variable: %s',
$name,
implode(', ', array_map(static function (string $name): string {
return sprintf('$%s', $name);
}, array_keys($variableNames)))
))->build();
}

return $errors;
}

}
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10753,6 +10753,11 @@ public function dataCompact(): array
return $this->gatherAssertTypes(__DIR__ . '/data/compact.php');
}

public function dataBug4500(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4500.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10966,6 +10971,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataBug4398
* @dataProvider dataBug4415
* @dataProvider dataCompact
* @dataProvider dataBug4500
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
74 changes: 74 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4500.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Bug4500TypeInference;

use function PHPStan\Analyser\assertType;

class Foo
{

public function doFirst(): void
{
global $foo;
assertType('mixed', $foo);
}

public function doFoo(): void
{
/** @var int */
global $foo;
assertType('int', $foo);
}

public function doBar(): void
{
/** @var int $foo */
global $foo;
assertType('int', $foo);
}

public function doBaz(): void
{
/** @var int */
global $foo, $bar;
assertType('mixed', $foo);
assertType('mixed', $bar);
}

public function doLorem(): void
{
/** @var int $foo */
global $foo, $bar;
assertType('int', $foo);
assertType('mixed', $bar);

$baz = 'foo';

/** @var int $baz */
global $lorem;
assertType('mixed', $lorem);
assertType('\'foo\'', $baz);
}

public function doIpsum(): void
{
/**
* @var int $foo
* @var string $bar
*/
global $foo, $bar;

assertType('int', $foo);
assertType('string', $bar);
}

public function doDolor(): void
{
/** @var int $baz */
global $lorem;

assertType('mixed', $lorem);
assertType('*ERROR*', $baz);
}

}
18 changes: 18 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,22 @@ public function testBug3515(): void
$this->analyse([__DIR__ . '/data/bug-3515.php'], []);
}

public function testBug4500(): void
{
$this->analyse([__DIR__ . '/data/bug-4500.php'], [
[
'PHPDoc tag @var above multiple global variables does not specify variable name.',
23,
],
[
'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem',
43,
],
[
'Variable $baz in PHPDoc tag @var does not match any global variable: $lorem',
49,
],
]);
}

}
52 changes: 52 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/bug-4500.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Bug4500;

class Foo
{

public function doFoo(): void
{
/** @var int */
global $foo;
}

public function doBar(): void
{
/** @var int $foo */
global $foo;
}

public function doBaz(): void
{
/** @var int */
global $foo, $bar;
}

public function doLorem(): void
{
/** @var int $foo */
global $foo, $bar;
}

public function doIpsum(): void
{
/**
* @var int $foo
* @var string $bar
*/
global $foo, $bar;

$baz = 'foo';

/** @var int $baz */
global $lorem;
}

public function doDolor(): void
{
/** @var int $baz */
global $lorem;
}

}

0 comments on commit fa5f8ec

Please sign in to comment.