Skip to content

Commit

Permalink
Extract resolving dynamic constants into a helper
Browse files Browse the repository at this point in the history
See phpstan#1163 for discussion. `TypeNodeResolver` needs to be able to resolve constants the same way as `MutatingScope` does
  • Loading branch information
rvanvelzen committed Apr 7, 2022
1 parent 86b5a51 commit 412bfbc
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 262 deletions.
3 changes: 3 additions & 0 deletions conf/config.neon
Expand Up @@ -484,6 +484,9 @@ services:
earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls%
implicitThrows: %exceptions.implicitThrows%

-
class: PHPStan\Analyser\ConstantResolver

-
implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory
arguments:
Expand Down
267 changes: 267 additions & 0 deletions src/Analyser/ConstantResolver.php
@@ -0,0 +1,267 @@
<?php declare(strict_types = 1);

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;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
use function in_array;
use const PHP_INT_SIZE;

class ConstantResolver
{

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

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

public function resolveConstant(Name $name, ?Scope $scope): ?Type
{
if (!$this->reflectionProvider->hasConstant($name, $scope)) {
return null;
}

/** @var string $resolvedConstantName */
$resolvedConstantName = $this->reflectionProvider->resolveConstantName($name, $scope);
// core, https://www.php.net/manual/en/reserved.constants.php
if ($resolvedConstantName === 'PHP_VERSION') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_MAJOR_VERSION') {
return IntegerRangeType::fromInterval(5, null);
}
if ($resolvedConstantName === 'PHP_MINOR_VERSION') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_RELEASE_VERSION') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_VERSION_ID') {
return IntegerRangeType::fromInterval(50207, null);
}
if ($resolvedConstantName === 'PHP_ZTS') {
return new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]);
}
if ($resolvedConstantName === 'PHP_DEBUG') {
return new UnionType([
new ConstantIntegerType(0),
new ConstantIntegerType(1),
]);
}
if ($resolvedConstantName === 'PHP_MAXPATHLEN') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'PHP_OS') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_OS_FAMILY') {
return new UnionType([
new ConstantStringType('Windows'),
new ConstantStringType('BSD'),
new ConstantStringType('Darwin'),
new ConstantStringType('Solaris'),
new ConstantStringType('Linux'),
new ConstantStringType('Unknown'),
]);
}
if ($resolvedConstantName === 'PHP_SAPI') {
return new UnionType([
new ConstantStringType('apache'),
new ConstantStringType('apache2handler'),
new ConstantStringType('cgi'),
new ConstantStringType('cli'),
new ConstantStringType('cli-server'),
new ConstantStringType('embed'),
new ConstantStringType('fpm-fcgi'),
new ConstantStringType('litespeed'),
new ConstantStringType('phpdbg'),
new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]),
]);
}
if ($resolvedConstantName === 'PHP_EOL') {
return new UnionType([
new ConstantStringType("\n"),
new ConstantStringType("\r\n"),
]);
}
if ($resolvedConstantName === 'PHP_INT_MAX') {
return PHP_INT_SIZE === 8
? new UnionType([new ConstantIntegerType(2147483647), new ConstantIntegerType(9223372036854775807)])
: new ConstantIntegerType(2147483647);
}
if ($resolvedConstantName === 'PHP_INT_MIN') {
// Why the -1 you might wonder, the answer is to fit it into an int :/ see https://3v4l.org/4SHIQ
return PHP_INT_SIZE === 8
? new UnionType([new ConstantIntegerType(-9223372036854775807 - 1), new ConstantIntegerType(-2147483647 - 1)])
: new ConstantIntegerType(-2147483647 - 1);
}
if ($resolvedConstantName === 'PHP_INT_SIZE') {
return new UnionType([
new ConstantIntegerType(4),
new ConstantIntegerType(8),
]);
}
if ($resolvedConstantName === 'PHP_FLOAT_DIG') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'PHP_EXTENSION_DIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_PREFIX') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_BINDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_BINARY') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_MANDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_LIBDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_DATADIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_SYSCONFDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_LOCALSTATEDIR') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_CONFIG_FILE_PATH') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
if ($resolvedConstantName === 'PHP_SHLIB_SUFFIX') {
return new UnionType([
new ConstantStringType('so'),
new ConstantStringType('dll'),
]);
}
if ($resolvedConstantName === 'PHP_FD_SETSIZE') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === '__COMPILER_HALT_OFFSET__') {
return IntegerRangeType::fromInterval(0, null);
}
// core other, https://www.php.net/manual/en/info.constants.php
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_MAJOR') {
return IntegerRangeType::fromInterval(4, null);
}
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_MINOR') {
return IntegerRangeType::fromInterval(0, null);
}
if ($resolvedConstantName === 'PHP_WINDOWS_VERSION_BUILD') {
return IntegerRangeType::fromInterval(1, null);
}
// dir, https://www.php.net/manual/en/dir.constants.php
if ($resolvedConstantName === 'DIRECTORY_SEPARATOR') {
return new UnionType([
new ConstantStringType('/'),
new ConstantStringType('\\'),
]);
}
if ($resolvedConstantName === 'PATH_SEPARATOR') {
return new UnionType([
new ConstantStringType(':'),
new ConstantStringType(';'),
]);
}
// iconv, https://www.php.net/manual/en/iconv.constants.php
if ($resolvedConstantName === 'ICONV_IMPL') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
// libxml, https://www.php.net/manual/en/libxml.constants.php
if ($resolvedConstantName === 'LIBXML_VERSION') {
return IntegerRangeType::fromInterval(1, null);
}
if ($resolvedConstantName === 'LIBXML_DOTTED_VERSION') {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}
// openssl, https://www.php.net/manual/en/openssl.constants.php
if ($resolvedConstantName === 'OPENSSL_VERSION_NUMBER') {
return IntegerRangeType::fromInterval(1, null);
}

