Skip to content

Commit

Permalink
Support const aliases properly
Browse files Browse the repository at this point in the history
  • Loading branch information
rvanvelzen committed Apr 7, 2022
1 parent defc51e commit db4d492
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 53 deletions.
2 changes: 2 additions & 0 deletions conf/config.neon
Expand Up @@ -486,9 +486,11 @@ services:

-
class: PHPStan\Analyser\ConstantResolver
factory: PHPStan\Analyser\ContainerConstantResolver

-
class: PHPStan\Analyser\ConstantResolverProvider
factory: PHPStan\Analyser\LazyConstantResolverProvider

-
implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory
Expand Down
27 changes: 18 additions & 9 deletions src/Analyser/ConstantResolver.php
Expand Up @@ -3,7 +3,6 @@
namespace PHPStan\Analyser;

use PhpParser\Node\Name;
use PHPStan\DependencyInjection\Container;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Constant\ConstantIntegerType;
Expand All @@ -18,18 +17,14 @@
use function in_array;
use const PHP_INT_SIZE;

class ConstantResolver
abstract class ConstantResolver
{

/** @var string[] */
private array $dynamicConstantNames;
private ?array $dynamicConstantNames = null;

public function __construct(
private ReflectionProvider $reflectionProvider,
Container $container,
)
public function __construct(private ReflectionProvider $reflectionProvider)
{
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
}

public function resolveConstant(Name $name, ?Scope $scope): ?Type
Expand Down Expand Up @@ -257,11 +252,25 @@ public function resolveConstant(Name $name, ?Scope $scope): ?Type

public function resolveConstantType(string $constantName, Type $constantType): Type
{
if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) {
if ($constantType instanceof ConstantType && $this->isDynamicConstantName($constantName)) {
return $constantType->generalize(GeneralizePrecision::lessSpecific());
}

return $constantType;
}

private function isDynamicConstantName(string $constantName): bool
{
if ($this->dynamicConstantNames === null) {
$this->dynamicConstantNames = $this->getDynamicConstantNames();
}

return in_array($constantName, $this->dynamicConstantNames, true);
}

/**
* @return string[]
*/
abstract protected function getDynamicConstantNames(): array;

}
13 changes: 2 additions & 11 deletions src/Analyser/ConstantResolverProvider.php
Expand Up @@ -2,18 +2,9 @@

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\Container;

class ConstantResolverProvider
interface ConstantResolverProvider
{

public function __construct(private Container $container)
{
}

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

}
24 changes: 24 additions & 0 deletions src/Analyser/ContainerConstantResolver.php
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\Container;
use PHPStan\Reflection\ReflectionProvider;

class ContainerConstantResolver extends ConstantResolver
{

public function __construct(
ReflectionProvider $reflectionProvider,
private Container $container,
)
{
parent::__construct($reflectionProvider);
}

protected function getDynamicConstantNames(): array
{
return $this->container->getParameter('dynamicConstantNames');
}

}
26 changes: 26 additions & 0 deletions src/Analyser/DirectConstantResolver.php
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Reflection\ReflectionProvider;

class DirectConstantResolver extends ConstantResolver
{

/**
* @param string[] $dynamicConstantNames
*/
public function __construct(
ReflectionProvider $reflectionProvider,
private array $dynamicConstantNames,
)
{
parent::__construct($reflectionProvider);
}

protected function getDynamicConstantNames(): array
{
return $this->dynamicConstantNames;
}

}
17 changes: 17 additions & 0 deletions src/Analyser/DirectConstantResolverProvider.php
@@ -0,0 +1,17 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

class DirectConstantResolverProvider implements ConstantResolverProvider
{

public function __construct(private ConstantResolver $constantResolver)
{
}

public function getConstantResolver(): ConstantResolver
{
return $this->constantResolver;
}

}
19 changes: 19 additions & 0 deletions src/Analyser/LazyConstantResolverProvider.php
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\DependencyInjection\Container;

class LazyConstantResolverProvider implements ConstantResolverProvider
{

public function __construct(private Container $container)
{
}

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

}
24 changes: 21 additions & 3 deletions src/Analyser/NameScope.php
Expand Up @@ -25,9 +25,10 @@ class NameScope
/**
* @api
* @param array<string, string> $uses alias(string) => fullName(string)
* @param array<string, string> $constUses alias(string) => fullName(string)
* @param array<string, true> $typeAliasesMap
*/
public function __construct(private ?string $namespace, private array $uses, private ?string $className = null, private ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, private array $typeAliasesMap = [], private bool $bypassTypeAliases = false)
public function __construct(private ?string $namespace, private array $uses, private ?string $className = null, private ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, private array $typeAliasesMap = [], private bool $bypassTypeAliases = false, private array $constUses = [])
{
$this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
}
Expand All @@ -50,6 +51,14 @@ public function hasUseAlias(string $name): bool
return isset($this->uses[strtolower($name)]);
}

/**
* @return array<string, string>
*/
public function getConstUses(): array
{
return $this->constUses;
}

public function getClassName(): ?string
{
return $this->className;
Expand Down Expand Up @@ -88,12 +97,15 @@ public function resolveConstantNames(string $name): array
}

$nameParts = explode('\\', $name);
$firstNamePart = strtolower($nameParts[0]);

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))];
}
} elseif (isset($this->constUses[$firstNamePart])) {
return [$this->constUses[$firstNamePart]];
}

if ($this->namespace !== null) {
Expand Down Expand Up @@ -149,6 +161,8 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self
$map->getTypes(),
)),
$this->typeAliasesMap,
$this->bypassTypeAliases,
$this->constUses,
);
}

Expand All @@ -166,12 +180,14 @@ public function unsetTemplateType(string $name): self
$this->functionName,
$this->templateTypeMap->unsetType($name),
$this->typeAliasesMap,
$this->bypassTypeAliases,
$this->constUses,
);
}

