From 412bfbc45a70cdfb8c9f8131f7c0ab06ab8b5114 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 7 Apr 2022 13:04:58 +0200 Subject: [PATCH] Extract resolving dynamic constants into a helper See #1163 for discussion. `TypeNodeResolver` needs to be able to resolve constants the same way as `MutatingScope` does --- conf/config.neon | 3 + src/Analyser/ConstantResolver.php | 267 ++++++++++++++++++++++++++ src/Analyser/DirectScopeFactory.php | 9 +- src/Analyser/LazyScopeFactory.php | 6 +- src/Analyser/MutatingScope.php | 251 ++---------------------- src/Testing/PHPStanTestCase.php | 19 +- src/Testing/TypeInferenceTestCase.php | 9 +- 7 files changed, 302 insertions(+), 262 deletions(-) create mode 100644 src/Analyser/ConstantResolver.php diff --git a/conf/config.neon b/conf/config.neon index d3553901e5..039f1675eb 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -484,6 +484,9 @@ services: earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls% implicitThrows: %exceptions.implicitThrows% + - + class: PHPStan\Analyser\ConstantResolver + - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory arguments: diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php new file mode 100644 index 0000000000..d7d421e73a --- /dev/null +++ b/src/Analyser/ConstantResolver.php @@ -0,0 +1,267 @@ +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; + } + +} diff --git a/src/Analyser/DirectScopeFactory.php b/src/Analyser/DirectScopeFactory.php index b7a00f5449..4364eb9ea0 100644 --- a/src/Analyser/DirectScopeFactory.php +++ b/src/Analyser/DirectScopeFactory.php @@ -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; @@ -23,9 +22,6 @@ class DirectScopeFactory implements ScopeFactory { - /** @var string[] */ - private array $dynamicConstantNames; - public function __construct( private string $scopeClass, private ReflectionProvider $reflectionProvider, @@ -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'); } /** @@ -91,6 +86,7 @@ public function create( $this->propertyReflectionFinder, $this->parser, $this->nodeScopeResolver, + $this->constantResolver, $context, $this->phpVersion, $declareStrictTypes, @@ -107,7 +103,6 @@ public function create( $currentlyAllowedUndefinedExpressions, $nativeExpressionTypes, $inFunctionCallsStack, - $this->dynamicConstantNames, $this->treatPhpDocTypesAsCertain, $afterExtractCall, $parentScope, diff --git a/src/Analyser/LazyScopeFactory.php b/src/Analyser/LazyScopeFactory.php index cf12f0b307..e5522da25b 100644 --- a/src/Analyser/LazyScopeFactory.php +++ b/src/Analyser/LazyScopeFactory.php @@ -19,9 +19,6 @@ class LazyScopeFactory implements ScopeFactory { - /** @var string[] */ - private array $dynamicConstantNames; - private bool $treatPhpDocTypesAsCertain; private bool $explicitMixedInUnknownGenericNew; @@ -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']; } @@ -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, @@ -98,7 +95,6 @@ public function create( $currentlyAllowedUndefinedExpressions, $nativeExpressionTypes, $inFunctionCallsStack, - $this->dynamicConstantNames, $this->treatPhpDocTypesAsCertain, $afterExtractCall, $parentScope, diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 5845dbb4a8..9f64bb087a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -75,7 +75,6 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; -use PHPStan\Type\ConstantType; use PHPStan\Type\ConstantTypeHelper; use PHPStan\Type\DynamicReturnTypeExtensionRegistry; use PHPStan\Type\Enum\EnumCaseObjectType; @@ -136,7 +135,6 @@ use function usort; use const PHP_INT_MAX; use const PHP_INT_MIN; -use const PHP_INT_SIZE; class MutatingScope implements Scope { @@ -171,7 +169,6 @@ class MutatingScope implements Scope * @param array $currentlyAllowedUndefinedExpressions * @param array $nativeExpressionTypes * @param array $inFunctionCallsStack - * @param string[] $dynamicConstantNames */ public function __construct( private ScopeFactory $scopeFactory, @@ -183,6 +180,7 @@ public function __construct( private PropertyReflectionFinder $propertyReflectionFinder, private Parser $parser, private NodeScopeResolver $nodeScopeResolver, + private ConstantResolver $constantResolver, private ScopeContext $context, private PhpVersion $phpVersion, private bool $declareStrictTypes = false, @@ -199,7 +197,6 @@ public function __construct( private array $currentlyAllowedUndefinedExpressions = [], private array $nativeExpressionTypes = [], private array $inFunctionCallsStack = [], - private array $dynamicConstantNames = [], private bool $treatPhpDocTypesAsCertain = true, private bool $afterExtractCall = false, private ?Scope $parentScope = null, @@ -2063,238 +2060,25 @@ private function resolveType(Expr $node): Type if ($node->name->isFullyQualified()) { if (array_key_exists($node->name->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($node->name->toString(), $this->constantTypes[$node->name->toCodeString()]); + return $this->constantResolver->resolveConstantType($node->name->toString(), $this->constantTypes[$node->name->toCodeString()]); } } if ($this->getNamespace() !== null) { $constantName = new FullyQualified([$this->getNamespace(), $constName]); if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); + return $this->constantResolver->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); } } $constantName = new FullyQualified($constName); if (array_key_exists($constantName->toCodeString(), $this->constantTypes)) { - return $this->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); + return $this->constantResolver->resolveConstantType($constantName->toString(), $this->constantTypes[$constantName->toCodeString()]); } - if ($this->reflectionProvider->hasConstant($node->name, $this)) { - /** @var string $resolvedConstantName */ - $resolvedConstantName = $this->reflectionProvider->resolveConstantName($node->name, $this); - // 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($node->name, $this)->getValueType(); - - return $this->resolveConstantType($resolvedConstantName, $constantType); + $constantType = $this->constantResolver->resolveConstant($node->name, $this); + if ($constantType !== null) { + return $constantType; } return new ErrorType(); @@ -2389,12 +2173,10 @@ private function resolveType(Expr $node): Type $constantType = ConstantTypeHelper::getTypeFromValue($constantReflection->getValue()); } - if ( - $constantType instanceof ConstantType - && in_array(sprintf('%s::%s', $constantClassReflection->getName(), $constantName), $this->dynamicConstantNames, true) - ) { - $constantType = $constantType->generalize(GeneralizePrecision::lessSpecific()); - } + $constantType = $this->constantResolver->resolveConstantType( + sprintf('%s::%s', $constantClassReflection->getName(), $constantName), + $constantType, + ); $types[] = $constantType; } @@ -2941,15 +2723,6 @@ private function createFirstClassCallable(array $variants): Type return TypeCombinator::union(...$closureTypes); } - private function resolveConstantType(string $constantName, Type $constantType): Type - { - if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { - return $constantType->generalize(GeneralizePrecision::lessSpecific()); - } - - return $constantType; - } - /** @api */ public function getNativeType(Expr $expr): Type { @@ -2990,6 +2763,7 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope $this->propertyReflectionFinder, $this->parser, $this->nodeScopeResolver, + $this->constantResolver, $this->context, $this->phpVersion, $this->declareStrictTypes, @@ -3006,7 +2780,6 @@ public function doNotTreatPhpDocTypesAsCertain(): Scope $this->currentlyAllowedUndefinedExpressions, $this->nativeExpressionTypes, $this->inFunctionCallsStack, - $this->dynamicConstantNames, false, $this->afterExtractCall, $this->parentScope, diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 3eb74e888c..9c547e5794 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -3,6 +3,7 @@ namespace PHPStan\Testing; use PhpParser\PrettyPrinter\Standard; +use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\DirectScopeFactory; use PHPStan\Analyser\Error; use PHPStan\Analyser\MutatingScope; @@ -30,7 +31,9 @@ use PHPStan\Type\UsefulTypeAliasResolver; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; +use ReflectionProperty; use function array_merge; +use function count; use function implode; use function is_dir; use function mkdir; @@ -146,10 +149,20 @@ public function getClassReflectionExtensionRegistryProvider(): ClassReflectionEx return self::getContainer()->getByType(ClassReflectionExtensionRegistryProvider::class); } - public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier): ScopeFactory + /** + * @param string[] $dynamicConstantNames + */ + public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier, array $dynamicConstantNames = []): ScopeFactory { $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); + } + return new DirectScopeFactory( MutatingScope::class, $reflectionProvider, @@ -159,11 +172,11 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS $typeSpecifier, new PropertyReflectionFinder(), $this->getParser(), - self::getContainer()->getByType(NodeScopeResolver::class), + $container->getByType(NodeScopeResolver::class), $this->shouldTreatPhpDocTypesAsCertain(), - $container, $container->getByType(PhpVersion::class), $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], + $constantResolver, ); } diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 7b4d63da20..948c4f0084 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; -use PHPStan\Analyser\DirectScopeFactory; use PHPStan\Analyser\NodeScopeResolver; use PHPStan\Analyser\Scope; use PHPStan\Analyser\ScopeContext; @@ -17,7 +16,6 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\VerbosityLevel; -use ReflectionProperty; use function array_map; use function array_merge; use function count; @@ -61,12 +59,7 @@ public function processFile( ); $resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], $this->getAdditionalAnalysedFiles()))); - $scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier); - if (count($dynamicConstantNames) > 0) { - $reflectionProperty = new ReflectionProperty(DirectScopeFactory::class, 'dynamicConstantNames'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($scopeFactory, $dynamicConstantNames); - } + $scopeFactory = $this->createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames); $scope = $scopeFactory->create(ScopeContext::create($file)); $resolver->processNodes(