From a2790ae8cb4a1119553b39a51f1941975e72044f Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 1 Apr 2022 17:35:13 +0200 Subject: [PATCH 1/9] Recognize constants as constant types Closes phpstan/phpstan#6160. This was just a wild idea. Probably not the best way forward, but at least it shows the potential --- src/PhpDoc/TypeNodeResolver.php | 26 +++++++++++++++++++ .../Analyser/AnalyserIntegrationTest.php | 10 +++++++ .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-6160.php | 25 ++++++++++++++++++ .../data/conditional-types-constant.php | 18 +++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-6160.php create mode 100644 tests/PHPStan/Analyser/data/conditional-types-constant.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index a1dfd2a34d..349ad47c7a 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -7,6 +7,7 @@ use Iterator; use IteratorAggregate; use Nette\Utils\Strings; +use PhpParser\Node\Name; use PHPStan\Analyser\NameScope; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; @@ -82,10 +83,12 @@ use function array_key_exists; use function array_map; use function count; +use function explode; use function get_class; use function in_array; use function max; use function min; +use function preg_match; use function preg_quote; use function str_replace; use function strpos; @@ -361,9 +364,32 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ErrorType(); } + if ($this->mightBeConstant($typeNode->name)) { + $constType = $this->tryResolveConstant($stringName) ?? $this->tryResolveConstant($typeNode->name); + if ($constType !== null) { + return $constType; + } + } + return new ObjectType($stringName); } + private function mightBeConstant(string $name): bool + { + return preg_match('(^[A-Z_][A-Z0-9_]*$)', $name) > 0; + } + + private function tryResolveConstant(string $name): ?Type + { + $nameNode = new Name\FullyQualified(explode('\\', $name)); + + if ($this->getReflectionProvider()->hasConstant($nameNode, null)) { + return $this->getReflectionProvider()->getConstant($nameNode, null)->getValueType(); + } + + return null; + } + private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type { if ($nameScope->hasUseAlias($typeNode->name)) { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 849385560f..dc96ba9100 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -665,6 +665,16 @@ public function testBug4732(): void $this->assertNoErrors($errors); } + public function testBug6160(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-6160.php'); + $this->assertCount(2, $errors); + $this->assertSame('Parameter #1 $flags of static method Bug6160\HelloWorld::split() expects 0|1|2, 94561 given.', $errors[0]->getMessage()); + $this->assertSame(19, $errors[0]->getLine()); + $this->assertSame('Parameter #1 $flags of static method Bug6160\HelloWorld::split() expects 0|1|2, \'sdf\' given.', $errors[1]->getMessage()); + $this->assertSame(23, $errors[1]->getLine()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index d4084c82a8..e98b1c2fd4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -868,6 +868,7 @@ 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'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-6160.php b/tests/PHPStan/Analyser/data/bug-6160.php new file mode 100644 index 0000000000..9470109e79 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-6160.php @@ -0,0 +1,25 @@ + + */ + public static function split($flags = 0){ + return []; + } + + public static function test(): void + { + self::split(94561); // should error + self::split(PREG_SPLIT_NO_EMPTY); // should work + self::split(PREG_SPLIT_DELIM_CAPTURE); // should work + self::split(PREG_SPLIT_NO_EMPTY_COPY); // should work + self::split("sdf"); // should error + } +} diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant.php b/tests/PHPStan/Analyser/data/conditional-types-constant.php new file mode 100644 index 0000000000..30821eacf9 --- /dev/null +++ b/tests/PHPStan/Analyser/data/conditional-types-constant.php @@ -0,0 +1,18 @@ +isPHP8()); + } +} From 42567e78a3395da33076ae48a8c2fdfd973c4f8d Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sat, 2 Apr 2022 06:48:27 +0200 Subject: [PATCH 2/9] Separate test cases for PHP 7 and PHP 8 --- .../PHPStan/Analyser/NodeScopeResolverTest.php | 6 ++++++ .../data/conditional-types-constant-php7.php | 18 ++++++++++++++++++ ...php => conditional-types-constant-php8.php} | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/conditional-types-constant-php7.php rename tests/PHPStan/Analyser/data/{conditional-types-constant.php => conditional-types-constant-php8.php} (86%) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index e98b1c2fd4..7493c7e4d4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -869,6 +869,12 @@ public function dataFileAsserts(): iterable 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'); + } } /** diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php b/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php new file mode 100644 index 0000000000..e7a0818119 --- /dev/null +++ b/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php @@ -0,0 +1,18 @@ +isPHP7()); + } +} diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant.php b/tests/PHPStan/Analyser/data/conditional-types-constant-php8.php similarity index 86% rename from tests/PHPStan/Analyser/data/conditional-types-constant.php rename to tests/PHPStan/Analyser/data/conditional-types-constant-php8.php index 30821eacf9..f808a86f03 100644 --- a/tests/PHPStan/Analyser/data/conditional-types-constant.php +++ b/tests/PHPStan/Analyser/data/conditional-types-constant-php8.php @@ -1,6 +1,6 @@ Date: Sat, 2 Apr 2022 07:34:48 +0200 Subject: [PATCH 3/9] Check class existence and add tests --- src/PhpDoc/TypeNodeResolver.php | 51 ++++++++++++++----- .../Analyser/NodeScopeResolverTest.php | 3 ++ .../Analyser/data/constant-phpdoc-type.php | 40 +++++++++++++++ 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/constant-phpdoc-type.php diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 349ad47c7a..51fb580840 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -86,6 +86,7 @@ use function explode; use function get_class; use function in_array; +use function ltrim; use function max; use function min; use function preg_match; @@ -364,30 +365,56 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ErrorType(); } - if ($this->mightBeConstant($typeNode->name)) { - $constType = $this->tryResolveConstant($stringName) ?? $this->tryResolveConstant($typeNode->name); - if ($constType !== null) { - return $constType; - } + if ($this->getReflectionProvider()->hasClass($stringName)) { + return new ObjectType($stringName); + } + + $constType = $this->tryResolveConstant($typeNode->name, $nameScope); + if ($constType !== null) { + return $constType; } return new ObjectType($stringName); } - private function mightBeConstant(string $name): bool + private function tryResolveConstant(string $name, NameScope $nameScope): ?Type { - return preg_match('(^[A-Z_][A-Z0-9_]*$)', $name) > 0; + if (!$this->mightBeConstant($name)) { + return null; + } + + $names = $this->getPossibleConstNames($name, $nameScope); + + foreach ($names as $name) { + $nameNode = new Name\FullyQualified(explode('\\', $name)); + + if ($this->getReflectionProvider()->hasConstant($nameNode, null)) { + return $this->getReflectionProvider()->getConstant($nameNode, null)->getValueType(); + } + } + + return null; } - private function tryResolveConstant(string $name): ?Type + /** + * @return non-empty-list + */ + private function getPossibleConstNames(string $name, NameScope $nameScope): array { - $nameNode = new Name\FullyQualified(explode('\\', $name)); + if (strpos($name, '\\') === 0) { + return [ltrim($name, '\\')]; + } - if ($this->getReflectionProvider()->hasConstant($nameNode, null)) { - return $this->getReflectionProvider()->getConstant($nameNode, null)->getValueType(); + if ($nameScope->getNamespace() !== null) { + return [$nameScope->resolveStringName($name), $name]; } - return null; + return [$name]; + } + + private function mightBeConstant(string $name): bool + { + return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0; } private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 7493c7e4d4..9d272dab83 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -875,6 +875,9 @@ public function dataFileAsserts(): iterable } else { yield from $this->gatherAssertTypes(__DIR__ . '/data/conditional-types-constant-php8.php'); } + + require_once __DIR__ . '/data/constant-phpdoc-type.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-phpdoc-type.php'); } /** diff --git a/tests/PHPStan/Analyser/data/constant-phpdoc-type.php b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php new file mode 100644 index 0000000000..4c8abab0c9 --- /dev/null +++ b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php @@ -0,0 +1,40 @@ + Date: Sat, 2 Apr 2022 07:54:29 +0200 Subject: [PATCH 4/9] Prevent unnecessary class existence checks --- src/PhpDoc/TypeNodeResolver.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 51fb580840..14e89fc069 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -365,7 +365,7 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ErrorType(); } - if ($this->getReflectionProvider()->hasClass($stringName)) { + if (!$this->mightBeConstant($typeNode->name) || $this->getReflectionProvider()->hasClass($stringName)) { return new ObjectType($stringName); } @@ -377,13 +377,14 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ObjectType($stringName); } - private function tryResolveConstant(string $name, NameScope $nameScope): ?Type + private function mightBeConstant(string $name): bool { - if (!$this->mightBeConstant($name)) { - return null; - } + return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0; + } - $names = $this->getPossibleConstNames($name, $nameScope); + private function tryResolveConstant(string $name, NameScope $nameScope): ?Type + { + $names = $this->getPossibleConstantNames($name, $nameScope); foreach ($names as $name) { $nameNode = new Name\FullyQualified(explode('\\', $name)); @@ -399,7 +400,7 @@ private function tryResolveConstant(string $name, NameScope $nameScope): ?Type /** * @return non-empty-list */ - private function getPossibleConstNames(string $name, NameScope $nameScope): array + private function getPossibleConstantNames(string $name, NameScope $nameScope): array { if (strpos($name, '\\') === 0) { return [ltrim($name, '\\')]; @@ -412,11 +413,6 @@ private function getPossibleConstNames(string $name, NameScope $nameScope): arra return [$name]; } - private function mightBeConstant(string $name): bool - { - return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0; - } - private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type { if ($nameScope->hasUseAlias($typeNode->name)) { From 2bf64999e32210a9d3caaef104ff3025367d7385 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Sat, 2 Apr 2022 08:25:03 +0200 Subject: [PATCH 5/9] Simplify --- src/PhpDoc/TypeNodeResolver.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 14e89fc069..4ff5857f2f 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -384,10 +384,8 @@ private function mightBeConstant(string $name): bool private function tryResolveConstant(string $name, NameScope $nameScope): ?Type { - $names = $this->getPossibleConstantNames($name, $nameScope); - - foreach ($names as $name) { - $nameNode = new Name\FullyQualified(explode('\\', $name)); + 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(); @@ -400,7 +398,7 @@ private function tryResolveConstant(string $name, NameScope $nameScope): ?Type /** * @return non-empty-list */ - private function getPossibleConstantNames(string $name, NameScope $nameScope): array + private function getPossibleFullyQualifiedConstantNames(string $name, NameScope $nameScope): array { if (strpos($name, '\\') === 0) { return [ltrim($name, '\\')]; From cbf784270ece0f2de2768fcc15a62a4551a40d10 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 7 Apr 2022 17:20:08 +0200 Subject: [PATCH 6/9] Fix dynamic constants --- conf/config.neon | 3 ++ src/Analyser/ConstantResolverProvider.php | 19 ++++++++++ src/Analyser/NameScope.php | 28 ++++++++++++++ src/PhpDoc/TypeNodeResolver.php | 37 ++++++++----------- .../Analyser/NodeScopeResolverTest.php | 7 +--- .../data/conditional-types-constant-php7.php | 18 --------- .../data/conditional-types-constant-php8.php | 18 --------- .../data/conditional-types-constant.php | 24 ++++++++++++ .../Analyser/data/constant-phpdoc-type.php | 17 ++++++++- 9 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 src/Analyser/ConstantResolverProvider.php delete mode 100644 tests/PHPStan/Analyser/data/conditional-types-constant-php7.php delete mode 100644 tests/PHPStan/Analyser/data/conditional-types-constant-php8.php create mode 100644 tests/PHPStan/Analyser/data/conditional-types-constant.php diff --git a/conf/config.neon b/conf/config.neon index 039f1675eb..0b12da0ad6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -487,6 +487,9 @@ services: - class: PHPStan\Analyser\ConstantResolver + - + class: PHPStan\Analyser\ConstantResolverProvider + - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory arguments: diff --git a/src/Analyser/ConstantResolverProvider.php b/src/Analyser/ConstantResolverProvider.php new file mode 100644 index 0000000000..1db8f90a78 --- /dev/null +++ b/src/Analyser/ConstantResolverProvider.php @@ -0,0 +1,19 @@ +container->getByType(ConstantResolver::class); + } + +} diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index 683732b28c..cc85e571a1 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -78,6 +78,34 @@ public function resolveStringName(string $name): string return $name; } + /** + * @return non-empty-list + */ + 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) { diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 4ff5857f2f..de923b0641 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -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; @@ -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; @@ -103,6 +105,8 @@ public function __construct( private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider, private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private TypeAliasResolverProvider $typeAliasResolverProvider, + private ConstantResolverProvider $constantResolverProvider, + private ScopeFactory $scopeFactory, ) { } @@ -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 - */ - 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)) { @@ -958,4 +946,9 @@ private function getTypeAliasResolver(): TypeAliasResolver return $this->typeAliasResolverProvider->getTypeAliasResolver(); } + private function getConstantResolver(): ConstantResolver + { + return $this->constantResolverProvider->getConstantResolver(); + } + } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9d272dab83..61f1872734 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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'); diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php b/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php deleted file mode 100644 index e7a0818119..0000000000 --- a/tests/PHPStan/Analyser/data/conditional-types-constant-php7.php +++ /dev/null @@ -1,18 +0,0 @@ -isPHP7()); - } -} diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant-php8.php b/tests/PHPStan/Analyser/data/conditional-types-constant-php8.php deleted file mode 100644 index f808a86f03..0000000000 --- a/tests/PHPStan/Analyser/data/conditional-types-constant-php8.php +++ /dev/null @@ -1,18 +0,0 @@ -isPHP8()); - } -} diff --git a/tests/PHPStan/Analyser/data/conditional-types-constant.php b/tests/PHPStan/Analyser/data/conditional-types-constant.php new file mode 100644 index 0000000000..2d19a65439 --- /dev/null +++ b/tests/PHPStan/Analyser/data/conditional-types-constant.php @@ -0,0 +1,24 @@ +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'])); + } +} diff --git a/tests/PHPStan/Analyser/data/constant-phpdoc-type.php b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php index 4c8abab0c9..eec4142d0b 100644 --- a/tests/PHPStan/Analyser/data/constant-phpdoc-type.php +++ b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php @@ -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; @@ -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, @@ -28,7 +33,10 @@ function foo( $four, $five, $six, - $seven + $seven, + $eight, + $nine, + $ten ) { assertType('100', $one); assertType('100|200', $two); @@ -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'; From a3af60671b706d2a759e2396b52d63d440aa457b Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 7 Apr 2022 17:51:58 +0200 Subject: [PATCH 7/9] Support const aliases properly --- conf/config.neon | 2 ++ src/Analyser/ConstantResolver.php | 27 ++++++++++------ src/Analyser/ConstantResolverProvider.php | 13 ++------ src/Analyser/ContainerConstantResolver.php | 24 ++++++++++++++ src/Analyser/DirectConstantResolver.php | 26 +++++++++++++++ .../DirectConstantResolverProvider.php | 17 ++++++++++ src/Analyser/LazyConstantResolverProvider.php | 19 +++++++++++ src/Analyser/NameScope.php | 24 ++++++++++++-- .../ExportedNode/ExportedPhpDocNode.php | 17 +++++----- src/Dependency/ExportedNodeResolver.php | 2 +- .../ValidateIgnoredErrorsExtension.php | 3 ++ src/PhpDoc/TypeNodeResolver.php | 2 -- src/Testing/PHPStanTestCase.php | 11 +++---- src/Type/FileTypeMapper.php | 32 ++++++++++++------- .../Analyser/data/constant-phpdoc-type.php | 2 +- 15 files changed, 168 insertions(+), 53 deletions(-) create mode 100644 src/Analyser/ContainerConstantResolver.php create mode 100644 src/Analyser/DirectConstantResolver.php create mode 100644 src/Analyser/DirectConstantResolverProvider.php create mode 100644 src/Analyser/LazyConstantResolverProvider.php diff --git a/conf/config.neon b/conf/config.neon index 0b12da0ad6..d2efc45d08 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -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 diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index d7d421e73a..59ebba7266 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -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; @@ -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 @@ -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; + } diff --git a/src/Analyser/ConstantResolverProvider.php b/src/Analyser/ConstantResolverProvider.php index 1db8f90a78..fa0a061a1d 100644 --- a/src/Analyser/ConstantResolverProvider.php +++ b/src/Analyser/ConstantResolverProvider.php @@ -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; } diff --git a/src/Analyser/ContainerConstantResolver.php b/src/Analyser/ContainerConstantResolver.php new file mode 100644 index 0000000000..e6bbbc6300 --- /dev/null +++ b/src/Analyser/ContainerConstantResolver.php @@ -0,0 +1,24 @@ +container->getParameter('dynamicConstantNames'); + } + +} diff --git a/src/Analyser/DirectConstantResolver.php b/src/Analyser/DirectConstantResolver.php new file mode 100644 index 0000000000..01c78ef20c --- /dev/null +++ b/src/Analyser/DirectConstantResolver.php @@ -0,0 +1,26 @@ +dynamicConstantNames; + } + +} diff --git a/src/Analyser/DirectConstantResolverProvider.php b/src/Analyser/DirectConstantResolverProvider.php new file mode 100644 index 0000000000..cc620e7aad --- /dev/null +++ b/src/Analyser/DirectConstantResolverProvider.php @@ -0,0 +1,17 @@ +constantResolver; + } + +} diff --git a/src/Analyser/LazyConstantResolverProvider.php b/src/Analyser/LazyConstantResolverProvider.php new file mode 100644 index 0000000000..1325ab6102 --- /dev/null +++ b/src/Analyser/LazyConstantResolverProvider.php @@ -0,0 +1,19 @@ +container->getByType(ConstantResolver::class); + } + +} diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index cc85e571a1..bb8a4347e6 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -25,9 +25,10 @@ class NameScope /** * @api * @param array $uses alias(string) => fullName(string) + * @param array $constUses alias(string) => fullName(string) * @param array $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(); } @@ -50,6 +51,14 @@ public function hasUseAlias(string $name): bool return isset($this->uses[strtolower($name)]); } + /** + * @return array + */ + public function getConstUses(): array + { + return $this->constUses; + } + public function getClassName(): ?string { return $this->className; @@ -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) { @@ -149,6 +161,8 @@ public function withTemplateTypeMap(TemplateTypeMap $map): self $map->getTypes(), )), $this->typeAliasesMap, + $this->bypassTypeAliases, + $this->constUses, ); } @@ -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 @@ -196,6 +212,8 @@ public static function __set_state(array $properties): self $properties['functionName'], $properties['templateTypeMap'], $properties['typeAliasesMap'], + $properties['bypassTypeAliases'], + $properties['constUses'], ); } diff --git a/src/Dependency/ExportedNode/ExportedPhpDocNode.php b/src/Dependency/ExportedNode/ExportedPhpDocNode.php index fbb3d3bd0d..9900790413 100644 --- a/src/Dependency/ExportedNode/ExportedPhpDocNode.php +++ b/src/Dependency/ExportedNode/ExportedPhpDocNode.php @@ -9,15 +9,12 @@ class ExportedPhpDocNode implements ExportedNode, JsonSerializable { - /** @var array alias(string) => fullName(string) */ - private array $uses; - /** - * @param array $uses + * @param array $uses alias(string) => fullName(string) + * @param array $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 @@ -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; } /** @@ -43,6 +41,7 @@ public function jsonSerialize() 'phpDocString' => $this->phpDocString, 'namespace' => $this->namespace, 'uses' => $this->uses, + 'constUses' => $this->constUses, ], ]; } @@ -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']); } /** @@ -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']); } } diff --git a/src/Dependency/ExportedNodeResolver.php b/src/Dependency/ExportedNodeResolver.php index 0a2dc26633..13a43d1f1a 100644 --- a/src/Dependency/ExportedNodeResolver.php +++ b/src/Dependency/ExportedNodeResolver.php @@ -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()); } /** diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 260cee8f6b..34b78e9104 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -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; @@ -81,6 +83,7 @@ public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type } }), + new DirectConstantResolverProvider(new DirectConstantResolver($reflectionProvider, [])), ), ), ); diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index de923b0641..001eba96e7 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -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; @@ -106,7 +105,6 @@ public function __construct( private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private TypeAliasResolverProvider $typeAliasResolverProvider, private ConstantResolverProvider $constantResolverProvider, - private ScopeFactory $scopeFactory, ) { } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 9c547e5794..4408d92ad8 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -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; @@ -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; @@ -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( diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index bda38fb67d..9b9cd8ea8f 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -244,9 +244,10 @@ private function createNameScopeMap( /** @var array $functionStack */ $functionStack = []; $uses = []; + $constUses = []; $this->processNodes( $this->phpParser->parseFile($fileName), - function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodAliases, $originalClassFileName, &$nameScopeMap, &$classStack, &$typeAliasStack, &$namespace, &$functionStack, &$uses, &$typeMapStack): ?int { + function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodAliases, $originalClassFileName, &$nameScopeMap, &$classStack, &$typeAliasStack, &$namespace, &$functionStack, &$uses, &$typeMapStack, &$constUses): ?int { if ($node instanceof Node\Stmt\ClassLike) { if ($traitFound && $fileName === $originalClassFileName) { return self::SKIP_NODE; @@ -296,11 +297,11 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { $phpDocString = GetLastDocComment::forNode($node); if ($phpDocString !== '') { - $typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack): TemplateTypeMap { + $typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack, $constUses): TemplateTypeMap { $phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString); $typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null; $currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null; - $nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap); + $nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, [], false, $constUses); $templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope); $templateTypeScope = $nameScope->getTemplateTypeScope(); if ($templateTypeScope === null) { @@ -344,6 +345,8 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA $functionName, ($typeMapCb !== null ? $typeMapCb() : TemplateTypeMap::createEmpty()), $typeAliasesMap, + false, + $constUses, ); } @@ -358,18 +361,24 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA if ($node instanceof Node\Stmt\Namespace_) { $namespace = (string) $node->name; - } elseif ($node instanceof Node\Stmt\Use_ && $node->type === Node\Stmt\Use_::TYPE_NORMAL) { - foreach ($node->uses as $use) { - $uses[strtolower($use->getAlias()->name)] = (string) $use->name; + } elseif ($node instanceof Node\Stmt\Use_) { + if ($node->type === Node\Stmt\Use_::TYPE_NORMAL) { + foreach ($node->uses as $use) { + $uses[strtolower($use->getAlias()->name)] = (string) $use->name; + } + } elseif ($node->type === Node\Stmt\Use_::TYPE_CONSTANT) { + foreach ($node->uses as $use) { + $constUses[strtolower($use->getAlias()->name)] = (string) $use->name; + } } } elseif ($node instanceof Node\Stmt\GroupUse) { $prefix = (string) $node->prefix; foreach ($node->uses as $use) { - if ($node->type !== Node\Stmt\Use_::TYPE_NORMAL && $use->type !== Node\Stmt\Use_::TYPE_NORMAL) { - continue; + if ($node->type === Node\Stmt\Use_::TYPE_NORMAL || $use->type === Node\Stmt\Use_::TYPE_NORMAL) { + $uses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name); + } elseif ($node->type === Node\Stmt\Use_::TYPE_CONSTANT || $use->type === Node\Stmt\Use_::TYPE_CONSTANT) { + $constUses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name); } - - $uses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name); } } elseif ($node instanceof Node\Stmt\TraitUse) { $traitMethodAliases = []; @@ -475,7 +484,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA return null; }, - static function (Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack): void { + static function (Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack, &$constUses): void { if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) { if (count($classStack) === 0) { throw new ShouldNotHappenException(); @@ -496,6 +505,7 @@ static function (Node $node, $callbackResult) use ($lookForTrait, &$namespace, & } elseif ($node instanceof Node\Stmt\Namespace_) { $namespace = null; $uses = []; + $constUses = []; } elseif ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) { if (count($functionStack) === 0) { throw new ShouldNotHappenException(); diff --git a/tests/PHPStan/Analyser/data/constant-phpdoc-type.php b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php index eec4142d0b..cc739c1d5e 100644 --- a/tests/PHPStan/Analyser/data/constant-phpdoc-type.php +++ b/tests/PHPStan/Analyser/data/constant-phpdoc-type.php @@ -43,7 +43,7 @@ function foo( assertType('1', $three); assertType('1|2', $four); assertType('1', $five); - assertType('ConstantPhpdocType\PREG_SPLIT_NO_EMPTY_ALIAS', $six); // use const not supported + assertType('1', $six); assertType('ConstantPhpdocType\BAR', $seven); // classes take precedence over constants assertType("'foo'", $eight); assertType("'foo'", $nine); From 36163f12d94bf0104e77d3bd128414fd9d605c7a Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 8 Apr 2022 08:51:51 +0200 Subject: [PATCH 8/9] Simplify --- conf/config.neon | 5 ++- src/Analyser/ConstantResolver.php | 32 +++++++------------ src/Analyser/ConstantResolverFactory.php | 26 +++++++++++++++ src/Analyser/ConstantResolverProvider.php | 10 ------ src/Analyser/ContainerConstantResolver.php | 24 -------------- src/Analyser/DirectConstantResolver.php | 26 --------------- .../DirectConstantResolverProvider.php | 17 ---------- src/Analyser/LazyConstantResolverProvider.php | 19 ----------- .../ValidateIgnoredErrorsExtension.php | 8 ++--- src/PhpDoc/TypeNodeResolver.php | 10 ++---- src/Testing/PHPStanTestCase.php | 12 +++---- 11 files changed, 51 insertions(+), 138 deletions(-) create mode 100644 src/Analyser/ConstantResolverFactory.php delete mode 100644 src/Analyser/ConstantResolverProvider.php delete mode 100644 src/Analyser/ContainerConstantResolver.php delete mode 100644 src/Analyser/DirectConstantResolver.php delete mode 100644 src/Analyser/DirectConstantResolverProvider.php delete mode 100644 src/Analyser/LazyConstantResolverProvider.php diff --git a/conf/config.neon b/conf/config.neon index d2efc45d08..7d8804d131 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -486,11 +486,10 @@ services: - class: PHPStan\Analyser\ConstantResolver - factory: PHPStan\Analyser\ContainerConstantResolver + factory: @PHPStan\Analyser\ConstantResolverFactory::create() - - class: PHPStan\Analyser\ConstantResolverProvider - factory: PHPStan\Analyser\LazyConstantResolverProvider + class: PHPStan\Analyser\ConstantResolverFactory - implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory diff --git a/src/Analyser/ConstantResolver.php b/src/Analyser/ConstantResolver.php index 59ebba7266..fea88fea51 100644 --- a/src/Analyser/ConstantResolver.php +++ b/src/Analyser/ConstantResolver.php @@ -4,6 +4,7 @@ use PhpParser\Node\Name; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; @@ -17,24 +18,24 @@ use function in_array; use const PHP_INT_SIZE; -abstract class ConstantResolver +class ConstantResolver { - /** @var string[] */ - private ?array $dynamicConstantNames = null; - - public function __construct(private ReflectionProvider $reflectionProvider) + /** + * @param string[] $dynamicConstantNames + */ + public function __construct(private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames) { } public function resolveConstant(Name $name, ?Scope $scope): ?Type { - if (!$this->reflectionProvider->hasConstant($name, $scope)) { + if (!$this->getReflectionProvider()->hasConstant($name, $scope)) { return null; } /** @var string $resolvedConstantName */ - $resolvedConstantName = $this->reflectionProvider->resolveConstantName($name, $scope); + $resolvedConstantName = $this->getReflectionProvider()->resolveConstantName($name, $scope); // core, https://www.php.net/manual/en/reserved.constants.php if ($resolvedConstantName === 'PHP_VERSION') { return new IntersectionType([ @@ -245,32 +246,23 @@ public function resolveConstant(Name $name, ?Scope $scope): ?Type return IntegerRangeType::fromInterval(1, null); } - $constantType = $this->reflectionProvider->getConstant($name, $scope)->getValueType(); + $constantType = $this->getReflectionProvider()->getConstant($name, $scope)->getValueType(); return $this->resolveConstantType($resolvedConstantName, $constantType); } public function resolveConstantType(string $constantName, Type $constantType): Type { - if ($constantType instanceof ConstantType && $this->isDynamicConstantName($constantName)) { + if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) { return $constantType->generalize(GeneralizePrecision::lessSpecific()); } return $constantType; } - private function isDynamicConstantName(string $constantName): bool + private function getReflectionProvider(): ReflectionProvider { - if ($this->dynamicConstantNames === null) { - $this->dynamicConstantNames = $this->getDynamicConstantNames(); - } - - return in_array($constantName, $this->dynamicConstantNames, true); + return $this->reflectionProviderProvider->getReflectionProvider(); } - /** - * @return string[] - */ - abstract protected function getDynamicConstantNames(): array; - } diff --git a/src/Analyser/ConstantResolverFactory.php b/src/Analyser/ConstantResolverFactory.php new file mode 100644 index 0000000000..bd63830f59 --- /dev/null +++ b/src/Analyser/ConstantResolverFactory.php @@ -0,0 +1,26 @@ +reflectionProviderProvider, + $this->container->getParameter('dynamicConstantNames'), + ); + } + +} diff --git a/src/Analyser/ConstantResolverProvider.php b/src/Analyser/ConstantResolverProvider.php deleted file mode 100644 index fa0a061a1d..0000000000 --- a/src/Analyser/ConstantResolverProvider.php +++ /dev/null @@ -1,10 +0,0 @@ -container->getParameter('dynamicConstantNames'); - } - -} diff --git a/src/Analyser/DirectConstantResolver.php b/src/Analyser/DirectConstantResolver.php deleted file mode 100644 index 01c78ef20c..0000000000 --- a/src/Analyser/DirectConstantResolver.php +++ /dev/null @@ -1,26 +0,0 @@ -dynamicConstantNames; - } - -} diff --git a/src/Analyser/DirectConstantResolverProvider.php b/src/Analyser/DirectConstantResolverProvider.php deleted file mode 100644 index cc620e7aad..0000000000 --- a/src/Analyser/DirectConstantResolverProvider.php +++ /dev/null @@ -1,17 +0,0 @@ -constantResolver; - } - -} diff --git a/src/Analyser/LazyConstantResolverProvider.php b/src/Analyser/LazyConstantResolverProvider.php deleted file mode 100644 index 1325ab6102..0000000000 --- a/src/Analyser/LazyConstantResolverProvider.php +++ /dev/null @@ -1,19 +0,0 @@ -container->getByType(ConstantResolver::class); - } - -} diff --git a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php index 34b78e9104..9c527ec946 100644 --- a/src/DependencyInjection/ValidateIgnoredErrorsExtension.php +++ b/src/DependencyInjection/ValidateIgnoredErrorsExtension.php @@ -7,8 +7,7 @@ use Nette\DI\CompilerExtension; use Nette\Utils\RegexpException; use Nette\Utils\Strings; -use PHPStan\Analyser\DirectConstantResolver; -use PHPStan\Analyser\DirectConstantResolverProvider; +use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\NameScope; use PHPStan\Command\IgnoredRegexValidator; use PHPStan\PhpDoc\DirectTypeNodeResolverExtensionRegistryProvider; @@ -52,6 +51,7 @@ public function loadConfiguration(): void /** @throws void */ $parser = Llk::load(new Read('hoa://Library/Regex/Grammar.pp')); $reflectionProvider = new DummyReflectionProvider(); + $reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider); ReflectionProviderStaticAccessor::registerInstance($reflectionProvider); $ignoredRegexValidator = new IgnoredRegexValidator( $parser, @@ -69,7 +69,7 @@ public function getExtensions(): array }, ), - new DirectReflectionProviderProvider($reflectionProvider), + $reflectionProviderProvider, new DirectTypeAliasResolverProvider(new class implements TypeAliasResolver { public function hasTypeAlias(string $aliasName, ?string $classNameScope): bool @@ -83,7 +83,7 @@ public function resolveTypeAlias(string $aliasName, NameScope $nameScope): ?Type } }), - new DirectConstantResolverProvider(new DirectConstantResolver($reflectionProvider, [])), + new ConstantResolver($reflectionProviderProvider, []), ), ), ); diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 001eba96e7..953af9e1b9 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -9,7 +9,6 @@ use Nette\Utils\Strings; use PhpParser\Node\Name; use PHPStan\Analyser\ConstantResolver; -use PHPStan\Analyser\ConstantResolverProvider; use PHPStan\Analyser\NameScope; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFalseNode; @@ -104,7 +103,7 @@ public function __construct( private TypeNodeResolverExtensionRegistryProvider $extensionRegistryProvider, private ReflectionProvider\ReflectionProviderProvider $reflectionProviderProvider, private TypeAliasResolverProvider $typeAliasResolverProvider, - private ConstantResolverProvider $constantResolverProvider, + private ConstantResolver $constantResolver, ) { } @@ -388,7 +387,7 @@ private function tryResolveConstant(string $name, NameScope $nameScope): ?Type { foreach ($nameScope->resolveConstantNames($name) as $constName) { $nameNode = new Name\FullyQualified(explode('\\', $constName)); - $constType = $this->getConstantResolver()->resolveConstant($nameNode, null); + $constType = $this->constantResolver->resolveConstant($nameNode, null); if ($constType !== null) { return $constType; } @@ -944,9 +943,4 @@ private function getTypeAliasResolver(): TypeAliasResolver return $this->typeAliasResolverProvider->getTypeAliasResolver(); } - private function getConstantResolver(): ConstantResolver - { - return $this->constantResolverProvider->getConstantResolver(); - } - } diff --git a/src/Testing/PHPStanTestCase.php b/src/Testing/PHPStanTestCase.php index 4408d92ad8..6a6302479f 100644 --- a/src/Testing/PHPStanTestCase.php +++ b/src/Testing/PHPStanTestCase.php @@ -3,8 +3,7 @@ namespace PHPStan\Testing; use PhpParser\PrettyPrinter\Standard; -use PHPStan\Analyser\ContainerConstantResolver; -use PHPStan\Analyser\DirectConstantResolver; +use PHPStan\Analyser\ConstantResolver; use PHPStan\Analyser\DirectScopeFactory; use PHPStan\Analyser\Error; use PHPStan\Analyser\MutatingScope; @@ -27,6 +26,7 @@ use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider; use PHPStan\Rules\Properties\PropertyReflectionFinder; use PHPStan\Type\TypeAliasResolver; use PHPStan\Type\UsefulTypeAliasResolver; @@ -156,10 +156,8 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS { $container = self::getContainer(); - if (count($dynamicConstantNames) > 0) { - $constantResolver = new DirectConstantResolver($reflectionProvider, $dynamicConstantNames); - } else { - $constantResolver = new ContainerConstantResolver($reflectionProvider, $container); + if (count($dynamicConstantNames) === 0) { + $dynamicConstantNames = $container->getParameter('dynamicConstantNames'); } return new DirectScopeFactory( @@ -175,7 +173,7 @@ public function createScopeFactory(ReflectionProvider $reflectionProvider, TypeS $this->shouldTreatPhpDocTypesAsCertain(), $container->getByType(PhpVersion::class), $container->getParameter('featureToggles')['explicitMixedInUnknownGenericNew'], - $constantResolver, + new ConstantResolver(new DirectReflectionProviderProvider($reflectionProvider), $dynamicConstantNames), ); } From 97f702664b18b327ce06e504739b55a493907b10 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Fri, 8 Apr 2022 10:18:14 +0200 Subject: [PATCH 9/9] Make condition clearer --- src/PhpDoc/TypeNodeResolver.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 953af9e1b9..257d1bba53 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -366,13 +366,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ErrorType(); } - if (!$this->mightBeConstant($typeNode->name) || $this->getReflectionProvider()->hasClass($stringName)) { - return new ObjectType($stringName); - } - - $constType = $this->tryResolveConstant($typeNode->name, $nameScope); - if ($constType !== null) { - return $constType; + if ($this->mightBeConstant($typeNode->name) && !$this->getReflectionProvider()->hasClass($stringName)) { + $constType = $this->tryResolveConstant($typeNode->name, $nameScope); + if ($constType !== null) { + return $constType; + } } return new ObjectType($stringName);