$constantType = $this->reflectionProvider->getConstant($name, $scope)->getValueType();

return $this->resolveConstantType($resolvedConstantName, $constantType);
}

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

return $constantType;
}

}
9 changes: 2 additions & 7 deletions src/Analyser/DirectScopeFactory.php
Expand Up @@ -3,7 +3,6 @@
namespace PHPStan\Analyser;

use PhpParser\PrettyPrinter\Standard;
use PHPStan\DependencyInjection\Container;
use PHPStan\DependencyInjection\Type\DynamicReturnTypeExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
use PHPStan\Parser\Parser;
Expand All @@ -23,9 +22,6 @@
class DirectScopeFactory implements ScopeFactory
{

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

public function __construct(
private string $scopeClass,
private ReflectionProvider $reflectionProvider,
Expand All @@ -37,12 +33,11 @@ public function __construct(
private Parser $parser,
private NodeScopeResolver $nodeScopeResolver,
private bool $treatPhpDocTypesAsCertain,
Container $container,
private PhpVersion $phpVersion,
private bool $explicitMixedInUnknownGenericNew,
private ConstantResolver $constantResolver,
)
{
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
}

/**
Expand Down Expand Up @@ -91,6 +86,7 @@ public function create(
$this->propertyReflectionFinder,
$this->parser,
$this->nodeScopeResolver,
$this->constantResolver,
$context,
$this->phpVersion,
$declareStrictTypes,
Expand All @@ -107,7 +103,6 @@ public function create(
$currentlyAllowedUndefinedExpressions,
$nativeExpressionTypes,
$inFunctionCallsStack,
$this->dynamicConstantNames,
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope,
Expand Down
6 changes: 1 addition & 5 deletions src/Analyser/LazyScopeFactory.php
Expand Up @@ -19,9 +19,6 @@
class LazyScopeFactory implements ScopeFactory
{

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

private bool $treatPhpDocTypesAsCertain;

private bool $explicitMixedInUnknownGenericNew;
Expand All @@ -31,7 +28,6 @@ public function __construct(
private Container $container,
)
{
$this->dynamicConstantNames = $container->getParameter('dynamicConstantNames');
$this->treatPhpDocTypesAsCertain = $container->getParameter('treatPhpDocTypesAsCertain');
$this->explicitMixedInUnknownGenericNew = $this->container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'];
}
Expand Down Expand Up @@ -82,6 +78,7 @@ public function create(
$this->container->getByType(PropertyReflectionFinder::class),
$this->container->getService('currentPhpVersionSimpleParser'),
$this->container->getByType(NodeScopeResolver::class),
$this->container->getByType(ConstantResolver::class),
$context,
$this->container->getByType(PhpVersion::class),
$declareStrictTypes,
Expand All @@ -98,7 +95,6 @@ public function create(
$currentlyAllowedUndefinedExpressions,
$nativeExpressionTypes,
$inFunctionCallsStack,
$this->dynamicConstantNames,
$this->treatPhpDocTypesAsCertain,
$afterExtractCall,
$parentScope,
Expand Down

0 comments on commit 412bfbc

Please sign in to comment.