diff --git a/src/Analyser/NameScope.php b/src/Analyser/NameScope.php index 76d1ed5a74..fe18eea6fc 100644 --- a/src/Analyser/NameScope.php +++ b/src/Analyser/NameScope.php @@ -47,6 +47,11 @@ public function getUses(): array return $this->uses; } + public function hasUseAlias(string $name): bool + { + return isset($this->uses[strtolower($name)]); + } + public function getClassName(): ?string { return $this->className; diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 5d89f80433..291ecf7b85 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -152,9 +152,21 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]); case 'number': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new UnionType([new IntegerType(), new FloatType()]); case 'numeric': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new UnionType([ new IntegerType(), new FloatType(), @@ -171,7 +183,15 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco ]); case 'bool': + return new BooleanType(); + case 'boolean': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new BooleanType(); case 'true': @@ -184,7 +204,15 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new NullType(); case 'float': + return new FloatType(); + case 'double': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new FloatType(); case 'array': @@ -204,6 +232,12 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new CallableType(); case 'resource': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + return new ResourceType(); case 'mixed': @@ -216,6 +250,14 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ObjectWithoutClassType(); case 'never': + $type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope); + + if ($type !== null) { + return $type; + } + + return new NeverType(true); + case 'never-return': case 'never-returns': case 'no-return': @@ -263,6 +305,25 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco return new ObjectType($stringName); } + private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type + { + if ($nameScope->hasUseAlias($typeNode->name)) { + return new ObjectType($nameScope->resolveStringName($typeNode->name)); + } + + if ($nameScope->getNamespace() === null) { + return null; + } + + $className = $nameScope->resolveStringName($typeNode->name); + + if ($this->getReflectionProvider()->hasClass($className)) { + return new ObjectType($className); + } + + return null; + } + private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type { $className = $nameScope->getClassName(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 258190aab0..fd7e49a467 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -5619,6 +5619,23 @@ public function dataArrayFunctions(): array ]; } + public function dataPseudoTypeOverrides(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-override.php'); + } + + public function dataPseudoTypeNamespace(): array + { + require_once __DIR__ . '/data/phpdoc-pseudotype-namespace.php'; + + return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-namespace.php'); + } + + public function dataPseudoTypeGlobal(): array + { + return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-global.php'); + } + /** * @dataProvider dataArrayFunctions * @param string $description @@ -11220,6 +11237,9 @@ private function gatherAssertTypes(string $file): array * @dataProvider dataNestedGenericIncompleteConstructor * @dataProvider dataIteratorIterator * @dataProvider dataBug4642 + * @dataProvider dataPseudoTypeGlobal + * @dataProvider dataPseudoTypeNamespace + * @dataProvider dataPseudoTypeOverrides * @param string $assertType * @param string $file * @param mixed ...$args diff --git a/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php new file mode 100644 index 0000000000..ac466625a4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/phpdoc-pseudotype-global.php @@ -0,0 +1,30 @@ +