Skip to content

Commit

Permalink
Fix dynamic constants
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen committed Apr 7, 2022
1 parent a561f7b commit defc51e
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 65 deletions.
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,9 @@ services:
-
class: PHPStan\Analyser\ConstantResolver

-
class: PHPStan\Analyser\ConstantResolverProvider

-
implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory
arguments:
Expand Down
19 changes: 19 additions & 0 deletions src/Analyser/ConstantResolverProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\Container;

class ConstantResolverProvider
{

public function __construct(private Container $container)
{
}

public function getConstantResolver(): ConstantResolver
{
return $this->container->getByType(ConstantResolver::class);
}

}
28 changes: 28 additions & 0 deletions src/Analyser/NameScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,34 @@ public function resolveStringName(string $name): string
return $name;
}

/**
* @return non-empty-list<string>
*/
public function resolveConstantNames(string $name): array
{
if (strpos($name, '\\') === 0) {
return [ltrim($name, '\\')];
}

$nameParts = explode('\\', $name);
if (count($nameParts) > 1) {
$firstNamePart = strtolower($nameParts[0]);
if (isset($this->uses[$firstNamePart])) {
array_shift($nameParts);
return [sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts))];
}
}

if ($this->namespace !== null) {
return [
sprintf('%s\\%s', $this->namespace, $name),
$name,
];
}

return [$name];
}

public function getTemplateTypeScope(): ?TemplateTypeScope
{
if ($this->className !== null) {
Expand Down
37 changes: 15 additions & 22 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use IteratorAggregate;
use Nette\Utils\Strings;
use PhpParser\Node\Name;
use PHPStan\Analyser\ConstantResolver;
use PHPStan\Analyser\ConstantResolverProvider;
use PHPStan\Analyser\NameScope;
use PHPStan\Analyser\ScopeFactory;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
Expand Down Expand Up @@ -86,7 +89,6 @@
use function explode;
use function get_class;
use function in_array;
use function ltrim;
use function max;
use function min;
use function preg_match;
Expand All @@ -103,6 +105,8 @@ public function __construct(
private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider,
private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider,
private TypeAliasResolverProvider $typeAliasResolverProvider,
private ConstantResolverProvider $constantResolverProvider,
private ScopeFactory $scopeFactory,
)
{
}
Expand Down Expand Up @@ -384,33 +388,17 @@ private function mightBeConstant(string $name): bool

private function tryResolveConstant(string $name, NameScope $nameScope): ?Type
{
foreach ($this->getPossibleFullyQualifiedConstantNames($name, $nameScope) as $fqn) {
$nameNode = new Name\FullyQualified(explode('\\', $fqn));

if ($this->getReflectionProvider()->hasConstant($nameNode, null)) {
return $this->getReflectionProvider()->getConstant($nameNode, null)->getValueType();
foreach ($nameScope->resolveConstantNames($name) as $constName) {
$nameNode = new Name\FullyQualified(explode('\\', $constName));
$constType = $this->getConstantResolver()->resolveConstant($nameNode, null);
if ($constType !== null) {
return $constType;
}
}

return null;
}

/**
* @return non-empty-list<string>
*/
private function getPossibleFullyQualifiedConstantNames(string $name, NameScope $nameScope): array
{
if (strpos($name, '\\') === 0) {
return [ltrim($name, '\\')];
}

if ($nameScope->getNamespace() !== null) {
return [$nameScope->resolveStringName($name), $name];
}

return [$name];
}

private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type
{
if ($nameScope->hasUseAlias($typeNode->name)) {
Expand Down Expand Up @@ -958,4 +946,9 @@ private function getTypeAliasResolver(): TypeAliasResolver
return $this->typeAliasResolverProvider->getTypeAliasResolver();
}

private function getConstantResolver(): ConstantResolver
{
return $this->constantResolverProvider->getConstantResolver();
}

}
7 changes: 1 addition & 6 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -868,13 +868,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-3284.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/int-mask.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-types-constant.php');

if (PHP_VERSION_ID < 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-types-constant-php7.php');
} else {
yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-types-constant-php8.php');
}
yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-types-constant.php');

require_once __DIR__ . '/data/constant-phpdoc-type.php';
yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-phpdoc-type.php');
Expand Down
18 changes: 0 additions & 18 deletions tests/PHPStan/Analyser/data/conditional-types-constant-php7.php

This file was deleted.

18 changes: 0 additions & 18 deletions tests/PHPStan/Analyser/data/conditional-types-constant-php8.php

This file was deleted.

24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/data/conditional-types-constant.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace ConditionalTypesConstant;

use function PHPStan\Testing\assertType;
use const PREG_SPLIT_NO_EMPTY;
use const PREG_SPLIT_OFFSET_CAPTURE;

abstract class Test
{
/**
* @return ($flag is PREG_SPLIT_NO_EMPTY ? true : false)
*/
abstract public function returnsTrueForPREG_SPLIT_NO_EMPTY(int $flag): bool;

public function test(): void
{
assertType('true', $this->returnsTrueForPREG_SPLIT_NO_EMPTY(PREG_SPLIT_NO_EMPTY));
assertType('true', $this->returnsTrueForPREG_SPLIT_NO_EMPTY(1));
assertType('false', $this->returnsTrueForPREG_SPLIT_NO_EMPTY(PREG_SPLIT_OFFSET_CAPTURE));
assertType('false', $this->returnsTrueForPREG_SPLIT_NO_EMPTY(4));
assertType('bool', $this->returnsTrueForPREG_SPLIT_NO_EMPTY($_GET['flag']));
}
}
17 changes: 16 additions & 1 deletion tests/PHPStan/Analyser/data/constant-phpdoc-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace ConstantPhpdocType;

use ConstantPhpdocType\Sub;
use ConstantPhpdocType\Sub as Aliased;
use const PREG_SPLIT_NO_EMPTY as PREG_SPLIT_NO_EMPTY_ALIAS;
use function PHPStan\Testing\assertType;

Expand All @@ -20,6 +22,9 @@ class BAR {}
* @param PREG_SPLIT_NO_EMPTY_COPY $five
* @param PREG_SPLIT_NO_EMPTY_ALIAS $six
* @param BAR $seven
* @param Sub\Nested\CONST_FROM_OTHER_NS $eight
* @param Aliased\Nested\CONST_FROM_OTHER_NS $nine
* @param PHP_INT_MAX $ten
*/
function foo(
$one,
Expand All @@ -28,7 +33,10 @@ function foo(
$four,
$five,
$six,
$seven
$seven,
$eight,
$nine,
$ten
) {
assertType('100', $one);
assertType('100|200', $two);
Expand All @@ -37,4 +45,11 @@ function foo(
assertType('1', $five);
assertType('ConstantPhpdocType\PREG_SPLIT_NO_EMPTY_ALIAS', $six); // use const not supported
assertType('ConstantPhpdocType\BAR', $seven); // classes take precedence over constants
assertType("'foo'", $eight);
assertType("'foo'", $nine);
assertType('2147483647|9223372036854775807', $ten);
}

namespace ConstantPhpdocType\Sub\Nested;

const CONST_FROM_OTHER_NS = 'foo';

0 comments on commit defc51e

Please sign in to comment.