public function bypassTypeAliases(): self
{
return new self($this->namespace, $this->uses, $this->className, $this->functionName, $this->templateTypeMap, $this->typeAliasesMap, true);
return new self($this->namespace, $this->uses, $this->className, $this->functionName, $this->templateTypeMap, $this->typeAliasesMap, true, $this->constUses);
}

public function shouldBypassTypeAliases(): bool
Expand All @@ -196,6 +212,8 @@ public static function __set_state(array $properties): self
$properties['functionName'],
$properties['templateTypeMap'],
$properties['typeAliasesMap'],
$properties['bypassTypeAliases'],
$properties['constUses'],
);
}

Expand Down
17 changes: 8 additions & 9 deletions src/Dependency/ExportedNode/ExportedPhpDocNode.php
Expand Up @@ -9,15 +9,12 @@
class ExportedPhpDocNode implements ExportedNode, JsonSerializable
{

/** @var array<string, string> alias(string) => fullName(string) */
private array $uses;

/**
* @param array<string, string> $uses
* @param array<string, string> $uses alias(string) => fullName(string)
* @param array<string, string> $constUses alias(string) => fullName(string)
*/
public function __construct(private string $phpDocString, private ?string $namespace, array $uses)
public function __construct(private string $phpDocString, private ?string $namespace, private array $uses, private array $constUses)
{
$this->uses = $uses;
}

public function equals(ExportedNode $node): bool
Expand All @@ -28,7 +25,8 @@ public function equals(ExportedNode $node): bool

return $this->phpDocString === $node->phpDocString
&& $this->namespace === $node->namespace
&& $this->uses === $node->uses;
&& $this->uses === $node->uses
&& $this->constUses === $node->constUses;
}

/**
Expand All @@ -43,6 +41,7 @@ public function jsonSerialize()
'phpDocString' => $this->phpDocString,
'namespace' => $this->namespace,
'uses' => $this->uses,
'constUses' => $this->constUses,
],
];
}
Expand All @@ -53,7 +52,7 @@ public function jsonSerialize()
*/
public static function __set_state(array $properties): ExportedNode
{
return new self($properties['phpDocString'], $properties['namespace'], $properties['uses']);
return new self($properties['phpDocString'], $properties['namespace'], $properties['uses'], $properties['constUses']);
}

/**
Expand All @@ -62,7 +61,7 @@ public static function __set_state(array $properties): ExportedNode
*/
public static function decode(array $data): ExportedNode
{
return new self($data['phpDocString'], $data['namespace'], $data['uses']);
return new self($data['phpDocString'], $data['namespace'], $data['uses'], $data['constUses']);
}

}
2 changes: 1 addition & 1 deletion src/Dependency/ExportedNodeResolver.php
Expand Up @@ -273,7 +273,7 @@ private function exportPhpDocNode(
return null;
}

return new ExportedPhpDocNode($text, $nameScope->getNamespace(), $nameScope->getUses());
return new ExportedPhpDocNode($text, $nameScope->getNamespace(), $nameScope->getUses(), $nameScope->getConstUses());
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/DependencyInjection/ValidateIgnoredErrorsExtension.php
Expand Up @@ -7,6 +7,8 @@
use Nette\DI\CompilerExtension;
use Nette\Utils\RegexpException;
use Nette\Utils\Strings;
use PHPStan\Analyser\DirectConstantResolver;
use PHPStan\Analyser\DirectConstantResolverProvider;
use PHPStan\Analyser\NameScope;
use PHPStan\Command\IgnoredRegexValidator;
use PHPStan\PhpDoc\DirectTypeNodeResolverExtensionRegistryProvider;
Expand Down Expand Up @@ -81,6 +83,7 @@ public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type
}

}),
new DirectConstantResolverProvider(new DirectConstantResolver($reflectionProvider, [])),
),
),
);
Expand Down
2 changes: 0 additions & 2 deletions src/PhpDoc/TypeNodeResolver.php
Expand Up @@ -11,7 +11,6 @@
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 @@ -106,7 +105,6 @@ public function __construct(
private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider,
private TypeAliasResolverProvider $typeAliasResolverProvider,
private ConstantResolverProvider $constantResolverProvider,
private ScopeFactory $scopeFactory,
)
{
}
Expand Down
11 changes: 5 additions & 6 deletions src/Testing/PHPStanTestCase.php
Expand Up @@ -3,7 +3,8 @@
namespace PHPStan\Testing;

use PhpParser\PrettyPrinter\Standard;
use PHPStan\Analyser\ConstantResolver;
use PHPStan\Analyser\ContainerConstantResolver;
use PHPStan\Analyser\DirectConstantResolver;
use PHPStan\Analyser\DirectScopeFactory;
use PHPStan\Analyser\Error;
use PHPStan\Analyser\MutatingScope;
Expand Down Expand Up @@ -31,7 +32,6 @@
use PHPStan\Type\UsefulTypeAliasResolver;
use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use function array_merge;
use function count;
use function implode;
Expand Down Expand Up @@ -156,11 +156,10 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS
{
$container = self::getContainer();

$constantResolver = new ConstantResolver($container->getByType(ReflectionProvider::class), $container);
if (count($dynamicConstantNames) > 0) {
$reflectionProperty = new ReflectionProperty(ConstantResolver::class, 'dynamicConstantNames');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($constantResolver, $dynamicConstantNames);
$constantResolver = new DirectConstantResolver($reflectionProvider, $dynamicConstantNames);
} else {
$constantResolver = new ContainerConstantResolver($reflectionProvider, $container);
}

return new DirectScopeFactory(
Expand Down

0 comments on commit db4d492

Please sign in to comment.